mpytool 2.5.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.5.0 → mpytool-2.6.0}/PKG-INFO +89 -14
- {mpytool-2.5.0 → mpytool-2.6.0}/README.md +88 -13
- {mpytool-2.5.0 → mpytool-2.6.0}/README_API.md +164 -6
- {mpytool-2.5.0 → mpytool-2.6.0}/completions/_mpytool +20 -6
- {mpytool-2.5.0 → mpytool-2.6.0}/completions/mpytool.bash +16 -7
- {mpytool-2.5.0 → mpytool-2.6.0}/mpytool/__init__.py +3 -1
- {mpytool-2.5.0 → mpytool-2.6.0}/mpytool/_version.py +3 -3
- {mpytool-2.5.0 → mpytool-2.6.0}/mpytool/conn.py +34 -5
- {mpytool-2.5.0 → mpytool-2.6.0}/mpytool/conn_serial.py +16 -3
- {mpytool-2.5.0 → mpytool-2.6.0}/mpytool/conn_socket.py +1 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/mpytool/mpy.py +418 -5
- {mpytool-2.5.0 → mpytool-2.6.0}/mpytool/mpy_comm.py +27 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/mpytool/mpytool.py +188 -20
- {mpytool-2.5.0 → mpytool-2.6.0}/mpytool/terminal_unix.py +24 -14
- {mpytool-2.5.0 → mpytool-2.6.0}/mpytool/utils.py +4 -1
- {mpytool-2.5.0 → mpytool-2.6.0}/mpytool.egg-info/PKG-INFO +89 -14
- {mpytool-2.5.0 → mpytool-2.6.0}/mpytool.egg-info/SOURCES.txt +1 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/mpytool.egg-info/scm_file_list.json +1 -0
- mpytool-2.6.0/mpytool.egg-info/scm_version.json +8 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/tests/test_cli.py +12 -0
- mpytool-2.6.0/tests/test_conn_serial.py +38 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/tests/test_mpy.py +395 -1
- {mpytool-2.5.0 → mpytool-2.6.0}/tests/test_mpytool.py +104 -5
- {mpytool-2.5.0 → mpytool-2.6.0}/tests/test_utils.py +1 -1
- mpytool-2.5.0/mpytool.egg-info/scm_version.json +0 -8
- {mpytool-2.5.0 → mpytool-2.6.0}/.github/workflows/integration.yml +0 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/.github/workflows/publish.yml +0 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/.github/workflows/tests.yml +0 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/.gitignore +0 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/LICENSE +0 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/README_bench.md +0 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/README_mpremote.md +0 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/mpytool/cmd_cp.py +0 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/mpytool/logger.py +0 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/mpytool/mount.py +0 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/mpytool/mpy_cross.py +0 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/mpytool/speedtest.py +0 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/mpytool/terminal.py +0 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/mpytool/terminal_win.py +0 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/mpytool.egg-info/dependency_links.txt +0 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/mpytool.egg-info/entry_points.txt +0 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/mpytool.egg-info/requires.txt +0 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/mpytool.egg-info/top_level.txt +0 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/pyproject.toml +0 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/setup.cfg +0 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/tests/__init__.py +0 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/tests/test_cmd_cp.py +0 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/tests/test_conn_socket.py +0 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/tests/test_errors.py +0 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/tests/test_helpers.py +0 -0
- {mpytool-2.5.0 → mpytool-2.6.0}/tests/test_integration.py +0 -0
- {mpytool-2.5.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
|
|
@@ -386,6 +386,35 @@ RUN: script.py (128 bytes)
|
|
|
386
386
|
Done!
|
|
387
387
|
```
|
|
388
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
|
+
|
|
389
418
|
### Edit file on device
|
|
390
419
|
```
|
|
391
420
|
$ mpytool edit :boot.py # edit file (uses $VISUAL or $EDITOR)
|
|
@@ -528,10 +557,25 @@ Version: 3.4.0; MicroPython v1.27.0 on 2025-12-09
|
|
|
528
557
|
Impl: micropython
|
|
529
558
|
Machine: Raspberry Pi Pico with RP2040
|
|
530
559
|
Serial: e660123456789abc
|
|
531
|
-
|
|
560
|
+
GC heap: 36.4 KB / 240 KB (15.15%)
|
|
532
561
|
Flash: 120 KB / 1.38 MB (8.52%)
|
|
533
562
|
```
|
|
534
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
|
+
|
|
535
579
|
On devices with WiFi or Ethernet, MAC addresses are also shown:
|
|
536
580
|
```
|
|
537
581
|
MAC WiFi: aa:bb:cc:dd:ee:01
|
|
@@ -548,15 +592,18 @@ $ mpytool flash erase # quick erase (reset filesystem)
|
|
|
548
592
|
$ mpytool flash erase --full # full erase
|
|
549
593
|
|
|
550
594
|
# ESP32 - partitions (by label)
|
|
551
|
-
$ mpytool flash # list all partitions
|
|
552
|
-
Label Type Subtype Address Size Block
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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
|
|
560
607
|
|
|
561
608
|
$ mpytool flash read vfs backup.bin # backup partition to file
|
|
562
609
|
$ mpytool flash write nvs nvs_backup.bin # restore partition from file
|
|
@@ -564,11 +611,39 @@ $ mpytool flash erase vfs # quick erase partition
|
|
|
564
611
|
$ mpytool flash erase vfs --full # full erase partition
|
|
565
612
|
```
|
|
566
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
|
+
|
|
567
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
|
+
|
|
568
644
|
```
|
|
569
|
-
$ mpytool flash ota
|
|
570
|
-
$ mpytool flash ota
|
|
571
|
-
$ 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
|
|
572
647
|
```
|
|
573
648
|
|
|
574
649
|
### Multiple commands separated by `--`
|
|
@@ -371,6 +371,35 @@ RUN: script.py (128 bytes)
|
|
|
371
371
|
Done!
|
|
372
372
|
```
|
|
373
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
|
+
|
|
374
403
|
### Edit file on device
|
|
375
404
|
```
|
|
376
405
|
$ mpytool edit :boot.py # edit file (uses $VISUAL or $EDITOR)
|
|
@@ -513,10 +542,25 @@ Version: 3.4.0; MicroPython v1.27.0 on 2025-12-09
|
|
|
513
542
|
Impl: micropython
|
|
514
543
|
Machine: Raspberry Pi Pico with RP2040
|
|
515
544
|
Serial: e660123456789abc
|
|
516
|
-
|
|
545
|
+
GC heap: 36.4 KB / 240 KB (15.15%)
|
|
517
546
|
Flash: 120 KB / 1.38 MB (8.52%)
|
|
518
547
|
```
|
|
519
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
|
+
|
|
520
564
|
On devices with WiFi or Ethernet, MAC addresses are also shown:
|
|
521
565
|
```
|
|
522
566
|
MAC WiFi: aa:bb:cc:dd:ee:01
|
|
@@ -533,15 +577,18 @@ $ mpytool flash erase # quick erase (reset filesystem)
|
|
|
533
577
|
$ mpytool flash erase --full # full erase
|
|
534
578
|
|
|
535
579
|
# ESP32 - partitions (by label)
|
|
536
|
-
$ mpytool flash # list all partitions
|
|
537
|
-
Label Type Subtype Address Size Block
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
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
|
|
545
592
|
|
|
546
593
|
$ mpytool flash read vfs backup.bin # backup partition to file
|
|
547
594
|
$ mpytool flash write nvs nvs_backup.bin # restore partition from file
|
|
@@ -549,11 +596,39 @@ $ mpytool flash erase vfs # quick erase partition
|
|
|
549
596
|
$ mpytool flash erase vfs --full # full erase partition
|
|
550
597
|
```
|
|
551
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
|
+
|
|
552
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
|
+
|
|
553
629
|
```
|
|
554
|
-
$ mpytool flash ota
|
|
555
|
-
$ mpytool flash ota
|
|
556
|
-
$ 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
|
|
557
632
|
```
|
|
558
633
|
|
|
559
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
|
|
|
@@ -281,11 +281,12 @@ _mpytool() {
|
|
|
281
281
|
[[ $nargs -ge 1 ]] && compadd -- '--'
|
|
282
282
|
;;
|
|
283
283
|
run)
|
|
284
|
-
#
|
|
284
|
+
# optional local .py file or '-' for STDIN, -- after it
|
|
285
285
|
if [[ $pos -eq 2 ]]; then
|
|
286
286
|
_files -g '*.py'
|
|
287
|
+
compadd -- '-'
|
|
287
288
|
fi
|
|
288
|
-
|
|
289
|
+
compadd -- '--'
|
|
289
290
|
;;
|
|
290
291
|
edit)
|
|
291
292
|
# --editor CMD + 1 remote file, -- after file
|
|
@@ -330,10 +331,23 @@ _mpytool() {
|
|
|
330
331
|
)
|
|
331
332
|
_describe -t subcmds 'flash subcommand' subcmds
|
|
332
333
|
elif [[ "$flash_subcmd" == "ota" ]]; then
|
|
333
|
-
# ota:
|
|
334
|
+
# ota: <firmware.bin> | confirm | boot [label]
|
|
335
|
+
local ota_action="${words[cmd_start+2]}"
|
|
334
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
|
|
335
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 -- '--'
|
|
336
349
|
else
|
|
350
|
+
compadd -- '--force'
|
|
337
351
|
compadd -- '--'
|
|
338
352
|
fi
|
|
339
353
|
elif [[ $pos -eq 3 && "$flash_subcmd" == "erase" ]]; then
|
|
@@ -351,9 +365,9 @@ _mpytool() {
|
|
|
351
365
|
# -- after complete subcommand
|
|
352
366
|
if [[ "$flash_subcmd" == "erase" && $pos -ge 3 ]]; then
|
|
353
367
|
: # already handled above
|
|
354
|
-
elif [[ "$flash_subcmd" == "ota"
|
|
355
|
-
|
|
356
|
-
elif [[ "$flash_subcmd" != "erase" &&
|
|
368
|
+
elif [[ "$flash_subcmd" == "ota" ]]; then
|
|
369
|
+
: # already handled above
|
|
370
|
+
elif [[ "$flash_subcmd" != "erase" && $pos -ge 5 ]]; then
|
|
357
371
|
compadd -- '--'
|
|
358
372
|
fi
|
|
359
373
|
;;
|
|
@@ -284,11 +284,12 @@ _mpytool() {
|
|
|
284
284
|
[[ $nargs -ge 1 && ( -z "$cur" || "--" == "$cur"* ) ]] && COMPREPLY+=("--")
|
|
285
285
|
;;
|
|
286
286
|
run)
|
|
287
|
-
#
|
|
287
|
+
# optional local .py file or '-' for STDIN, -- after it
|
|
288
288
|
if [[ $pos -eq 2 ]]; then
|
|
289
289
|
COMPREPLY=($(compgen -f -X '!*.py' -- "$cur"))
|
|
290
|
+
[[ "-" == "$cur"* ]] && COMPREPLY+=("-")
|
|
290
291
|
fi
|
|
291
|
-
[[
|
|
292
|
+
[[ -z "$cur" || "--" == "$cur"* ]] && COMPREPLY+=("--")
|
|
292
293
|
;;
|
|
293
294
|
edit)
|
|
294
295
|
# --editor CMD + 1 remote file, -- after file
|
|
@@ -324,10 +325,18 @@ _mpytool() {
|
|
|
324
325
|
if [[ $pos -eq 2 ]]; then
|
|
325
326
|
COMPREPLY=($(compgen -W "read write erase ota" -- "$cur"))
|
|
326
327
|
elif [[ "$flash_subcmd" == "ota" ]]; then
|
|
327
|
-
# ota:
|
|
328
|
+
# ota: <firmware.bin> | confirm | boot [label]
|
|
329
|
+
local ota_action="${COMP_WORDS[cmd_start+2]}"
|
|
328
330
|
if [[ $pos -eq 3 ]]; then
|
|
329
|
-
COMPREPLY=($(compgen -
|
|
331
|
+
COMPREPLY=($(compgen -W "confirm boot --force" -- "$cur"))
|
|
332
|
+
COMPREPLY+=($(compgen -f -X '!*.bin' -- "$cur"))
|
|
333
|
+
elif [[ "$ota_action" == "boot" && $pos -eq 4 ]]; then
|
|
334
|
+
# partition label (device-specific, no completion)
|
|
335
|
+
[[ -z "$cur" || "--" == "$cur"* ]] && COMPREPLY+=("--")
|
|
336
|
+
elif [[ "$ota_action" == "confirm" ]]; then
|
|
337
|
+
[[ -z "$cur" || "--" == "$cur"* ]] && COMPREPLY+=("--")
|
|
330
338
|
else
|
|
339
|
+
COMPREPLY=($(compgen -W "--force" -- "$cur"))
|
|
331
340
|
[[ -z "$cur" || "--" == "$cur"* ]] && COMPREPLY+=("--")
|
|
332
341
|
fi
|
|
333
342
|
elif [[ $pos -eq 3 && "$flash_subcmd" == "erase" ]]; then
|
|
@@ -343,9 +352,9 @@ _mpytool() {
|
|
|
343
352
|
[[ -z "$cur" || "--" == "$cur"* ]] && COMPREPLY+=("--")
|
|
344
353
|
fi
|
|
345
354
|
# -- after complete subcommand
|
|
346
|
-
if [[ "$flash_subcmd" == "ota"
|
|
347
|
-
|
|
348
|
-
elif [[ "$flash_subcmd" != "erase" &&
|
|
355
|
+
if [[ "$flash_subcmd" == "ota" ]]; then
|
|
356
|
+
: # already handled above
|
|
357
|
+
elif [[ "$flash_subcmd" != "erase" && $pos -ge 5 ]]; then
|
|
349
358
|
[[ -z "$cur" || "--" == "$cur"* ]] && COMPREPLY+=("--")
|
|
350
359
|
fi
|
|
351
360
|
;;
|
|
@@ -4,5 +4,7 @@ from mpytool.conn import ConnError, Timeout
|
|
|
4
4
|
from mpytool.conn_serial import ConnSerial
|
|
5
5
|
from mpytool.conn_socket import ConnSocket
|
|
6
6
|
from mpytool.mpy_comm import MpyComm, MpyError, CmdError
|
|
7
|
-
from mpytool.mpy import
|
|
7
|
+
from mpytool.mpy import (
|
|
8
|
+
Mpy, PathNotFound, FileNotFound, DirNotFound, parse_esp_image,
|
|
9
|
+
esp_chip_name)
|
|
8
10
|
from mpytool.logger import SimpleColorLogger
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '2.
|
|
22
|
-
__version_tuple__ = version_tuple = (2,
|
|
21
|
+
__version__ = version = '2.6.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 6, 0)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g69a52db1e'
|