mpytool 2.2.2__tar.gz → 2.2.4__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 (30) hide show
  1. {mpytool-2.2.2 → mpytool-2.2.4}/PKG-INFO +52 -23
  2. {mpytool-2.2.2 → mpytool-2.2.4}/README.md +51 -22
  3. {mpytool-2.2.2 → mpytool-2.2.4}/mpytool/mpy.py +28 -6
  4. {mpytool-2.2.2 → mpytool-2.2.4}/mpytool/mpy_comm.py +18 -7
  5. mpytool-2.2.4/mpytool/mpy_cross.py +155 -0
  6. {mpytool-2.2.2 → mpytool-2.2.4}/mpytool/mpytool.py +95 -48
  7. mpytool-2.2.4/mpytool/speedtest.py +121 -0
  8. {mpytool-2.2.2 → mpytool-2.2.4}/mpytool/utils.py +15 -0
  9. {mpytool-2.2.2 → mpytool-2.2.4}/mpytool.egg-info/PKG-INFO +52 -23
  10. {mpytool-2.2.2 → mpytool-2.2.4}/mpytool.egg-info/SOURCES.txt +2 -0
  11. {mpytool-2.2.2 → mpytool-2.2.4}/pyproject.toml +1 -1
  12. {mpytool-2.2.2 → mpytool-2.2.4}/tests/test_integration.py +92 -0
  13. {mpytool-2.2.2 → mpytool-2.2.4}/tests/test_mpy.py +78 -0
  14. {mpytool-2.2.2 → mpytool-2.2.4}/tests/test_mpytool.py +304 -0
  15. {mpytool-2.2.2 → mpytool-2.2.4}/LICENSE +0 -0
  16. {mpytool-2.2.2 → mpytool-2.2.4}/mpytool/__init__.py +0 -0
  17. {mpytool-2.2.2 → mpytool-2.2.4}/mpytool/conn.py +0 -0
  18. {mpytool-2.2.2 → mpytool-2.2.4}/mpytool/conn_serial.py +0 -0
  19. {mpytool-2.2.2 → mpytool-2.2.4}/mpytool/conn_socket.py +0 -0
  20. {mpytool-2.2.2 → mpytool-2.2.4}/mpytool/logger.py +0 -0
  21. {mpytool-2.2.2 → mpytool-2.2.4}/mpytool/terminal.py +0 -0
  22. {mpytool-2.2.2 → mpytool-2.2.4}/mpytool/terminal_unix.py +0 -0
  23. {mpytool-2.2.2 → mpytool-2.2.4}/mpytool/terminal_win.py +0 -0
  24. {mpytool-2.2.2 → mpytool-2.2.4}/mpytool.egg-info/dependency_links.txt +0 -0
  25. {mpytool-2.2.2 → mpytool-2.2.4}/mpytool.egg-info/entry_points.txt +0 -0
  26. {mpytool-2.2.2 → mpytool-2.2.4}/mpytool.egg-info/requires.txt +0 -0
  27. {mpytool-2.2.2 → mpytool-2.2.4}/mpytool.egg-info/top_level.txt +0 -0
  28. {mpytool-2.2.2 → mpytool-2.2.4}/setup.cfg +0 -0
  29. {mpytool-2.2.2 → mpytool-2.2.4}/tests/test_errors.py +0 -0
  30. {mpytool-2.2.2 → mpytool-2.2.4}/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.4
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
@@ -103,36 +103,46 @@ $ mpytool cp :/main.py ./ # download file to current directory
103
103
  $ mpytool cp :/ ./backup/ # download entire device to backup/
104
104
  $ mpytool cp :/old.py :/new.py # copy file on device
105
105
  $ mpytool cp -f main.py :/ # force upload even if unchanged
106
+ $ mpytool cp -m myapp :/ # compile .py to .mpy via mpy-cross
106
107
  ```
107
108
 
108
109
  Path semantics: `:` = device CWD, `:/` = device root. Trailing `/` on source = copy contents only.
109
110
  Unchanged files are automatically skipped (compares size and SHA256 hash).
110
111
  Use `-f` or `--force` to upload all files regardless.
111
112
 
112
- transfer options:
113
+ ### Transfer options
113
114
  ```
114
115
  $ mpytool cp -z main.py :/ # force compression (auto-detected by default)
115
116
  $ mpytool cp --no-compress data.bin :/ # disable compression
116
117
  $ mpytool -c 8K cp main.py :/ # set chunk size (512, 1K, 2K, 4K, 8K, 16K, 32K)
118
+ $ mpytool cp -m myapp :/ # compile .py to .mpy before upload
119
+ $ mpytool cp -fm myapp :/ # force upload + compile .mpy
117
120
  ```
118
121
 
119
122
  Compression is auto-detected based on device RAM and deflate module availability.
120
123
  Chunk size is auto-detected based on free RAM (larger chunks = faster transfer).
121
124
 
122
- move/rename on device:
125
+ The `-m`/`--mpy` flag compiles `.py` files to `.mpy` bytecode using `mpy-cross` before upload.
126
+ Compiled files are cached in `__pycache__/` (recompiled only when source changes).
127
+ `boot.py` and `main.py` are always uploaded as `.py` (firmware requirement).
128
+ Requires `mpy-cross` in PATH (`pip install mpy-cross`). If mpy-cross version differs
129
+ from device, automatically uses `-b` flag to target the correct bytecode version.
130
+ If `mpy-cross` is not found, falls back to uploading `.py` with a warning.
131
+
132
+ ### Move/rename on device
123
133
  ```
124
134
  $ mpytool mv :/old.py :/new.py # rename file
125
135
  $ mpytool mv :/file.py :/lib/ # move file to directory
126
136
  $ mpytool mv :/a.py :/b.py :/lib/ # move multiple files to directory
127
137
  ```
128
138
 
129
- view file contents:
139
+ ### View file contents
130
140
  ```
131
141
  $ mpytool cat :boot.py # print file from CWD
132
142
  $ mpytool cat :/lib/module.py # print file with absolute path
133
143
  ```
134
144
 
135
- make directory, delete files (: prefix = device path):
145
+ ### Make directory, delete files (: prefix = device path)
136
146
  ```
137
147
  $ mpytool mkdir :lib :data # create directories in CWD
138
148
  $ mpytool mkdir :/lib/subdir # create with absolute path
@@ -143,7 +153,7 @@ $ mpytool rm : # delete everything in CWD
143
153
  $ mpytool rm :/ # delete everything on device (root)
144
154
  ```
145
155
 
146
- current working directory:
156
+ ### Current working directory
147
157
  ```
148
158
  $ mpytool pwd # print current directory
149
159
  /
@@ -153,7 +163,7 @@ $ mpytool cd :.. # change to parent directory
153
163
  $ mpytool cd :/lib -- ls # change directory and list files
154
164
  ```
155
165
 
156
- reset and REPL:
166
+ ### Reset and REPL
157
167
  ```
158
168
  $ mpytool reset # soft reset (Ctrl-D, runs boot.py/main.py)
159
169
  $ mpytool reset --raw # soft reset in raw REPL (clears RAM only)
@@ -167,7 +177,7 @@ $ mpytool repl # enter REPL mode
167
177
  $ mpytool sleep 2 # sleep for 2 seconds (useful between commands)
168
178
  ```
169
179
 
170
- serial terminal and monitor (general purpose):
180
+ ### Serial terminal and monitor (general purpose)
171
181
  ```
172
182
  $ mpytool repl # auto-detect port, 115200 baud
173
183
  $ mpytool -p /dev/ttyUSB0 repl # specify port
@@ -177,13 +187,32 @@ $ mpytool -p /dev/ttyUSB0 -b 9600 monitor # monitor at 9600 baud
177
187
 
178
188
  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
189
 
180
- execute Python code on device:
190
+ ### Execute Python code on device
181
191
  ```
182
192
  $ mpytool exec "print('Hello!')"
183
193
  $ mpytool exec "import sys; print(sys.version)"
184
194
  ```
185
195
 
186
- show device information:
196
+ ### Run local Python file on device
197
+ ```
198
+ $ mpytool run script.py # run script (fire-and-forget)
199
+ $ mpytool run script.py -- monitor # run script and capture output
200
+ ```
201
+
202
+ ### Serial link speed test
203
+ ```
204
+ $ mpytool speedtest
205
+ SPEEDTEST
206
+ upload download verify
207
+ 0B - - ok
208
+ 16B 11.8K/s 24.4K/s ok
209
+ 32B 13.1K/s 42.6K/s ok
210
+ ...
211
+ 4.00K 120K/s 11.1K/s ok
212
+ 8.00K 157K/s 11.0K/s ok
213
+ ```
214
+
215
+ ### Show device information
187
216
  ```
188
217
  $ mpytool info
189
218
  Platform: rp2
@@ -201,7 +230,7 @@ MAC WiFi: aa:bb:cc:dd:ee:01
201
230
  MAC WiFi AP: aa:bb:cc:dd:ee:02
202
231
  ```
203
232
 
204
- flash operations (RP2 and ESP32):
233
+ ### Flash operations (RP2 and ESP32)
205
234
  ```
206
235
  # RP2 - user flash (entire filesystem area)
207
236
  $ mpytool flash # show flash info and filesystem type
@@ -227,20 +256,20 @@ $ mpytool flash erase vfs # quick erase partition
227
256
  $ mpytool flash erase vfs --full # full erase partition
228
257
  ```
229
258
 
230
- OTA firmware update (ESP32):
259
+ ### OTA firmware update (ESP32)
231
260
  ```
232
261
  $ mpytool ota firmware.app-bin # flash to next OTA partition
233
262
  $ mpytool ota firmware.app-bin -- reset --machine # flash and reboot
234
263
  $ mpytool ota firmware.app-bin -- reset --machine -t 30 # flash and reboot with 30s timeout
235
264
  ```
236
265
 
237
- multiple commands separated by `--`:
266
+ ### Multiple commands separated by `--`
238
267
  ```
239
268
  $ mpytool cp main.py boot.py : -- reset -- monitor
240
269
  $ mpytool rm :old.py -- cp new.py : -- reset
241
270
  ```
242
271
 
243
- auto-detect serial port (if only one device is connected):
272
+ ### Auto-detect serial port (if only one device is connected)
244
273
  ```
245
274
  $ mpytool ls lib/
246
275
  uhttp/
@@ -248,7 +277,7 @@ $ mpytool ls lib/
248
277
  4.95 KB wlan_http.py
249
278
  ```
250
279
 
251
- tree view:
280
+ ### Tree view
252
281
  ```
253
282
  $ mpytool tree
254
283
  142 KB ./
@@ -266,18 +295,18 @@ $ mpytool tree
266
295
  3.03 KB └─ main.py
267
296
  ```
268
297
 
269
- connect over network (TCP, default port 23):
298
+ ### Connect over network (TCP, default port 23)
270
299
  ```
271
300
  $ mpytool -a 192.168.1.100 ls
272
301
  $ mpytool -a 192.168.1.100:8266 tree
273
302
  ```
274
303
 
275
- set baudrate (default 115200):
304
+ ### Set baudrate (default 115200)
276
305
  ```
277
306
  $ mpytool -p /dev/ttyACM0 -b 9600 ls
278
307
  ```
279
308
 
280
- show version:
309
+ ### Show version
281
310
  ```
282
311
  $ mpytool -V
283
312
  ```
@@ -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
@@ -89,36 +89,46 @@ $ mpytool cp :/main.py ./ # download file to current directory
89
89
  $ mpytool cp :/ ./backup/ # download entire device to backup/
90
90
  $ mpytool cp :/old.py :/new.py # copy file on device
91
91
  $ mpytool cp -f main.py :/ # force upload even if unchanged
92
+ $ mpytool cp -m myapp :/ # compile .py to .mpy via mpy-cross
92
93
  ```
93
94
 
94
95
  Path semantics: `:` = device CWD, `:/` = device root. Trailing `/` on source = copy contents only.
95
96
  Unchanged files are automatically skipped (compares size and SHA256 hash).
96
97
  Use `-f` or `--force` to upload all files regardless.
97
98
 
98
- transfer options:
99
+ ### Transfer options
99
100
  ```
100
101
  $ mpytool cp -z main.py :/ # force compression (auto-detected by default)
101
102
  $ mpytool cp --no-compress data.bin :/ # disable compression
102
103
  $ mpytool -c 8K cp main.py :/ # set chunk size (512, 1K, 2K, 4K, 8K, 16K, 32K)
104
+ $ mpytool cp -m myapp :/ # compile .py to .mpy before upload
105
+ $ mpytool cp -fm myapp :/ # force upload + compile .mpy
103
106
  ```
104
107
 
105
108
  Compression is auto-detected based on device RAM and deflate module availability.
106
109
  Chunk size is auto-detected based on free RAM (larger chunks = faster transfer).
107
110
 
108
- move/rename on device:
111
+ The `-m`/`--mpy` flag compiles `.py` files to `.mpy` bytecode using `mpy-cross` before upload.
112
+ Compiled files are cached in `__pycache__/` (recompiled only when source changes).
113
+ `boot.py` and `main.py` are always uploaded as `.py` (firmware requirement).
114
+ Requires `mpy-cross` in PATH (`pip install mpy-cross`). If mpy-cross version differs
115
+ from device, automatically uses `-b` flag to target the correct bytecode version.
116
+ If `mpy-cross` is not found, falls back to uploading `.py` with a warning.
117
+
118
+ ### Move/rename on device
109
119
  ```
110
120
  $ mpytool mv :/old.py :/new.py # rename file
111
121
  $ mpytool mv :/file.py :/lib/ # move file to directory
112
122
  $ mpytool mv :/a.py :/b.py :/lib/ # move multiple files to directory
113
123
  ```
114
124
 
115
- view file contents:
125
+ ### View file contents
116
126
  ```
117
127
  $ mpytool cat :boot.py # print file from CWD
118
128
  $ mpytool cat :/lib/module.py # print file with absolute path
119
129
  ```
120
130
 
121
- make directory, delete files (: prefix = device path):
131
+ ### Make directory, delete files (: prefix = device path)
122
132
  ```
123
133
  $ mpytool mkdir :lib :data # create directories in CWD
124
134
  $ mpytool mkdir :/lib/subdir # create with absolute path
@@ -129,7 +139,7 @@ $ mpytool rm : # delete everything in CWD
129
139
  $ mpytool rm :/ # delete everything on device (root)
130
140
  ```
131
141
 
132
- current working directory:
142
+ ### Current working directory
133
143
  ```
134
144
  $ mpytool pwd # print current directory
135
145
  /
@@ -139,7 +149,7 @@ $ mpytool cd :.. # change to parent directory
139
149
  $ mpytool cd :/lib -- ls # change directory and list files
140
150
  ```
141
151
 
142
- reset and REPL:
152
+ ### Reset and REPL
143
153
  ```
144
154
  $ mpytool reset # soft reset (Ctrl-D, runs boot.py/main.py)
145
155
  $ mpytool reset --raw # soft reset in raw REPL (clears RAM only)
@@ -153,7 +163,7 @@ $ mpytool repl # enter REPL mode
153
163
  $ mpytool sleep 2 # sleep for 2 seconds (useful between commands)
154
164
  ```
155
165
 
156
- serial terminal and monitor (general purpose):
166
+ ### Serial terminal and monitor (general purpose)
157
167
  ```
158
168
  $ mpytool repl # auto-detect port, 115200 baud
159
169
  $ mpytool -p /dev/ttyUSB0 repl # specify port
@@ -163,13 +173,32 @@ $ mpytool -p /dev/ttyUSB0 -b 9600 monitor # monitor at 9600 baud
163
173
 
164
174
  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
175
 
166
- execute Python code on device:
176
+ ### Execute Python code on device
167
177
  ```
168
178
  $ mpytool exec "print('Hello!')"
169
179
  $ mpytool exec "import sys; print(sys.version)"
170
180
  ```
171
181
 
172
- show device information:
182
+ ### Run local Python file on device
183
+ ```
184
+ $ mpytool run script.py # run script (fire-and-forget)
185
+ $ mpytool run script.py -- monitor # run script and capture output
186
+ ```
187
+
188
+ ### Serial link speed test
189
+ ```
190
+ $ mpytool speedtest
191
+ SPEEDTEST
192
+ upload download verify
193
+ 0B - - ok
194
+ 16B 11.8K/s 24.4K/s ok
195
+ 32B 13.1K/s 42.6K/s ok
196
+ ...
197
+ 4.00K 120K/s 11.1K/s ok
198
+ 8.00K 157K/s 11.0K/s ok
199
+ ```
200
+
201
+ ### Show device information
173
202
  ```
174
203
  $ mpytool info
175
204
  Platform: rp2
@@ -187,7 +216,7 @@ MAC WiFi: aa:bb:cc:dd:ee:01
187
216
  MAC WiFi AP: aa:bb:cc:dd:ee:02
188
217
  ```
189
218
 
190
- flash operations (RP2 and ESP32):
219
+ ### Flash operations (RP2 and ESP32)
191
220
  ```
192
221
  # RP2 - user flash (entire filesystem area)
193
222
  $ mpytool flash # show flash info and filesystem type
@@ -213,20 +242,20 @@ $ mpytool flash erase vfs # quick erase partition
213
242
  $ mpytool flash erase vfs --full # full erase partition
214
243
  ```
215
244
 
216
- OTA firmware update (ESP32):
245
+ ### OTA firmware update (ESP32)
217
246
  ```
218
247
  $ mpytool ota firmware.app-bin # flash to next OTA partition
219
248
  $ mpytool ota firmware.app-bin -- reset --machine # flash and reboot
220
249
  $ mpytool ota firmware.app-bin -- reset --machine -t 30 # flash and reboot with 30s timeout
221
250
  ```
222
251
 
223
- multiple commands separated by `--`:
252
+ ### Multiple commands separated by `--`
224
253
  ```
225
254
  $ mpytool cp main.py boot.py : -- reset -- monitor
226
255
  $ mpytool rm :old.py -- cp new.py : -- reset
227
256
  ```
228
257
 
229
- auto-detect serial port (if only one device is connected):
258
+ ### Auto-detect serial port (if only one device is connected)
230
259
  ```
231
260
  $ mpytool ls lib/
232
261
  uhttp/
@@ -234,7 +263,7 @@ $ mpytool ls lib/
234
263
  4.95 KB wlan_http.py
235
264
  ```
236
265
 
237
- tree view:
266
+ ### Tree view
238
267
  ```
239
268
  $ mpytool tree
240
269
  142 KB ./
@@ -252,18 +281,18 @@ $ mpytool tree
252
281
  3.03 KB └─ main.py
253
282
  ```
254
283
 
255
- connect over network (TCP, default port 23):
284
+ ### Connect over network (TCP, default port 23)
256
285
  ```
257
286
  $ mpytool -a 192.168.1.100 ls
258
287
  $ mpytool -a 192.168.1.100:8266 tree
259
288
  ```
260
289
 
261
- set baudrate (default 115200):
290
+ ### Set baudrate (default 115200)
262
291
  ```
263
292
  $ mpytool -p /dev/ttyACM0 -b 9600 ls
264
293
  ```
265
294
 
266
- show version:
295
+ ### Show version
267
296
  ```
268
297
  $ mpytool -V
269
298
  ```
@@ -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,25 @@ 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
+ mpy_arch = (mpy_int >> 10) & 0x0F
572
+ except (_mpy_comm.CmdError, AttributeError):
573
+ mpy_ver = None
574
+ mpy_sub = None
575
+ mpy_arch = 0
576
+
559
577
  return {
560
578
  'platform': platform,
561
579
  'version': version,
562
- 'impl': impl,
580
+ 'impl_name': impl_name,
581
+ 'impl_version': impl_version,
563
582
  'machine': machine,
583
+ 'mpy_ver': mpy_ver,
584
+ 'mpy_sub': mpy_sub,
585
+ 'mpy_arch': mpy_arch,
564
586
  }
565
587
 
566
588
  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))