mpytool 2.2.2__tar.gz → 2.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. {mpytool-2.2.2 → mpytool-2.2.3}/PKG-INFO +42 -23
  2. {mpytool-2.2.2 → mpytool-2.2.3}/README.md +41 -22
  3. {mpytool-2.2.2 → mpytool-2.2.3}/mpytool/mpy.py +25 -6
  4. {mpytool-2.2.2 → mpytool-2.2.3}/mpytool/mpy_comm.py +18 -7
  5. {mpytool-2.2.2 → mpytool-2.2.3}/mpytool/mpytool.py +48 -39
  6. mpytool-2.2.3/mpytool/speedtest.py +121 -0
  7. {mpytool-2.2.2 → mpytool-2.2.3}/mpytool/utils.py +15 -0
  8. {mpytool-2.2.2 → mpytool-2.2.3}/mpytool.egg-info/PKG-INFO +42 -23
  9. {mpytool-2.2.2 → mpytool-2.2.3}/mpytool.egg-info/SOURCES.txt +1 -0
  10. {mpytool-2.2.2 → mpytool-2.2.3}/pyproject.toml +1 -1
  11. {mpytool-2.2.2 → mpytool-2.2.3}/tests/test_integration.py +92 -0
  12. {mpytool-2.2.2 → mpytool-2.2.3}/tests/test_mpy.py +78 -0
  13. {mpytool-2.2.2 → mpytool-2.2.3}/tests/test_mpytool.py +50 -0
  14. {mpytool-2.2.2 → mpytool-2.2.3}/LICENSE +0 -0
  15. {mpytool-2.2.2 → mpytool-2.2.3}/mpytool/__init__.py +0 -0
  16. {mpytool-2.2.2 → mpytool-2.2.3}/mpytool/conn.py +0 -0
  17. {mpytool-2.2.2 → mpytool-2.2.3}/mpytool/conn_serial.py +0 -0
  18. {mpytool-2.2.2 → mpytool-2.2.3}/mpytool/conn_socket.py +0 -0
  19. {mpytool-2.2.2 → mpytool-2.2.3}/mpytool/logger.py +0 -0
  20. {mpytool-2.2.2 → mpytool-2.2.3}/mpytool/terminal.py +0 -0
  21. {mpytool-2.2.2 → mpytool-2.2.3}/mpytool/terminal_unix.py +0 -0
  22. {mpytool-2.2.2 → mpytool-2.2.3}/mpytool/terminal_win.py +0 -0
  23. {mpytool-2.2.2 → mpytool-2.2.3}/mpytool.egg-info/dependency_links.txt +0 -0
  24. {mpytool-2.2.2 → mpytool-2.2.3}/mpytool.egg-info/entry_points.txt +0 -0
  25. {mpytool-2.2.2 → mpytool-2.2.3}/mpytool.egg-info/requires.txt +0 -0
  26. {mpytool-2.2.2 → mpytool-2.2.3}/mpytool.egg-info/top_level.txt +0 -0
  27. {mpytool-2.2.2 → mpytool-2.2.3}/setup.cfg +0 -0
  28. {mpytool-2.2.2 → mpytool-2.2.3}/tests/test_errors.py +0 -0
  29. {mpytool-2.2.2 → mpytool-2.2.3}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mpytool
3
- Version: 2.2.2
3
+ Version: 2.2.3
4
4
  Summary: MPY tool - manage files on devices running MicroPython
5
5
  Author-email: Pavel Revak <pavel.revak@gmail.com>
6
6
  License-Expression: MIT
@@ -74,25 +74,25 @@ Then restart your shell (`exec zsh` or `exec bash`) and use `mpytool` directly.
74
74
 
75
75
  Adding venv bin at the end of PATH keeps your system `python` and `pip` as default, while making `mpytool` available when not found elsewhere.
76
76
 
77
- ## Examples:
77
+ ## Examples
78
78
 
79
- help:
79
+ ### Help
80
80
  ```
81
81
  $ mpytool --help
82
82
  ```
83
83
 
84
- list files:
84
+ ### List files
85
85
  ```
86
86
  $ mpytool -p /dev/ttyACM0 ls # list CWD (default)
87
87
  $ mpytool -p /dev/ttyACM0 ls :/lib # list /lib
88
88
  ```
89
89
 
90
- tree:
90
+ ### Tree
91
91
  ```
92
92
  $ mpytool -p /dev/ttyACM0 tree # tree of CWD (default)
93
93
  ```
94
94
 
95
- copy files (: prefix = device path):
95
+ ### Copy files (: prefix = device path)
96
96
  ```
97
97
  $ mpytool cp main.py :/ # upload file to device root
98
98
  $ mpytool cp main.py lib.py :/lib/ # upload multiple files to directory
@@ -109,7 +109,7 @@ Path semantics: `:` = device CWD, `:/` = device root. Trailing `/` on source = c
109
109
  Unchanged files are automatically skipped (compares size and SHA256 hash).
110
110
  Use `-f` or `--force` to upload all files regardless.
111
111
 
112
- transfer options:
112
+ ### Transfer options
113
113
  ```
114
114
  $ mpytool cp -z main.py :/ # force compression (auto-detected by default)
115
115
  $ mpytool cp --no-compress data.bin :/ # disable compression
@@ -119,20 +119,20 @@ $ mpytool -c 8K cp main.py :/ # set chunk size (512, 1K, 2K, 4K, 8K, 16K,
119
119
  Compression is auto-detected based on device RAM and deflate module availability.
120
120
  Chunk size is auto-detected based on free RAM (larger chunks = faster transfer).
121
121
 
122
- move/rename on device:
122
+ ### Move/rename on device
123
123
  ```
124
124
  $ mpytool mv :/old.py :/new.py # rename file
125
125
  $ mpytool mv :/file.py :/lib/ # move file to directory
126
126
  $ mpytool mv :/a.py :/b.py :/lib/ # move multiple files to directory
127
127
  ```
128
128
 
129
- view file contents:
129
+ ### View file contents
130
130
  ```
131
131
  $ mpytool cat :boot.py # print file from CWD
132
132
  $ mpytool cat :/lib/module.py # print file with absolute path
133
133
  ```
134
134
 
135
- make directory, delete files (: prefix = device path):
135
+ ### Make directory, delete files (: prefix = device path)
136
136
  ```
137
137
  $ mpytool mkdir :lib :data # create directories in CWD
138
138
  $ mpytool mkdir :/lib/subdir # create with absolute path
@@ -143,7 +143,7 @@ $ mpytool rm : # delete everything in CWD
143
143
  $ mpytool rm :/ # delete everything on device (root)
144
144
  ```
145
145
 
146
- current working directory:
146
+ ### Current working directory
147
147
  ```
148
148
  $ mpytool pwd # print current directory
149
149
  /
@@ -153,7 +153,7 @@ $ mpytool cd :.. # change to parent directory
153
153
  $ mpytool cd :/lib -- ls # change directory and list files
154
154
  ```
155
155
 
156
- reset and REPL:
156
+ ### Reset and REPL
157
157
  ```
158
158
  $ mpytool reset # soft reset (Ctrl-D, runs boot.py/main.py)
159
159
  $ mpytool reset --raw # soft reset in raw REPL (clears RAM only)
@@ -167,7 +167,7 @@ $ mpytool repl # enter REPL mode
167
167
  $ mpytool sleep 2 # sleep for 2 seconds (useful between commands)
168
168
  ```
169
169
 
170
- serial terminal and monitor (general purpose):
170
+ ### Serial terminal and monitor (general purpose)
171
171
  ```
172
172
  $ mpytool repl # auto-detect port, 115200 baud
173
173
  $ mpytool -p /dev/ttyUSB0 repl # specify port
@@ -177,13 +177,32 @@ $ mpytool -p /dev/ttyUSB0 -b 9600 monitor # monitor at 9600 baud
177
177
 
178
178
  Both `repl` and `monitor` can be used as general-purpose serial tools - not just for MicroPython devices. Use them to interact with any serial device (Arduino, ESP with custom firmware, GPS modules, etc.). When only one serial port is detected, it is used automatically. Default baudrate is 115200.
179
179
 
180
- execute Python code on device:
180
+ ### Execute Python code on device
181
181
  ```
182
182
  $ mpytool exec "print('Hello!')"
183
183
  $ mpytool exec "import sys; print(sys.version)"
184
184
  ```
185
185
 
186
- show device information:
186
+ ### Run local Python file on device
187
+ ```
188
+ $ mpytool run script.py # run script (fire-and-forget)
189
+ $ mpytool run script.py -- monitor # run script and capture output
190
+ ```
191
+
192
+ ### Serial link speed test
193
+ ```
194
+ $ mpytool speedtest
195
+ SPEEDTEST
196
+ upload download verify
197
+ 0B - - ok
198
+ 16B 11.8K/s 24.4K/s ok
199
+ 32B 13.1K/s 42.6K/s ok
200
+ ...
201
+ 4.00K 120K/s 11.1K/s ok
202
+ 8.00K 157K/s 11.0K/s ok
203
+ ```
204
+
205
+ ### Show device information
187
206
  ```
188
207
  $ mpytool info
189
208
  Platform: rp2
@@ -201,7 +220,7 @@ MAC WiFi: aa:bb:cc:dd:ee:01
201
220
  MAC WiFi AP: aa:bb:cc:dd:ee:02
202
221
  ```
203
222
 
204
- flash operations (RP2 and ESP32):
223
+ ### Flash operations (RP2 and ESP32)
205
224
  ```
206
225
  # RP2 - user flash (entire filesystem area)
207
226
  $ mpytool flash # show flash info and filesystem type
@@ -227,20 +246,20 @@ $ mpytool flash erase vfs # quick erase partition
227
246
  $ mpytool flash erase vfs --full # full erase partition
228
247
  ```
229
248
 
230
- OTA firmware update (ESP32):
249
+ ### OTA firmware update (ESP32)
231
250
  ```
232
251
  $ mpytool ota firmware.app-bin # flash to next OTA partition
233
252
  $ mpytool ota firmware.app-bin -- reset --machine # flash and reboot
234
253
  $ mpytool ota firmware.app-bin -- reset --machine -t 30 # flash and reboot with 30s timeout
235
254
  ```
236
255
 
237
- multiple commands separated by `--`:
256
+ ### Multiple commands separated by `--`
238
257
  ```
239
258
  $ mpytool cp main.py boot.py : -- reset -- monitor
240
259
  $ mpytool rm :old.py -- cp new.py : -- reset
241
260
  ```
242
261
 
243
- auto-detect serial port (if only one device is connected):
262
+ ### Auto-detect serial port (if only one device is connected)
244
263
  ```
245
264
  $ mpytool ls lib/
246
265
  uhttp/
@@ -248,7 +267,7 @@ $ mpytool ls lib/
248
267
  4.95 KB wlan_http.py
249
268
  ```
250
269
 
251
- tree view:
270
+ ### Tree view
252
271
  ```
253
272
  $ mpytool tree
254
273
  142 KB ./
@@ -266,18 +285,18 @@ $ mpytool tree
266
285
  3.03 KB └─ main.py
267
286
  ```
268
287
 
269
- connect over network (TCP, default port 23):
288
+ ### Connect over network (TCP, default port 23)
270
289
  ```
271
290
  $ mpytool -a 192.168.1.100 ls
272
291
  $ mpytool -a 192.168.1.100:8266 tree
273
292
  ```
274
293
 
275
- set baudrate (default 115200):
294
+ ### Set baudrate (default 115200)
276
295
  ```
277
296
  $ mpytool -p /dev/ttyACM0 -b 9600 ls
278
297
  ```
279
298
 
280
- show version:
299
+ ### Show version
281
300
  ```
282
301
  $ mpytool -V
283
302
  ```
@@ -60,25 +60,25 @@ Then restart your shell (`exec zsh` or `exec bash`) and use `mpytool` directly.
60
60
 
61
61
  Adding venv bin at the end of PATH keeps your system `python` and `pip` as default, while making `mpytool` available when not found elsewhere.
62
62
 
63
- ## Examples:
63
+ ## Examples
64
64
 
65
- help:
65
+ ### Help
66
66
  ```
67
67
  $ mpytool --help
68
68
  ```
69
69
 
70
- list files:
70
+ ### List files
71
71
  ```
72
72
  $ mpytool -p /dev/ttyACM0 ls # list CWD (default)
73
73
  $ mpytool -p /dev/ttyACM0 ls :/lib # list /lib
74
74
  ```
75
75
 
76
- tree:
76
+ ### Tree
77
77
  ```
78
78
  $ mpytool -p /dev/ttyACM0 tree # tree of CWD (default)
79
79
  ```
80
80
 
81
- copy files (: prefix = device path):
81
+ ### Copy files (: prefix = device path)
82
82
  ```
83
83
  $ mpytool cp main.py :/ # upload file to device root
84
84
  $ mpytool cp main.py lib.py :/lib/ # upload multiple files to directory
@@ -95,7 +95,7 @@ Path semantics: `:` = device CWD, `:/` = device root. Trailing `/` on source = c
95
95
  Unchanged files are automatically skipped (compares size and SHA256 hash).
96
96
  Use `-f` or `--force` to upload all files regardless.
97
97
 
98
- transfer options:
98
+ ### Transfer options
99
99
  ```
100
100
  $ mpytool cp -z main.py :/ # force compression (auto-detected by default)
101
101
  $ mpytool cp --no-compress data.bin :/ # disable compression
@@ -105,20 +105,20 @@ $ mpytool -c 8K cp main.py :/ # set chunk size (512, 1K, 2K, 4K, 8K, 16K,
105
105
  Compression is auto-detected based on device RAM and deflate module availability.
106
106
  Chunk size is auto-detected based on free RAM (larger chunks = faster transfer).
107
107
 
108
- move/rename on device:
108
+ ### Move/rename on device
109
109
  ```
110
110
  $ mpytool mv :/old.py :/new.py # rename file
111
111
  $ mpytool mv :/file.py :/lib/ # move file to directory
112
112
  $ mpytool mv :/a.py :/b.py :/lib/ # move multiple files to directory
113
113
  ```
114
114
 
115
- view file contents:
115
+ ### View file contents
116
116
  ```
117
117
  $ mpytool cat :boot.py # print file from CWD
118
118
  $ mpytool cat :/lib/module.py # print file with absolute path
119
119
  ```
120
120
 
121
- make directory, delete files (: prefix = device path):
121
+ ### Make directory, delete files (: prefix = device path)
122
122
  ```
123
123
  $ mpytool mkdir :lib :data # create directories in CWD
124
124
  $ mpytool mkdir :/lib/subdir # create with absolute path
@@ -129,7 +129,7 @@ $ mpytool rm : # delete everything in CWD
129
129
  $ mpytool rm :/ # delete everything on device (root)
130
130
  ```
131
131
 
132
- current working directory:
132
+ ### Current working directory
133
133
  ```
134
134
  $ mpytool pwd # print current directory
135
135
  /
@@ -139,7 +139,7 @@ $ mpytool cd :.. # change to parent directory
139
139
  $ mpytool cd :/lib -- ls # change directory and list files
140
140
  ```
141
141
 
142
- reset and REPL:
142
+ ### Reset and REPL
143
143
  ```
144
144
  $ mpytool reset # soft reset (Ctrl-D, runs boot.py/main.py)
145
145
  $ mpytool reset --raw # soft reset in raw REPL (clears RAM only)
@@ -153,7 +153,7 @@ $ mpytool repl # enter REPL mode
153
153
  $ mpytool sleep 2 # sleep for 2 seconds (useful between commands)
154
154
  ```
155
155
 
156
- serial terminal and monitor (general purpose):
156
+ ### Serial terminal and monitor (general purpose)
157
157
  ```
158
158
  $ mpytool repl # auto-detect port, 115200 baud
159
159
  $ mpytool -p /dev/ttyUSB0 repl # specify port
@@ -163,13 +163,32 @@ $ mpytool -p /dev/ttyUSB0 -b 9600 monitor # monitor at 9600 baud
163
163
 
164
164
  Both `repl` and `monitor` can be used as general-purpose serial tools - not just for MicroPython devices. Use them to interact with any serial device (Arduino, ESP with custom firmware, GPS modules, etc.). When only one serial port is detected, it is used automatically. Default baudrate is 115200.
165
165
 
166
- execute Python code on device:
166
+ ### Execute Python code on device
167
167
  ```
168
168
  $ mpytool exec "print('Hello!')"
169
169
  $ mpytool exec "import sys; print(sys.version)"
170
170
  ```
171
171
 
172
- show device information:
172
+ ### Run local Python file on device
173
+ ```
174
+ $ mpytool run script.py # run script (fire-and-forget)
175
+ $ mpytool run script.py -- monitor # run script and capture output
176
+ ```
177
+
178
+ ### Serial link speed test
179
+ ```
180
+ $ mpytool speedtest
181
+ SPEEDTEST
182
+ upload download verify
183
+ 0B - - ok
184
+ 16B 11.8K/s 24.4K/s ok
185
+ 32B 13.1K/s 42.6K/s ok
186
+ ...
187
+ 4.00K 120K/s 11.1K/s ok
188
+ 8.00K 157K/s 11.0K/s ok
189
+ ```
190
+
191
+ ### Show device information
173
192
  ```
174
193
  $ mpytool info
175
194
  Platform: rp2
@@ -187,7 +206,7 @@ MAC WiFi: aa:bb:cc:dd:ee:01
187
206
  MAC WiFi AP: aa:bb:cc:dd:ee:02
188
207
  ```
189
208
 
190
- flash operations (RP2 and ESP32):
209
+ ### Flash operations (RP2 and ESP32)
191
210
  ```
192
211
  # RP2 - user flash (entire filesystem area)
193
212
  $ mpytool flash # show flash info and filesystem type
@@ -213,20 +232,20 @@ $ mpytool flash erase vfs # quick erase partition
213
232
  $ mpytool flash erase vfs --full # full erase partition
214
233
  ```
215
234
 
216
- OTA firmware update (ESP32):
235
+ ### OTA firmware update (ESP32)
217
236
  ```
218
237
  $ mpytool ota firmware.app-bin # flash to next OTA partition
219
238
  $ mpytool ota firmware.app-bin -- reset --machine # flash and reboot
220
239
  $ mpytool ota firmware.app-bin -- reset --machine -t 30 # flash and reboot with 30s timeout
221
240
  ```
222
241
 
223
- multiple commands separated by `--`:
242
+ ### Multiple commands separated by `--`
224
243
  ```
225
244
  $ mpytool cp main.py boot.py : -- reset -- monitor
226
245
  $ mpytool rm :old.py -- cp new.py : -- reset
227
246
  ```
228
247
 
229
- auto-detect serial port (if only one device is connected):
248
+ ### Auto-detect serial port (if only one device is connected)
230
249
  ```
231
250
  $ mpytool ls lib/
232
251
  uhttp/
@@ -234,7 +253,7 @@ $ mpytool ls lib/
234
253
  4.95 KB wlan_http.py
235
254
  ```
236
255
 
237
- tree view:
256
+ ### Tree view
238
257
  ```
239
258
  $ mpytool tree
240
259
  142 KB ./
@@ -252,18 +271,18 @@ $ mpytool tree
252
271
  3.03 KB └─ main.py
253
272
  ```
254
273
 
255
- connect over network (TCP, default port 23):
274
+ ### Connect over network (TCP, default port 23)
256
275
  ```
257
276
  $ mpytool -a 192.168.1.100 ls
258
277
  $ mpytool -a 192.168.1.100:8266 tree
259
278
  ```
260
279
 
261
- set baudrate (default 115200):
280
+ ### Set baudrate (default 115200)
262
281
  ```
263
282
  $ mpytool -p /dev/ttyACM0 -b 9600 ls
264
283
  ```
265
284
 
266
- show version:
285
+ ### Show version
267
286
  ```
268
287
  $ mpytool -V
269
288
  ```
@@ -5,6 +5,9 @@ import zlib
5
5
 
6
6
  import mpytool.mpy_comm as _mpy_comm
7
7
 
8
+ # Timeout for flash I/O operations (open/close/gc) - some FLASH GC can block >5s
9
+ FLASH_IO_TIMEOUT = 20
10
+
8
11
 
9
12
  def _escape_path(path: str) -> str:
10
13
  """Escape path for use in Python string literal"""
@@ -515,7 +518,7 @@ def _mt_pfind(label):
515
518
  self.import_module('deflate as df')
516
519
  self.import_module('io as _io')
517
520
 
518
- self._mpy_comm.exec(f"f = open('{_escape_path(path)}', 'wb')")
521
+ self._mpy_comm.exec(f"f = open('{_escape_path(path)}', 'wb')", timeout=FLASH_IO_TIMEOUT)
519
522
  while data:
520
523
  chunk = data[:chunk_size]
521
524
  cmd, orig_size, enc_type = self._encode_chunk(chunk, compress)
@@ -527,10 +530,10 @@ def _mt_pfind(label):
527
530
  transferred += orig_size
528
531
  if progress_callback:
529
532
  progress_callback(transferred, total_size)
530
- self._mpy_comm.exec("f.close()")
533
+ self._mpy_comm.exec("f.close()", timeout=FLASH_IO_TIMEOUT)
531
534
  # Run garbage collection to free memory and allow flash to settle
532
535
  self.import_module('gc')
533
- self._mpy_comm.exec("gc.collect()")
536
+ self._mpy_comm.exec("gc.collect()", timeout=FLASH_IO_TIMEOUT)
534
537
  return encodings_used, wire_bytes
535
538
 
536
539
  def platform(self):
@@ -540,15 +543,20 @@ def _mt_pfind(label):
540
543
  dict with keys:
541
544
  'platform': platform name (e.g. 'esp32')
542
545
  'version': MicroPython version string
543
- 'impl': implementation name (e.g. 'micropython')
546
+ 'impl_name': implementation name (e.g. 'micropython')
547
+ 'impl_version': implementation version tuple (e.g. (1, 24, 0))
544
548
  'machine': machine description (or None)
549
+ 'mpy_ver': mpy bytecode major version (e.g. 6) or None
550
+ 'mpy_sub': mpy bytecode sub-version (e.g. 3) or None
545
551
  """
546
552
  self.import_module('sys')
547
553
  self.import_module('os')
548
554
 
549
555
  platform = self._mpy_comm.exec_eval("repr(sys.platform)")
550
556
  version = self._mpy_comm.exec_eval("repr(sys.version)")
551
- impl = self._mpy_comm.exec_eval("repr(sys.implementation.name)")
557
+ impl_name = self._mpy_comm.exec_eval("repr(sys.implementation.name)")
558
+ impl_version = self._mpy_comm.exec_eval(
559
+ "tuple(sys.implementation.version)")
552
560
 
553
561
  try:
554
562
  uname = self._mpy_comm.exec_eval("tuple(os.uname())")
@@ -556,11 +564,22 @@ def _mt_pfind(label):
556
564
  except _mpy_comm.CmdError:
557
565
  machine = None
558
566
 
567
+ try:
568
+ mpy_int = self._mpy_comm.exec_eval("sys.implementation._mpy")
569
+ mpy_ver = mpy_int & 0xFF
570
+ mpy_sub = (mpy_int >> 8) & 3
571
+ except (_mpy_comm.CmdError, AttributeError):
572
+ mpy_ver = None
573
+ mpy_sub = None
574
+
559
575
  return {
560
576
  'platform': platform,
561
577
  'version': version,
562
- 'impl': impl,
578
+ 'impl_name': impl_name,
579
+ 'impl_version': impl_version,
563
580
  'machine': machine,
581
+ 'mpy_ver': mpy_ver,
582
+ 'mpy_sub': mpy_sub,
564
583
  }
565
584
 
566
585
  def memory(self):
@@ -174,7 +174,8 @@ class MpyComm():
174
174
 
175
175
  Arguments:
176
176
  command: command to execute
177
- timeout: maximum waiting time for result
177
+ timeout: maximum waiting time for result,
178
+ 0 = submit only (send code, don't wait for output)
178
179
 
179
180
  Returns:
180
181
  command STDOUT result
@@ -182,12 +183,16 @@ class MpyComm():
182
183
  Raises:
183
184
  CmdError when command return error
184
185
  """
186
+ send_timeout = 5 if timeout == 0 else timeout
185
187
  self.enter_raw_repl()
186
188
  if self._log:
187
189
  self._log.info("CMD: %s", command)
188
190
  self._conn.write(bytes(command, 'utf-8'))
189
191
  self._conn.write(CTRL_D)
190
- self._conn.read_until(b'OK', timeout)
192
+ self._conn.read_until(b'OK', send_timeout)
193
+ if timeout == 0:
194
+ self._repl_mode = False
195
+ return b''
191
196
  result = self._conn.read_until(CTRL_D, timeout)
192
197
  if result:
193
198
  if self._log:
@@ -209,7 +214,8 @@ class MpyComm():
209
214
 
210
215
  Arguments:
211
216
  command: command to execute (str or bytes)
212
- timeout: maximum waiting time for result
217
+ timeout: maximum waiting time for result,
218
+ 0 = submit only (send code, don't wait for output)
213
219
 
214
220
  Returns:
215
221
  command STDOUT result
@@ -218,6 +224,7 @@ class MpyComm():
218
224
  CmdError when command returns error
219
225
  MpyError when raw-paste mode is not supported
220
226
  """
227
+ send_timeout = 5 if timeout == 0 else timeout
221
228
  self.enter_raw_repl()
222
229
 
223
230
  if isinstance(command, str):
@@ -229,7 +236,7 @@ class MpyComm():
229
236
  self._conn.write(RAW_PASTE_ENTER)
230
237
 
231
238
  # Read response: 'R' + status (0=not supported, 1=supported)
232
- header, status = self._conn.read_bytes(2, timeout)
239
+ header, status = self._conn.read_bytes(2, send_timeout)
233
240
  if header != ord('R'):
234
241
  raise MpyError(f"Unexpected raw-paste header: {header!r}")
235
242
 
@@ -243,7 +250,7 @@ class MpyComm():
243
250
  self._raw_paste_supported = True
244
251
 
245
252
  # Read window size (16-bit little-endian)
246
- window_bytes = self._conn.read_bytes(2, timeout)
253
+ window_bytes = self._conn.read_bytes(2, send_timeout)
247
254
  window_size = int.from_bytes(window_bytes, 'little')
248
255
  if self._log:
249
256
  self._log.info("Raw-paste window size: %d", window_size)
@@ -255,7 +262,7 @@ class MpyComm():
255
262
  while offset < len(command):
256
263
  # Check for incoming flow control byte (non-blocking)
257
264
  if remaining_window == 0 or self._conn._has_data(0):
258
- flow_byte = self._conn.read_bytes(1, timeout)
265
+ flow_byte = self._conn.read_bytes(1, send_timeout)
259
266
  if flow_byte == RAW_PASTE_ACK:
260
267
  remaining_window += window_size
261
268
  elif flow_byte == CTRL_D:
@@ -274,10 +281,14 @@ class MpyComm():
274
281
 
275
282
  # Consume remaining ACKs and wait for CTRL_D echo
276
283
  while True:
277
- byte = self._conn.read_bytes(1, timeout)
284
+ byte = self._conn.read_bytes(1, send_timeout)
278
285
  if byte == CTRL_D:
279
286
  break
280
287
 
288
+ if timeout == 0:
289
+ self._repl_mode = False
290
+ return b''
291
+
281
292
  result = self._conn.read_until(CTRL_D, timeout)
282
293
  if result and self._log:
283
294
  self._log.info('RES: %s', bytes(result))