Skip to content

Commit f108d65

Browse files
committed
Make upload-file.py work better on CDC-ACM console.
1 parent 2e2d231 commit f108d65

File tree

1 file changed

+92
-28
lines changed

1 file changed

+92
-28
lines changed

scripts/upload-file.py

Lines changed: 92 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,15 @@
1515

1616
# The loader we send to NodeMCU so that we may upload a (binary) file safely.
1717
# 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.
1820
loader = b'''
1921
(function()
2022
local function transmission_receiver(chunk_cb)
2123
local inframe = false
2224
local escaped = false
2325
local done = false
26+
local len = 0
2427
local STX = 2
2528
local ETX = 3
2629
local DLE = 16
@@ -30,6 +33,11 @@
3033
end
3134
return function(data)
3235
if done then return end
36+
len = len + #data
37+
while len >= @BLOCKSIZE@ do
38+
len = len - @BLOCKSIZE@
39+
console.write("> ")
40+
end
3341
local from
3442
local to
3543
for i = 1, #data
@@ -38,10 +46,7 @@
3846
if inframe
3947
then
4048
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
4550
if b == DLE
4651
then
4752
escaped = true
@@ -57,8 +62,7 @@
5762
else -- look for an (unescaped) STX to sync frame start
5863
if b == DLE then escaped = true
5964
elseif b == STX and not escaped then inframe = true
60-
else escaped = false
61-
end
65+
else escaped = false end
6266
end
6367
-- else ignore byte outside of framing
6468
end
@@ -70,17 +74,18 @@
7074
local function file_saver(name)
7175
local f = io.open(name, "w")
7276
return function(chunk)
73-
if chunk then f:write(chunk)
74-
else
77+
if chunk then f:write(chunk) else
7578
f:close()
7679
console.on("data", 0, nil)
7780
console.mode(console.INTERACTIVE)
7881
end
7982
end
8083
end
8184
82-
console.on("data", 0, transmission_receiver(file_saver("@FILENAME@")))
85+
console.on("data", 0, transmission_receiver(file_saver(
86+
"@FILENAME@")))
8387
console.mode(console.NONINTERACTIVE)
88+
console.write("ready")
8489
end)()
8590
'''
8691

@@ -91,6 +96,7 @@ def parse_args():
9196
parser.add_argument("name", nargs="?", help="Name to upload file as.")
9297
parser.add_argument("-p", "--port", default="/dev/ttyUSB0", help="Serial port (default: /dev/ttyUSB0).")
9398
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)")
94100
return parser.parse_args()
95101

96102
def load_file(filename):
@@ -103,26 +109,49 @@ def load_file(filename):
103109
print(f"Error reading file {filename}: {e}")
104110
sys.exit(1)
105111

106-
def wait_prompt(ser):
112+
def xprint(msg):
113+
print(msg, end='', flush=True)
114+
115+
def wait_prompt(ser, ignore):
107116
"""Wait until we see the '> ' prompt, or the serial times out"""
108117
buf = bytearray()
109118
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)
112133
if buf.find(b'> ') != -1:
113134
return True
114135
b = ser.read()
136+
xprint(buf.decode())
115137
return False
116138

117139
def sync(ser):
118140
"""Get ourselves to a clean prompt so we can understand the output"""
119141
ser.write(b'\x03\x03\n')
120-
wait_prompt(ser)
142+
if not wait_prompt(ser, True):
143+
return False
121144
ser.write(b"print('sync')\n")
122145
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)
126155

127156
def cleanup():
128157
"""Cleanup function to send final data and close the serial port."""
@@ -140,7 +169,26 @@ def line_interactive_send(ser, data):
140169
for line in data.split(b'\n'):
141170
ser.write(line)
142171
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
144192

145193
def transmission(data):
146194
"""Perform STX/ETX/DLE framing and escaping of the data"""
@@ -159,30 +207,46 @@ def transmission(data):
159207
upload_name = args.name if args.name else args.file
160208

161209
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())
162213

163214
try:
164-
ser = serial.Serial(args.port, args.bitrate, timeout=1)
215+
ser = serial.Serial(port=args.port, baudrate=args.bitrate, timeout=1)
165216
except serial.SerialException as e:
166217
print(f"Error opening serial port {args.port}: {e}")
167218
sys.exit(1)
168219

169-
print("Synchronising serial...")
220+
print("Synchronising serial...", end='')
170221
if not sync(ser):
171-
print("NodeMCU not responding\n")
222+
print("\nNodeMCU not responding\n")
172223
sys.exit(1)
173224

174-
print(f'Uploading "{args.file}" as "{upload_name}"')
225+
print(f' ok\nUploading "{args.file}" as "{upload_name}"')
175226

176227
atexit.register(cleanup)
177228

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\nWaiting for go-ahead...")
237+
line = ser.readline()
238+
while line.find(b"ready") == -1:
239+
xprint('.')
240+
line = ser.readline()
241+
xprint(f" ok\nSending file contents (using blocksize {args.blocksize})")
242+
ok = chunk_interactive_send(
243+
ser, transmission(file_data), int(blocksize))
244+
ser.write(b"\n")
181245

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)
185249

186250
ser.close()
187251
ser = None
188-
print("Done.")
252+
print("Upload complete.")

0 commit comments

Comments
 (0)