Part 2: guarded migration to the exact Tasmota Safeboot layout
Status
This is the reviewed design behind the staged tools. The operator procedure is
in part-2-safeboot-migration-runbook.md.
The destructive commit remains locked by default and has not been trialled on
this S60 hardware.
The source S60TPG table was decoded from a complete 4 MiB flash dump and its
MD5 record was verified. See
s60-actual-partition-map.md.
Objective
Replace the stock S60 table with an exact copy of the partition table embedded
in the official release tasmota32c3.factory.bin:
| Partition | Type | Subtype | Start | End (exclusive) | Size |
|---|---|---|---|---|---|
nvs |
data | NVS | 0x009000 |
0x00e000 |
20 KiB (0x005000) |
otadata |
data | OTA | 0x00e000 |
0x010000 |
8 KiB (0x002000) |
safeboot |
app | factory | 0x010000 |
0x0e0000 |
832 KiB (0x0d0000) |
app0 |
app | OTA 0 | 0x0e0000 |
0x3b0000 |
2,880 KiB (0x2d0000) |
spiffs |
data | SPIFFS | 0x3b0000 |
0x400000 |
320 KiB (0x050000) |
Every entry and flag, the table MD5 record, and erased-byte padding in the 4 KiB table sector must match the selected official factory artifact exactly.
Consequences of the exact layout
The exact table deliberately does not preserve the S60-specific system-data layout:
nvsshrinks from 64 KiB to the official 20 KiB;reserveis removed;otadatamoves from0x01d000to0x00e000;phy_initis removed;- Safeboot starts at the official
0x010000instead of the S60 app boundary at0x020000.
Safeboot has no initial Wi-Fi configuration UI. The existing first 20 KiB of NVS must therefore be proven readable after the logical shrink before commit. Configuration export is mandatory, but it cannot restore connectivity until normal Tasmota is running.
The captured dump does not presently pass that requirement. Its live
Settings blob index declares two chunks: chunk 1 is within the retained
20 KiB, while chunk 0 is in the page beginning at 0x010000. That page will
become Safeboot. Seeing the Settings key in the retained pages is therefore
not sufficient; every indexed chunk and its CRC must validate inside the
retained window.
Required artifacts
Freeze these before touching the layout:
- The private original 4 MiB S60 dump.
- The original table sector and both old
otadatasectors. - The current
0x009000..0x00dfffNVS bytes. - A Tasmota configuration export and the S60 GPIO template.
- The exact Bluetooth image currently running.
- One pinned official release
tasmota32c3.factory.bin. - The official Safeboot and normal C3 application images from the same release.
- The official table sector extracted from that factory image.
- SHA-256 values and byte lengths for every artifact.
- The immutable
tasmota/installcommit and corresponding Tasmota source commit which produced the official set.
The private dump and NVS data must remain outside Git.
Phase 0: offline validation
Do not write the plug during this phase.
- Extract
0x8000..0x8ffffrom the pinned official factory image rather than regenerating an approximately equivalent table. - Parse the extracted table independently and require exact agreement with the target above, including flags and MD5.
- Verify image magic and complete ESP image boundaries at
0x010000and0x0e0000inside the factory image. - Require Safeboot to fit within
0x0d0000and final Tasmota within0x2d0000. - Parse the current first five NVS pages (
0x009000..0x00dfff). Require an active page, adequate free space, and complete, CRC-valid blob indexes and chunks for TasmotaSettingsand Wi-Fista.apinfo. The frozen source dump is expected to fail this check and is retained as evidence, not accepted as migration-ready NVS. - Prepare an inspect-only Berry preflight that reports the live table hash, running partition, flash size, NVS state and every artifact hash.
- Prepare a separate commit operation protected by an exact, single-use confirmation token.
Phase 1: put Bluetooth in the high stock slot
- While Bluetooth Tasmota runs from old
ota_0at0x020000, upload the exact same Bluetooth binary through the normal Firmware Upgrade page. - Let Tasmota write and validate old
ota_1at0x210000, select it and reboot. - Require:
- the expected Tasmota version and build hash;
- Tasmota Information marks old
ota_1active; - Berry
flash.current_ota()returns1; - the live partition sector still matches the original S60 hash;
- Wi-Fi and Berry remain usable.
- Back up Tasmota configuration, the S60 template, rules and custom files.
- Disable periodic settings saves, then run the read-only preflight. This does not suppress every ESP-IDF Wi-Fi-stack NVS write, so stage and commit still require an exact match to the captured NVS hash. The high Bluetooth first boot may have compacted the current record; if this passes, skip the reset.
- If the live NVS remains incomplete, issue documented Tasmota
Reset 4while high Bluetooth is active. This resets firmware settings to defaults while retaining Wi-Fi and may cause NVS to recreate compact records. Reconfirm Wi-Fi, Berry access and oldota_1after restart. - Disable periodic settings saves again, then capture and validate the first
20 KiB with a fresh preflight.
Reset 4is not the proof: stop unless it independently finds every declaredSettingsandsta.apinfoblob chunk, correct total sizes, valid entry/data CRCs, and usable page structure wholly within that range.
Before the table commit, the low Bluetooth image remains a bootable fallback. Run the live phases without unnecessary delay on a stable Wi-Fi link. If the NVS hash changes, rerun preflight and staging against the new baseline rather than weakening the hash or evidence-age gates.
Phase 2: stage the exact official Safeboot payload
Safeboot ultimately belongs at 0x010000, which overlaps the source table’s
NVS tail, reserve, old otadata, phy_init, and low application. It must be
written only while Bluetooth executes from the high slot.
- Serve the pinned Safeboot payload from the local workstation. Do not fetch an unpinned moving URL during migration.
- Stage and verify a complete copy in the inactive low stock application
area at
0x020000first. - Read the staged bytes back using the RAM-only flash-download service and require their SHA-256 to match the pinned artifact.
- Explicitly erase the final staging sector before the partial final write,
then require the bytes from the image end to the sector boundary to remain
erased. A partial Berry
flash.writeotherwise preserves that sector tail. - Keep the official table sector, original table sector and exact Safeboot length available for the final operation.
Staging at 0x020000 proves the payload before any system-data address is
overwritten. It is temporary; the commit operation copies the verified image
downward to the canonical 0x010000 address while running from high flash.
Phase 3: canonical-layout commit
This is the destructive phase. External power must be stable and isolated serial recovery must be accepted as unavailable.
Immediately before commit, require:
- execution from old
ota_1at0x210000; - the live table equals the allow-listed S60 table-sector hash;
- the staged Safeboot copy equals its allow-listed hash;
- the first 20 KiB NVS preflight still passes;
- the target sector exactly equals
0x8000..0x8ffffrom the pinned official factory image; - the original table and live old
otadataare buffered in RAM for a same-session source-layout restoration attempt if any commit write fails.
Commit sequence:
- Copy the verified Safeboot image downward from staging at
0x020000to its canonical address0x010000, one 4 KiB sector at a time. Read a source sector before erasing/writing its lower destination. - Verify the canonical Safeboot hash and its erased partition tail in flash.
- Erase and verify the new
otadatarange at0x00e000..0x00ffffso the bootloader selects the factory partition after the table changes. - Erase/write the exact official table sector at
0x8000..0x8fff. - Read the table back and compare every byte and its SHA-256.
- If a commit operation fails while Bluetooth remains alive, make one
controlled attempt to restore and verify the original table and live old
otadata. Do not reboot after a rollback attempt. - Reboot immediately using the already-running application; do not invoke
flash.factory()after changing the table.
flash.factory() must not be used in this exact-layout migration. ESP-IDF’s
in-RAM partition registry still describes the source S60 table until reboot,
so the function would erase the old otadata address at 0x01d000. That
address is inside canonical Safeboot after it has been copied to 0x010000,
and erasing it would corrupt the staged recovery image. With the new
0x00e000 otadata sectors erased and a factory entry present, the bootloader
selects Safeboot without that call.
Writing Safeboot at 0x010000 destroys the old NVS tail and old boot metadata.
After that copy begins, an unexpected reset relies on the bootloader locating
the still-valid high Bluetooth image until the official table is committed.
Power loss during the table-sector erase/write can require physical recovery.
Contingency when retained NVS cannot pass
Do not weaken the official-Safeboot NVS gate. After one controlled Reset 4
attempt still fails, use only the separately reviewed procedure in
part-2-safeboot-recovery-contingency.md.
It temporarily installs a private Safeboot build that recreates compile-time
Wi-Fi defaults in RAM, commits the same byte-exact official partition table,
installs the pinned official app, and finally restores the exact official
Safeboot payload from that running app.
Phase 4: verify official Safeboot
- Require the device to return at its reserved address using configuration from the retained canonical 20 KiB NVS region.
- Confirm the running application is factory
safebootat0x010000. - Confirm the live table sector exactly matches the official factory image.
- Confirm
app0at0x0e0000is not selected before a valid image is installed. - Stop if the Safeboot firmware-upload page is unavailable.
There is no Wi-Fi setup fallback in Safeboot. Failure to read the retained NVS configuration is not recoverable over Wi-Fi.
Phase 5: install normal Tasmota
- Upload the pinned normal
tasmota32c3.binthrough Safeboot. - Safeboot writes it to official
app0at0x0e0000. - Confirm the running partition offset, version and image hash after reboot.
- Confirm SPIFFS at
0x3b0000can be formatted, written, read and retained across restart. - Restore or verify the S60 template and test relay, button, LED, CSE7766 metering, Wi-Fi, web UI and normal restart.
- Perform one subsequent Safeboot-mediated OTA cycle before declaring the migration complete.
Layout progression
| Stage | Low region | High region | Partition table |
|---|---|---|---|
| Initial | Bluetooth in old ota_0 |
Previous Tasmota in old ota_1 |
Original S60 |
| High staging | Bluetooth in old ota_0 |
Bluetooth active in old ota_1 |
Original S60 |
| Safeboot staging | Verified Safeboot copy begins at 0x020000 |
Bluetooth active at 0x210000 |
Original S60 |
| Commit | Safeboot copied to 0x010000 |
Bluetooth remains executing at 0x210000 |
Replaced once with exact official table |
| First canonical boot | Safeboot active at 0x010000 |
Old bytes are now inside unpopulated app0/SPIFFS regions |
Exact official |
| Final | Safeboot at 0x010000 |
Normal Tasmota in app0; SPIFFS formatted |
Exact official |
Optimizations retained after review
- Use normal Tasmota OTA to establish the high Bluetooth copy instead of raw partition copying.
- Extract the table sector from the pinned factory binary; do not recreate it from CSV during the live operation.
- Stage and hash Safeboot before overwriting canonical low addresses.
- Perform only one partition-table replacement.
- Separate inspect, stage and commit operations; commit remains disabled by default.
- Verify large images from the workstation instead of retaining them in Berry RAM.
- Preserve an in-RAM original table for immediate restoration after a detected table-write error.
Go/no-go conditions
Implementation may begin only if all answers are yes:
- Is an exact copy of the selected official release table required despite the increased Wi-Fi recovery risk?
- After backup and controlled NVS recreation, does the live first 20 KiB NVS
region independently validate complete
Settingsandsta.apinfoblobs? - Are all artifacts from one pinned official release and independently hashed?
- Are Safeboot and final Tasmota smaller than their official partitions?
- Are inspect and commit separate, with commit disabled by default?
- Is stable power available for the sole table-sector write?
- Will the private dump and NVS data remain outside Git?