idftool 0.2.2__tar.gz → 0.3.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.
- idftool-0.3.0/CHANGELOG.md +94 -0
- {idftool-0.2.2 → idftool-0.3.0}/PKG-INFO +71 -13
- {idftool-0.2.2 → idftool-0.3.0}/README.md +70 -12
- {idftool-0.2.2 → idftool-0.3.0}/idftool/__main__.py +274 -21
- {idftool-0.2.2 → idftool-0.3.0}/idftool.egg-info/PKG-INFO +71 -13
- {idftool-0.2.2 → idftool-0.3.0}/idftool.egg-info/SOURCES.txt +2 -0
- idftool-0.3.0/idftool.egg-info/scm_file_list.json +19 -0
- idftool-0.3.0/idftool.egg-info/scm_version.json +8 -0
- idftool-0.2.2/CHANGELOG.md +0 -48
- {idftool-0.2.2 → idftool-0.3.0}/.github/workflows/build.yml +0 -0
- {idftool-0.2.2 → idftool-0.3.0}/.github/workflows/publish.yml +0 -0
- {idftool-0.2.2 → idftool-0.3.0}/.gitignore +0 -0
- {idftool-0.2.2 → idftool-0.3.0}/CONTRIBUTING.md +0 -0
- {idftool-0.2.2 → idftool-0.3.0}/LICENSE.md +0 -0
- {idftool-0.2.2 → idftool-0.3.0}/Makefile +0 -0
- {idftool-0.2.2 → idftool-0.3.0}/idftool/__init__.py +0 -0
- {idftool-0.2.2 → idftool-0.3.0}/idftool/traceback.py +0 -0
- {idftool-0.2.2 → idftool-0.3.0}/idftool-onedir.spec +0 -0
- {idftool-0.2.2 → idftool-0.3.0}/idftool.egg-info/dependency_links.txt +0 -0
- {idftool-0.2.2 → idftool-0.3.0}/idftool.egg-info/entry_points.txt +0 -0
- {idftool-0.2.2 → idftool-0.3.0}/idftool.egg-info/requires.txt +0 -0
- {idftool-0.2.2 → idftool-0.3.0}/idftool.egg-info/top_level.txt +0 -0
- {idftool-0.2.2 → idftool-0.3.0}/idftool.py +0 -0
- {idftool-0.2.2 → idftool-0.3.0}/idftool.spec +0 -0
- {idftool-0.2.2 → idftool-0.3.0}/pyproject.toml +0 -0
- {idftool-0.2.2 → idftool-0.3.0}/setup.cfg +0 -0
|
@@ -0,0 +1,94 @@
|
|
|
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.3.0] — 2026-07-15
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Partition table file commands that make the table itself the subject
|
|
11
|
+
(a positional file), keeping the global `--partition-table-file`
|
|
12
|
+
option for its existing role of overriding the layout of other
|
|
13
|
+
commands:
|
|
14
|
+
- `convert-table <in> <out>` — convert a partition table between CSV
|
|
15
|
+
and binary, offline. Format is auto-detected on input and inferred
|
|
16
|
+
from the output extension (`.csv`/`.bin`) or `--format`.
|
|
17
|
+
- `print-table [file]` — pretty-print a partition table from a CSV or
|
|
18
|
+
binary file (offline), or from the device when no file is given.
|
|
19
|
+
- `dump-table [output]` — read the partition table from the device and
|
|
20
|
+
save it as CSV or binary (default filename
|
|
21
|
+
`{chip}-{mac}-{timestamp}-partition-table.csv`).
|
|
22
|
+
- `write-table <file>` — flash a partition table from a CSV or binary
|
|
23
|
+
file to the device. Requires an explicit file (so it can never read
|
|
24
|
+
the device's current table and write it back to itself), verifies
|
|
25
|
+
the table before flashing (override with `--force`), and warns that
|
|
26
|
+
only the map is replaced — existing partition data is not moved or
|
|
27
|
+
erased.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
- `list` is now an alias of `print-table` (both print the device's
|
|
31
|
+
partition table identically). Its `--partition-type`,
|
|
32
|
+
`--partition-subtype`, and `--partition-name` flags — which were never
|
|
33
|
+
implemented — have been removed.
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
- Working with empty or truncated input files now reports a clear error
|
|
37
|
+
instead of a confusing parser failure (e.g. "Partition table is
|
|
38
|
+
missing an end-of-table marker" for a zero-byte image). Empty images,
|
|
39
|
+
app binaries, and bundle ZIPs are rejected up front, as are images too
|
|
40
|
+
small to reach the partition table offset and corrupt (non-ZIP)
|
|
41
|
+
bundles.
|
|
42
|
+
- A parsed-but-empty partition table (all-`0xFF` binary region,
|
|
43
|
+
comment-only CSV, etc.) is now rejected across all parse paths rather
|
|
44
|
+
than silently yielding a table with no partitions.
|
|
45
|
+
- Loading a partition table CSV that contains bootloader rows (such as one
|
|
46
|
+
produced by `dump-table`) no longer requires re-specifying
|
|
47
|
+
`--primary-bootloader-offset`: the offset embedded in the CSV is
|
|
48
|
+
recovered from the file, so dumped tables round-trip through
|
|
49
|
+
`print-table`, `convert-table`, `write-table`, and `--partition-table-file`
|
|
50
|
+
without extra arguments. The flag still works as an explicit override.
|
|
51
|
+
|
|
52
|
+
## [v0.2.2] — 2026-06-15
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
- `print-image` — inspect a flash image file offline: prints the
|
|
56
|
+
partition table and, for each app partition that contains a valid
|
|
57
|
+
app, the project name, version, IDF version, compile time, ELF
|
|
58
|
+
SHA256, and target chip.
|
|
59
|
+
- `print-bundle` — same output for a partition bundle ZIP, driven by
|
|
60
|
+
the embedded `partition_table.csv` and the per-partition `.bin`
|
|
61
|
+
entries.
|
|
62
|
+
|
|
63
|
+
### Changed
|
|
64
|
+
- Bumped `esp-idf-defs` to `~=0.1.3`. `print_partition_table` now takes
|
|
65
|
+
a `(offset, length) -> bytes` accessor instead of an `ESPLoader`
|
|
66
|
+
instance, so the same printing path serves device, image, and bundle
|
|
67
|
+
inspection.
|
|
68
|
+
|
|
69
|
+
## [v0.2.1] — 2026-06-10
|
|
70
|
+
|
|
71
|
+
### Changed
|
|
72
|
+
- Boot partition state now uses `OtaImageState` for clearer reporting.
|
|
73
|
+
|
|
74
|
+
## [v0.2.0] — 2026-05-26
|
|
75
|
+
|
|
76
|
+
### Added
|
|
77
|
+
- `create-nvs` and `write-nvs` commands for generating NVS partition
|
|
78
|
+
images from CSV.
|
|
79
|
+
- Rich-style boxed traceback formatter for friendlier error output.
|
|
80
|
+
|
|
81
|
+
### Documentation
|
|
82
|
+
- README installation section and command index table.
|
|
83
|
+
- Recommend `pipx` over the standalone binary for installation.
|
|
84
|
+
|
|
85
|
+
## [v0.1.0] — 2026-05-25
|
|
86
|
+
|
|
87
|
+
### Added
|
|
88
|
+
- Initial release.
|
|
89
|
+
|
|
90
|
+
[v0.3.0]: https://github.com/nebkat/idftool/releases/tag/v0.3.0
|
|
91
|
+
[v0.2.2]: https://github.com/nebkat/idftool/releases/tag/v0.2.2
|
|
92
|
+
[v0.2.1]: https://github.com/nebkat/idftool/releases/tag/v0.2.1
|
|
93
|
+
[v0.2.0]: https://github.com/nebkat/idftool/releases/tag/v0.2.0
|
|
94
|
+
[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.
|
|
3
|
+
Version: 0.3.0
|
|
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
|
|
@@ -103,7 +103,7 @@ Setting boot partition to 'ota_1'...
|
|
|
103
103
|
|---------|-------------|
|
|
104
104
|
| **Discovery** | |
|
|
105
105
|
| [`devices`](#devices) | List serial ports with hardware IDs |
|
|
106
|
-
| [`list`](#
|
|
106
|
+
| [`list`](#print-table) | Print the partition table (alias of `print-table`) |
|
|
107
107
|
| **Partition I/O** | |
|
|
108
108
|
| [`read`](#read) | Read a partition (or slice) into a file |
|
|
109
109
|
| [`write`](#write) | Write one or more files to named partitions |
|
|
@@ -126,6 +126,11 @@ Setting boot partition to 'ota_1'...
|
|
|
126
126
|
| [`dump-bundle`](#dump-bundle) | Pack every partition from the device into a ZIP |
|
|
127
127
|
| [`write-bundle`](#write-bundle) | Flash every binary in a bundle ZIP |
|
|
128
128
|
| [`print-bundle`](#print-bundle) | Print partition table and app info from a bundle ZIP |
|
|
129
|
+
| **Partition table** | |
|
|
130
|
+
| [`convert-table`](#convert-table) | Convert a partition table file between CSV and binary |
|
|
131
|
+
| [`print-table`](#print-table) | Print a partition table from a CSV or binary file, or the device |
|
|
132
|
+
| [`dump-table`](#dump-table) | Read the partition table from the device into a file |
|
|
133
|
+
| [`write-table`](#write-table) | Flash a partition table from a CSV or binary file |
|
|
129
134
|
| **NVS** | |
|
|
130
135
|
| [`create-nvs`](#create-nvs) | Generate an NVS partition image from a CSV file |
|
|
131
136
|
| [`write-nvs`](#write-nvs) | Generate an NVS image from CSV and flash it |
|
|
@@ -141,17 +146,6 @@ hardware IDs.
|
|
|
141
146
|
idftool devices
|
|
142
147
|
```
|
|
143
148
|
|
|
144
|
-
#### `list`
|
|
145
|
-
Print the device's partition table. With a connected ESP, idftool also
|
|
146
|
-
reads the application descriptor of every app partition (project name,
|
|
147
|
-
version) and marks the currently-active OTA slot with a trailing `*`. The
|
|
148
|
-
selected otadata copy is shown next to the otadata partition's subtype
|
|
149
|
-
(`ota (A)`, `ota (B)`, or `ota (invalid)` when otadata is erased).
|
|
150
|
-
```text
|
|
151
|
-
idftool list
|
|
152
|
-
idftool --partition-table-file partitions.csv list # offline
|
|
153
|
-
```
|
|
154
|
-
|
|
155
149
|
### Partition I/O
|
|
156
150
|
|
|
157
151
|
#### `read`
|
|
@@ -312,6 +306,70 @@ version, IDF version, compile time, ELF SHA256, and target chip.
|
|
|
312
306
|
idftool print-bundle release.zip
|
|
313
307
|
```
|
|
314
308
|
|
|
309
|
+
### Partition table
|
|
310
|
+
|
|
311
|
+
These commands work directly on a partition table as a file — the table
|
|
312
|
+
is the subject, passed as a positional argument. This is distinct from the
|
|
313
|
+
global `--partition-table-file` option, which overrides the layout *other*
|
|
314
|
+
commands use to address partitions by name. Input format (CSV or binary)
|
|
315
|
+
is auto-detected; output format is inferred from the file extension
|
|
316
|
+
(`.csv`/`.bin`) or set with `--format`.
|
|
317
|
+
|
|
318
|
+
When the input is a CSV that includes a bootloader row, pass
|
|
319
|
+
`--primary-bootloader-offset` (an offset or a chip name, e.g. `esp32s3`)
|
|
320
|
+
so the offset can be resolved.
|
|
321
|
+
|
|
322
|
+
#### `convert-table`
|
|
323
|
+
Convert a partition table between CSV and binary, offline. Handles both
|
|
324
|
+
directions; the binary output includes the MD5 checksum and padding, so it
|
|
325
|
+
is ready to flash or embed in an image.
|
|
326
|
+
```text
|
|
327
|
+
idftool convert-table partitions.csv partitions.bin
|
|
328
|
+
idftool convert-table partitions.bin partitions.csv
|
|
329
|
+
idftool --primary-bootloader-offset esp32s3 convert-table partitions.csv partitions.bin
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
#### `print-table`
|
|
333
|
+
Pretty-print a partition table. Given a CSV or binary file it runs offline;
|
|
334
|
+
with no file it reads the table from the device (or from
|
|
335
|
+
`--partition-table-file`). With a connected ESP, idftool also reads the
|
|
336
|
+
application descriptor of every app partition (project name, version) and
|
|
337
|
+
marks the currently-active OTA slot with a trailing `*`; the selected otadata
|
|
338
|
+
copy is shown next to the otadata partition's subtype (`ota (A)`, `ota (B)`,
|
|
339
|
+
or `ota (invalid)` when otadata is erased).
|
|
340
|
+
|
|
341
|
+
Aliased as `list`.
|
|
342
|
+
```text
|
|
343
|
+
idftool print-table partitions.bin # from a file, offline
|
|
344
|
+
idftool print-table # from the device
|
|
345
|
+
idftool list # same thing
|
|
346
|
+
idftool --partition-table-file partitions.csv print-table
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
#### `dump-table`
|
|
350
|
+
Read the partition table from the connected device and save it. Defaults to
|
|
351
|
+
CSV with an auto-generated filename; pass an output file or `--format` to
|
|
352
|
+
choose otherwise.
|
|
353
|
+
```text
|
|
354
|
+
idftool dump-table # auto-named .csv
|
|
355
|
+
idftool dump-table backup.bin
|
|
356
|
+
idftool dump-table backup.csv --format csv
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
#### `write-table`
|
|
360
|
+
Flash a partition table from a CSV or binary file to the device. The file is
|
|
361
|
+
required, so this never reads the device's current table and writes it back
|
|
362
|
+
to itself — use `dump-table` to pull the current table. The table is verified
|
|
363
|
+
before flashing (override with `--force`).
|
|
364
|
+
|
|
365
|
+
Only the partition map (at the partition table offset) is replaced; existing
|
|
366
|
+
partition **data** on flash is not moved, resized, or erased, so a table that
|
|
367
|
+
no longer matches the flash contents can make the device unbootable.
|
|
368
|
+
```text
|
|
369
|
+
idftool write-table partitions.csv
|
|
370
|
+
idftool write-table partitions.bin --force
|
|
371
|
+
```
|
|
372
|
+
|
|
315
373
|
### NVS
|
|
316
374
|
|
|
317
375
|
idftool can generate a binary NVS (Non-Volatile Storage) partition image
|
|
@@ -89,7 +89,7 @@ Setting boot partition to 'ota_1'...
|
|
|
89
89
|
|---------|-------------|
|
|
90
90
|
| **Discovery** | |
|
|
91
91
|
| [`devices`](#devices) | List serial ports with hardware IDs |
|
|
92
|
-
| [`list`](#
|
|
92
|
+
| [`list`](#print-table) | Print the partition table (alias of `print-table`) |
|
|
93
93
|
| **Partition I/O** | |
|
|
94
94
|
| [`read`](#read) | Read a partition (or slice) into a file |
|
|
95
95
|
| [`write`](#write) | Write one or more files to named partitions |
|
|
@@ -112,6 +112,11 @@ Setting boot partition to 'ota_1'...
|
|
|
112
112
|
| [`dump-bundle`](#dump-bundle) | Pack every partition from the device into a ZIP |
|
|
113
113
|
| [`write-bundle`](#write-bundle) | Flash every binary in a bundle ZIP |
|
|
114
114
|
| [`print-bundle`](#print-bundle) | Print partition table and app info from a bundle ZIP |
|
|
115
|
+
| **Partition table** | |
|
|
116
|
+
| [`convert-table`](#convert-table) | Convert a partition table file between CSV and binary |
|
|
117
|
+
| [`print-table`](#print-table) | Print a partition table from a CSV or binary file, or the device |
|
|
118
|
+
| [`dump-table`](#dump-table) | Read the partition table from the device into a file |
|
|
119
|
+
| [`write-table`](#write-table) | Flash a partition table from a CSV or binary file |
|
|
115
120
|
| **NVS** | |
|
|
116
121
|
| [`create-nvs`](#create-nvs) | Generate an NVS partition image from a CSV file |
|
|
117
122
|
| [`write-nvs`](#write-nvs) | Generate an NVS image from CSV and flash it |
|
|
@@ -127,17 +132,6 @@ hardware IDs.
|
|
|
127
132
|
idftool devices
|
|
128
133
|
```
|
|
129
134
|
|
|
130
|
-
#### `list`
|
|
131
|
-
Print the device's partition table. With a connected ESP, idftool also
|
|
132
|
-
reads the application descriptor of every app partition (project name,
|
|
133
|
-
version) and marks the currently-active OTA slot with a trailing `*`. The
|
|
134
|
-
selected otadata copy is shown next to the otadata partition's subtype
|
|
135
|
-
(`ota (A)`, `ota (B)`, or `ota (invalid)` when otadata is erased).
|
|
136
|
-
```text
|
|
137
|
-
idftool list
|
|
138
|
-
idftool --partition-table-file partitions.csv list # offline
|
|
139
|
-
```
|
|
140
|
-
|
|
141
135
|
### Partition I/O
|
|
142
136
|
|
|
143
137
|
#### `read`
|
|
@@ -298,6 +292,70 @@ version, IDF version, compile time, ELF SHA256, and target chip.
|
|
|
298
292
|
idftool print-bundle release.zip
|
|
299
293
|
```
|
|
300
294
|
|
|
295
|
+
### Partition table
|
|
296
|
+
|
|
297
|
+
These commands work directly on a partition table as a file — the table
|
|
298
|
+
is the subject, passed as a positional argument. This is distinct from the
|
|
299
|
+
global `--partition-table-file` option, which overrides the layout *other*
|
|
300
|
+
commands use to address partitions by name. Input format (CSV or binary)
|
|
301
|
+
is auto-detected; output format is inferred from the file extension
|
|
302
|
+
(`.csv`/`.bin`) or set with `--format`.
|
|
303
|
+
|
|
304
|
+
When the input is a CSV that includes a bootloader row, pass
|
|
305
|
+
`--primary-bootloader-offset` (an offset or a chip name, e.g. `esp32s3`)
|
|
306
|
+
so the offset can be resolved.
|
|
307
|
+
|
|
308
|
+
#### `convert-table`
|
|
309
|
+
Convert a partition table between CSV and binary, offline. Handles both
|
|
310
|
+
directions; the binary output includes the MD5 checksum and padding, so it
|
|
311
|
+
is ready to flash or embed in an image.
|
|
312
|
+
```text
|
|
313
|
+
idftool convert-table partitions.csv partitions.bin
|
|
314
|
+
idftool convert-table partitions.bin partitions.csv
|
|
315
|
+
idftool --primary-bootloader-offset esp32s3 convert-table partitions.csv partitions.bin
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
#### `print-table`
|
|
319
|
+
Pretty-print a partition table. Given a CSV or binary file it runs offline;
|
|
320
|
+
with no file it reads the table from the device (or from
|
|
321
|
+
`--partition-table-file`). With a connected ESP, idftool also reads the
|
|
322
|
+
application descriptor of every app partition (project name, version) and
|
|
323
|
+
marks the currently-active OTA slot with a trailing `*`; the selected otadata
|
|
324
|
+
copy is shown next to the otadata partition's subtype (`ota (A)`, `ota (B)`,
|
|
325
|
+
or `ota (invalid)` when otadata is erased).
|
|
326
|
+
|
|
327
|
+
Aliased as `list`.
|
|
328
|
+
```text
|
|
329
|
+
idftool print-table partitions.bin # from a file, offline
|
|
330
|
+
idftool print-table # from the device
|
|
331
|
+
idftool list # same thing
|
|
332
|
+
idftool --partition-table-file partitions.csv print-table
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
#### `dump-table`
|
|
336
|
+
Read the partition table from the connected device and save it. Defaults to
|
|
337
|
+
CSV with an auto-generated filename; pass an output file or `--format` to
|
|
338
|
+
choose otherwise.
|
|
339
|
+
```text
|
|
340
|
+
idftool dump-table # auto-named .csv
|
|
341
|
+
idftool dump-table backup.bin
|
|
342
|
+
idftool dump-table backup.csv --format csv
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
#### `write-table`
|
|
346
|
+
Flash a partition table from a CSV or binary file to the device. The file is
|
|
347
|
+
required, so this never reads the device's current table and writes it back
|
|
348
|
+
to itself — use `dump-table` to pull the current table. The table is verified
|
|
349
|
+
before flashing (override with `--force`).
|
|
350
|
+
|
|
351
|
+
Only the partition map (at the partition table offset) is replaced; existing
|
|
352
|
+
partition **data** on flash is not moved, resized, or erased, so a table that
|
|
353
|
+
no longer matches the flash contents can make the device unbootable.
|
|
354
|
+
```text
|
|
355
|
+
idftool write-table partitions.csv
|
|
356
|
+
idftool write-table partitions.bin --force
|
|
357
|
+
```
|
|
358
|
+
|
|
301
359
|
### NVS
|
|
302
360
|
|
|
303
361
|
idftool can generate a binary NVS (Non-Volatile Storage) partition image
|
|
@@ -4,7 +4,7 @@ import re
|
|
|
4
4
|
import sys
|
|
5
5
|
import time
|
|
6
6
|
from typing import Callable, Optional, Literal
|
|
7
|
-
from zipfile import ZipFile
|
|
7
|
+
from zipfile import BadZipFile, ZipFile
|
|
8
8
|
|
|
9
9
|
from esptool import ESPLoader, CHIP_DEFS, flash_size_bytes
|
|
10
10
|
from serial import SerialException
|
|
@@ -17,7 +17,7 @@ from esp_idf_defs import ImageMetadata, ChipId
|
|
|
17
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
|
-
DATA_TYPE
|
|
20
|
+
DATA_TYPE, get_encoding
|
|
21
21
|
from esptool.cmds import detect_chip, read_flash, write_flash, erase_region, merge_bin, erase_flash, detect_flash_size
|
|
22
22
|
|
|
23
23
|
PARTITION_SLICE_REGEX = re.compile(r"^(?P<partition>.+?)(?:\[(?:(?P<start>[-+]?(?:0x)?[0-9A-Fa-f]+)?(?::(?P<stop>[-+]?(?:0x)?[0-9A-Fa-f]+)?)?)?])?$")
|
|
@@ -239,6 +239,99 @@ def write_otadata(esp: ESPLoader, partition: PartitionDefinition, otadata: OtaDa
|
|
|
239
239
|
esp.flash_block(data=otadata.otadata.to_bytes(), seq=0)
|
|
240
240
|
esp.flash_finish()
|
|
241
241
|
|
|
242
|
+
def check_image_file(path: str, partition_table_offset: int) -> int:
|
|
243
|
+
"""Validate an image file is non-empty and large enough to contain a partition table.
|
|
244
|
+
|
|
245
|
+
Returns the image size in bytes.
|
|
246
|
+
"""
|
|
247
|
+
image_size = os.path.getsize(path)
|
|
248
|
+
if image_size == 0:
|
|
249
|
+
raise RuntimeError(f"Image file '{path}' is empty")
|
|
250
|
+
if image_size <= partition_table_offset:
|
|
251
|
+
raise RuntimeError(
|
|
252
|
+
f"Image file '{path}' ({image_size:#x} bytes) is smaller than the partition table "
|
|
253
|
+
f"offset {partition_table_offset:#x}; it does not appear to contain a partition table")
|
|
254
|
+
return image_size
|
|
255
|
+
|
|
256
|
+
def require_partitions(partition_table: PartitionTable, source: str) -> PartitionTable:
|
|
257
|
+
"""Raise a clear error if a parsed partition table contains no partitions."""
|
|
258
|
+
if len(partition_table) == 0:
|
|
259
|
+
raise RuntimeError(f"Partition table from {source} is empty (no partitions defined)")
|
|
260
|
+
return partition_table
|
|
261
|
+
|
|
262
|
+
def load_partition_table_file(
|
|
263
|
+
path: str,
|
|
264
|
+
partition_table_offset: int,
|
|
265
|
+
primary_bootloader_offset: Optional[int],
|
|
266
|
+
recovery_bootloader_offset: Optional[int],
|
|
267
|
+
) -> PartitionTable:
|
|
268
|
+
"""Load a partition table from a CSV or binary file (format auto-detected)."""
|
|
269
|
+
if os.path.getsize(path) == 0:
|
|
270
|
+
raise RuntimeError(f"Partition table file '{path}' is empty")
|
|
271
|
+
with open(path, 'rb') as f:
|
|
272
|
+
data = f.read()
|
|
273
|
+
|
|
274
|
+
if data[:2] == PartitionDefinition.MAGIC_BYTES:
|
|
275
|
+
partition_table = PartitionTable.from_binary(data)
|
|
276
|
+
else:
|
|
277
|
+
csv_text = data.decode(get_encoding(data))
|
|
278
|
+
# A CSV with bootloader rows can't be parsed without the bootloader offsets. idftool writes
|
|
279
|
+
# those offsets into the CSV as literal bootloader rows, so recover them from the file itself
|
|
280
|
+
# when the user didn't pass them — this lets a dumped table round-trip without re-specifying
|
|
281
|
+
# --primary-bootloader-offset. (print-bundle does the same for bundle CSVs.)
|
|
282
|
+
if primary_bootloader_offset is None or recovery_bootloader_offset is None:
|
|
283
|
+
csv_primary, csv_recovery = _extract_csv_bootloader_offsets(csv_text)
|
|
284
|
+
if primary_bootloader_offset is None:
|
|
285
|
+
primary_bootloader_offset = csv_primary
|
|
286
|
+
if recovery_bootloader_offset is None:
|
|
287
|
+
recovery_bootloader_offset = csv_recovery
|
|
288
|
+
try:
|
|
289
|
+
partition_table = PartitionTable.from_csv(
|
|
290
|
+
csv_text,
|
|
291
|
+
partition_table_offset=partition_table_offset,
|
|
292
|
+
primary_bootloader_offset=primary_bootloader_offset,
|
|
293
|
+
recovery_bootloader_offset=recovery_bootloader_offset,
|
|
294
|
+
)
|
|
295
|
+
except RuntimeError as e:
|
|
296
|
+
msg = str(e)
|
|
297
|
+
if 'bootloader offset is not provided' in msg.lower():
|
|
298
|
+
which = 'recovery' if 'recovery' in msg.lower() else 'primary'
|
|
299
|
+
flag = ('--primary-bootloader-offset (an address or a chip name, e.g. esp32s3)'
|
|
300
|
+
if which == 'primary' else '--recovery-bootloader-offset')
|
|
301
|
+
line = re.search(r'line (\d+)', msg)
|
|
302
|
+
where = f" (line {line.group(1)})" if line else ""
|
|
303
|
+
raise RuntimeError(
|
|
304
|
+
f"The {which} bootloader entry in '{path}'{where} has no offset. "
|
|
305
|
+
f"Add the offset to the CSV, or pass {flag}."
|
|
306
|
+
) from None
|
|
307
|
+
raise
|
|
308
|
+
return require_partitions(partition_table, f"file '{path}'")
|
|
309
|
+
|
|
310
|
+
def resolve_partition_table_format(output_file: Optional[str], explicit: Optional[str]) -> Literal['csv', 'bin']:
|
|
311
|
+
"""Pick the output format from an explicit flag, else the output file extension (default csv)."""
|
|
312
|
+
if explicit:
|
|
313
|
+
return explicit
|
|
314
|
+
if output_file:
|
|
315
|
+
ext = os.path.splitext(output_file)[1].lower()
|
|
316
|
+
if ext == '.csv':
|
|
317
|
+
return 'csv'
|
|
318
|
+
if ext in ('.bin', '.img'):
|
|
319
|
+
return 'bin'
|
|
320
|
+
raise RuntimeError(
|
|
321
|
+
f"Cannot infer partition table format from '{output_file}'; "
|
|
322
|
+
f"pass --format csv|bin or use a .csv/.bin extension")
|
|
323
|
+
return 'csv'
|
|
324
|
+
|
|
325
|
+
def write_partition_table_file(partition_table: PartitionTable, output_file: str, output_format: Literal['csv', 'bin']):
|
|
326
|
+
"""Serialise a partition table to CSV or binary and write it to output_file."""
|
|
327
|
+
if output_format == 'csv':
|
|
328
|
+
data = partition_table.to_csv().encode('utf-8')
|
|
329
|
+
else:
|
|
330
|
+
data = partition_table.to_binary()
|
|
331
|
+
with open(output_file, 'wb') as f:
|
|
332
|
+
f.write(data)
|
|
333
|
+
print(f"Wrote {output_format} partition table ({len(data):#x} bytes) to {output_file}")
|
|
334
|
+
|
|
242
335
|
def validate_app_binary(esp: ESPLoader, app_binary: bytes) -> tuple[bytes, ImageMetadata]:
|
|
243
336
|
try:
|
|
244
337
|
image_metadata = ImageMetadata.from_bytes(app_binary, app_required=True)
|
|
@@ -256,6 +349,8 @@ def validate_app_binary(esp: ESPLoader, app_binary: bytes) -> tuple[bytes, Image
|
|
|
256
349
|
|
|
257
350
|
def load_app_binary(esp: ESPLoader, app_binary_file: str, partition: PartitionDefinition) -> tuple[bytes, ImageMetadata]:
|
|
258
351
|
app_binary_size = os.path.getsize(app_binary_file)
|
|
352
|
+
if app_binary_size == 0:
|
|
353
|
+
raise RuntimeError(f"Application binary '{app_binary_file}' is empty")
|
|
259
354
|
if app_binary_size > partition.size:
|
|
260
355
|
raise RuntimeError(
|
|
261
356
|
f"Application binary size '{app_binary_size}' is greater than partition size {partition.size}")
|
|
@@ -404,7 +499,13 @@ def command_create_bundle(
|
|
|
404
499
|
print(f"Adding partition table CSV to bundle")
|
|
405
500
|
|
|
406
501
|
def check_write_bundle_has_partition_table(file: str) -> bool:
|
|
407
|
-
|
|
502
|
+
if os.path.getsize(file) == 0:
|
|
503
|
+
raise RuntimeError(f"Bundle '{file}' is empty")
|
|
504
|
+
try:
|
|
505
|
+
bundle_zip = ZipFile(file, 'r')
|
|
506
|
+
except BadZipFile as e:
|
|
507
|
+
raise RuntimeError(f"Bundle '{file}' is not a valid ZIP archive") from e
|
|
508
|
+
with bundle_zip as zf:
|
|
408
509
|
return any(m == 'partition_table.csv' for m in zf.namelist())
|
|
409
510
|
|
|
410
511
|
def command_write_bundle(
|
|
@@ -569,6 +670,8 @@ def command_write_nvs(
|
|
|
569
670
|
write_flash(esp=esp, addr_data=[(partition.offset, image)], flash_size='detect')
|
|
570
671
|
|
|
571
672
|
def command_app_info(app_binary_file: str):
|
|
673
|
+
if os.path.getsize(app_binary_file) == 0:
|
|
674
|
+
raise RuntimeError(f"Application binary '{app_binary_file}' is empty")
|
|
572
675
|
app_binary = open(app_binary_file, 'rb').read()
|
|
573
676
|
try:
|
|
574
677
|
image_metadata = ImageMetadata.from_bytes(app_binary, app_required=True)
|
|
@@ -578,6 +681,8 @@ def command_app_info(app_binary_file: str):
|
|
|
578
681
|
print_app_info(image_metadata.app_description, image_metadata.header)
|
|
579
682
|
|
|
580
683
|
def command_write_image(esp: ESPLoader, bootloader_entry: PartitionDefinition, image_file_path: str):
|
|
684
|
+
if os.path.getsize(image_file_path) == 0:
|
|
685
|
+
raise RuntimeError(f"Image file '{image_file_path}' is empty")
|
|
581
686
|
image_file = open(image_file_path, 'rb')
|
|
582
687
|
image_file.seek(bootloader_entry.offset)
|
|
583
688
|
bootloader_binary = image_file.read(bootloader_entry.size)
|
|
@@ -638,7 +743,7 @@ def print_partition_table_and_apps(
|
|
|
638
743
|
print_app_info(image_metadata.app_description, image_metadata.header, indent=" ")
|
|
639
744
|
|
|
640
745
|
def command_print_image(image_file_path: str, partition_table_offset: int, partition_table_size: int):
|
|
641
|
-
image_size =
|
|
746
|
+
image_size = check_image_file(image_file_path, partition_table_offset)
|
|
642
747
|
with open(image_file_path, 'rb') as f:
|
|
643
748
|
f.seek(partition_table_offset)
|
|
644
749
|
partition_table_binary = f.read(partition_table_size)
|
|
@@ -646,6 +751,7 @@ def command_print_image(image_file_path: str, partition_table_offset: int, parti
|
|
|
646
751
|
partition_table = PartitionTable.from_binary(partition_table_binary)
|
|
647
752
|
except (RuntimeError, ValueError) as e:
|
|
648
753
|
raise RuntimeError(f"Could not parse partition table at offset {partition_table_offset:#x}") from e
|
|
754
|
+
require_partitions(partition_table, f"image at offset {partition_table_offset:#x}")
|
|
649
755
|
|
|
650
756
|
def read(offset: int, length: int) -> bytes:
|
|
651
757
|
if offset >= image_size:
|
|
@@ -686,11 +792,19 @@ def command_print_bundle(
|
|
|
686
792
|
recovery_bootloader_offset: Optional[int],
|
|
687
793
|
):
|
|
688
794
|
bundle_size = os.path.getsize(bundle_file_path)
|
|
689
|
-
|
|
795
|
+
if bundle_size == 0:
|
|
796
|
+
raise RuntimeError(f"Bundle '{bundle_file_path}' is empty")
|
|
797
|
+
try:
|
|
798
|
+
bundle_zip = ZipFile(bundle_file_path, 'r')
|
|
799
|
+
except BadZipFile as e:
|
|
800
|
+
raise RuntimeError(f"Bundle '{bundle_file_path}' is not a valid ZIP archive") from e
|
|
801
|
+
with bundle_zip as zf:
|
|
690
802
|
names = zf.namelist()
|
|
691
803
|
if 'partition_table.csv' not in names:
|
|
692
804
|
raise RuntimeError(f"Bundle {bundle_file_path} does not contain a partition_table.csv")
|
|
693
805
|
csv_text = zf.read('partition_table.csv').decode('utf-8')
|
|
806
|
+
if not csv_text.strip():
|
|
807
|
+
raise RuntimeError(f"Bundle '{bundle_file_path}' contains an empty partition_table.csv")
|
|
694
808
|
|
|
695
809
|
# idftool-generated bundles encode bootloader offsets in the CSV; PartitionTable.from_csv
|
|
696
810
|
# ignores the literal for bootloader rows and requires an explicit offset, so fall back to
|
|
@@ -708,6 +822,7 @@ def command_print_bundle(
|
|
|
708
822
|
primary_bootloader_offset=primary_bootloader_offset,
|
|
709
823
|
recovery_bootloader_offset=recovery_bootloader_offset,
|
|
710
824
|
)
|
|
825
|
+
require_partitions(partition_table, f"bundle '{bundle_file_path}'")
|
|
711
826
|
|
|
712
827
|
partition_data = {
|
|
713
828
|
name[:-4]: zf.read(name)
|
|
@@ -731,6 +846,80 @@ def command_print_bundle(
|
|
|
731
846
|
print()
|
|
732
847
|
print_partition_table_and_apps(partition_table, read)
|
|
733
848
|
|
|
849
|
+
def command_print_table(
|
|
850
|
+
table_file_path: str,
|
|
851
|
+
partition_table_offset: int,
|
|
852
|
+
primary_bootloader_offset: Optional[int],
|
|
853
|
+
recovery_bootloader_offset: Optional[int],
|
|
854
|
+
):
|
|
855
|
+
partition_table = load_partition_table_file(
|
|
856
|
+
table_file_path, partition_table_offset, primary_bootloader_offset, recovery_bootloader_offset)
|
|
857
|
+
print(f"Partition table: {table_file_path}")
|
|
858
|
+
print()
|
|
859
|
+
print_partition_table(partition_table)
|
|
860
|
+
|
|
861
|
+
def command_convert_table(
|
|
862
|
+
input_file: str,
|
|
863
|
+
output_file: str,
|
|
864
|
+
output_format: Optional[str],
|
|
865
|
+
partition_table_offset: int,
|
|
866
|
+
primary_bootloader_offset: Optional[int],
|
|
867
|
+
recovery_bootloader_offset: Optional[int],
|
|
868
|
+
):
|
|
869
|
+
partition_table = load_partition_table_file(
|
|
870
|
+
input_file, partition_table_offset, primary_bootloader_offset, recovery_bootloader_offset)
|
|
871
|
+
output_format = resolve_partition_table_format(output_file, output_format)
|
|
872
|
+
write_partition_table_file(partition_table, output_file, output_format)
|
|
873
|
+
|
|
874
|
+
def command_dump_table(
|
|
875
|
+
esp: ESPLoader,
|
|
876
|
+
partition_table: PartitionTable,
|
|
877
|
+
output_file: Optional[str],
|
|
878
|
+
output_format: Optional[str],
|
|
879
|
+
):
|
|
880
|
+
if not output_file:
|
|
881
|
+
chip = esp.CHIP_NAME.lower().replace(' ', '-')
|
|
882
|
+
try:
|
|
883
|
+
mac = esp.read_mac("BASE_MAC")
|
|
884
|
+
serial = ''.join(f"{b:02x}" for b in mac)
|
|
885
|
+
except Exception:
|
|
886
|
+
serial = "unknown"
|
|
887
|
+
timestamp = time.strftime("%Y%m%d-%H%M%S")
|
|
888
|
+
extension = 'bin' if output_format == 'bin' else 'csv'
|
|
889
|
+
output_file = f"{chip}-{serial}-{timestamp}-partition-table.{extension}"
|
|
890
|
+
output_format = resolve_partition_table_format(output_file, output_format)
|
|
891
|
+
write_partition_table_file(partition_table, output_file, output_format)
|
|
892
|
+
|
|
893
|
+
def command_write_table(
|
|
894
|
+
esp: ESPLoader,
|
|
895
|
+
table_file_path: str,
|
|
896
|
+
partition_table_offset: int,
|
|
897
|
+
primary_bootloader_offset: Optional[int],
|
|
898
|
+
recovery_bootloader_offset: Optional[int],
|
|
899
|
+
force: bool = False,
|
|
900
|
+
):
|
|
901
|
+
partition_table = load_partition_table_file(
|
|
902
|
+
table_file_path, partition_table_offset, primary_bootloader_offset, recovery_bootloader_offset)
|
|
903
|
+
|
|
904
|
+
try:
|
|
905
|
+
partition_table.verify(partition_table_offset=partition_table_offset)
|
|
906
|
+
except RuntimeError as e:
|
|
907
|
+
if not force:
|
|
908
|
+
raise RuntimeError(
|
|
909
|
+
f"Partition table failed verification: {e}. Re-run with --force to flash it anyway.") from e
|
|
910
|
+
print(f"Warning: partition table failed verification: {e} (continuing due to --force)")
|
|
911
|
+
|
|
912
|
+
partition_table.verify_size_fits(flash_size_bytes(detect_flash_size(esp)))
|
|
913
|
+
|
|
914
|
+
binary = partition_table.to_binary()
|
|
915
|
+
print_partition_table(partition_table)
|
|
916
|
+
print()
|
|
917
|
+
print(f"Writing partition table ({len(binary):#x} bytes) to offset {partition_table_offset:#x}...")
|
|
918
|
+
print("Note: this replaces only the partition map; existing partition data on flash is not moved, "
|
|
919
|
+
"resized, or erased. A table that no longer matches the flash contents can make the device unbootable.")
|
|
920
|
+
write_flash(esp=esp, addr_data=[(partition_table_offset, binary)], flash_size='detect')
|
|
921
|
+
print("Partition table written")
|
|
922
|
+
|
|
734
923
|
def command_dump_image(esp: ESPLoader, output_file: Optional[str]):
|
|
735
924
|
flash_size = flash_size_bytes(detect_flash_size(esp))
|
|
736
925
|
|
|
@@ -821,30 +1010,68 @@ def main(args):
|
|
|
821
1010
|
)
|
|
822
1011
|
return
|
|
823
1012
|
|
|
1013
|
+
# print-table and convert-table operate purely on a local partition table file. print-table
|
|
1014
|
+
# with no file falls through to the shared loader, printing the device's table (or the one
|
|
1015
|
+
# given via --partition-table-file).
|
|
1016
|
+
if args.command == 'print-table' and args.table_file:
|
|
1017
|
+
command_print_table(
|
|
1018
|
+
table_file_path=args.table_file,
|
|
1019
|
+
partition_table_offset=args.partition_table_offset,
|
|
1020
|
+
primary_bootloader_offset=args.primary_bootloader_offset,
|
|
1021
|
+
recovery_bootloader_offset=args.recovery_bootloader_offset,
|
|
1022
|
+
)
|
|
1023
|
+
return
|
|
1024
|
+
|
|
1025
|
+
if args.command == 'convert-table':
|
|
1026
|
+
command_convert_table(
|
|
1027
|
+
input_file=args.input_file,
|
|
1028
|
+
output_file=args.output_file,
|
|
1029
|
+
output_format=args.format,
|
|
1030
|
+
partition_table_offset=args.partition_table_offset,
|
|
1031
|
+
primary_bootloader_offset=args.primary_bootloader_offset,
|
|
1032
|
+
recovery_bootloader_offset=args.recovery_bootloader_offset,
|
|
1033
|
+
)
|
|
1034
|
+
return
|
|
1035
|
+
|
|
824
1036
|
# Connect to ESP device if required
|
|
825
|
-
requires_esp = args.command not in ['
|
|
1037
|
+
requires_esp = args.command not in ['create-image', 'create-bundle', 'create-nvs', 'print-table'] or not args.partition_table_file
|
|
826
1038
|
esp = get_esp(port=args.port, baud=args.baud) if requires_esp else None
|
|
827
|
-
if
|
|
1039
|
+
if args.primary_bootloader_offset is None and esp:
|
|
828
1040
|
args.primary_bootloader_offset = esp.BOOTLOADER_FLASH_OFFSET
|
|
829
1041
|
|
|
1042
|
+
# write-table flashes a partition table from its own positional file, bypassing the shared
|
|
1043
|
+
# loader so it never reads the device's current table and writes it straight back to itself.
|
|
1044
|
+
if args.command == 'write-table':
|
|
1045
|
+
command_write_table(
|
|
1046
|
+
esp=esp,
|
|
1047
|
+
table_file_path=args.table_file,
|
|
1048
|
+
partition_table_offset=args.partition_table_offset,
|
|
1049
|
+
primary_bootloader_offset=args.primary_bootloader_offset,
|
|
1050
|
+
recovery_bootloader_offset=args.recovery_bootloader_offset,
|
|
1051
|
+
force=args.force,
|
|
1052
|
+
)
|
|
1053
|
+
if esp and not args.no_reset:
|
|
1054
|
+
esp.hard_reset()
|
|
1055
|
+
return
|
|
1056
|
+
|
|
830
1057
|
if args.command == 'dump-image':
|
|
831
1058
|
command_dump_image(esp=esp, output_file=args.output_file)
|
|
832
1059
|
return
|
|
833
1060
|
|
|
834
1061
|
# Load partition table
|
|
835
1062
|
if args.command == 'write-image':
|
|
1063
|
+
check_image_file(args.image_file, args.partition_table_offset)
|
|
836
1064
|
with open(args.image_file, 'rb') as f:
|
|
837
1065
|
f.seek(args.partition_table_offset)
|
|
838
1066
|
partition_table_binary = f.read(args.partition_table_size)
|
|
839
1067
|
partition_table = PartitionTable.from_binary(partition_table_binary)
|
|
840
1068
|
elif args.partition_table_file:
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
)[0]
|
|
1069
|
+
partition_table = load_partition_table_file(
|
|
1070
|
+
args.partition_table_file,
|
|
1071
|
+
partition_table_offset=args.partition_table_offset,
|
|
1072
|
+
primary_bootloader_offset=args.primary_bootloader_offset,
|
|
1073
|
+
recovery_bootloader_offset=args.recovery_bootloader_offset,
|
|
1074
|
+
)
|
|
848
1075
|
elif args.command == 'write-bundle' and check_write_bundle_has_partition_table(args.input_file):
|
|
849
1076
|
with ZipFile(args.input_file, 'r') as tar:
|
|
850
1077
|
partition_table_file = tar.read('partition_table.csv')
|
|
@@ -863,6 +1090,8 @@ def main(args):
|
|
|
863
1090
|
except RuntimeError as e:
|
|
864
1091
|
raise RuntimeError("Partition table could not be loaded") from e
|
|
865
1092
|
|
|
1093
|
+
require_partitions(partition_table, "the loaded source")
|
|
1094
|
+
|
|
866
1095
|
# Read otadata so the printed table can mark the active app partition
|
|
867
1096
|
otadata_params: OtaDataParameters | None = None
|
|
868
1097
|
if esp:
|
|
@@ -897,7 +1126,8 @@ def main(args):
|
|
|
897
1126
|
) if args.primary_bootloader_offset is not None else None
|
|
898
1127
|
)
|
|
899
1128
|
|
|
900
|
-
if args.command == '
|
|
1129
|
+
if args.command == 'print-table':
|
|
1130
|
+
# The shared path above already loaded and printed the table.
|
|
901
1131
|
pass
|
|
902
1132
|
elif args.command == 'read':
|
|
903
1133
|
command_read(
|
|
@@ -955,6 +1185,13 @@ def main(args):
|
|
|
955
1185
|
)
|
|
956
1186
|
elif args.command == 'dump-bundle':
|
|
957
1187
|
command_dump_bundle(esp=esp, partition_table=partition_table, output_file=args.output_file)
|
|
1188
|
+
elif args.command == 'dump-table':
|
|
1189
|
+
command_dump_table(
|
|
1190
|
+
esp=esp,
|
|
1191
|
+
partition_table=partition_table,
|
|
1192
|
+
output_file=args.output_file,
|
|
1193
|
+
output_format=args.format,
|
|
1194
|
+
)
|
|
958
1195
|
elif args.command == 'write-bundle':
|
|
959
1196
|
command_write_bundle(
|
|
960
1197
|
esp=esp,
|
|
@@ -1012,12 +1249,6 @@ def _main():
|
|
|
1012
1249
|
# Devices subcommand
|
|
1013
1250
|
devices_parser = subparsers.add_parser('devices', help='Device list')
|
|
1014
1251
|
|
|
1015
|
-
# List subcommand
|
|
1016
|
-
list_parser = subparsers.add_parser('list', help='List partitions')
|
|
1017
|
-
list_parser.add_argument('--partition-type', help='Filter by partition type')
|
|
1018
|
-
list_parser.add_argument('--partition-subtype', help='Filter by partition subtype')
|
|
1019
|
-
list_parser.add_argument('--partition-name', help='Filter by partition name')
|
|
1020
|
-
|
|
1021
1252
|
# Read subcommand
|
|
1022
1253
|
read_parser = subparsers.add_parser('read', help='Read partition')
|
|
1023
1254
|
read_parser.add_argument('partition', help='Name of the partition to read')
|
|
@@ -1090,6 +1321,26 @@ def _main():
|
|
|
1090
1321
|
print_bundle_parser = subparsers.add_parser('print-bundle', help='Print partition table and app info from a partition bundle ZIP')
|
|
1091
1322
|
print_bundle_parser.add_argument('bundle_file', help='Input ZIP bundle file')
|
|
1092
1323
|
|
|
1324
|
+
# Print Table subcommand
|
|
1325
|
+
print_table_parser = subparsers.add_parser('print-table', aliases=['list'], help='Print a partition table from a CSV or binary file, or from the device')
|
|
1326
|
+
print_table_parser.add_argument('table_file', nargs='?', help='Partition table CSV or binary file (default: read from the device, or --partition-table-file)')
|
|
1327
|
+
|
|
1328
|
+
# Convert Table subcommand
|
|
1329
|
+
convert_table_parser = subparsers.add_parser('convert-table', help='Convert a partition table file between CSV and binary')
|
|
1330
|
+
convert_table_parser.add_argument('input_file', help='Input partition table CSV or binary file')
|
|
1331
|
+
convert_table_parser.add_argument('output_file', help='Output partition table file (.csv or .bin)')
|
|
1332
|
+
convert_table_parser.add_argument('-f', '--format', choices=['csv', 'bin'], help='Output format (default: inferred from output extension)')
|
|
1333
|
+
|
|
1334
|
+
# Dump Table subcommand
|
|
1335
|
+
dump_table_parser = subparsers.add_parser('dump-table', help='Dump the partition table from the device to a file')
|
|
1336
|
+
dump_table_parser.add_argument('output_file', nargs='?', help='Output file (default: {chip}-{mac}-{timestamp}-partition-table.csv)')
|
|
1337
|
+
dump_table_parser.add_argument('-f', '--format', choices=['csv', 'bin'], help='Output format (default: inferred from output extension, else csv)')
|
|
1338
|
+
|
|
1339
|
+
# Write Table subcommand
|
|
1340
|
+
write_table_parser = subparsers.add_parser('write-table', help='Write a partition table from a CSV or binary file to the device')
|
|
1341
|
+
write_table_parser.add_argument('table_file', help='Partition table CSV or binary file to flash')
|
|
1342
|
+
write_table_parser.add_argument('--force', action='store_true', help='Flash even if the partition table fails verification')
|
|
1343
|
+
|
|
1093
1344
|
# Create NVS subcommand
|
|
1094
1345
|
create_nvs_parser = subparsers.add_parser('create-nvs', help='Generate NVS partition image from CSV')
|
|
1095
1346
|
create_nvs_parser.add_argument('csv_file', help='Input NVS CSV file')
|
|
@@ -1127,6 +1378,8 @@ def _main():
|
|
|
1127
1378
|
args = parser.parse_args()
|
|
1128
1379
|
if args.command == 'reflash':
|
|
1129
1380
|
args.command = 'write-image'
|
|
1381
|
+
if args.command == 'list':
|
|
1382
|
+
args.command = 'print-table'
|
|
1130
1383
|
if args.command == 'enter-bootloader' and not args.port:
|
|
1131
1384
|
parser.error("enter-bootloader requires -p/--port")
|
|
1132
1385
|
try:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: idftool
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
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
|
|
@@ -103,7 +103,7 @@ Setting boot partition to 'ota_1'...
|
|
|
103
103
|
|---------|-------------|
|
|
104
104
|
| **Discovery** | |
|
|
105
105
|
| [`devices`](#devices) | List serial ports with hardware IDs |
|
|
106
|
-
| [`list`](#
|
|
106
|
+
| [`list`](#print-table) | Print the partition table (alias of `print-table`) |
|
|
107
107
|
| **Partition I/O** | |
|
|
108
108
|
| [`read`](#read) | Read a partition (or slice) into a file |
|
|
109
109
|
| [`write`](#write) | Write one or more files to named partitions |
|
|
@@ -126,6 +126,11 @@ Setting boot partition to 'ota_1'...
|
|
|
126
126
|
| [`dump-bundle`](#dump-bundle) | Pack every partition from the device into a ZIP |
|
|
127
127
|
| [`write-bundle`](#write-bundle) | Flash every binary in a bundle ZIP |
|
|
128
128
|
| [`print-bundle`](#print-bundle) | Print partition table and app info from a bundle ZIP |
|
|
129
|
+
| **Partition table** | |
|
|
130
|
+
| [`convert-table`](#convert-table) | Convert a partition table file between CSV and binary |
|
|
131
|
+
| [`print-table`](#print-table) | Print a partition table from a CSV or binary file, or the device |
|
|
132
|
+
| [`dump-table`](#dump-table) | Read the partition table from the device into a file |
|
|
133
|
+
| [`write-table`](#write-table) | Flash a partition table from a CSV or binary file |
|
|
129
134
|
| **NVS** | |
|
|
130
135
|
| [`create-nvs`](#create-nvs) | Generate an NVS partition image from a CSV file |
|
|
131
136
|
| [`write-nvs`](#write-nvs) | Generate an NVS image from CSV and flash it |
|
|
@@ -141,17 +146,6 @@ hardware IDs.
|
|
|
141
146
|
idftool devices
|
|
142
147
|
```
|
|
143
148
|
|
|
144
|
-
#### `list`
|
|
145
|
-
Print the device's partition table. With a connected ESP, idftool also
|
|
146
|
-
reads the application descriptor of every app partition (project name,
|
|
147
|
-
version) and marks the currently-active OTA slot with a trailing `*`. The
|
|
148
|
-
selected otadata copy is shown next to the otadata partition's subtype
|
|
149
|
-
(`ota (A)`, `ota (B)`, or `ota (invalid)` when otadata is erased).
|
|
150
|
-
```text
|
|
151
|
-
idftool list
|
|
152
|
-
idftool --partition-table-file partitions.csv list # offline
|
|
153
|
-
```
|
|
154
|
-
|
|
155
149
|
### Partition I/O
|
|
156
150
|
|
|
157
151
|
#### `read`
|
|
@@ -312,6 +306,70 @@ version, IDF version, compile time, ELF SHA256, and target chip.
|
|
|
312
306
|
idftool print-bundle release.zip
|
|
313
307
|
```
|
|
314
308
|
|
|
309
|
+
### Partition table
|
|
310
|
+
|
|
311
|
+
These commands work directly on a partition table as a file — the table
|
|
312
|
+
is the subject, passed as a positional argument. This is distinct from the
|
|
313
|
+
global `--partition-table-file` option, which overrides the layout *other*
|
|
314
|
+
commands use to address partitions by name. Input format (CSV or binary)
|
|
315
|
+
is auto-detected; output format is inferred from the file extension
|
|
316
|
+
(`.csv`/`.bin`) or set with `--format`.
|
|
317
|
+
|
|
318
|
+
When the input is a CSV that includes a bootloader row, pass
|
|
319
|
+
`--primary-bootloader-offset` (an offset or a chip name, e.g. `esp32s3`)
|
|
320
|
+
so the offset can be resolved.
|
|
321
|
+
|
|
322
|
+
#### `convert-table`
|
|
323
|
+
Convert a partition table between CSV and binary, offline. Handles both
|
|
324
|
+
directions; the binary output includes the MD5 checksum and padding, so it
|
|
325
|
+
is ready to flash or embed in an image.
|
|
326
|
+
```text
|
|
327
|
+
idftool convert-table partitions.csv partitions.bin
|
|
328
|
+
idftool convert-table partitions.bin partitions.csv
|
|
329
|
+
idftool --primary-bootloader-offset esp32s3 convert-table partitions.csv partitions.bin
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
#### `print-table`
|
|
333
|
+
Pretty-print a partition table. Given a CSV or binary file it runs offline;
|
|
334
|
+
with no file it reads the table from the device (or from
|
|
335
|
+
`--partition-table-file`). With a connected ESP, idftool also reads the
|
|
336
|
+
application descriptor of every app partition (project name, version) and
|
|
337
|
+
marks the currently-active OTA slot with a trailing `*`; the selected otadata
|
|
338
|
+
copy is shown next to the otadata partition's subtype (`ota (A)`, `ota (B)`,
|
|
339
|
+
or `ota (invalid)` when otadata is erased).
|
|
340
|
+
|
|
341
|
+
Aliased as `list`.
|
|
342
|
+
```text
|
|
343
|
+
idftool print-table partitions.bin # from a file, offline
|
|
344
|
+
idftool print-table # from the device
|
|
345
|
+
idftool list # same thing
|
|
346
|
+
idftool --partition-table-file partitions.csv print-table
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
#### `dump-table`
|
|
350
|
+
Read the partition table from the connected device and save it. Defaults to
|
|
351
|
+
CSV with an auto-generated filename; pass an output file or `--format` to
|
|
352
|
+
choose otherwise.
|
|
353
|
+
```text
|
|
354
|
+
idftool dump-table # auto-named .csv
|
|
355
|
+
idftool dump-table backup.bin
|
|
356
|
+
idftool dump-table backup.csv --format csv
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
#### `write-table`
|
|
360
|
+
Flash a partition table from a CSV or binary file to the device. The file is
|
|
361
|
+
required, so this never reads the device's current table and writes it back
|
|
362
|
+
to itself — use `dump-table` to pull the current table. The table is verified
|
|
363
|
+
before flashing (override with `--force`).
|
|
364
|
+
|
|
365
|
+
Only the partition map (at the partition table offset) is replaced; existing
|
|
366
|
+
partition **data** on flash is not moved, resized, or erased, so a table that
|
|
367
|
+
no longer matches the flash contents can make the device unbootable.
|
|
368
|
+
```text
|
|
369
|
+
idftool write-table partitions.csv
|
|
370
|
+
idftool write-table partitions.bin --force
|
|
371
|
+
```
|
|
372
|
+
|
|
315
373
|
### NVS
|
|
316
374
|
|
|
317
375
|
idftool can generate a binary NVS (Non-Volatile Storage) partition image
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"files": [
|
|
3
|
+
"README.md",
|
|
4
|
+
"Makefile",
|
|
5
|
+
"idftool.spec",
|
|
6
|
+
"pyproject.toml",
|
|
7
|
+
"CHANGELOG.md",
|
|
8
|
+
"CONTRIBUTING.md",
|
|
9
|
+
"LICENSE.md",
|
|
10
|
+
"idftool-onedir.spec",
|
|
11
|
+
".gitignore",
|
|
12
|
+
"idftool.py",
|
|
13
|
+
"idftool/__init__.py",
|
|
14
|
+
"idftool/traceback.py",
|
|
15
|
+
"idftool/__main__.py",
|
|
16
|
+
".github/workflows/build.yml",
|
|
17
|
+
".github/workflows/publish.yml"
|
|
18
|
+
]
|
|
19
|
+
}
|
idftool-0.2.2/CHANGELOG.md
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|