idftool 0.2.0__tar.gz → 0.2.2__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.
- idftool-0.2.2/CHANGELOG.md +48 -0
- {idftool-0.2.0/idftool.egg-info → idftool-0.2.2}/PKG-INFO +22 -2
- idftool-0.2.0/PKG-INFO → idftool-0.2.2/README.md +20 -14
- {idftool-0.2.0 → idftool-0.2.2}/idftool/__main__.py +157 -17
- idftool-0.2.0/README.md → idftool-0.2.2/idftool.egg-info/PKG-INFO +34 -0
- {idftool-0.2.0 → idftool-0.2.2}/idftool.egg-info/SOURCES.txt +1 -0
- {idftool-0.2.0 → idftool-0.2.2}/idftool.egg-info/requires.txt +1 -1
- {idftool-0.2.0 → idftool-0.2.2}/pyproject.toml +1 -1
- {idftool-0.2.0 → idftool-0.2.2}/.github/workflows/build.yml +0 -0
- {idftool-0.2.0 → idftool-0.2.2}/.github/workflows/publish.yml +0 -0
- {idftool-0.2.0 → idftool-0.2.2}/.gitignore +0 -0
- {idftool-0.2.0 → idftool-0.2.2}/CONTRIBUTING.md +0 -0
- {idftool-0.2.0 → idftool-0.2.2}/LICENSE.md +0 -0
- {idftool-0.2.0 → idftool-0.2.2}/Makefile +0 -0
- {idftool-0.2.0 → idftool-0.2.2}/idftool/__init__.py +0 -0
- {idftool-0.2.0 → idftool-0.2.2}/idftool/traceback.py +0 -0
- {idftool-0.2.0 → idftool-0.2.2}/idftool-onedir.spec +0 -0
- {idftool-0.2.0 → idftool-0.2.2}/idftool.egg-info/dependency_links.txt +0 -0
- {idftool-0.2.0 → idftool-0.2.2}/idftool.egg-info/entry_points.txt +0 -0
- {idftool-0.2.0 → idftool-0.2.2}/idftool.egg-info/top_level.txt +0 -0
- {idftool-0.2.0 → idftool-0.2.2}/idftool.py +0 -0
- {idftool-0.2.0 → idftool-0.2.2}/idftool.spec +0 -0
- {idftool-0.2.0 → idftool-0.2.2}/setup.cfg +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format is
|
|
4
|
+
based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
|
5
|
+
this project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [v0.2.2] — 2026-06-15
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- `print-image` — inspect a flash image file offline: prints the
|
|
11
|
+
partition table and, for each app partition that contains a valid
|
|
12
|
+
app, the project name, version, IDF version, compile time, ELF
|
|
13
|
+
SHA256, and target chip.
|
|
14
|
+
- `print-bundle` — same output for a partition bundle ZIP, driven by
|
|
15
|
+
the embedded `partition_table.csv` and the per-partition `.bin`
|
|
16
|
+
entries.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- Bumped `esp-idf-defs` to `~=0.1.3`. `print_partition_table` now takes
|
|
20
|
+
a `(offset, length) -> bytes` accessor instead of an `ESPLoader`
|
|
21
|
+
instance, so the same printing path serves device, image, and bundle
|
|
22
|
+
inspection.
|
|
23
|
+
|
|
24
|
+
## [v0.2.1] — 2026-06-10
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- Boot partition state now uses `OtaImageState` for clearer reporting.
|
|
28
|
+
|
|
29
|
+
## [v0.2.0] — 2026-05-26
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
- `create-nvs` and `write-nvs` commands for generating NVS partition
|
|
33
|
+
images from CSV.
|
|
34
|
+
- Rich-style boxed traceback formatter for friendlier error output.
|
|
35
|
+
|
|
36
|
+
### Documentation
|
|
37
|
+
- README installation section and command index table.
|
|
38
|
+
- Recommend `pipx` over the standalone binary for installation.
|
|
39
|
+
|
|
40
|
+
## [v0.1.0] — 2026-05-25
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
- Initial release.
|
|
44
|
+
|
|
45
|
+
[v0.2.2]: https://github.com/nebkat/idftool/releases/tag/v0.2.2
|
|
46
|
+
[v0.2.1]: https://github.com/nebkat/idftool/releases/tag/v0.2.1
|
|
47
|
+
[v0.2.0]: https://github.com/nebkat/idftool/releases/tag/v0.2.0
|
|
48
|
+
[v0.1.0]: https://github.com/nebkat/idftool/releases/tag/v0.1.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: idftool
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Utility for flashing, provisioning, and interacting with Espressif SOCs running ESP-IDF
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Source, https://github.com/nebkat/idftool
|
|
@@ -8,7 +8,7 @@ Requires-Python: >=3.10
|
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
License-File: LICENSE.md
|
|
10
10
|
Requires-Dist: esptool~=5.2.0
|
|
11
|
-
Requires-Dist: esp-idf-defs~=0.1.
|
|
11
|
+
Requires-Dist: esp-idf-defs~=0.1.3
|
|
12
12
|
Requires-Dist: esp-idf-nvs-partition-gen~=0.2.0
|
|
13
13
|
Dynamic: license-file
|
|
14
14
|
|
|
@@ -120,10 +120,12 @@ Setting boot partition to 'ota_1'...
|
|
|
120
120
|
| [`create-image`](#create-image) | Merge partition binaries into a single flash image |
|
|
121
121
|
| [`dump-image`](#dump-image) | Dump the entire flash to an image file |
|
|
122
122
|
| [`write-image`](#write-image-alias-reflash) | Write a full flash image to the device |
|
|
123
|
+
| [`print-image`](#print-image) | Print partition table and app info from a flash image file |
|
|
123
124
|
| **Bundles** | |
|
|
124
125
|
| [`create-bundle`](#create-bundle) | Pack partition images into a ZIP bundle |
|
|
125
126
|
| [`dump-bundle`](#dump-bundle) | Pack every partition from the device into a ZIP |
|
|
126
127
|
| [`write-bundle`](#write-bundle) | Flash every binary in a bundle ZIP |
|
|
128
|
+
| [`print-bundle`](#print-bundle) | Print partition table and app info from a bundle ZIP |
|
|
127
129
|
| **NVS** | |
|
|
128
130
|
| [`create-nvs`](#create-nvs) | Generate an NVS partition image from a CSV file |
|
|
129
131
|
| [`write-nvs`](#write-nvs) | Generate an NVS image from CSV and flash it |
|
|
@@ -259,6 +261,15 @@ counterpart of `dump-image`.
|
|
|
259
261
|
idftool write-image build/full-flash.img
|
|
260
262
|
```
|
|
261
263
|
|
|
264
|
+
#### `print-image`
|
|
265
|
+
Inspect a flash image file without touching a device: prints the
|
|
266
|
+
partition table embedded in the image and, for each app partition that
|
|
267
|
+
contains a valid app, the project name, version, IDF version, compile
|
|
268
|
+
time, ELF SHA256, and target chip.
|
|
269
|
+
```text
|
|
270
|
+
idftool print-image build/full-flash.img
|
|
271
|
+
```
|
|
272
|
+
|
|
262
273
|
### Bundles
|
|
263
274
|
|
|
264
275
|
A bundle is a plain ZIP file containing one `*.bin` per partition (named
|
|
@@ -292,6 +303,15 @@ to match) instead of reading the table from flash.
|
|
|
292
303
|
idftool write-bundle release.zip
|
|
293
304
|
```
|
|
294
305
|
|
|
306
|
+
#### `print-bundle`
|
|
307
|
+
Inspect a bundle ZIP without touching a device: prints the partition
|
|
308
|
+
table from the embedded `partition_table.csv` and, for each app
|
|
309
|
+
partition whose `.bin` is present in the bundle, the project name,
|
|
310
|
+
version, IDF version, compile time, ELF SHA256, and target chip.
|
|
311
|
+
```text
|
|
312
|
+
idftool print-bundle release.zip
|
|
313
|
+
```
|
|
314
|
+
|
|
295
315
|
### NVS
|
|
296
316
|
|
|
297
317
|
idftool can generate a binary NVS (Non-Volatile Storage) partition image
|
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: idftool
|
|
3
|
-
Version: 0.2.0
|
|
4
|
-
Summary: Utility for flashing, provisioning, and interacting with Espressif SOCs running ESP-IDF
|
|
5
|
-
License-Expression: Apache-2.0
|
|
6
|
-
Project-URL: Source, https://github.com/nebkat/idftool
|
|
7
|
-
Requires-Python: >=3.10
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
License-File: LICENSE.md
|
|
10
|
-
Requires-Dist: esptool~=5.2.0
|
|
11
|
-
Requires-Dist: esp-idf-defs~=0.1.0
|
|
12
|
-
Requires-Dist: esp-idf-nvs-partition-gen~=0.2.0
|
|
13
|
-
Dynamic: license-file
|
|
14
|
-
|
|
15
1
|
# idftool
|
|
16
2
|
|
|
17
3
|
A CLI tool built on top of [`esptool`](https://github.com/espressif/esptool)
|
|
@@ -120,10 +106,12 @@ Setting boot partition to 'ota_1'...
|
|
|
120
106
|
| [`create-image`](#create-image) | Merge partition binaries into a single flash image |
|
|
121
107
|
| [`dump-image`](#dump-image) | Dump the entire flash to an image file |
|
|
122
108
|
| [`write-image`](#write-image-alias-reflash) | Write a full flash image to the device |
|
|
109
|
+
| [`print-image`](#print-image) | Print partition table and app info from a flash image file |
|
|
123
110
|
| **Bundles** | |
|
|
124
111
|
| [`create-bundle`](#create-bundle) | Pack partition images into a ZIP bundle |
|
|
125
112
|
| [`dump-bundle`](#dump-bundle) | Pack every partition from the device into a ZIP |
|
|
126
113
|
| [`write-bundle`](#write-bundle) | Flash every binary in a bundle ZIP |
|
|
114
|
+
| [`print-bundle`](#print-bundle) | Print partition table and app info from a bundle ZIP |
|
|
127
115
|
| **NVS** | |
|
|
128
116
|
| [`create-nvs`](#create-nvs) | Generate an NVS partition image from a CSV file |
|
|
129
117
|
| [`write-nvs`](#write-nvs) | Generate an NVS image from CSV and flash it |
|
|
@@ -259,6 +247,15 @@ counterpart of `dump-image`.
|
|
|
259
247
|
idftool write-image build/full-flash.img
|
|
260
248
|
```
|
|
261
249
|
|
|
250
|
+
#### `print-image`
|
|
251
|
+
Inspect a flash image file without touching a device: prints the
|
|
252
|
+
partition table embedded in the image and, for each app partition that
|
|
253
|
+
contains a valid app, the project name, version, IDF version, compile
|
|
254
|
+
time, ELF SHA256, and target chip.
|
|
255
|
+
```text
|
|
256
|
+
idftool print-image build/full-flash.img
|
|
257
|
+
```
|
|
258
|
+
|
|
262
259
|
### Bundles
|
|
263
260
|
|
|
264
261
|
A bundle is a plain ZIP file containing one `*.bin` per partition (named
|
|
@@ -292,6 +289,15 @@ to match) instead of reading the table from flash.
|
|
|
292
289
|
idftool write-bundle release.zip
|
|
293
290
|
```
|
|
294
291
|
|
|
292
|
+
#### `print-bundle`
|
|
293
|
+
Inspect a bundle ZIP without touching a device: prints the partition
|
|
294
|
+
table from the embedded `partition_table.csv` and, for each app
|
|
295
|
+
partition whose `.bin` is present in the bundle, the project name,
|
|
296
|
+
version, IDF version, compile time, ELF SHA256, and target chip.
|
|
297
|
+
```text
|
|
298
|
+
idftool print-bundle release.zip
|
|
299
|
+
```
|
|
300
|
+
|
|
295
301
|
### NVS
|
|
296
302
|
|
|
297
303
|
idftool can generate a binary NVS (Non-Volatile Storage) partition image
|
|
@@ -3,7 +3,7 @@ import os.path
|
|
|
3
3
|
import re
|
|
4
4
|
import sys
|
|
5
5
|
import time
|
|
6
|
-
from typing import Optional, Literal
|
|
6
|
+
from typing import Callable, Optional, Literal
|
|
7
7
|
from zipfile import ZipFile
|
|
8
8
|
|
|
9
9
|
from esptool import ESPLoader, CHIP_DEFS, flash_size_bytes
|
|
@@ -14,7 +14,7 @@ from serial.tools.list_ports_common import ListPortInfo
|
|
|
14
14
|
from idftool.traceback import install as install_excepthook, print_exception
|
|
15
15
|
|
|
16
16
|
from esp_idf_defs import ImageMetadata, ChipId
|
|
17
|
-
from esp_idf_defs.otadata import OtaDataParameters, OtaDataSelectEntry
|
|
17
|
+
from esp_idf_defs.otadata import OtaDataParameters, OtaDataSelectEntry, OtaImageState
|
|
18
18
|
from esp_idf_defs.partitions import PARTITION_TABLE_SIZE, PARTITION_TABLE_OFFSET, PartitionTable, print_partition_table, \
|
|
19
19
|
PartitionDefinition, BOOTLOADER_TYPE, SUBTYPES, PARTITION_TABLE_TYPE, APP_TYPE, NUM_PARTITION_SUBTYPE_APP_OTA, \
|
|
20
20
|
DATA_TYPE
|
|
@@ -451,7 +451,7 @@ def command_get_boot(esp: ESPLoader, partition_table: PartitionTable):
|
|
|
451
451
|
if otadata.slot is None:
|
|
452
452
|
print("OTA slot not set")
|
|
453
453
|
else:
|
|
454
|
-
print(f"OTA slot 'ota_{otadata.slot}' (seq={otadata.otadata.seq}, state={otadata.otadata.ota_state})")
|
|
454
|
+
print(f"OTA slot 'ota_{otadata.slot}' (seq={otadata.otadata.seq}, state={otadata.otadata.ota_state.name})")
|
|
455
455
|
|
|
456
456
|
def command_set_boot(esp: ESPLoader, partition_table: PartitionTable, label: str):
|
|
457
457
|
otadata_partition, otadata = read_otadata(esp, partition_table)
|
|
@@ -465,6 +465,7 @@ def command_set_boot(esp: ESPLoader, partition_table: PartitionTable, label: str
|
|
|
465
465
|
|
|
466
466
|
print(f"Setting boot partition to '{partition.name}'...")
|
|
467
467
|
otadata = otadata.incremented_and_swapped(ota_slot)
|
|
468
|
+
otadata.otadata.ota_state = OtaImageState.VALID
|
|
468
469
|
write_otadata(esp, otadata_partition, otadata)
|
|
469
470
|
|
|
470
471
|
def command_clear_boot(esp: ESPLoader, partition_table: PartitionTable):
|
|
@@ -574,19 +575,7 @@ def command_app_info(app_binary_file: str):
|
|
|
574
575
|
except ValueError as e:
|
|
575
576
|
raise RuntimeError(f"Invalid application binary: {e}")
|
|
576
577
|
|
|
577
|
-
|
|
578
|
-
header = image_metadata.header
|
|
579
|
-
|
|
580
|
-
print(f"Project name: {desc.project_name}")
|
|
581
|
-
print(f"Version: {desc.version}")
|
|
582
|
-
print(f"IDF version: {desc.idf_version}")
|
|
583
|
-
print(f"Secure version: {desc.secure_version}")
|
|
584
|
-
if desc.compiled:
|
|
585
|
-
print(f"Compiled: {desc.compiled}")
|
|
586
|
-
print(f"ELF SHA256: {desc.elf_sha256.hex()}")
|
|
587
|
-
max_rev = header.max_chip_rev_full if header.max_chip_rev_full != 0xFFFF else None
|
|
588
|
-
rev_range = f"rev {header.min_chip_rev_full} to {max_rev}" if max_rev else f"rev {header.min_chip_rev_full}+"
|
|
589
|
-
print(f"Chip: {header.chip_id.name} ({rev_range})")
|
|
578
|
+
print_app_info(image_metadata.app_description, image_metadata.header)
|
|
590
579
|
|
|
591
580
|
def command_write_image(esp: ESPLoader, bootloader_entry: PartitionDefinition, image_file_path: str):
|
|
592
581
|
image_file = open(image_file_path, 'rb')
|
|
@@ -618,6 +607,130 @@ def command_write_image(esp: ESPLoader, bootloader_entry: PartitionDefinition, i
|
|
|
618
607
|
flash_size='detect',
|
|
619
608
|
)
|
|
620
609
|
|
|
610
|
+
def print_app_info(desc, header, indent: str = ""):
|
|
611
|
+
print(f"{indent}Project name: {desc.project_name}")
|
|
612
|
+
print(f"{indent}Version: {desc.version}")
|
|
613
|
+
print(f"{indent}IDF version: {desc.idf_version}")
|
|
614
|
+
print(f"{indent}Secure version: {desc.secure_version}")
|
|
615
|
+
if desc.compiled:
|
|
616
|
+
print(f"{indent}Compiled: {desc.compiled}")
|
|
617
|
+
print(f"{indent}ELF SHA256: {desc.elf_sha256.hex()}")
|
|
618
|
+
max_rev = header.max_chip_rev_full if header.max_chip_rev_full != 0xFFFF else None
|
|
619
|
+
rev_range = f"rev {header.min_chip_rev_full} to {max_rev}" if max_rev else f"rev {header.min_chip_rev_full}+"
|
|
620
|
+
print(f"{indent}Chip: {header.chip_id.name} ({rev_range})")
|
|
621
|
+
|
|
622
|
+
def print_partition_table_and_apps(
|
|
623
|
+
partition_table: PartitionTable,
|
|
624
|
+
read: Callable[[int, int], bytes],
|
|
625
|
+
):
|
|
626
|
+
print_partition_table(partition_table, read)
|
|
627
|
+
|
|
628
|
+
for part in partition_table:
|
|
629
|
+
if part.type != APP_TYPE:
|
|
630
|
+
continue
|
|
631
|
+
app_binary = read(part.offset, part.size)
|
|
632
|
+
try:
|
|
633
|
+
image_metadata = ImageMetadata.from_bytes(app_binary, app_required=True)
|
|
634
|
+
except (RuntimeError, ValueError):
|
|
635
|
+
continue
|
|
636
|
+
print()
|
|
637
|
+
print(f"Partition '{part.name}' (offset={part.offset:#x}):")
|
|
638
|
+
print_app_info(image_metadata.app_description, image_metadata.header, indent=" ")
|
|
639
|
+
|
|
640
|
+
def command_print_image(image_file_path: str, partition_table_offset: int, partition_table_size: int):
|
|
641
|
+
image_size = os.path.getsize(image_file_path)
|
|
642
|
+
with open(image_file_path, 'rb') as f:
|
|
643
|
+
f.seek(partition_table_offset)
|
|
644
|
+
partition_table_binary = f.read(partition_table_size)
|
|
645
|
+
try:
|
|
646
|
+
partition_table = PartitionTable.from_binary(partition_table_binary)
|
|
647
|
+
except (RuntimeError, ValueError) as e:
|
|
648
|
+
raise RuntimeError(f"Could not parse partition table at offset {partition_table_offset:#x}") from e
|
|
649
|
+
|
|
650
|
+
def read(offset: int, length: int) -> bytes:
|
|
651
|
+
if offset >= image_size:
|
|
652
|
+
return b"\xff" * length
|
|
653
|
+
f.seek(offset)
|
|
654
|
+
data = f.read(length)
|
|
655
|
+
if len(data) < length:
|
|
656
|
+
data += b"\xff" * (length - len(data))
|
|
657
|
+
return data
|
|
658
|
+
|
|
659
|
+
print(f"Image: {image_file_path} ({image_size:#x} bytes)")
|
|
660
|
+
print()
|
|
661
|
+
print_partition_table_and_apps(partition_table, read)
|
|
662
|
+
|
|
663
|
+
def _extract_csv_bootloader_offsets(csv_text: str) -> tuple[Optional[int], Optional[int]]:
|
|
664
|
+
primary = recovery = None
|
|
665
|
+
for line in csv_text.splitlines():
|
|
666
|
+
line = line.strip()
|
|
667
|
+
if not line or line.startswith('#'):
|
|
668
|
+
continue
|
|
669
|
+
parts = [p.strip() for p in line.split(',')]
|
|
670
|
+
if len(parts) < 4 or parts[1] != 'bootloader':
|
|
671
|
+
continue
|
|
672
|
+
try:
|
|
673
|
+
offset = int(parts[3], 0)
|
|
674
|
+
except ValueError:
|
|
675
|
+
continue
|
|
676
|
+
if parts[2] == 'primary' and primary is None:
|
|
677
|
+
primary = offset
|
|
678
|
+
elif parts[2] == 'recovery' and recovery is None:
|
|
679
|
+
recovery = offset
|
|
680
|
+
return primary, recovery
|
|
681
|
+
|
|
682
|
+
def command_print_bundle(
|
|
683
|
+
bundle_file_path: str,
|
|
684
|
+
partition_table_offset: int,
|
|
685
|
+
primary_bootloader_offset: Optional[int],
|
|
686
|
+
recovery_bootloader_offset: Optional[int],
|
|
687
|
+
):
|
|
688
|
+
bundle_size = os.path.getsize(bundle_file_path)
|
|
689
|
+
with ZipFile(bundle_file_path, 'r') as zf:
|
|
690
|
+
names = zf.namelist()
|
|
691
|
+
if 'partition_table.csv' not in names:
|
|
692
|
+
raise RuntimeError(f"Bundle {bundle_file_path} does not contain a partition_table.csv")
|
|
693
|
+
csv_text = zf.read('partition_table.csv').decode('utf-8')
|
|
694
|
+
|
|
695
|
+
# idftool-generated bundles encode bootloader offsets in the CSV; PartitionTable.from_csv
|
|
696
|
+
# ignores the literal for bootloader rows and requires an explicit offset, so fall back to
|
|
697
|
+
# what the CSV says when the user didn't pass one.
|
|
698
|
+
if primary_bootloader_offset is None or recovery_bootloader_offset is None:
|
|
699
|
+
csv_primary, csv_recovery = _extract_csv_bootloader_offsets(csv_text)
|
|
700
|
+
if primary_bootloader_offset is None:
|
|
701
|
+
primary_bootloader_offset = csv_primary
|
|
702
|
+
if recovery_bootloader_offset is None:
|
|
703
|
+
recovery_bootloader_offset = csv_recovery
|
|
704
|
+
|
|
705
|
+
partition_table = PartitionTable.from_csv(
|
|
706
|
+
csv_text,
|
|
707
|
+
partition_table_offset=partition_table_offset,
|
|
708
|
+
primary_bootloader_offset=primary_bootloader_offset,
|
|
709
|
+
recovery_bootloader_offset=recovery_bootloader_offset,
|
|
710
|
+
)
|
|
711
|
+
|
|
712
|
+
partition_data = {
|
|
713
|
+
name[:-4]: zf.read(name)
|
|
714
|
+
for name in names if name.endswith('.bin')
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
def read(offset: int, length: int) -> bytes:
|
|
718
|
+
for part in partition_table:
|
|
719
|
+
if part.offset <= offset < part.offset + part.size:
|
|
720
|
+
local = offset - part.offset
|
|
721
|
+
data = partition_data.get(part.name, b"")
|
|
722
|
+
chunk = data[local:local + length]
|
|
723
|
+
if len(chunk) < length:
|
|
724
|
+
chunk += b"\xff" * (length - len(chunk))
|
|
725
|
+
return chunk
|
|
726
|
+
return b"\xff" * length
|
|
727
|
+
|
|
728
|
+
included = sorted(partition_data.keys())
|
|
729
|
+
print(f"Bundle: {bundle_file_path} ({bundle_size:#x} bytes)")
|
|
730
|
+
print(f"Partitions included: {', '.join(included) if included else '(none)'}")
|
|
731
|
+
print()
|
|
732
|
+
print_partition_table_and_apps(partition_table, read)
|
|
733
|
+
|
|
621
734
|
def command_dump_image(esp: ESPLoader, output_file: Optional[str]):
|
|
622
735
|
flash_size = flash_size_bytes(detect_flash_size(esp))
|
|
623
736
|
|
|
@@ -689,6 +802,25 @@ def main(args):
|
|
|
689
802
|
command_create_nvs(args.csv_file, args.size, args.output)
|
|
690
803
|
return
|
|
691
804
|
|
|
805
|
+
# print-image inspects a local image file and needs neither device nor external partition table
|
|
806
|
+
if args.command == 'print-image':
|
|
807
|
+
command_print_image(
|
|
808
|
+
image_file_path=args.image_file,
|
|
809
|
+
partition_table_offset=args.partition_table_offset,
|
|
810
|
+
partition_table_size=args.partition_table_size,
|
|
811
|
+
)
|
|
812
|
+
return
|
|
813
|
+
|
|
814
|
+
# print-bundle inspects a local bundle ZIP and needs neither device nor external partition table
|
|
815
|
+
if args.command == 'print-bundle':
|
|
816
|
+
command_print_bundle(
|
|
817
|
+
bundle_file_path=args.bundle_file,
|
|
818
|
+
partition_table_offset=args.partition_table_offset,
|
|
819
|
+
primary_bootloader_offset=args.primary_bootloader_offset,
|
|
820
|
+
recovery_bootloader_offset=args.recovery_bootloader_offset,
|
|
821
|
+
)
|
|
822
|
+
return
|
|
823
|
+
|
|
692
824
|
# Connect to ESP device if required
|
|
693
825
|
requires_esp = args.command not in ['list', 'create-image', 'create-bundle', 'create-nvs'] or not args.partition_table_file
|
|
694
826
|
esp = get_esp(port=args.port, baud=args.baud) if requires_esp else None
|
|
@@ -740,7 +872,7 @@ def main(args):
|
|
|
740
872
|
pass
|
|
741
873
|
|
|
742
874
|
# Print partition table
|
|
743
|
-
print_partition_table(partition_table, esp, otadata=otadata_params)
|
|
875
|
+
print_partition_table(partition_table, esp.read_flash if esp else None, otadata=otadata_params)
|
|
744
876
|
|
|
745
877
|
if esp:
|
|
746
878
|
flash_size_str = detect_flash_size(esp)
|
|
@@ -931,6 +1063,10 @@ def _main():
|
|
|
931
1063
|
write_image_parser = subparsers.add_parser('write-image', aliases=['reflash'], help='Write a full flash image to the device')
|
|
932
1064
|
write_image_parser.add_argument('image_file', help='Input file containing flash image')
|
|
933
1065
|
|
|
1066
|
+
# Print Image subcommand
|
|
1067
|
+
print_image_parser = subparsers.add_parser('print-image', help='Print partition table and app info from a flash image file')
|
|
1068
|
+
print_image_parser.add_argument('image_file', help='Input file containing flash image')
|
|
1069
|
+
|
|
934
1070
|
# Create bundle subcommand
|
|
935
1071
|
create_bundle_parser = subparsers.add_parser('create-bundle', help='Create a ZIP bundle of partition binaries')
|
|
936
1072
|
create_bundle_parser.add_argument('-o', '--output', help='Output ZIP filename', dest='output_file', required=True)
|
|
@@ -950,6 +1086,10 @@ def _main():
|
|
|
950
1086
|
write_bundle_parser = subparsers.add_parser('write-bundle', help='Write a ZIP bundle of partition binaries')
|
|
951
1087
|
write_bundle_parser.add_argument('input_file', help='Input ZIP filename')
|
|
952
1088
|
|
|
1089
|
+
# Print Bundle subcommand
|
|
1090
|
+
print_bundle_parser = subparsers.add_parser('print-bundle', help='Print partition table and app info from a partition bundle ZIP')
|
|
1091
|
+
print_bundle_parser.add_argument('bundle_file', help='Input ZIP bundle file')
|
|
1092
|
+
|
|
953
1093
|
# Create NVS subcommand
|
|
954
1094
|
create_nvs_parser = subparsers.add_parser('create-nvs', help='Generate NVS partition image from CSV')
|
|
955
1095
|
create_nvs_parser.add_argument('csv_file', help='Input NVS CSV file')
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: idftool
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: Utility for flashing, provisioning, and interacting with Espressif SOCs running ESP-IDF
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Project-URL: Source, https://github.com/nebkat/idftool
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE.md
|
|
10
|
+
Requires-Dist: esptool~=5.2.0
|
|
11
|
+
Requires-Dist: esp-idf-defs~=0.1.3
|
|
12
|
+
Requires-Dist: esp-idf-nvs-partition-gen~=0.2.0
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
1
15
|
# idftool
|
|
2
16
|
|
|
3
17
|
A CLI tool built on top of [`esptool`](https://github.com/espressif/esptool)
|
|
@@ -106,10 +120,12 @@ Setting boot partition to 'ota_1'...
|
|
|
106
120
|
| [`create-image`](#create-image) | Merge partition binaries into a single flash image |
|
|
107
121
|
| [`dump-image`](#dump-image) | Dump the entire flash to an image file |
|
|
108
122
|
| [`write-image`](#write-image-alias-reflash) | Write a full flash image to the device |
|
|
123
|
+
| [`print-image`](#print-image) | Print partition table and app info from a flash image file |
|
|
109
124
|
| **Bundles** | |
|
|
110
125
|
| [`create-bundle`](#create-bundle) | Pack partition images into a ZIP bundle |
|
|
111
126
|
| [`dump-bundle`](#dump-bundle) | Pack every partition from the device into a ZIP |
|
|
112
127
|
| [`write-bundle`](#write-bundle) | Flash every binary in a bundle ZIP |
|
|
128
|
+
| [`print-bundle`](#print-bundle) | Print partition table and app info from a bundle ZIP |
|
|
113
129
|
| **NVS** | |
|
|
114
130
|
| [`create-nvs`](#create-nvs) | Generate an NVS partition image from a CSV file |
|
|
115
131
|
| [`write-nvs`](#write-nvs) | Generate an NVS image from CSV and flash it |
|
|
@@ -245,6 +261,15 @@ counterpart of `dump-image`.
|
|
|
245
261
|
idftool write-image build/full-flash.img
|
|
246
262
|
```
|
|
247
263
|
|
|
264
|
+
#### `print-image`
|
|
265
|
+
Inspect a flash image file without touching a device: prints the
|
|
266
|
+
partition table embedded in the image and, for each app partition that
|
|
267
|
+
contains a valid app, the project name, version, IDF version, compile
|
|
268
|
+
time, ELF SHA256, and target chip.
|
|
269
|
+
```text
|
|
270
|
+
idftool print-image build/full-flash.img
|
|
271
|
+
```
|
|
272
|
+
|
|
248
273
|
### Bundles
|
|
249
274
|
|
|
250
275
|
A bundle is a plain ZIP file containing one `*.bin` per partition (named
|
|
@@ -278,6 +303,15 @@ to match) instead of reading the table from flash.
|
|
|
278
303
|
idftool write-bundle release.zip
|
|
279
304
|
```
|
|
280
305
|
|
|
306
|
+
#### `print-bundle`
|
|
307
|
+
Inspect a bundle ZIP without touching a device: prints the partition
|
|
308
|
+
table from the embedded `partition_table.csv` and, for each app
|
|
309
|
+
partition whose `.bin` is present in the bundle, the project name,
|
|
310
|
+
version, IDF version, compile time, ELF SHA256, and target chip.
|
|
311
|
+
```text
|
|
312
|
+
idftool print-bundle release.zip
|
|
313
|
+
```
|
|
314
|
+
|
|
281
315
|
### NVS
|
|
282
316
|
|
|
283
317
|
idftool can generate a binary NVS (Non-Volatile Storage) partition image
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|