mpytool 2.4.0__tar.gz → 2.6.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.4.0 → mpytool-2.6.0}/PKG-INFO +133 -14
- {mpytool-2.4.0 → mpytool-2.6.0}/README.md +132 -13
- {mpytool-2.4.0 → mpytool-2.6.0}/README_API.md +164 -6
- {mpytool-2.4.0 → mpytool-2.6.0}/completions/_mpytool +27 -6
- {mpytool-2.4.0 → mpytool-2.6.0}/completions/mpytool.bash +23 -7
- {mpytool-2.4.0 → mpytool-2.6.0}/mpytool/__init__.py +3 -1
- {mpytool-2.4.0 → mpytool-2.6.0}/mpytool/_version.py +3 -3
- {mpytool-2.4.0 → mpytool-2.6.0}/mpytool/conn.py +34 -5
- {mpytool-2.4.0 → mpytool-2.6.0}/mpytool/conn_serial.py +16 -3
- {mpytool-2.4.0 → mpytool-2.6.0}/mpytool/conn_socket.py +1 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/mpytool/mpy.py +418 -5
- {mpytool-2.4.0 → mpytool-2.6.0}/mpytool/mpy_comm.py +27 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/mpytool/mpytool.py +313 -22
- {mpytool-2.4.0 → mpytool-2.6.0}/mpytool/terminal_unix.py +24 -14
- {mpytool-2.4.0 → mpytool-2.6.0}/mpytool/utils.py +4 -1
- {mpytool-2.4.0 → mpytool-2.6.0}/mpytool.egg-info/PKG-INFO +133 -14
- {mpytool-2.4.0 → mpytool-2.6.0}/mpytool.egg-info/SOURCES.txt +3 -0
- mpytool-2.6.0/mpytool.egg-info/scm_file_list.json +44 -0
- mpytool-2.6.0/mpytool.egg-info/scm_version.json +8 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/tests/test_cli.py +25 -0
- mpytool-2.6.0/tests/test_conn_serial.py +38 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/tests/test_mpy.py +395 -1
- {mpytool-2.4.0 → mpytool-2.6.0}/tests/test_mpytool.py +236 -5
- {mpytool-2.4.0 → mpytool-2.6.0}/tests/test_utils.py +1 -1
- {mpytool-2.4.0 → mpytool-2.6.0}/.github/workflows/integration.yml +0 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/.github/workflows/publish.yml +0 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/.github/workflows/tests.yml +0 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/.gitignore +0 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/LICENSE +0 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/README_bench.md +0 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/README_mpremote.md +0 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/mpytool/cmd_cp.py +0 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/mpytool/logger.py +0 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/mpytool/mount.py +0 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/mpytool/mpy_cross.py +0 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/mpytool/speedtest.py +0 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/mpytool/terminal.py +0 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/mpytool/terminal_win.py +0 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/mpytool.egg-info/dependency_links.txt +0 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/mpytool.egg-info/entry_points.txt +0 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/mpytool.egg-info/requires.txt +0 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/mpytool.egg-info/top_level.txt +0 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/pyproject.toml +0 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/setup.cfg +0 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/tests/__init__.py +0 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/tests/test_cmd_cp.py +0 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/tests/test_conn_socket.py +0 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/tests/test_errors.py +0 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/tests/test_helpers.py +0 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/tests/test_integration.py +0 -0
- {mpytool-2.4.0 → mpytool-2.6.0}/tests/test_mount.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mpytool
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.0
|
|
4
4
|
Summary: MPY tool - manage files on devices running MicroPython
|
|
5
5
|
Author-email: Pavel Revak <pavelrevak@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -33,6 +33,9 @@ It is an alternative to the official [mpremote](https://docs.micropython.org/en/
|
|
|
33
33
|
bootloader entry
|
|
34
34
|
- **Wipe device** - erase the whole filesystem and machine-reset
|
|
35
35
|
for a clean slate (handy at the start of automated tests)
|
|
36
|
+
- **Deploy projects** - `sync` uploads a whole project to the device
|
|
37
|
+
using a `.mpyproject` config file (deploy map, excludes, `.mpy`
|
|
38
|
+
compilation)
|
|
36
39
|
- **General-purpose serial terminal** - `repl` and `monitor` work
|
|
37
40
|
with any serial device
|
|
38
41
|
- **Mount local directory** - VFS mount (read-only or read-write)
|
|
@@ -168,6 +171,47 @@ Requires `mpy-cross` in PATH (`pip install mpy-cross`). If mpy-cross version dif
|
|
|
168
171
|
from device, automatically uses `-b` flag to target the correct bytecode version.
|
|
169
172
|
If `mpy-cross` is not found, falls back to uploading `.py` with a warning.
|
|
170
173
|
|
|
174
|
+
### Deploy project (sync)
|
|
175
|
+
|
|
176
|
+
`sync` uploads a whole project to the device based on a `.mpyproject`
|
|
177
|
+
configuration file. It is the command-line counterpart of the deploy feature in
|
|
178
|
+
the [Sublime Text plugin](https://github.com/cortexm/mpytool-sublime) and shares
|
|
179
|
+
the same `.mpyproject` format. Unlike the plugin it is non-interactive: when more
|
|
180
|
+
than one device is connected, pass `-p` to pick the port.
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
$ mpytool sync # deploy project in CWD (.mpyproject searched upward)
|
|
184
|
+
$ mpytool sync ~/projects/blink # deploy a project in another directory
|
|
185
|
+
$ mpytool -p /dev/ttyACM0 sync # pick port explicitly (multiple devices)
|
|
186
|
+
$ mpytool sync -- reset -- monitor # deploy, then reset and watch output
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The `.mpyproject` file (JSON, trailing commas tolerated) describes what to upload:
|
|
190
|
+
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"name": "blink",
|
|
194
|
+
"compile": true,
|
|
195
|
+
"deploy": {
|
|
196
|
+
"": ["./"],
|
|
197
|
+
"/lib/": ["../shared/utils.py"],
|
|
198
|
+
"/main.py": "src/main_dev.py"
|
|
199
|
+
},
|
|
200
|
+
"exclude": ["*.bak", "tests"]
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
- `deploy` maps a device destination to its source(s), relative to the project
|
|
205
|
+
root. A key of `""` or one ending in `/` is a **directory** (value is a list of
|
|
206
|
+
sources); any other key is a **rename** (value is a single source file).
|
|
207
|
+
Defaults to `{"": ["./"]}` (whole project to root) when omitted.
|
|
208
|
+
- `compile: true` compiles `.py` to `.mpy` (same as `cp -m`).
|
|
209
|
+
- `exclude` adds ignore patterns on top of the defaults (`__pycache__`, `*.pyc`,
|
|
210
|
+
dotfiles). Combined with global `-e` patterns.
|
|
211
|
+
|
|
212
|
+
The connection fields (`port`, `address`) used by the Sublime plugin are ignored
|
|
213
|
+
by the CLI — use the global `-p` / `-a` options instead.
|
|
214
|
+
|
|
171
215
|
### Move/rename on device
|
|
172
216
|
```
|
|
173
217
|
$ mpytool mv :/old.py :/new.py # rename file
|
|
@@ -342,6 +386,35 @@ RUN: script.py (128 bytes)
|
|
|
342
386
|
Done!
|
|
343
387
|
```
|
|
344
388
|
|
|
389
|
+
The script can also come from **STDIN** using the `-` convention (or simply by
|
|
390
|
+
piping when no file is given), which is handy for generated code or one-off
|
|
391
|
+
snippets:
|
|
392
|
+
```
|
|
393
|
+
$ cat script.py | mpytool run - # explicit '-' = read STDIN
|
|
394
|
+
$ mpytool run - <<< 'print("hi")' # here-string (single line)
|
|
395
|
+
$ my_codegen | mpytool run # piped, no file argument
|
|
396
|
+
$ cat boot.py | mpytool run - -- reset -- monitor # run from STDIN, then chain
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
For **multi-line** code, feed a here-document straight into `run -` (no `cat`,
|
|
400
|
+
no pipe needed - the here-doc *is* the standard input):
|
|
401
|
+
```
|
|
402
|
+
$ mpytool run - << 'EOF'
|
|
403
|
+
import machine, time
|
|
404
|
+
led = machine.Pin('LED', machine.Pin.OUT)
|
|
405
|
+
for _ in range(3):
|
|
406
|
+
led.toggle()
|
|
407
|
+
time.sleep(0.2)
|
|
408
|
+
print('done')
|
|
409
|
+
EOF
|
|
410
|
+
```
|
|
411
|
+
Quote the delimiter (`'EOF'`) so the shell does not expand `$`, backticks or
|
|
412
|
+
`{}` inside the Python code. The closing `EOF` must be on a line of its own.
|
|
413
|
+
|
|
414
|
+
Because STDIN can be read only once, at most one command per invocation may
|
|
415
|
+
consume it: combining a STDIN `run` with another STDIN `run` or with `repl`
|
|
416
|
+
(which needs the terminal) is rejected up front with a clear error.
|
|
417
|
+
|
|
345
418
|
### Edit file on device
|
|
346
419
|
```
|
|
347
420
|
$ mpytool edit :boot.py # edit file (uses $VISUAL or $EDITOR)
|
|
@@ -484,10 +557,25 @@ Version: 3.4.0; MicroPython v1.27.0 on 2025-12-09
|
|
|
484
557
|
Impl: micropython
|
|
485
558
|
Machine: Raspberry Pi Pico with RP2040
|
|
486
559
|
Serial: e660123456789abc
|
|
487
|
-
|
|
560
|
+
GC heap: 36.4 KB / 240 KB (15.15%)
|
|
488
561
|
Flash: 120 KB / 1.38 MB (8.52%)
|
|
489
562
|
```
|
|
490
563
|
|
|
564
|
+
On ESP32 the physical memory regions are listed too (from the ESP-IDF
|
|
565
|
+
allocator), which stays stable regardless of what the GC heap is doing:
|
|
566
|
+
|
|
567
|
+
```
|
|
568
|
+
SRAM: 412 KB / 640 KB (64.38%)
|
|
569
|
+
PSRAM: 12.0 MB / 32.0 MB (37.50%)
|
|
570
|
+
GC heap: 209 KB / 20.0 MB (1.02%)
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
`GC heap` is the heap MicroPython uses for Python objects - **not** the amount
|
|
574
|
+
of RAM in the chip. On builds with PSRAM it grows and shrinks on demand, so its
|
|
575
|
+
total changes over time: right after a reset it can claim nearly all free PSRAM,
|
|
576
|
+
and it shrinks once drivers allocate PSRAM outside the GC heap (e.g. camera
|
|
577
|
+
framebuffers). Use the `SRAM` / `PSRAM` lines to judge actual memory pressure.
|
|
578
|
+
|
|
491
579
|
On devices with WiFi or Ethernet, MAC addresses are also shown:
|
|
492
580
|
```
|
|
493
581
|
MAC WiFi: aa:bb:cc:dd:ee:01
|
|
@@ -504,15 +592,18 @@ $ mpytool flash erase # quick erase (reset filesystem)
|
|
|
504
592
|
$ mpytool flash erase --full # full erase
|
|
505
593
|
|
|
506
594
|
# ESP32 - partitions (by label)
|
|
507
|
-
$ mpytool flash # list all partitions
|
|
508
|
-
Label Type Subtype Address Size Block
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
595
|
+
$ mpytool flash # list all partitions, sorted by address
|
|
596
|
+
Label Type Subtype Address Size Block Content Flags
|
|
597
|
+
--------------------------------------------------------------------------------------------
|
|
598
|
+
nvs data nvs 0x9000 16.0K
|
|
599
|
+
otadata data ota 0xd000 8.00K
|
|
600
|
+
phy_init data phy 0xf000 4.00K
|
|
601
|
+
ota_0 app ota_0 0x10000 2.50M app ESP32-P4 running, ota:valid
|
|
602
|
+
ota_1 app ota_1 0x290000 2.50M app ESP32-P4 ota:valid
|
|
603
|
+
vfs data fat 0x510000 10.9M 4.00K littlefs2
|
|
604
|
+
|
|
605
|
+
Boot partition: ota_0
|
|
606
|
+
Next OTA: ota_1
|
|
516
607
|
|
|
517
608
|
$ mpytool flash read vfs backup.bin # backup partition to file
|
|
518
609
|
$ mpytool flash write nvs nvs_backup.bin # restore partition from file
|
|
@@ -520,11 +611,39 @@ $ mpytool flash erase vfs # quick erase partition
|
|
|
520
611
|
$ mpytool flash erase vfs --full # full erase partition
|
|
521
612
|
```
|
|
522
613
|
|
|
614
|
+
`Content` shows what a partition actually holds - the detected filesystem for
|
|
615
|
+
data partitions, the image type for app partitions (`app <CHIP>`, or
|
|
616
|
+
`BOOTLOADER!` / `INVALID!` / `empty`). The `ota:` flags come from the `otadata`
|
|
617
|
+
partition and describe the **rollback bookkeeping** of a slot, not the image
|
|
618
|
+
stored in it: a slot can read `ota:valid` while holding an unbootable image.
|
|
619
|
+
|
|
523
620
|
### OTA firmware update (ESP32)
|
|
621
|
+
|
|
622
|
+
OTA needs an **app-only** image (`micropython.bin`), not the combined
|
|
623
|
+
`firmware.bin` (bootloader + partition table + app) that `esptool` writes.
|
|
624
|
+
mpytool checks the image header before uploading, so a wrong file is rejected
|
|
625
|
+
immediately instead of failing with `ESP_ERR_OTA_VALIDATE_FAILED` after the
|
|
626
|
+
whole transfer. It also refuses an image built for a different chip.
|
|
627
|
+
|
|
628
|
+
```
|
|
629
|
+
$ mpytool flash ota micropython.bin # write to next OTA partition
|
|
630
|
+
$ mpytool flash ota micropython.bin -- reset --machine # write and reboot
|
|
631
|
+
$ mpytool flash ota micropython.bin -- reset --machine -t 30 # ... with 30s timeout
|
|
632
|
+
$ mpytool flash ota micropython.bin --force # write despite failed validation
|
|
633
|
+
|
|
634
|
+
$ mpytool flash ota confirm # mark running app valid (cancel rollback)
|
|
635
|
+
$ mpytool flash ota boot # boot the other slot (manual rollback)
|
|
636
|
+
$ mpytool flash ota boot ota_1 # boot a specific app partition
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
If the firmware enables rollback, a freshly booted OTA image stays in
|
|
640
|
+
`pending-verify` and reverts on the next reset until it is confirmed - either by
|
|
641
|
+
the app itself (`esp32.Partition(esp32.Partition.RUNNING).mark_app_valid_cancel_rollback()`)
|
|
642
|
+
or from the host:
|
|
643
|
+
|
|
524
644
|
```
|
|
525
|
-
$ mpytool flash ota
|
|
526
|
-
$ mpytool flash ota
|
|
527
|
-
$ mpytool flash ota firmware.app-bin -- reset --machine -t 30 # flash and reboot with 30s timeout
|
|
645
|
+
$ mpytool flash ota micropython.bin -- reset --machine
|
|
646
|
+
$ mpytool flash ota confirm
|
|
528
647
|
```
|
|
529
648
|
|
|
530
649
|
### Multiple commands separated by `--`
|
|
@@ -18,6 +18,9 @@ It is an alternative to the official [mpremote](https://docs.micropython.org/en/
|
|
|
18
18
|
bootloader entry
|
|
19
19
|
- **Wipe device** - erase the whole filesystem and machine-reset
|
|
20
20
|
for a clean slate (handy at the start of automated tests)
|
|
21
|
+
- **Deploy projects** - `sync` uploads a whole project to the device
|
|
22
|
+
using a `.mpyproject` config file (deploy map, excludes, `.mpy`
|
|
23
|
+
compilation)
|
|
21
24
|
- **General-purpose serial terminal** - `repl` and `monitor` work
|
|
22
25
|
with any serial device
|
|
23
26
|
- **Mount local directory** - VFS mount (read-only or read-write)
|
|
@@ -153,6 +156,47 @@ Requires `mpy-cross` in PATH (`pip install mpy-cross`). If mpy-cross version dif
|
|
|
153
156
|
from device, automatically uses `-b` flag to target the correct bytecode version.
|
|
154
157
|
If `mpy-cross` is not found, falls back to uploading `.py` with a warning.
|
|
155
158
|
|
|
159
|
+
### Deploy project (sync)
|
|
160
|
+
|
|
161
|
+
`sync` uploads a whole project to the device based on a `.mpyproject`
|
|
162
|
+
configuration file. It is the command-line counterpart of the deploy feature in
|
|
163
|
+
the [Sublime Text plugin](https://github.com/cortexm/mpytool-sublime) and shares
|
|
164
|
+
the same `.mpyproject` format. Unlike the plugin it is non-interactive: when more
|
|
165
|
+
than one device is connected, pass `-p` to pick the port.
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
$ mpytool sync # deploy project in CWD (.mpyproject searched upward)
|
|
169
|
+
$ mpytool sync ~/projects/blink # deploy a project in another directory
|
|
170
|
+
$ mpytool -p /dev/ttyACM0 sync # pick port explicitly (multiple devices)
|
|
171
|
+
$ mpytool sync -- reset -- monitor # deploy, then reset and watch output
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The `.mpyproject` file (JSON, trailing commas tolerated) describes what to upload:
|
|
175
|
+
|
|
176
|
+
```json
|
|
177
|
+
{
|
|
178
|
+
"name": "blink",
|
|
179
|
+
"compile": true,
|
|
180
|
+
"deploy": {
|
|
181
|
+
"": ["./"],
|
|
182
|
+
"/lib/": ["../shared/utils.py"],
|
|
183
|
+
"/main.py": "src/main_dev.py"
|
|
184
|
+
},
|
|
185
|
+
"exclude": ["*.bak", "tests"]
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
- `deploy` maps a device destination to its source(s), relative to the project
|
|
190
|
+
root. A key of `""` or one ending in `/` is a **directory** (value is a list of
|
|
191
|
+
sources); any other key is a **rename** (value is a single source file).
|
|
192
|
+
Defaults to `{"": ["./"]}` (whole project to root) when omitted.
|
|
193
|
+
- `compile: true` compiles `.py` to `.mpy` (same as `cp -m`).
|
|
194
|
+
- `exclude` adds ignore patterns on top of the defaults (`__pycache__`, `*.pyc`,
|
|
195
|
+
dotfiles). Combined with global `-e` patterns.
|
|
196
|
+
|
|
197
|
+
The connection fields (`port`, `address`) used by the Sublime plugin are ignored
|
|
198
|
+
by the CLI — use the global `-p` / `-a` options instead.
|
|
199
|
+
|
|
156
200
|
### Move/rename on device
|
|
157
201
|
```
|
|
158
202
|
$ mpytool mv :/old.py :/new.py # rename file
|
|
@@ -327,6 +371,35 @@ RUN: script.py (128 bytes)
|
|
|
327
371
|
Done!
|
|
328
372
|
```
|
|
329
373
|
|
|
374
|
+
The script can also come from **STDIN** using the `-` convention (or simply by
|
|
375
|
+
piping when no file is given), which is handy for generated code or one-off
|
|
376
|
+
snippets:
|
|
377
|
+
```
|
|
378
|
+
$ cat script.py | mpytool run - # explicit '-' = read STDIN
|
|
379
|
+
$ mpytool run - <<< 'print("hi")' # here-string (single line)
|
|
380
|
+
$ my_codegen | mpytool run # piped, no file argument
|
|
381
|
+
$ cat boot.py | mpytool run - -- reset -- monitor # run from STDIN, then chain
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
For **multi-line** code, feed a here-document straight into `run -` (no `cat`,
|
|
385
|
+
no pipe needed - the here-doc *is* the standard input):
|
|
386
|
+
```
|
|
387
|
+
$ mpytool run - << 'EOF'
|
|
388
|
+
import machine, time
|
|
389
|
+
led = machine.Pin('LED', machine.Pin.OUT)
|
|
390
|
+
for _ in range(3):
|
|
391
|
+
led.toggle()
|
|
392
|
+
time.sleep(0.2)
|
|
393
|
+
print('done')
|
|
394
|
+
EOF
|
|
395
|
+
```
|
|
396
|
+
Quote the delimiter (`'EOF'`) so the shell does not expand `$`, backticks or
|
|
397
|
+
`{}` inside the Python code. The closing `EOF` must be on a line of its own.
|
|
398
|
+
|
|
399
|
+
Because STDIN can be read only once, at most one command per invocation may
|
|
400
|
+
consume it: combining a STDIN `run` with another STDIN `run` or with `repl`
|
|
401
|
+
(which needs the terminal) is rejected up front with a clear error.
|
|
402
|
+
|
|
330
403
|
### Edit file on device
|
|
331
404
|
```
|
|
332
405
|
$ mpytool edit :boot.py # edit file (uses $VISUAL or $EDITOR)
|
|
@@ -469,10 +542,25 @@ Version: 3.4.0; MicroPython v1.27.0 on 2025-12-09
|
|
|
469
542
|
Impl: micropython
|
|
470
543
|
Machine: Raspberry Pi Pico with RP2040
|
|
471
544
|
Serial: e660123456789abc
|
|
472
|
-
|
|
545
|
+
GC heap: 36.4 KB / 240 KB (15.15%)
|
|
473
546
|
Flash: 120 KB / 1.38 MB (8.52%)
|
|
474
547
|
```
|
|
475
548
|
|
|
549
|
+
On ESP32 the physical memory regions are listed too (from the ESP-IDF
|
|
550
|
+
allocator), which stays stable regardless of what the GC heap is doing:
|
|
551
|
+
|
|
552
|
+
```
|
|
553
|
+
SRAM: 412 KB / 640 KB (64.38%)
|
|
554
|
+
PSRAM: 12.0 MB / 32.0 MB (37.50%)
|
|
555
|
+
GC heap: 209 KB / 20.0 MB (1.02%)
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
`GC heap` is the heap MicroPython uses for Python objects - **not** the amount
|
|
559
|
+
of RAM in the chip. On builds with PSRAM it grows and shrinks on demand, so its
|
|
560
|
+
total changes over time: right after a reset it can claim nearly all free PSRAM,
|
|
561
|
+
and it shrinks once drivers allocate PSRAM outside the GC heap (e.g. camera
|
|
562
|
+
framebuffers). Use the `SRAM` / `PSRAM` lines to judge actual memory pressure.
|
|
563
|
+
|
|
476
564
|
On devices with WiFi or Ethernet, MAC addresses are also shown:
|
|
477
565
|
```
|
|
478
566
|
MAC WiFi: aa:bb:cc:dd:ee:01
|
|
@@ -489,15 +577,18 @@ $ mpytool flash erase # quick erase (reset filesystem)
|
|
|
489
577
|
$ mpytool flash erase --full # full erase
|
|
490
578
|
|
|
491
579
|
# ESP32 - partitions (by label)
|
|
492
|
-
$ mpytool flash # list all partitions
|
|
493
|
-
Label Type Subtype Address Size Block
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
580
|
+
$ mpytool flash # list all partitions, sorted by address
|
|
581
|
+
Label Type Subtype Address Size Block Content Flags
|
|
582
|
+
--------------------------------------------------------------------------------------------
|
|
583
|
+
nvs data nvs 0x9000 16.0K
|
|
584
|
+
otadata data ota 0xd000 8.00K
|
|
585
|
+
phy_init data phy 0xf000 4.00K
|
|
586
|
+
ota_0 app ota_0 0x10000 2.50M app ESP32-P4 running, ota:valid
|
|
587
|
+
ota_1 app ota_1 0x290000 2.50M app ESP32-P4 ota:valid
|
|
588
|
+
vfs data fat 0x510000 10.9M 4.00K littlefs2
|
|
589
|
+
|
|
590
|
+
Boot partition: ota_0
|
|
591
|
+
Next OTA: ota_1
|
|
501
592
|
|
|
502
593
|
$ mpytool flash read vfs backup.bin # backup partition to file
|
|
503
594
|
$ mpytool flash write nvs nvs_backup.bin # restore partition from file
|
|
@@ -505,11 +596,39 @@ $ mpytool flash erase vfs # quick erase partition
|
|
|
505
596
|
$ mpytool flash erase vfs --full # full erase partition
|
|
506
597
|
```
|
|
507
598
|
|
|
599
|
+
`Content` shows what a partition actually holds - the detected filesystem for
|
|
600
|
+
data partitions, the image type for app partitions (`app <CHIP>`, or
|
|
601
|
+
`BOOTLOADER!` / `INVALID!` / `empty`). The `ota:` flags come from the `otadata`
|
|
602
|
+
partition and describe the **rollback bookkeeping** of a slot, not the image
|
|
603
|
+
stored in it: a slot can read `ota:valid` while holding an unbootable image.
|
|
604
|
+
|
|
508
605
|
### OTA firmware update (ESP32)
|
|
606
|
+
|
|
607
|
+
OTA needs an **app-only** image (`micropython.bin`), not the combined
|
|
608
|
+
`firmware.bin` (bootloader + partition table + app) that `esptool` writes.
|
|
609
|
+
mpytool checks the image header before uploading, so a wrong file is rejected
|
|
610
|
+
immediately instead of failing with `ESP_ERR_OTA_VALIDATE_FAILED` after the
|
|
611
|
+
whole transfer. It also refuses an image built for a different chip.
|
|
612
|
+
|
|
613
|
+
```
|
|
614
|
+
$ mpytool flash ota micropython.bin # write to next OTA partition
|
|
615
|
+
$ mpytool flash ota micropython.bin -- reset --machine # write and reboot
|
|
616
|
+
$ mpytool flash ota micropython.bin -- reset --machine -t 30 # ... with 30s timeout
|
|
617
|
+
$ mpytool flash ota micropython.bin --force # write despite failed validation
|
|
618
|
+
|
|
619
|
+
$ mpytool flash ota confirm # mark running app valid (cancel rollback)
|
|
620
|
+
$ mpytool flash ota boot # boot the other slot (manual rollback)
|
|
621
|
+
$ mpytool flash ota boot ota_1 # boot a specific app partition
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
If the firmware enables rollback, a freshly booted OTA image stays in
|
|
625
|
+
`pending-verify` and reverts on the next reset until it is confirmed - either by
|
|
626
|
+
the app itself (`esp32.Partition(esp32.Partition.RUNNING).mark_app_valid_cancel_rollback()`)
|
|
627
|
+
or from the host:
|
|
628
|
+
|
|
509
629
|
```
|
|
510
|
-
$ mpytool flash ota
|
|
511
|
-
$ mpytool flash ota
|
|
512
|
-
$ mpytool flash ota firmware.app-bin -- reset --machine -t 30 # flash and reboot with 30s timeout
|
|
630
|
+
$ mpytool flash ota micropython.bin -- reset --machine
|
|
631
|
+
$ mpytool flash ota confirm
|
|
513
632
|
```
|
|
514
633
|
|
|
515
634
|
### Multiple commands separated by `--`
|
|
@@ -681,14 +681,45 @@ mpy.unique_id()
|
|
|
681
681
|
|
|
682
682
|
#### memory()
|
|
683
683
|
|
|
684
|
-
Get
|
|
684
|
+
Get MicroPython **GC heap** usage - the heap available to Python objects, not
|
|
685
|
+
physical RAM. On ESP32 builds with PSRAM the GC heap grows and shrinks on
|
|
686
|
+
demand, so `total` changes over time: right after a reset it can claim nearly
|
|
687
|
+
all free PSRAM, and it shrinks again once drivers allocate PSRAM outside the GC
|
|
688
|
+
heap (e.g. camera framebuffers via `heap_caps_malloc`). Use
|
|
689
|
+
[`esp_ram()`](#esp_ram) for the physical memory regions.
|
|
685
690
|
|
|
686
691
|
```python
|
|
687
692
|
mpy.memory()
|
|
688
693
|
```
|
|
689
694
|
|
|
690
695
|
**Returns:**
|
|
691
|
-
- `dict`: `{'alloc': bytes_used, 'free': bytes_free, 'total':
|
|
696
|
+
- `dict`: `{'alloc': bytes_used, 'free': bytes_free, 'total': gc_heap_bytes}`
|
|
697
|
+
|
|
698
|
+
#### esp_ram()
|
|
699
|
+
|
|
700
|
+
Get physical RAM usage per region (ESP32 only), read via
|
|
701
|
+
`esp32.idf_heap_info()`. Unlike `memory()` this is stable, since it reflects the
|
|
702
|
+
actual silicon rather than the floating GC heap.
|
|
703
|
+
|
|
704
|
+
```python
|
|
705
|
+
mpy.esp_ram()
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
**Returns:**
|
|
709
|
+
- `dict` with `'sram'` and, when PSRAM is fitted, `'psram'`; each is
|
|
710
|
+
`{'total': bytes, 'free': bytes, 'used': bytes}`. Empty dict on non-ESP32
|
|
711
|
+
devices or builds without `esp32.idf_heap_info`.
|
|
712
|
+
|
|
713
|
+
**Note:** totals cover memory registered with the ESP-IDF allocator, so they
|
|
714
|
+
exclude static `.data`/`.bss` and code in IRAM - they are the heap-available
|
|
715
|
+
portion of each region, not the raw chip capacity.
|
|
716
|
+
|
|
717
|
+
**Example:**
|
|
718
|
+
```python
|
|
719
|
+
>>> mpy.esp_ram()
|
|
720
|
+
{'sram': {'total': 640000, 'free': 140000, 'used': 500000},
|
|
721
|
+
'psram': {'total': 33554432, 'free': 20971520, 'used': 12582912}}
|
|
722
|
+
```
|
|
692
723
|
|
|
693
724
|
### Mount
|
|
694
725
|
|
|
@@ -953,9 +984,14 @@ Get partition table information (ESP32 only).
|
|
|
953
984
|
mpy.partitions()
|
|
954
985
|
```
|
|
955
986
|
|
|
987
|
+
Partitions are sorted by flash offset.
|
|
988
|
+
|
|
956
989
|
**Returns:**
|
|
957
990
|
- `dict` with keys:
|
|
958
|
-
- `'partitions'`: List of partition dicts with keys: `label`, `type`, `type_name`, `subtype`, `subtype_name`, `offset`, `size`, `encrypted`, `running`, `filesystem`, `fs_block_size
|
|
991
|
+
- `'partitions'`: List of partition dicts with keys: `label`, `type`, `type_name`, `subtype`, `subtype_name`, `offset`, `size`, `encrypted`, `running`, `filesystem`, `fs_block_size`, plus for OTA app partitions:
|
|
992
|
+
- `'ota_state'`: rollback state recorded in `otadata` - `'new'`, `'pending-verify'`, `'valid'`, `'invalid'`, `'aborted'` or `'undefined'`. This is bookkeeping for the *slot*; it says nothing about the image currently stored there.
|
|
993
|
+
- `'ota_boot'`: `True` for the slot the bootloader will start
|
|
994
|
+
- `'image'`: what the partition actually holds, as returned by [`parse_esp_image()`](#parse_esp_imagedata)
|
|
959
995
|
- `'boot'`: Boot partition label or None
|
|
960
996
|
- `'next_ota'`: Next OTA partition label or None
|
|
961
997
|
- `'next_ota_size'`: Next OTA partition size or None
|
|
@@ -1068,24 +1104,146 @@ mpy.flash_erase(label=None, full=False, progress_callback=None)
|
|
|
1068
1104
|
>>> mpy.flash_erase(label='nvs', full=True)
|
|
1069
1105
|
```
|
|
1070
1106
|
|
|
1071
|
-
#### ota_write(data, progress_callback=None, compress=None)
|
|
1107
|
+
#### ota_write(data, progress_callback=None, compress=None, validate=True, force=False)
|
|
1072
1108
|
|
|
1073
1109
|
Write firmware to next OTA partition and set it as boot partition.
|
|
1074
1110
|
|
|
1111
|
+
The image must be an **app-only** build (`micropython.bin`), not the combined
|
|
1112
|
+
`firmware.bin` that also contains the bootloader and partition table.
|
|
1113
|
+
|
|
1075
1114
|
```python
|
|
1076
|
-
mpy.ota_write(data, progress_callback=None, compress=None
|
|
1115
|
+
mpy.ota_write(data, progress_callback=None, compress=None,
|
|
1116
|
+
validate=True, force=False)
|
|
1077
1117
|
```
|
|
1078
1118
|
|
|
1079
1119
|
**Parameters:**
|
|
1080
1120
|
- `data` (bytes): Firmware content (.app-bin file)
|
|
1081
1121
|
- `progress_callback` (callable, optional): Callback `(transferred, total, wire_bytes)`
|
|
1082
1122
|
- `compress` (bool, optional): Enable/disable compression
|
|
1123
|
+
- `validate` (bool): Check the image header first; set `False` only if `check_ota_image()` was already called on this data
|
|
1124
|
+
- `force` (bool): With `validate`, warn instead of refusing a bad image
|
|
1083
1125
|
|
|
1084
1126
|
**Returns:**
|
|
1085
1127
|
- `dict`: `{'target': label, 'offset': partition_offset, 'size': fw_size, 'wire_bytes': bytes_sent, 'compressed': bool}`
|
|
1086
1128
|
|
|
1087
1129
|
**Raises:**
|
|
1088
|
-
- `MpyError`: If OTA not available
|
|
1130
|
+
- `MpyError`: If OTA not available, firmware too large, or the image fails validation
|
|
1131
|
+
|
|
1132
|
+
#### ota_confirm()
|
|
1133
|
+
|
|
1134
|
+
Mark the running app valid, cancelling a pending OTA rollback (wraps
|
|
1135
|
+
`esp32.Partition.mark_app_valid_cancel_rollback()`). After a fresh OTA boot the
|
|
1136
|
+
image is in `pending-verify` and reverts on the next reset until confirmed.
|
|
1137
|
+
|
|
1138
|
+
```python
|
|
1139
|
+
mpy.ota_confirm()
|
|
1140
|
+
```
|
|
1141
|
+
|
|
1142
|
+
**Returns:**
|
|
1143
|
+
- `str`: Label of the running partition
|
|
1144
|
+
|
|
1145
|
+
**Raises:**
|
|
1146
|
+
- `MpyError`: On non-ESP32 devices, without OTA partitions, or if rollback is unsupported
|
|
1147
|
+
|
|
1148
|
+
#### ota_set_boot(label=None)
|
|
1149
|
+
|
|
1150
|
+
Switch the boot partition for the next reset.
|
|
1151
|
+
|
|
1152
|
+
```python
|
|
1153
|
+
mpy.ota_set_boot(label=None)
|
|
1154
|
+
```
|
|
1155
|
+
|
|
1156
|
+
**Parameters:**
|
|
1157
|
+
- `label` (str, optional): Target app partition (e.g. `'ota_1'`). If `None`, the passive slot from `get_next_update()` is used - a manual rollback.
|
|
1158
|
+
|
|
1159
|
+
**Returns:**
|
|
1160
|
+
- `str`: Label of the partition set as boot
|
|
1161
|
+
|
|
1162
|
+
**Raises:**
|
|
1163
|
+
- `MpyError`: On non-ESP32 devices, no OTA layout, or unknown label
|
|
1164
|
+
|
|
1165
|
+
**Example:**
|
|
1166
|
+
```python
|
|
1167
|
+
>>> mpy.ota_set_boot() # boot the other image
|
|
1168
|
+
'ota_1'
|
|
1169
|
+
>>> mpy.machine_reset()
|
|
1170
|
+
```
|
|
1171
|
+
|
|
1172
|
+
#### check_ota_image(data, force=False)
|
|
1173
|
+
|
|
1174
|
+
Validate firmware before an OTA upload. Rejects bootloader/combined images,
|
|
1175
|
+
empty or corrupt data, and images built for a different chip - turning a long
|
|
1176
|
+
upload followed by `ESP_ERR_OTA_VALIDATE_FAILED` into an immediate error.
|
|
1177
|
+
|
|
1178
|
+
```python
|
|
1179
|
+
mpy.check_ota_image(data, force=False)
|
|
1180
|
+
```
|
|
1181
|
+
|
|
1182
|
+
**Parameters:**
|
|
1183
|
+
- `data` (bytes): Firmware image
|
|
1184
|
+
- `force` (bool): Report problems as warnings and continue instead of raising
|
|
1185
|
+
|
|
1186
|
+
**Raises:**
|
|
1187
|
+
- `MpyError`: If the data is not an application image for this chip (unless `force`)
|
|
1188
|
+
|
|
1189
|
+
#### running_chip_id()
|
|
1190
|
+
|
|
1191
|
+
Chip id of the currently running application, read from its image header. The
|
|
1192
|
+
bootloader validates `chip_id` against the real silicon before booting, so a
|
|
1193
|
+
running image proves its id matches the actual chip - and the header layout is
|
|
1194
|
+
stable across IDF/MicroPython versions.
|
|
1195
|
+
|
|
1196
|
+
```python
|
|
1197
|
+
mpy.running_chip_id()
|
|
1198
|
+
```
|
|
1199
|
+
|
|
1200
|
+
The value is cached; `reset_state()` clears it.
|
|
1201
|
+
|
|
1202
|
+
**Returns:**
|
|
1203
|
+
- `int`: Chip id (see `mpytool.mpy.ESP_CHIP_IDS`), or `None` if undetermined
|
|
1204
|
+
|
|
1205
|
+
### Module Functions
|
|
1206
|
+
|
|
1207
|
+
#### esp_chip_name(chip_id)
|
|
1208
|
+
|
|
1209
|
+
Display name for an `esp_chip_id_t` value.
|
|
1210
|
+
|
|
1211
|
+
```python
|
|
1212
|
+
from mpytool import esp_chip_name
|
|
1213
|
+
```
|
|
1214
|
+
|
|
1215
|
+
**Parameters:**
|
|
1216
|
+
- `chip_id` (int or None): Chip id, e.g. from `running_chip_id()`
|
|
1217
|
+
|
|
1218
|
+
**Returns:**
|
|
1219
|
+
- `str`: Name such as `'ESP32-P4'`, or `'0x0099'` for a chip not in `ESP_CHIP_IDS` (the table is display-only, so newer chips still work); `None` if `chip_id` is `None`
|
|
1220
|
+
|
|
1221
|
+
#### parse_esp_image(data)
|
|
1222
|
+
|
|
1223
|
+
Parse the header of an ESP32 flash image. Distinguishes an application image
|
|
1224
|
+
(what OTA requires) from a bootloader or combined flash image.
|
|
1225
|
+
|
|
1226
|
+
```python
|
|
1227
|
+
from mpytool import parse_esp_image
|
|
1228
|
+
```
|
|
1229
|
+
|
|
1230
|
+
**Parameters:**
|
|
1231
|
+
- `data` (bytes): At least the first ~176 bytes of the image
|
|
1232
|
+
|
|
1233
|
+
**Returns:**
|
|
1234
|
+
- `dict` with `'kind'` (`'app'`, `'bootloader'`, `'empty'` or `'invalid'`), plus:
|
|
1235
|
+
- `'chip_id'`, `'chip_name'`, `'segments'` (app/bootloader)
|
|
1236
|
+
- `'idf_ver'`, `'date'`, `'time'`, `'version'`, `'project'` (app only; MicroPython leaves version/project empty)
|
|
1237
|
+
- `'app_offset'` - where the app starts inside a combined image (bootloader only)
|
|
1238
|
+
|
|
1239
|
+
**Example:**
|
|
1240
|
+
```python
|
|
1241
|
+
>>> parse_esp_image(open('micropython.bin', 'rb').read())
|
|
1242
|
+
{'segments': 8, 'chip_id': 18, 'chip_name': 'ESP32-P4', 'kind': 'app', ...}
|
|
1243
|
+
>>> parse_esp_image(open('firmware.bin', 'rb').read())
|
|
1244
|
+
{'segments': 3, 'chip_id': 18, 'chip_name': 'ESP32-P4', 'kind': 'bootloader',
|
|
1245
|
+
'app_offset': 57344}
|
|
1246
|
+
```
|
|
1089
1247
|
|
|
1090
1248
|
## MpyComm Class
|
|
1091
1249
|
|
|
@@ -269,16 +269,24 @@ _mpytool() {
|
|
|
269
269
|
fi
|
|
270
270
|
fi
|
|
271
271
|
;;
|
|
272
|
+
sync)
|
|
273
|
+
# Optional local project directory, -- anytime
|
|
274
|
+
if [[ $pos -eq 2 ]]; then
|
|
275
|
+
_files -/
|
|
276
|
+
fi
|
|
277
|
+
compadd -- '--'
|
|
278
|
+
;;
|
|
272
279
|
exec)
|
|
273
280
|
# 1 code string, -- after it
|
|
274
281
|
[[ $nargs -ge 1 ]] && compadd -- '--'
|
|
275
282
|
;;
|
|
276
283
|
run)
|
|
277
|
-
#
|
|
284
|
+
# optional local .py file or '-' for STDIN, -- after it
|
|
278
285
|
if [[ $pos -eq 2 ]]; then
|
|
279
286
|
_files -g '*.py'
|
|
287
|
+
compadd -- '-'
|
|
280
288
|
fi
|
|
281
|
-
|
|
289
|
+
compadd -- '--'
|
|
282
290
|
;;
|
|
283
291
|
edit)
|
|
284
292
|
# --editor CMD + 1 remote file, -- after file
|
|
@@ -323,10 +331,23 @@ _mpytool() {
|
|
|
323
331
|
)
|
|
324
332
|
_describe -t subcmds 'flash subcommand' subcmds
|
|
325
333
|
elif [[ "$flash_subcmd" == "ota" ]]; then
|
|
326
|
-
# ota:
|
|
334
|
+
# ota: <firmware.bin> | confirm | boot [label]
|
|
335
|
+
local ota_action="${words[cmd_start+2]}"
|
|
327
336
|
if [[ $pos -eq 3 ]]; then
|
|
337
|
+
local -a otacmds=(
|
|
338
|
+
'confirm:mark running app valid (cancel rollback)'
|
|
339
|
+
'boot:switch boot partition'
|
|
340
|
+
)
|
|
341
|
+
_describe -t otacmds 'ota action' otacmds
|
|
328
342
|
_files -g "*.bin"
|
|
343
|
+
compadd -- '--force'
|
|
344
|
+
elif [[ "$ota_action" == "boot" && $pos -eq 4 ]]; then
|
|
345
|
+
# partition label (device-specific, no completion)
|
|
346
|
+
compadd -- '--'
|
|
347
|
+
elif [[ "$ota_action" == "confirm" ]]; then
|
|
348
|
+
compadd -- '--'
|
|
329
349
|
else
|
|
350
|
+
compadd -- '--force'
|
|
330
351
|
compadd -- '--'
|
|
331
352
|
fi
|
|
332
353
|
elif [[ $pos -eq 3 && "$flash_subcmd" == "erase" ]]; then
|
|
@@ -344,9 +365,9 @@ _mpytool() {
|
|
|
344
365
|
# -- after complete subcommand
|
|
345
366
|
if [[ "$flash_subcmd" == "erase" && $pos -ge 3 ]]; then
|
|
346
367
|
: # already handled above
|
|
347
|
-
elif [[ "$flash_subcmd" == "ota"
|
|
348
|
-
|
|
349
|
-
elif [[ "$flash_subcmd" != "erase" &&
|
|
368
|
+
elif [[ "$flash_subcmd" == "ota" ]]; then
|
|
369
|
+
: # already handled above
|
|
370
|
+
elif [[ "$flash_subcmd" != "erase" && $pos -ge 5 ]]; then
|
|
350
371
|
compadd -- '--'
|
|
351
372
|
fi
|
|
352
373
|
;;
|