Skip to content

OTA Update Mechanism

Why a State Machine

OTA updates are long-running, multi-step operations that can fail at any stage. A finite state machine (FSM) makes the allowed transitions explicit, prevents illegal state combinations, and provides a clear recovery path from every failure mode. The current state is always observable via GetOTAStatus.

State Persistence and Power-Loss Recovery

When entering one of six checkpointable states (update_available, downloading, paused, update_ready, download_failed, update_failed), OTAStateMachine writes an OTASnapshot to a YAML checkpoint file at DEFAULT_CHECKPOINT_FILE (/data/tolomeo/ota-service.yml). The snapshot records the current state, installed firmware info, and pending update info. Entering updated or update_successful instead deletes the checkpoint.

Note

If /data does not exist at runtime (development machines, CI environments), the checkpoint falls back to <tempdir>/tolomeo/ota-service.yml (e.g. /tmp/tolomeo/ota-service.yml on Linux). Production edge devices are expected to have /data mounted.

At startup, the FSM enters the bootstrapping state and reads the checkpoint file. It selects one of the six detect_* transitions to move to the correct runtime state:

bootstrapping → updated           (no pending update)
bootstrapping → update_available  (update was announced)
bootstrapping → paused            (download was in progress but paused)
bootstrapping → update_ready      (download completed, not yet installed)
bootstrapping → download_failed   (download was in progress and failed)
bootstrapping → update_failed     (install was in progress and failed)

This means the service survives a power cut at any point in the update lifecycle and resumes from exactly where it left off.

Download Task Synchronisation

OTAPlugin does not drive the FSM directly during a download. Instead, it registers callbacks on TaskManager via _on_download_state_change and _on_download_error. When the download task completes, the callback fires fsm.download_success(); on failure it fires fsm.download_fail(). This decouples the FSM from the asyncio task scheduler.

Error Recovery

Every failure state has exactly one recovery path:

State Recovery command Resulting state
download_failed AbortOTAUpdate update_available
update_failed AbortOTAUpdate update_available
Any state ResetOTAState Returns the FSM to a clean baseline (bootstrapping → re-detect → updated), discarding any pending update

CancelOTAUpdate applies to in-progress states (downloading, paused, update_ready) and returns to update_available without clearing the pending update info, allowing an immediate retry.