Skip to content

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_size is zero
  • block_size is 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

  1. Connect, subscribe, and obtain write authorization.
  2. Negotiate MTU.
  3. Send START with total_size and block_size.
  4. Write raw data blocks to the OTA data characteristic.
  5. Periodically send STATUS.
  6. Retransmit any missing_blocks.
  7. Send FINALIZE.
  8. 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.