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.
Files changed (26) hide show
  1. idftool-0.3.0/CHANGELOG.md +94 -0
  2. {idftool-0.2.2 → idftool-0.3.0}/PKG-INFO +71 -13
  3. {idftool-0.2.2 → idftool-0.3.0}/README.md +70 -12
  4. {idftool-0.2.2 → idftool-0.3.0}/idftool/__main__.py +274 -21
  5. {idftool-0.2.2 → idftool-0.3.0}/idftool.egg-info/PKG-INFO +71 -13
  6. {idftool-0.2.2 → idftool-0.3.0}/idftool.egg-info/SOURCES.txt +2 -0
  7. idftool-0.3.0/idftool.egg-info/scm_file_list.json +19 -0
  8. idftool-0.3.0/idftool.egg-info/scm_version.json +8 -0
  9. idftool-0.2.2/CHANGELOG.md +0 -48
  10. {idftool-0.2.2 → idftool-0.3.0}/.github/workflows/build.yml +0 -0
  11. {idftool-0.2.2 → idftool-0.3.0}/.github/workflows/publish.yml +0 -0
  12. {idftool-0.2.2 → idftool-0.3.0}/.gitignore +0 -0
  13. {idftool-0.2.2 → idftool-0.3.0}/CONTRIBUTING.md +0 -0
  14. {idftool-0.2.2 → idftool-0.3.0}/LICENSE.md +0 -0
  15. {idftool-0.2.2 → idftool-0.3.0}/Makefile +0 -0
  16. {idftool-0.2.2 → idftool-0.3.0}/idftool/__init__.py +0 -0
  17. {idftool-0.2.2 → idftool-0.3.0}/idftool/traceback.py +0 -0
  18. {idftool-0.2.2 → idftool-0.3.0}/idftool-onedir.spec +0 -0
  19. {idftool-0.2.2 → idftool-0.3.0}/idftool.egg-info/dependency_links.txt +0 -0
  20. {idftool-0.2.2 → idftool-0.3.0}/idftool.egg-info/entry_points.txt +0 -0
  21. {idftool-0.2.2 → idftool-0.3.0}/idftool.egg-info/requires.txt +0 -0
  22. {idftool-0.2.2 → idftool-0.3.0}/idftool.egg-info/top_level.txt +0 -0
  23. {idftool-0.2.2 → idftool-0.3.0}/idftool.py +0 -0
  24. {idftool-0.2.2 → idftool-0.3.0}/idftool.spec +0 -0
  25. {idftool-0.2.2 → idftool-0.3.0}/pyproject.toml +0 -0
  26. {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.2.2
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`](#list) | Print the partition table |
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`](#list) | Print the partition table |
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
- with ZipFile(file, 'r') as zf:
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 = os.path.getsize(image_file_path)
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
- with ZipFile(bundle_file_path, 'r') as zf:
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 ['list', 'create-image', 'create-bundle', 'create-nvs'] or not args.partition_table_file
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 not args.primary_bootloader_offset and esp:
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
- with open(args.partition_table_file, 'rb') as f:
842
- partition_table = PartitionTable.from_file(
843
- f,
844
- partition_table_offset=args.partition_table_offset,
845
- primary_bootloader_offset=args.primary_bootloader_offset,
846
- recovery_bootloader_offset=args.recovery_bootloader_offset
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 == 'list':
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.2.2
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`](#list) | Print the partition table |
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
@@ -18,4 +18,6 @@ idftool.egg-info/SOURCES.txt
18
18
  idftool.egg-info/dependency_links.txt
19
19
  idftool.egg-info/entry_points.txt
20
20
  idftool.egg-info/requires.txt
21
+ idftool.egg-info/scm_file_list.json
22
+ idftool.egg-info/scm_version.json
21
23
  idftool.egg-info/top_level.txt
@@ -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
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.3.0",
3
+ "distance": 0,
4
+ "node": "gbf3a2f9bff2743367428ab9e8273189243b01ba5",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-07-15"
8
+ }
@@ -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