mpytool 1.2.0__tar.gz → 2.0.0__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.
- mpytool-2.0.0/PKG-INFO +233 -0
- mpytool-2.0.0/README.md +219 -0
- {mpytool-1.2.0 → mpytool-2.0.0}/mpytool/__init__.py +2 -0
- mpytool-2.0.0/mpytool/conn.py +100 -0
- mpytool-2.0.0/mpytool/conn_serial.py +34 -0
- mpytool-2.0.0/mpytool/conn_socket.py +53 -0
- mpytool-2.0.0/mpytool/logger.py +87 -0
- {mpytool-1.2.0 → mpytool-2.0.0}/mpytool/mpy.py +79 -14
- {mpytool-1.2.0 → mpytool-2.0.0}/mpytool/mpy_comm.py +44 -15
- mpytool-2.0.0/mpytool/mpytool.py +913 -0
- mpytool-2.0.0/mpytool/terminal.py +76 -0
- mpytool-2.0.0/mpytool/utils.py +82 -0
- mpytool-2.0.0/mpytool.egg-info/PKG-INFO +233 -0
- {mpytool-1.2.0 → mpytool-2.0.0}/mpytool.egg-info/SOURCES.txt +9 -3
- {mpytool-1.2.0 → mpytool-2.0.0}/mpytool.egg-info/entry_points.txt +0 -1
- mpytool-2.0.0/pyproject.toml +24 -0
- mpytool-2.0.0/tests/test_errors.py +64 -0
- mpytool-2.0.0/tests/test_integration.py +551 -0
- mpytool-2.0.0/tests/test_mpy.py +27 -0
- mpytool-2.0.0/tests/test_utils.py +145 -0
- mpytool-1.2.0/PKG-INFO +0 -22
- mpytool-1.2.0/README.md +0 -124
- mpytool-1.2.0/mpytool/__about__.py +0 -12
- mpytool-1.2.0/mpytool/conn.py +0 -38
- mpytool-1.2.0/mpytool/conn_serial.py +0 -67
- mpytool-1.2.0/mpytool/mpytool.py +0 -328
- mpytool-1.2.0/mpytool/terminal.py +0 -47
- mpytool-1.2.0/mpytool.egg-info/PKG-INFO +0 -22
- mpytool-1.2.0/setup.py +0 -47
- {mpytool-1.2.0 → mpytool-2.0.0}/LICENSE +0 -0
- {mpytool-1.2.0 → mpytool-2.0.0}/mpytool.egg-info/dependency_links.txt +0 -0
- {mpytool-1.2.0 → mpytool-2.0.0}/mpytool.egg-info/requires.txt +0 -0
- {mpytool-1.2.0 → mpytool-2.0.0}/mpytool.egg-info/top_level.txt +0 -0
- {mpytool-1.2.0 → mpytool-2.0.0}/setup.cfg +0 -0
mpytool-2.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mpytool
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: MPY tool - manage files on devices running MicroPython
|
|
5
|
+
Author-email: Pavel Revak <pavel.revak@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/pavelrevak/mpytool
|
|
8
|
+
Keywords: MPY,micropython
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: pyserial>=3.0
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# mpytool
|
|
16
|
+
|
|
17
|
+
MPY tool - manage files on devices running MicroPython
|
|
18
|
+
|
|
19
|
+
It is an alternative to [ampy](https://github.com/scientifichackers/ampy)
|
|
20
|
+
|
|
21
|
+
Target of this project is to make more clean code, faster, better verbose output...
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
pip3 install mpytool
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Examples:
|
|
30
|
+
|
|
31
|
+
help:
|
|
32
|
+
```
|
|
33
|
+
$ mpytool --help
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
list files:
|
|
37
|
+
```
|
|
38
|
+
$ mpytool -p /dev/ttyACM0 ls
|
|
39
|
+
$ mpytool -p /dev/ttyACM0 ls lib
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
tree files:
|
|
43
|
+
```
|
|
44
|
+
$ mpytool -p /dev/ttyACM0 tree
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
copy files (: prefix = device path):
|
|
48
|
+
```
|
|
49
|
+
$ mpytool cp main.py :/ # upload file to device root
|
|
50
|
+
$ mpytool cp main.py lib.py :/lib/ # upload multiple files to directory
|
|
51
|
+
$ mpytool cp myapp/ :/ # upload directory (creates /myapp/)
|
|
52
|
+
$ mpytool cp myapp/ :/lib/ # upload directory into /lib/
|
|
53
|
+
$ mpytool cp :/main.py ./ # download file to current directory
|
|
54
|
+
$ mpytool cp :/ ./backup/ # download entire device to backup/
|
|
55
|
+
$ mpytool cp :/old.py :/new.py # copy file on device
|
|
56
|
+
$ mpytool cp -f main.py :/ # force upload even if unchanged
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Unchanged files are automatically skipped (compares size and SHA256 hash).
|
|
60
|
+
Use `-f` or `--force` to upload all files regardless.
|
|
61
|
+
|
|
62
|
+
move/rename on device:
|
|
63
|
+
```
|
|
64
|
+
$ mpytool mv :/old.py :/new.py # rename file
|
|
65
|
+
$ mpytool mv :/file.py :/lib/ # move file to directory
|
|
66
|
+
$ mpytool mv :/a.py :/b.py :/lib/ # move multiple files to directory
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
legacy upload/download (still available):
|
|
70
|
+
```
|
|
71
|
+
$ mpytool put boot.py /
|
|
72
|
+
$ mpytool get boot.py >> boot.py
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
make directory, delete files:
|
|
76
|
+
```
|
|
77
|
+
$ mpytool mkdir a/b/c/d xyz/abc # create directories
|
|
78
|
+
$ mpytool rm mydir # delete directory and contents
|
|
79
|
+
$ mpytool rm mydir/ # delete contents only, keep directory
|
|
80
|
+
$ mpytool rm / # delete everything on device
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
reset only, reset and follow output, REPL mode:
|
|
84
|
+
```
|
|
85
|
+
$ mpytool -p /dev/ttyACM0 reset
|
|
86
|
+
$ mpytool -p /dev/ttyACM0 reset follow
|
|
87
|
+
$ mpytool -p /dev/ttyACM0 repl
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
execute Python code on device:
|
|
91
|
+
```
|
|
92
|
+
$ mpytool exec "print('Hello!')"
|
|
93
|
+
$ mpytool exec "import sys; print(sys.version)"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
show device information:
|
|
97
|
+
```
|
|
98
|
+
$ mpytool info
|
|
99
|
+
Platform: rp2
|
|
100
|
+
Version: 3.4.0; MicroPython v1.27.0 on 2025-12-09
|
|
101
|
+
Impl: micropython
|
|
102
|
+
Machine: Raspberry Pi Pico with RP2040
|
|
103
|
+
Memory: 36.4 KB / 240 KB (15.15%)
|
|
104
|
+
Flash: 120 KB / 1.38 MB (8.52%)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
multiple commands separated by `--`:
|
|
108
|
+
```
|
|
109
|
+
$ mpytool -p /dev/ttyACM0 put main.py / -- reset -- follow
|
|
110
|
+
$ mpytool -p /dev/ttyACM0 delete old.py -- put new.py / -- reset
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
auto-detect serial port (if only one device is connected):
|
|
114
|
+
```
|
|
115
|
+
$ mpytool ls
|
|
116
|
+
Using /dev/ttyACM0
|
|
117
|
+
215 boot.py
|
|
118
|
+
2938 net.py
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
connect over network (TCP, default port 23):
|
|
122
|
+
```
|
|
123
|
+
$ mpytool -a 192.168.1.100 ls
|
|
124
|
+
$ mpytool -a 192.168.1.100:8266 tree
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
set baudrate (default 115200):
|
|
128
|
+
```
|
|
129
|
+
$ mpytool -p /dev/ttyACM0 -b 9600 ls
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
show version:
|
|
133
|
+
```
|
|
134
|
+
$ mpytool -V
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
command aliases:
|
|
138
|
+
- `dir` = `ls`
|
|
139
|
+
- `cat` = `get`
|
|
140
|
+
- `del`, `rm` = `delete`
|
|
141
|
+
|
|
142
|
+
## Examples using API from Python
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
>>> import mpytool
|
|
146
|
+
>>> conn = mpytool.ConnSerial(port='/dev/ttyACM0', baudrate=115200)
|
|
147
|
+
>>> mpy = mpytool.Mpy(conn)
|
|
148
|
+
>>> mpy.ls()
|
|
149
|
+
[('ehome', None), ('boot.py', 215), ('net.py', 2938), ('project.json', 6404)]
|
|
150
|
+
>>> mpy.mkdir('a/b/c')
|
|
151
|
+
>>> mpy.ls()
|
|
152
|
+
[('a', None),
|
|
153
|
+
('ehome', None),
|
|
154
|
+
('boot.py', 215),
|
|
155
|
+
('net.py', 2938),
|
|
156
|
+
('project.json', 6404)]
|
|
157
|
+
>>> mpy.get('boot.py')
|
|
158
|
+
b"import time\nimport net\n\nwlan = net.Wlan()\nwlan.refresh_network()\n\nwhile wlan.ifconfig()[0] == '0.0.0.0':\n time.sleep(.1)\n\nprint('IP: ' + wlan.ifconfig()[0])\n\nimport ehome.ehome\n\nehome.ehome.start('project.json')\n"
|
|
159
|
+
>>> mpy.delete('a/b')
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Progress and verbose output
|
|
163
|
+
|
|
164
|
+
Progress is shown by default during file transfers:
|
|
165
|
+
```
|
|
166
|
+
$ mpytool cp main.py lib.py :/lib/
|
|
167
|
+
[1/2] 100% 1.2KB main.py -> :/lib/main.py
|
|
168
|
+
[2/2] 100% 3.4KB lib.py -> :/lib/lib.py
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
use `-v` or `--verbose` to also show commands being executed:
|
|
172
|
+
```
|
|
173
|
+
$ mpytool -v rm /old.py
|
|
174
|
+
delete: /old.py
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
use `-q` or `--quiet` to disable all output:
|
|
178
|
+
```
|
|
179
|
+
$ mpytool -q cp main.py :/
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Debug output
|
|
183
|
+
|
|
184
|
+
- `-d` print warnings (yellow)
|
|
185
|
+
- `-dd` print info messages (purple)
|
|
186
|
+
- `-ddd` print debug messages (blue)
|
|
187
|
+
|
|
188
|
+
for reporting bugs, please provide in to issue also -ddd messages
|
|
189
|
+
|
|
190
|
+
## MPYTOOL vs other tools
|
|
191
|
+
|
|
192
|
+
Benchmark on RP2040 (Raspberry Pi Pico) over native USB, January 2025:
|
|
193
|
+
|
|
194
|
+
| Test | mpytool | mpremote |
|
|
195
|
+
|------|---------|----------|
|
|
196
|
+
| 50 small files, 5 dirs (200KB) | **4.2s** | 9.4s |
|
|
197
|
+
| 5 large files (260KB) | **8.0s** | 17.3s |
|
|
198
|
+
| re-upload unchanged files | **1.3s** | 5.0s |
|
|
199
|
+
|
|
200
|
+
mpytool is **2x faster** for uploads and **4x faster** for re-uploads (skips unchanged files).
|
|
201
|
+
|
|
202
|
+
mpytool advantages:
|
|
203
|
+
- Fastest file transfers (2x faster than mpremote)
|
|
204
|
+
- Skip unchanged files (compares size + SHA256 hash)
|
|
205
|
+
- Progress indicator with file counts (`[3/10] 50% file.py -> :/lib/`)
|
|
206
|
+
- Single tool for all operations (no need to chain commands)
|
|
207
|
+
- Clean verbose output (`-v`) for debugging
|
|
208
|
+
|
|
209
|
+
## Requirements
|
|
210
|
+
|
|
211
|
+
Working only with MicroPython boards, not with CircuitPython
|
|
212
|
+
|
|
213
|
+
- python v3.10+
|
|
214
|
+
- pyserial v3.0+
|
|
215
|
+
|
|
216
|
+
### Running on:
|
|
217
|
+
|
|
218
|
+
- Linux
|
|
219
|
+
- MacOS
|
|
220
|
+
- Windows (limited support - REPL mode is disabled)
|
|
221
|
+
|
|
222
|
+
## Credits
|
|
223
|
+
|
|
224
|
+
(c) 2022 by Pavel Revak
|
|
225
|
+
|
|
226
|
+
### License
|
|
227
|
+
|
|
228
|
+
MIT
|
|
229
|
+
|
|
230
|
+
### Support
|
|
231
|
+
|
|
232
|
+
- Basic support is free over GitHub issues.
|
|
233
|
+
- Professional support is available over email: [Pavel Revak](mailto:pavel.revak@gmail.com?subject=[GitHub]%20mpytool).
|
mpytool-2.0.0/README.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# mpytool
|
|
2
|
+
|
|
3
|
+
MPY tool - manage files on devices running MicroPython
|
|
4
|
+
|
|
5
|
+
It is an alternative to [ampy](https://github.com/scientifichackers/ampy)
|
|
6
|
+
|
|
7
|
+
Target of this project is to make more clean code, faster, better verbose output...
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
pip3 install mpytool
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Examples:
|
|
16
|
+
|
|
17
|
+
help:
|
|
18
|
+
```
|
|
19
|
+
$ mpytool --help
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
list files:
|
|
23
|
+
```
|
|
24
|
+
$ mpytool -p /dev/ttyACM0 ls
|
|
25
|
+
$ mpytool -p /dev/ttyACM0 ls lib
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
tree files:
|
|
29
|
+
```
|
|
30
|
+
$ mpytool -p /dev/ttyACM0 tree
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
copy files (: prefix = device path):
|
|
34
|
+
```
|
|
35
|
+
$ mpytool cp main.py :/ # upload file to device root
|
|
36
|
+
$ mpytool cp main.py lib.py :/lib/ # upload multiple files to directory
|
|
37
|
+
$ mpytool cp myapp/ :/ # upload directory (creates /myapp/)
|
|
38
|
+
$ mpytool cp myapp/ :/lib/ # upload directory into /lib/
|
|
39
|
+
$ mpytool cp :/main.py ./ # download file to current directory
|
|
40
|
+
$ mpytool cp :/ ./backup/ # download entire device to backup/
|
|
41
|
+
$ mpytool cp :/old.py :/new.py # copy file on device
|
|
42
|
+
$ mpytool cp -f main.py :/ # force upload even if unchanged
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Unchanged files are automatically skipped (compares size and SHA256 hash).
|
|
46
|
+
Use `-f` or `--force` to upload all files regardless.
|
|
47
|
+
|
|
48
|
+
move/rename on device:
|
|
49
|
+
```
|
|
50
|
+
$ mpytool mv :/old.py :/new.py # rename file
|
|
51
|
+
$ mpytool mv :/file.py :/lib/ # move file to directory
|
|
52
|
+
$ mpytool mv :/a.py :/b.py :/lib/ # move multiple files to directory
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
legacy upload/download (still available):
|
|
56
|
+
```
|
|
57
|
+
$ mpytool put boot.py /
|
|
58
|
+
$ mpytool get boot.py >> boot.py
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
make directory, delete files:
|
|
62
|
+
```
|
|
63
|
+
$ mpytool mkdir a/b/c/d xyz/abc # create directories
|
|
64
|
+
$ mpytool rm mydir # delete directory and contents
|
|
65
|
+
$ mpytool rm mydir/ # delete contents only, keep directory
|
|
66
|
+
$ mpytool rm / # delete everything on device
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
reset only, reset and follow output, REPL mode:
|
|
70
|
+
```
|
|
71
|
+
$ mpytool -p /dev/ttyACM0 reset
|
|
72
|
+
$ mpytool -p /dev/ttyACM0 reset follow
|
|
73
|
+
$ mpytool -p /dev/ttyACM0 repl
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
execute Python code on device:
|
|
77
|
+
```
|
|
78
|
+
$ mpytool exec "print('Hello!')"
|
|
79
|
+
$ mpytool exec "import sys; print(sys.version)"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
show device information:
|
|
83
|
+
```
|
|
84
|
+
$ mpytool info
|
|
85
|
+
Platform: rp2
|
|
86
|
+
Version: 3.4.0; MicroPython v1.27.0 on 2025-12-09
|
|
87
|
+
Impl: micropython
|
|
88
|
+
Machine: Raspberry Pi Pico with RP2040
|
|
89
|
+
Memory: 36.4 KB / 240 KB (15.15%)
|
|
90
|
+
Flash: 120 KB / 1.38 MB (8.52%)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
multiple commands separated by `--`:
|
|
94
|
+
```
|
|
95
|
+
$ mpytool -p /dev/ttyACM0 put main.py / -- reset -- follow
|
|
96
|
+
$ mpytool -p /dev/ttyACM0 delete old.py -- put new.py / -- reset
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
auto-detect serial port (if only one device is connected):
|
|
100
|
+
```
|
|
101
|
+
$ mpytool ls
|
|
102
|
+
Using /dev/ttyACM0
|
|
103
|
+
215 boot.py
|
|
104
|
+
2938 net.py
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
connect over network (TCP, default port 23):
|
|
108
|
+
```
|
|
109
|
+
$ mpytool -a 192.168.1.100 ls
|
|
110
|
+
$ mpytool -a 192.168.1.100:8266 tree
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
set baudrate (default 115200):
|
|
114
|
+
```
|
|
115
|
+
$ mpytool -p /dev/ttyACM0 -b 9600 ls
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
show version:
|
|
119
|
+
```
|
|
120
|
+
$ mpytool -V
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
command aliases:
|
|
124
|
+
- `dir` = `ls`
|
|
125
|
+
- `cat` = `get`
|
|
126
|
+
- `del`, `rm` = `delete`
|
|
127
|
+
|
|
128
|
+
## Examples using API from Python
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
>>> import mpytool
|
|
132
|
+
>>> conn = mpytool.ConnSerial(port='/dev/ttyACM0', baudrate=115200)
|
|
133
|
+
>>> mpy = mpytool.Mpy(conn)
|
|
134
|
+
>>> mpy.ls()
|
|
135
|
+
[('ehome', None), ('boot.py', 215), ('net.py', 2938), ('project.json', 6404)]
|
|
136
|
+
>>> mpy.mkdir('a/b/c')
|
|
137
|
+
>>> mpy.ls()
|
|
138
|
+
[('a', None),
|
|
139
|
+
('ehome', None),
|
|
140
|
+
('boot.py', 215),
|
|
141
|
+
('net.py', 2938),
|
|
142
|
+
('project.json', 6404)]
|
|
143
|
+
>>> mpy.get('boot.py')
|
|
144
|
+
b"import time\nimport net\n\nwlan = net.Wlan()\nwlan.refresh_network()\n\nwhile wlan.ifconfig()[0] == '0.0.0.0':\n time.sleep(.1)\n\nprint('IP: ' + wlan.ifconfig()[0])\n\nimport ehome.ehome\n\nehome.ehome.start('project.json')\n"
|
|
145
|
+
>>> mpy.delete('a/b')
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Progress and verbose output
|
|
149
|
+
|
|
150
|
+
Progress is shown by default during file transfers:
|
|
151
|
+
```
|
|
152
|
+
$ mpytool cp main.py lib.py :/lib/
|
|
153
|
+
[1/2] 100% 1.2KB main.py -> :/lib/main.py
|
|
154
|
+
[2/2] 100% 3.4KB lib.py -> :/lib/lib.py
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
use `-v` or `--verbose` to also show commands being executed:
|
|
158
|
+
```
|
|
159
|
+
$ mpytool -v rm /old.py
|
|
160
|
+
delete: /old.py
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
use `-q` or `--quiet` to disable all output:
|
|
164
|
+
```
|
|
165
|
+
$ mpytool -q cp main.py :/
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Debug output
|
|
169
|
+
|
|
170
|
+
- `-d` print warnings (yellow)
|
|
171
|
+
- `-dd` print info messages (purple)
|
|
172
|
+
- `-ddd` print debug messages (blue)
|
|
173
|
+
|
|
174
|
+
for reporting bugs, please provide in to issue also -ddd messages
|
|
175
|
+
|
|
176
|
+
## MPYTOOL vs other tools
|
|
177
|
+
|
|
178
|
+
Benchmark on RP2040 (Raspberry Pi Pico) over native USB, January 2025:
|
|
179
|
+
|
|
180
|
+
| Test | mpytool | mpremote |
|
|
181
|
+
|------|---------|----------|
|
|
182
|
+
| 50 small files, 5 dirs (200KB) | **4.2s** | 9.4s |
|
|
183
|
+
| 5 large files (260KB) | **8.0s** | 17.3s |
|
|
184
|
+
| re-upload unchanged files | **1.3s** | 5.0s |
|
|
185
|
+
|
|
186
|
+
mpytool is **2x faster** for uploads and **4x faster** for re-uploads (skips unchanged files).
|
|
187
|
+
|
|
188
|
+
mpytool advantages:
|
|
189
|
+
- Fastest file transfers (2x faster than mpremote)
|
|
190
|
+
- Skip unchanged files (compares size + SHA256 hash)
|
|
191
|
+
- Progress indicator with file counts (`[3/10] 50% file.py -> :/lib/`)
|
|
192
|
+
- Single tool for all operations (no need to chain commands)
|
|
193
|
+
- Clean verbose output (`-v`) for debugging
|
|
194
|
+
|
|
195
|
+
## Requirements
|
|
196
|
+
|
|
197
|
+
Working only with MicroPython boards, not with CircuitPython
|
|
198
|
+
|
|
199
|
+
- python v3.10+
|
|
200
|
+
- pyserial v3.0+
|
|
201
|
+
|
|
202
|
+
### Running on:
|
|
203
|
+
|
|
204
|
+
- Linux
|
|
205
|
+
- MacOS
|
|
206
|
+
- Windows (limited support - REPL mode is disabled)
|
|
207
|
+
|
|
208
|
+
## Credits
|
|
209
|
+
|
|
210
|
+
(c) 2022 by Pavel Revak
|
|
211
|
+
|
|
212
|
+
### License
|
|
213
|
+
|
|
214
|
+
MIT
|
|
215
|
+
|
|
216
|
+
### Support
|
|
217
|
+
|
|
218
|
+
- Basic support is free over GitHub issues.
|
|
219
|
+
- Professional support is available over email: [Pavel Revak](mailto:pavel.revak@gmail.com?subject=[GitHub]%20mpytool).
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""MicroPython tool: abstract connector"""
|
|
2
|
+
|
|
3
|
+
import time as _time
|
|
4
|
+
import select as _select
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ConnError(Exception):
|
|
8
|
+
"""General connection error"""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Timeout(ConnError):
|
|
12
|
+
"""Timeout"""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Conn():
|
|
16
|
+
def __init__(self, log=None):
|
|
17
|
+
self._log = log
|
|
18
|
+
self._buffer = bytearray(b'')
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def fd(self):
|
|
22
|
+
"""Return file descriptor for select()"""
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
def _has_data(self, timeout=0):
|
|
26
|
+
"""Check if data is available to read using select()"""
|
|
27
|
+
fd = self.fd
|
|
28
|
+
if fd is None:
|
|
29
|
+
return False
|
|
30
|
+
readable, _, _ = _select.select([fd], [], [], timeout)
|
|
31
|
+
return bool(readable)
|
|
32
|
+
|
|
33
|
+
def _read_available(self):
|
|
34
|
+
"""Read available data from device (must be implemented by subclass)"""
|
|
35
|
+
raise NotImplementedError
|
|
36
|
+
|
|
37
|
+
def _write_raw(self, data):
|
|
38
|
+
"""Write data to device, return bytes written (must be implemented by subclass)"""
|
|
39
|
+
raise NotImplementedError
|
|
40
|
+
|
|
41
|
+
def _read_to_buffer(self, wait_timeout=0):
|
|
42
|
+
"""Read available data into buffer
|
|
43
|
+
|
|
44
|
+
Arguments:
|
|
45
|
+
wait_timeout: how long to wait for data (0 = non-blocking)
|
|
46
|
+
"""
|
|
47
|
+
if self._has_data(wait_timeout):
|
|
48
|
+
data = self._read_available()
|
|
49
|
+
if data:
|
|
50
|
+
self._buffer += data
|
|
51
|
+
return True
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
def flush(self):
|
|
55
|
+
"""Flush and return buffer contents"""
|
|
56
|
+
buffer = bytes(self._buffer)
|
|
57
|
+
del self._buffer[:]
|
|
58
|
+
return buffer
|
|
59
|
+
|
|
60
|
+
def read(self):
|
|
61
|
+
"""Read available data from device"""
|
|
62
|
+
if self._has_data():
|
|
63
|
+
return self._read_available()
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
def write(self, data):
|
|
67
|
+
"""Write data to device"""
|
|
68
|
+
if self._log:
|
|
69
|
+
self._log.debug("wr: %s", bytes(data))
|
|
70
|
+
while data:
|
|
71
|
+
count = self._write_raw(data)
|
|
72
|
+
data = data[count:]
|
|
73
|
+
|
|
74
|
+
def read_until(self, end, timeout=1):
|
|
75
|
+
"""Read until end marker is found"""
|
|
76
|
+
if self._log:
|
|
77
|
+
self._log.debug("wait for %s", end)
|
|
78
|
+
start_time = _time.time()
|
|
79
|
+
while True:
|
|
80
|
+
# Use select() with 1ms timeout instead of sleep - wakes immediately on data
|
|
81
|
+
if self._read_to_buffer(wait_timeout=0.001):
|
|
82
|
+
start_time = _time.time() # reset timeout on data received
|
|
83
|
+
if end in self._buffer:
|
|
84
|
+
break
|
|
85
|
+
if timeout is not None and start_time + timeout < _time.time():
|
|
86
|
+
if self._buffer:
|
|
87
|
+
raise Timeout(
|
|
88
|
+
f"During timeout received: {bytes(self._buffer)}")
|
|
89
|
+
raise Timeout("No data received")
|
|
90
|
+
index = self._buffer.index(end)
|
|
91
|
+
data = self._buffer[:index]
|
|
92
|
+
del self._buffer[:index + len(end)]
|
|
93
|
+
if self._log:
|
|
94
|
+
self._log.debug("rd: %s", bytes(data + end))
|
|
95
|
+
return data
|
|
96
|
+
|
|
97
|
+
def read_line(self, timeout=None):
|
|
98
|
+
"""Read single line"""
|
|
99
|
+
line = self.read_until(b'\n', timeout)
|
|
100
|
+
return line.strip(b'\r')
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""MicroPython tool: serial connector"""
|
|
2
|
+
|
|
3
|
+
import serial as _serial
|
|
4
|
+
import mpytool.conn as _conn
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ConnSerial(_conn.Conn):
|
|
8
|
+
def __init__(self, log=None, **serial_config):
|
|
9
|
+
super().__init__(log)
|
|
10
|
+
try:
|
|
11
|
+
self._serial = _serial.Serial(**serial_config)
|
|
12
|
+
except _serial.serialutil.SerialException as err:
|
|
13
|
+
self._serial = None
|
|
14
|
+
raise _conn.ConnError(
|
|
15
|
+
f"Error opening serial port {serial_config['port']}") from err
|
|
16
|
+
|
|
17
|
+
def __del__(self):
|
|
18
|
+
if self._serial:
|
|
19
|
+
self._serial.close()
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def fd(self):
|
|
23
|
+
return self._serial.fd if self._serial else None
|
|
24
|
+
|
|
25
|
+
def _read_available(self):
|
|
26
|
+
"""Read available data from serial port"""
|
|
27
|
+
in_waiting = self._serial.in_waiting
|
|
28
|
+
if in_waiting > 0:
|
|
29
|
+
return self._serial.read(in_waiting)
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
def _write_raw(self, data):
|
|
33
|
+
"""Write data to serial port"""
|
|
34
|
+
return self._serial.write(data)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""MicroPython tool: socket connector"""
|
|
2
|
+
|
|
3
|
+
import socket as _socket
|
|
4
|
+
import mpytool.conn as _conn
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ConnSocket(_conn.Conn):
|
|
8
|
+
def __init__(self, address, log=None):
|
|
9
|
+
super().__init__(log)
|
|
10
|
+
self._socket = None
|
|
11
|
+
sock = _socket.socket(_socket.AF_INET, _socket.SOCK_STREAM)
|
|
12
|
+
sock.settimeout(5)
|
|
13
|
+
if ':' in address:
|
|
14
|
+
host, port = address.split(':')
|
|
15
|
+
port = int(port)
|
|
16
|
+
else:
|
|
17
|
+
host = address
|
|
18
|
+
port = 23
|
|
19
|
+
if log:
|
|
20
|
+
log.info(f"Connecting to: {host}:{port}")
|
|
21
|
+
try:
|
|
22
|
+
sock.connect((host, port))
|
|
23
|
+
except _socket.timeout as err:
|
|
24
|
+
raise _conn.ConnError(f"Timeout connecting to {host}:{port}") from err
|
|
25
|
+
except _socket.error as err:
|
|
26
|
+
raise _conn.ConnError(f"Cannot connect to {host}:{port}: {err}") from err
|
|
27
|
+
sock.settimeout(None)
|
|
28
|
+
sock.setblocking(False)
|
|
29
|
+
self._socket = sock
|
|
30
|
+
if log:
|
|
31
|
+
log.info("connected")
|
|
32
|
+
|
|
33
|
+
def __del__(self):
|
|
34
|
+
if self._socket:
|
|
35
|
+
self._socket.close()
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def fd(self):
|
|
39
|
+
return self._socket.fileno() if self._socket else None
|
|
40
|
+
|
|
41
|
+
def _read_available(self):
|
|
42
|
+
"""Read available data from socket"""
|
|
43
|
+
try:
|
|
44
|
+
data = self._socket.recv(4096)
|
|
45
|
+
if data:
|
|
46
|
+
return data
|
|
47
|
+
except BlockingIOError:
|
|
48
|
+
pass
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
def _write_raw(self, data):
|
|
52
|
+
"""Write data to socket"""
|
|
53
|
+
return self._socket.send(data)
|