Firmware Update
Firmware update uses two BLE paths:
- control commands on the protocol characteristic as
Reaction.firmware_update - raw block writes on the OTA data characteristic
Commands
| Command | Path | Response |
|---|---|---|
START |
protocol characteristic, protobuf plus SLIP | status response |
DATA |
protobuf command is implemented, but clients should use the raw OTA data characteristic | no response when sent through the raw OTA path |
STATUS |
protocol characteristic, protobuf plus SLIP | status response |
FINALIZE |
protocol characteristic, protobuf plus SLIP | status response |
ABORT |
protocol characteristic, protobuf plus SLIP | status response |
The BLE protocol handler sends a firmware response for all firmware commands except protobuf DATA.
START
Request fields:
| Field | Meaning |
|---|---|
total_size |
firmware image size in bytes |
block_size |
firmware bytes per block |
sha256 |
accepted by the message; current start handling logs it when exactly 32 bytes but does not store it for later validation |
On success, OTA state becomes RECEIVING.
The ESP OTA manager rejects START when:
- another update is already in progress
total_sizeis zeroblock_sizeis zero- no update partition is available
- ESP-IDF OTA begin fails
DATA Blocks
Recommended raw OTA data characteristic payload:
uint32_le block_index
raw block bytes
Rules enforced by the OTA manager:
- block index must be less than
total_blocks - block data must be non-empty
- duplicate blocks are accepted and ignored
- blocks may arrive out of order because writes use
esp_ota_write_with_offset - the last block may be shorter than
block_size - any non-last block longer than its expected length is rejected
- block zero validates enough ESP image header bytes when available
For MTU 247, the usual raw block payload is:
247 - 3 ATT bytes - 4 block-index bytes = 240 firmware bytes
STATUS
The status response includes:
| Field | Meaning |
|---|---|
state |
current OTA state |
result |
last OTA result |
total_blocks |
total expected block count |
received_blocks |
count of received blocks |
missing_blocks |
up to the first 100 missing block indexes |
Use STATUS to resume after interruption and to decide which missing blocks to retransmit.
FINALIZE
Finalize succeeds only from RECEIVING and only when every block has been received.
When sha256 is exactly 32 bytes on FINALIZE, the firmware compares it against the SHA-256 of the update partition. If omitted, the ESP image validation and boot partition selection still run.
On success:
- state becomes
COMPLETE - boot partition is set to the update partition
- the firmware schedules a reboot
Current code detail: FirmwareUpdateHandler waits 2000 ms before calling RebootToNewFirmware(), and EspOtaManager::RebootToNewFirmware() waits another 2000 ms before calling esp_restart().
ABORT
ABORT calls the OTA manager abort path, clears reboot scheduling, and returns status.
If OTA is already idle, abort is effectively harmless.
State Model
stateDiagram-v2
[*] --> IDLE
IDLE --> RECEIVING: START success
RECEIVING --> VALIDATING: FINALIZE with all blocks
VALIDATING --> COMPLETE: validation and boot partition success
VALIDATING --> ERROR: validation or flash failure
RECEIVING --> IDLE: ABORT
ERROR --> IDLE: ABORT
COMPLETE --> [*]: reboot
Client Flow
- Connect, subscribe, and obtain write authorization.
- Negotiate MTU.
- Send
STARTwithtotal_sizeandblock_size. - Write raw data blocks to the OTA data characteristic.
- Periodically send
STATUS. - Retransmit any
missing_blocks. - Send
FINALIZE. - Wait for disconnect/reboot, then reconnect and query device info.
Use write-with-response for control commands. Use write-without-response for raw data blocks.