15
15
16
16
# The loader we send to NodeMCU so that we may upload a (binary) file safely.
17
17
# Uses STX/ETX/DLE framing and escaping.
18
+ # The CDC-ACM console gets overwhelmed unless we throttle the send by using
19
+ # an ack scheme. We use a fake prompt for simplicity's sake for that.
18
20
loader = b'''
19
21
(function()
20
22
local function transmission_receiver(chunk_cb)
21
23
local inframe = false
22
24
local escaped = false
23
25
local done = false
26
+ local len = 0
24
27
local STX = 2
25
28
local ETX = 3
26
29
local DLE = 16
30
33
end
31
34
return function(data)
32
35
if done then return end
36
+ len = len + #data
37
+ while len >= @BLOCKSIZE@ do
38
+ len = len - @BLOCKSIZE@
39
+ console.write("> ")
40
+ end
33
41
local from
34
42
local to
35
43
for i = 1, #data
38
46
if inframe
39
47
then
40
48
if not from then from = i end -- first valid byte
41
- if escaped
42
- then
43
- escaped = false
44
- else
49
+ if escaped then escaped = false else
45
50
if b == DLE
46
51
then
47
52
escaped = true
57
62
else -- look for an (unescaped) STX to sync frame start
58
63
if b == DLE then escaped = true
59
64
elseif b == STX and not escaped then inframe = true
60
- else escaped = false
61
- end
65
+ else escaped = false end
62
66
end
63
67
-- else ignore byte outside of framing
64
68
end
70
74
local function file_saver(name)
71
75
local f = io.open(name, "w")
72
76
return function(chunk)
73
- if chunk then f:write(chunk)
74
- else
77
+ if chunk then f:write(chunk) else
75
78
f:close()
76
79
console.on("data", 0, nil)
77
80
console.mode(console.INTERACTIVE)
78
81
end
79
82
end
80
83
end
81
84
82
- console.on("data", 0, transmission_receiver(file_saver("@FILENAME@")))
85
+ console.on("data", 0, transmission_receiver(file_saver(
86
+ "@FILENAME@")))
83
87
console.mode(console.NONINTERACTIVE)
88
+ console.write("ready")
84
89
end)()
85
90
'''
86
91
@@ -91,6 +96,7 @@ def parse_args():
91
96
parser .add_argument ("name" , nargs = "?" , help = "Name to upload file as." )
92
97
parser .add_argument ("-p" , "--port" , default = "/dev/ttyUSB0" , help = "Serial port (default: /dev/ttyUSB0)." )
93
98
parser .add_argument ("-b" , "--bitrate" , type = int , default = 115200 , help = "Bitrate (default: 115200)." )
99
+ parser .add_argument ("-s" , "--blocksize" , type = int , default = 80 , help = "Block size of file data, tweak for speed/reliability of upload (default: 80)" )
94
100
return parser .parse_args ()
95
101
96
102
def load_file (filename ):
@@ -103,26 +109,49 @@ def load_file(filename):
103
109
print (f"Error reading file { filename } : { e } " )
104
110
sys .exit (1 )
105
111
106
- def wait_prompt (ser ):
112
+ def xprint (msg ):
113
+ print (msg , end = '' , flush = True )
114
+
115
+ def wait_prompt (ser , ignore ):
107
116
"""Wait until we see the '> ' prompt, or the serial times out"""
108
117
buf = bytearray ()
109
118
b = ser .read ()
110
- while b != b'' :
111
- buf .extend (b )
119
+ timeout = 5
120
+ while timeout > 0 :
121
+ if b == b'' :
122
+ timeout -= 1
123
+ xprint ('!' )
124
+ else :
125
+ buf .extend (b )
126
+ if not ignore and buf .find (b'Lua error:' ) != - 1 :
127
+ xprint (buf .decode ())
128
+ line = ser .readline ()
129
+ while line != b'' :
130
+ xprint (line .decode ())
131
+ line = ser .readline ()
132
+ sys .exit (1 )
112
133
if buf .find (b'> ' ) != - 1 :
113
134
return True
114
135
b = ser .read ()
136
+ xprint (buf .decode ())
115
137
return False
116
138
117
139
def sync (ser ):
118
140
"""Get ourselves to a clean prompt so we can understand the output"""
119
141
ser .write (b'\x03 \x03 \n ' )
120
- wait_prompt (ser )
142
+ if not wait_prompt (ser , True ):
143
+ return False
121
144
ser .write (b"print('sync')\n " )
122
145
line = ser .readline ()
123
- while line != b"sync\n " :
124
- line = ser .readline ()
125
- return wait_prompt (ser )
146
+ timeout = 5
147
+ while timeout > 0 :
148
+ if line == b'sync\n ' :
149
+ return True
150
+ elif line == b'' :
151
+ timeout -= 1
152
+ xprint ('!' )
153
+ line = ser .readline ()
154
+ return wait_prompt (ser , True )
126
155
127
156
def cleanup ():
128
157
"""Cleanup function to send final data and close the serial port."""
@@ -140,7 +169,26 @@ def line_interactive_send(ser, data):
140
169
for line in data .split (b'\n ' ):
141
170
ser .write (line )
142
171
ser .write (b'\n ' )
143
- wait_prompt (ser )
172
+ if not wait_prompt (ser , False ):
173
+ return False
174
+ xprint ('.' )
175
+ return True
176
+
177
+ def chunk_data (data , size ):
178
+ return (data [0 + i :size + i ] for i in range (0 , len (data ), size ))
179
+
180
+ def chunk_interactive_send (ser , data , size ):
181
+ """Send the data chunked into blocks, waiting for an ack in between"""
182
+ n = 0
183
+ for chunk in chunk_data (data , size ):
184
+ ser .write (chunk )
185
+ if len (chunk ) == size and not wait_prompt (ser , False ):
186
+ print (f"failed after sending { n } blocks" )
187
+ return False
188
+ xprint ('.' )
189
+ n += 1
190
+ print (f" ok, sent { n } blocks" )
191
+ return True
144
192
145
193
def transmission (data ):
146
194
"""Perform STX/ETX/DLE framing and escaping of the data"""
@@ -159,30 +207,46 @@ def transmission(data):
159
207
upload_name = args .name if args .name else args .file
160
208
161
209
file_data = load_file (args .file )
210
+ print (f"Loaded { len (file_data )} bytes of file contents" )
211
+
212
+ blocksize = bytes (str (args .blocksize ).encode ())
162
213
163
214
try :
164
- ser = serial .Serial (args .port , args .bitrate , timeout = 1 )
215
+ ser = serial .Serial (port = args .port , baudrate = args .bitrate , timeout = 1 )
165
216
except serial .SerialException as e :
166
217
print (f"Error opening serial port { args .port } : { e } " )
167
218
sys .exit (1 )
168
219
169
- print ("Synchronising serial..." )
220
+ print ("Synchronising serial..." , end = '' )
170
221
if not sync (ser ):
171
- print ("NodeMCU not responding\n " )
222
+ print ("\n NodeMCU not responding\n " )
172
223
sys .exit (1 )
173
224
174
- print (f'Uploading "{ args .file } " as "{ upload_name } "' )
225
+ print (f' ok \n Uploading "{ args .file } " as "{ upload_name } "' )
175
226
176
227
atexit .register (cleanup )
177
228
178
- print ("Sending loader..." )
179
- line_interactive_send (
180
- ser , loader .replace (b"@FILENAME@" , upload_name .encode ()))
229
+ xprint ("Sending loader" )
230
+ ok = line_interactive_send (
231
+ ser , loader .replace (
232
+ b"@FILENAME@" , upload_name .encode ()).replace (
233
+ b"@BLOCKSIZE@" , blocksize ))
234
+
235
+ if ok :
236
+ xprint (" ok\n Waiting for go-ahead..." )
237
+ line = ser .readline ()
238
+ while line .find (b"ready" ) == - 1 :
239
+ xprint ('.' )
240
+ line = ser .readline ()
241
+ xprint (f" ok\n Sending file contents (using blocksize { args .blocksize } )" )
242
+ ok = chunk_interactive_send (
243
+ ser , transmission (file_data ), int (blocksize ))
244
+ ser .write (b"\n " )
181
245
182
- print ( "Sending file contents..." )
183
- ser . write ( transmission ( file_data ) )
184
- wait_prompt ( ser )
246
+ if not ok or not wait_prompt ( ser , False ):
247
+ print ( " transmission timed out" )
248
+ sys . exit ( 1 )
185
249
186
250
ser .close ()
187
251
ser = None
188
- print ("Done ." )
252
+ print ("Upload complete ." )
0 commit comments