Rollback¶
SimpleOTAClient v0.2.0 adds trial-install rollback for Arduino. After a
successful apply() the new firmware is marked as a trial: it must call
confirmRunning() within a timeout or the library boots the device back into
the previous partition. The outcome (confirmed or rolled_back) is reported
to the server automatically on the next /check/.
This guide covers the mechanism in detail. For the quick-start see Arduino integration.
How a trial install works¶
%%{init: {"flowchart": {"rankSpacing": 35, "nodeSpacing": 20}}}%%
flowchart TD
classDef device fill:#e0e7ff,color:#312e81,stroke:#6366f1
classDef lib fill:#e0f2fe,color:#075985,stroke:#0ea5e9
classDef ok fill:#dcfce7,color:#166534,stroke:#16a34a
classDef bad fill:#fee2e2,color:#991b1b,stroke:#dc2626
A["<code>apply()</code> called"]:::device --> B["<code>download_started</code>, <code>downloaded</code><br/>SHA-256 verified"]:::lib
B --> C["<code>flashed</code><br/>written to OTA partition"]:::lib
C --> D["<code>validated</code><br/><code>sota_trial=1</code> saved to NVS"]:::lib
D --> E["<code>reboot</code> event, <code>esp_restart()</code>"]:::lib
E --> F(["New image boots"]):::device
F --> G["<code>processBootValidation()</code><br/>detects <code>sota_trial=1</code>"]:::lib
G --> H["Timer armed on first <code>begin()</code>, <code>check()</code>,<br/><code>isTrialInstall()</code>, or <code>confirmRunning()</code>"]:::lib
H --> I{"<code>confirmRunning()</code> called<br/>within timeout?"}
I -- Yes --> J["Trial state cleared from NVS"]:::ok
J --> K["next <code>check()</code>: POST /status/<br/>event=confirmed"]:::ok
K --> L(["Build recorded as stable"]):::ok
I -- "No: timer fires" --> M["<code>performRollback()</code><br/><code>sota_trial=2</code>, <code>esp_restart()</code>"]:::bad
M --> N(["Previous image boots"]):::bad
N --> O["<code>processBootValidation()</code><br/>detects <code>sota_trial=2</code>"]:::bad
O --> P["next <code>check()</code>: POST /status/<br/>event=rolled_back"]:::bad
P --> Q(["Deployment auto-paused"]):::bad
Colours: indigo = app/device layer; blue = library automatic behaviour; green = confirmation path; red = rollback path.
The library operates at the application layer: it manages the OTA partition swap and the confirm window independently of the ESP32 bootloader's rollback flag. No bootloader configuration is required.
Confirm modes¶
Managed mode, auto-confirm (default)¶
The OTA background task (started by begin()) confirms the trial as soon as
it gets a 2xx response from /check/. This proves that boot, network, TLS,
and token auth are all working.
No extra code is needed:
ota.begin(3600, nullptr, wifiUp);
// Trial is confirmed automatically on the first successful /check/.
Managed mode, manual confirm¶
Disable auto-confirm to gate the trial on your own application health:
// In setup():
ota.setManagedAutoConfirm(false);
ota.setConfirmTimeout(120); // seconds
ota.begin(3600, nullptr, wifiUp);
// In loop():
static bool confirmed = false;
if (!confirmed && ota.isTrialInstall() && applicationHealthy()) {
ota.confirmRunning();
confirmed = true;
}
isTrialInstall() lets you skip the health-check work entirely on normal
(non-trial) boots, keeping the loop lightweight.
Polling mode, explicit confirm¶
The library never auto-confirms in polling mode. Call confirmRunning() once
per loop, before check(), at a point where you are confident the application
is working:
void loop() {
ota.confirmRunning(); // no-op outside a trial
if (ota.check()) {
ota.apply(); // reboots on success by default
}
delay((uint32_t)SIMPLEOTA_CHECK_INTERVAL_S * 1000UL);
}
Omitting this call means the device rolls back after the timeout on every OTA, regardless of whether the new firmware is healthy.
Timeout¶
The confirm window starts when the library initialises (on the first call to
begin(), check(), isTrialInstall(), or confirmRunning()), not when
apply() was called on the previous boot.
Default: 300 seconds. Override with:
Guidelines:
| Transport | Suggested timeout |
|---|---|
| Wi-Fi (fast home network) | 60-120 s |
| Wi-Fi (enterprise / captive portal) | 180-300 s |
| Cellular (LTE-M / NB-IoT) | 300-600 s |
Set the timeout to the worst-case time from device power-on to your application's healthy state, with margin. If in doubt, use the default.
Dashboard visibility¶
When a trial is confirmed the device's status moves to confirmed in the deployment progress view. When a trial rolls back:
- The device reverts to its previous build number.
- The
rolled_backevent appears in the device event log. - The deployment progress counter does not count the device as confirmed.
Automatic deployment pause¶
By default, the deployment pauses automatically the moment the first
rolled_back event is received. From that point, new /check/ requests
behave as if the deployment does not exist, so no additional devices are
offered the update while you investigate.
This behaviour is controlled by two settings on the deployment, both configurable from the deployment form and the deployment detail page:
- Auto-pause on rollback (default: on): whether to pause at all.
- Rollback pause threshold (default: 1): number of
rolled_backevents required before the pause fires. Raise this if you expect transient rollbacks (for example, a cellular fleet where some devices may time out on slow modem registration) and want to require multiple failures before stopping the rollout.
After the automatic pause you can resume the deployment (if the rollback was a known transient) or cancel it and deploy a corrected build.
Limitations¶
- Pre-library crashes are not caught. The rollback timer is armed lazily
inside
begin(), or the first call tocheck(),isTrialInstall(), orconfirmRunning(). If the firmware crashes before any of these run (for example in a global constructor or duringSerial.begin()), the timer is never armed and the device reboot-loops into the bad image indefinitely. This is a fundamental difference from bootloader-level rollback and the most important limitation to understand. - Single OTA partition:
apply()fails withOTA_FLASH_FAILbefore trial state is written to NVS, so no trial is started and no rollback occurs. The net effect is the same as having rollback disabled. - ESP-IDF: the trial-install mechanism is specific to SimpleOTAClient
(Arduino). For ESP-IDF, use
esp_ota_mark_app_valid_cancel_rollback()after your own health check. See ESP-IDF integration.
Related¶
- Arduino integration: quick-start and API table
- Pausing deployments: server-side levers for fleet-wide problems
- Library README: canonical API reference