Firmware update internals
How the station stores, checks and starts a firmware image uploaded over the network, and what it does when the new firmware does not come up. Steps for doing an update: Update the firmware. API fields: REST API.
Scope
- The update writes one application slot. It never changes the bootloader or the partition table.
- Settings, Wi-Fi credentials and scale calibration are stored outside the application slots and are kept.
- Images are not signed and the upload uses plain HTTP. The checks below detect a damaged or wrong file; they do not prove who built it. Install only files from the project's releases, on a network you trust, and set an access token.
- There is no download-from-URL updater, no version-order or downgrade check, and
device.update_channelhas no effect.
Flash layout
16 MiB flash, from
partitions.csv:
| Partition | Offset | Size | Holds |
|---|---|---|---|
nvs |
0x9000 |
20 KiB | Scale calibration copy, update record, reset markers |
otadata |
0xe000 |
8 KiB | Which application slot starts |
app0 |
0x10000 |
5 MiB | Application slot |
app1 |
0x510000 |
5 MiB | Application slot |
littlefs |
0xa10000 |
5.8125 MiB | /configuration.json, its backup and staging file; the tag-writer recovery record /writer-recovery.bin and its staging file |
coredump |
0xfe0000 |
128 KiB | Crash dump |
One slot runs; an upload always goes to the other one. The client cannot choose the slot. The
browser page and its assets are part of the application image, not of littlefs.
Accepting an image
An upload can start in state idle, failed, confirmed or rolled_back, and not while the
running firmware is itself still waiting to be confirmed.
| Check | When | Failure message |
|---|---|---|
| Length is 1 byte to 5 MiB and fits the other slot | Before writing | "Firmware image size is zero or exceeds the inactive OTA slot" |
X-OpenTag-Expected-Generation equals the current generation |
Before writing | "OTA generation changed before the upload began" |
| Bytes received equal the declared length | End of upload | "Firmware upload ended before the declared byte count" |
SHA-256 of the received bytes equals X-OpenTag-Image-SHA256 |
End of upload | "Firmware SHA-256 does not match the declared digest" |
| Valid ESP32-S3 application image with its appended hash | After writing | "Staged image size, chip header, or appended hash is invalid" / "Firmware image structure validation failed" |
Embedded hardware ID is wt32-sc01-plus-rev-a |
After writing | "Firmware image target is incompatible with this station" |
Embedded project name is OpenTag Station |
After writing | "Firmware image project identity is incompatible" |
A failed check leaves the running firmware selected. A check that fails before writing rejects
the request and leaves state unchanged; a check that fails later puts the update in state
failed.
After a successful upload the image is stored but not selected (ready_to_reboot). Nothing
changes until POST /update/reboot; POST /update/cancel discards the image instead.
States
state in GET /api/v1/update:
| State | Meaning |
|---|---|
idle |
No update in progress. |
upload_receiving |
Upload accepted; the other slot is open for writing. |
writing |
Image data is being written and hashed. |
validating |
Upload complete; the checks above are running. |
ready_to_reboot |
Image is valid and stored. Can be started or cancelled. |
reboot_pending |
The new slot is selected and the station is restarting. |
candidate_boot |
The new firmware is running and not yet confirmed. |
validating_candidate |
The health window is running. |
confirmed |
The new firmware passed the health check and is now permanent. |
rollback_pending |
The new firmware failed the health check; the station is returning to the previous one. |
rolled_back |
The previous firmware is running again; the new image is marked invalid. |
failed |
The last upload or update was rejected or interrupted; last_error says why. |
The enum also contains ready_to_activate. It is not produced by an upload; a stored record in
that state is read as ready_to_reboot.
capabilities in the same response reports upload_available, cancel_available,
reboot_available, maximum_image_bytes and owner_ready.
Health window and rollback
New firmware is never confirmed at start-up. It must run for 30 seconds, and at that point all of the following must be up:
- storage, and configuration either loaded or in its safe degraded mode
- the application, display and touchscreen task
- the configuration, backend, scale, network, device-control and update tasks
- the scale command queue
- the local web server
Not required: Wi-Fi being connected, Spoolman or FilaBridge being reachable, the NFC reader.
| Result | What happens |
|---|---|
| All requirements met at 30 seconds | The firmware is confirmed (confirmed). Rollback is no longer possible. |
| A requirement is missing at 30 seconds, or start-up reports a fatal error | The image is marked invalid and the station restarts into the previous firmware (rolled_back). |
| The new firmware crashes or is reset before it is confirmed | Intended: the ESP-IDF bootloader's app rollback starts the previous firmware, and the station then reports rolled_back. No automated test covers the bootloader step; step 31 of the hardware acceptance checks checks it. |
| A factory reset is waiting to finish | The reset completes first; the health decision waits for it. |
Interruptions
| Power lost or connection dropped | State after restart | Next step |
|---|---|---|
| During upload or validation | failed: "OTA upload was interrupted before image validation". Previous firmware runs. |
Upload again. |
| After validation, before the restart was requested | ready_to_reboot; the image is kept. |
Start it or cancel it. |
| After the new slot was selected, before the new firmware started | Still selected. | The next restart starts the new firmware. |
| During the 30-second health window | Intended: previous firmware runs (bootloader rollback, checked by the hardware acceptance); rolled_back. |
Upload again. |
| The stored record does not match what is running | failed: "The previous update did not complete; upload it again." or a similar message. |
Upload again. |
A browser that loses its connection during the restart should read GET /update again once the
station is back; the receipt of the restart request is not proof that the update succeeded.
Limits
| Item | Value |
|---|---|
| Largest image | 5 MiB (5,242,880 bytes) |
| Upload stall timeout | 5 seconds without data |
| Upload total timeout | 180 seconds |
| Health window | 30 seconds |
| Concurrent updates | One. Reboot, factory reset and update restart are refused while a tag or Spoolman operation is queued or running. |
| Update record | NVS namespace opentagOta; survives restarts and updates |
Recovery
| Situation | Path |
|---|---|
| Station runs and is reachable | Upload again over the network. |
| New firmware failed | Nothing to do; the station returned to the previous firmware by itself. Check last_error. |
| Station does not start, or the web page is unreachable | Reinstall over USB: Install the firmware over USB. |
| Settings are suspect after recovery | Factory reset and USB recovery |
How release images and the installer are built: Release process and Build from source.