mpytool 1.2.0__py3-none-any.whl → 2.0.0__py3-none-any.whl

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/terminal.py CHANGED
@@ -11,9 +11,12 @@ try:
11
11
  AVAILABLE = True
12
12
 
13
13
  class Terminal:
14
- def __init__(self):
14
+ def __init__(self, conn, log):
15
+ self._log = log
16
+ self._conn = conn
15
17
  self._stdin_fd = _sys.stdin.fileno()
16
18
  self._last_attr = _termios.tcgetattr(self._stdin_fd)
19
+ self._running = None
17
20
 
18
21
  def __del__(self):
19
22
  if self._last_attr:
@@ -25,19 +28,45 @@ try:
25
28
  def write(self, buf):
26
29
  _sys.stdout.buffer.raw.write(buf)
27
30
 
28
- def run(self, conn):
31
+ def _read_event_terminal(self):
32
+ data = self.read()
33
+ self._log.info('from terminal: %s', data)
34
+ if 0x1d in data: # CTRL + ]
35
+ self._running = False
36
+ self._conn.write(data)
37
+
38
+ def _read_event_device(self):
39
+ data = self._conn.read()
40
+ self._log.info('from device: %s', data)
41
+ if data:
42
+ self.write(data)
43
+
44
+ def _flush_device(self):
45
+ data = self._conn.flush()
46
+ if data:
47
+ self.write(data)
48
+
49
+ def _read_event(self, event):
50
+ if self._stdin_fd in event:
51
+ self._read_event_terminal()
52
+ if self._conn.fd in event:
53
+ self._read_event_device()
54
+
55
+ def run(self):
29
56
  _tty.setraw(self._stdin_fd)
30
- select_fds = [self._stdin_fd, conn.fd, ]
31
- while True:
32
- ret = _select.select(select_fds, [], [], 1)
33
- if self._stdin_fd in ret[0]:
34
- data = self.read()
35
- if 0x1d in data:
36
- break
37
- conn.write(data)
38
- if conn.fd in ret[0]:
39
- data = conn.read()
40
- self.write(data)
57
+ self._running = True
58
+ try:
59
+ self._flush_device()
60
+ select_fds = [self._stdin_fd, self._conn.fd, ]
61
+ self._log.info("select: %s", select_fds)
62
+ while self._running:
63
+ ret = _select.select(select_fds, [], [], 1)
64
+ self._log.info("selected: %s", ret)
65
+ if ret[0]:
66
+ self._read_event(ret[0])
67
+ except OSError as err:
68
+ if self._log:
69
+ self._log.error("OSError: %s", err)
41
70
  _termios.tcsetattr(self._stdin_fd, _termios.TCSANOW, self._last_attr)
42
71
  self._last_attr = None
43
72
  self.write(b'\r\n')
mpytool/utils.py ADDED
@@ -0,0 +1,82 @@
1
+ """Utility functions for mpytool"""
2
+
3
+ import sys
4
+
5
+
6
+ def is_remote_path(path: str) -> bool:
7
+ """Check if path is remote (starts with :)"""
8
+ return path.startswith(":")
9
+
10
+
11
+ def parse_remote_path(path: str) -> str:
12
+ """Parse remote path, strip leading :
13
+
14
+ Args:
15
+ path: path with : prefix
16
+
17
+ Returns:
18
+ path without : prefix, or '/' if path is just ':'
19
+ """
20
+ if not is_remote_path(path):
21
+ raise ValueError(f"Not a remote path: {path}")
22
+ result = path[1:]
23
+ return result if result else "/"
24
+
25
+
26
+ def split_commands(args: list[str], separator: str = "--") -> list[list[str]]:
27
+ """Split command arguments by separator
28
+
29
+ Args:
30
+ args: list of arguments
31
+ separator: separator string (default: --)
32
+
33
+ Returns:
34
+ list of command groups
35
+
36
+ Example:
37
+ split_commands(["cp", "a", ":/", "--", "reset"])
38
+ => [["cp", "a", ":/"], ["reset"]]
39
+ """
40
+ if separator not in args:
41
+ return [args] if args else []
42
+
43
+ groups = []
44
+ current = []
45
+ for arg in args:
46
+ if arg == separator:
47
+ if current:
48
+ groups.append(current)
49
+ current = []
50
+ else:
51
+ current.append(arg)
52
+ if current:
53
+ groups.append(current)
54
+ return groups
55
+
56
+
57
+ def detect_serial_ports() -> list[str]:
58
+ """Detect available serial ports for MicroPython devices
59
+
60
+ Returns:
61
+ list of port paths sorted by likelihood of being MicroPython device
62
+ """
63
+ import glob
64
+
65
+ patterns = []
66
+ if sys.platform == "darwin":
67
+ patterns = [
68
+ "/dev/tty.usbmodem*",
69
+ "/dev/tty.usbserial*",
70
+ "/dev/tty.usb*",
71
+ ]
72
+ elif sys.platform == "linux":
73
+ patterns = [
74
+ "/dev/ttyACM*",
75
+ "/dev/ttyUSB*",
76
+ ]
77
+ # Windows not yet supported
78
+
79
+ ports = []
80
+ for pattern in patterns:
81
+ ports.extend(glob.glob(pattern))
82
+ return sorted(set(ports))
@@ -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).
@@ -0,0 +1,16 @@
1
+ mpytool/__init__.py,sha256=4BRbfLRLpReWcDDbUA1A2UO1B8sNa6O5lgXRObbnP20,266
2
+ mpytool/conn.py,sha256=2CU6nJSRKwKf1TOGt-uLj4__lOXFTMXxFc0E7-3jrtA,3042
3
+ mpytool/conn_serial.py,sha256=DdX9a-SS_VtmuvjSXQdLKqcNUwanrPP5tqaiTsBsbSo,994
4
+ mpytool/conn_socket.py,sha256=EZh6KGMwPgNoNgl0wK4l47N9KVtaSLd3DeCTIiZ4bRw,1531
5
+ mpytool/logger.py,sha256=e9xaDf52nsMXU4T4Kv_pogzGq5hzP-643RxOMEDZjC0,2727
6
+ mpytool/mpy.py,sha256=i-QdxJdfUflj0VHGjy42OZD7-GejMj9ak76pR5QHfKE,9946
7
+ mpytool/mpy_comm.py,sha256=4LR2dqErVi1VtyR6wcR0Kg0iw_2ZaSKeJiyE4-6MIY0,4529
8
+ mpytool/mpytool.py,sha256=MrC02v9RPrAuNiWOR9vur-jeuevXhh-YhJwFqL4hdoQ,37113
9
+ mpytool/terminal.py,sha256=TKHIKBKa_prJ0E5CTHkqchGjJUZqvJhd6aOu50WN_nk,2320
10
+ mpytool/utils.py,sha256=VYXs_EuXKCnx8Ipt2ne6X6DY1XeW6XzSfos26kBdgUc,1933
11
+ mpytool-2.0.0.dist-info/licenses/LICENSE,sha256=wfMg5yKH2O86Digcowcm2g8Wh4zAo_gX-VqQXjmpf74,1078
12
+ mpytool-2.0.0.dist-info/METADATA,sha256=hrzauXVatXOzQ9OQrN2khUEfX5E41tYojgda19pjPNg,5763
13
+ mpytool-2.0.0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
14
+ mpytool-2.0.0.dist-info/entry_points.txt,sha256=wmnyPWEKLEbPJFR98Cc2Ky0krbMxA2BcVsS5KCTGP8Q,49
15
+ mpytool-2.0.0.dist-info/top_level.txt,sha256=4ZornVQbsgLIpB6d_5V1tOzX5bJvHxxDOuk8qFRjP2c,8
16
+ mpytool-2.0.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.37.1)
2
+ Generator: setuptools (80.10.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,3 +1,2 @@
1
1
  [console_scripts]
2
2
  mpytool = mpytool.mpytool:main
3
-
mpytool/__about__.py DELETED
@@ -1,12 +0,0 @@
1
- """ABOUT
2
- """
3
-
4
- APP_NAME = "mpytool"
5
- VERSION = "v1.2.0"
6
- AUTHOR = "Pavel Revak"
7
- AUTHOR_EMAIL = "pavel.revak@gmail.com"
8
- URL = "https://github.com/pavelrevak/mpytool"
9
- DESCRIPTION = "MPY tool - manage files on devices running MicroPython"
10
- LONG_DESCRIPTION = DESCRIPTION + "\n\n" + URL
11
- KEYWORDS = "MPY micropython"
12
- LICENSE = "MIT"
@@ -1,23 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: mpytool
3
- Version: 1.2.0
4
- Summary: MPY tool - manage files on devices running MicroPython
5
- Home-page: https://github.com/pavelrevak/mpytool
6
- Author: Pavel Revak
7
- Author-email: pavel.revak@gmail.com
8
- License: MIT
9
- Keywords: MPY micropython
10
- Platform: UNKNOWN
11
- Classifier: Development Status :: 3 - Alpha
12
- Classifier: Intended Audience :: Developers
13
- Classifier: Topic :: Software Development :: Embedded Systems
14
- Classifier: License :: OSI Approved :: MIT License
15
- Classifier: Programming Language :: Python :: 3
16
- Requires-Python: >3.5
17
- License-File: LICENSE
18
- Requires-Dist: pyserial (>=3.0)
19
-
20
- MPY tool - manage files on devices running MicroPython
21
-
22
- https://github.com/pavelrevak/mpytool
23
-
@@ -1,14 +0,0 @@
1
- mpytool/__about__.py,sha256=BnSX2iNC1a_2RgwplsrA8cFWCHKRXY4sNYpnfNayaGA,324
2
- mpytool/__init__.py,sha256=oGVGDrqw69-iBtu4OjakjaEYPK6xyQrMASWowMGQBzg,178
3
- mpytool/conn.py,sha256=nVlANjQ-qYp9xQgO3UXE7Qbg8w28nZtng3CUeIs3xqY,744
4
- mpytool/conn_serial.py,sha256=QlneedB55y4sZ-Gm0Mt_MQJ5uqmCbOvRNunof-FQqMA,2036
5
- mpytool/mpy.py,sha256=wEMt0PcssuDn7YNt4L16gONO7HqgJ1KnrcqEKjo-3m8,7720
6
- mpytool/mpy_comm.py,sha256=fB_xMisrkPKmCs0b02UsoVIOztAfjtTeJPKHZknG4uI,3298
7
- mpytool/mpytool.py,sha256=WxR6pm8-eZVGVhhdkZgCHxJKxnj4yFlO0jksm94X1C0,11030
8
- mpytool/terminal.py,sha256=E9ACsJeE3kTM1FZ7D4wKHC5TdwuNaUIF2pm0YSclFQ4,1346
9
- mpytool-1.2.0.dist-info/LICENSE,sha256=wfMg5yKH2O86Digcowcm2g8Wh4zAo_gX-VqQXjmpf74,1078
10
- mpytool-1.2.0.dist-info/METADATA,sha256=OUnOFvcxg-M6Fi917i0hXBfUNePta1eWwU8eWw6pIaQ,698
11
- mpytool-1.2.0.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
12
- mpytool-1.2.0.dist-info/entry_points.txt,sha256=w9DIg9V_P9JQ99KLinz4skCnIz4BybXvk-AqN0ciUlk,50
13
- mpytool-1.2.0.dist-info/top_level.txt,sha256=4ZornVQbsgLIpB6d_5V1tOzX5bJvHxxDOuk8qFRjP2c,8
14
- mpytool-1.2.0.dist-info/RECORD,,