Upgrading to 1.0.0¶
Arduino SimpleOTAClient 1.0.0 and ESP-IDF simpleota 1.0.0 share one update core and the same simpleota.h API. Inside simpleota_apply(), and before any download, both refuse an offer whose build number is not strictly above the running build and report that refusal as failed with reason downgrade_refused. simpleota_check() still returns the offer. SimpleOTA is roll-forward only: to move the fleet back to older code, upload that binary again so the platform assigns a new, higher build number and deploy it.
Trial-boot rollback still works the same way, so a new image that isn't confirmed within the timeout returns to the previous one. Every OTA apply runs as a trial on both platforms when bootloader or software rollback is enabled.
Install 1.0.0 from the Arduino Library Manager or the Arduino releases page, and on ESP-IDF with idf.py add-dependency "wassie-dev/simpleota^1.0.0". The snippets below describe the API at those versions.
Shared: image_build¶
When firmware is flashed over USB, or installed any other way than by SimpleOTA, the client stores the running image ELF SHA-256 in NVS. On boot, if that fingerprint changed and no pending SimpleOTA trial explains it, the device reports image_build from config (default 0) instead of the build number left from an earlier OTA. Reflashing the same image keeps the stored build; image_build does not override that.
In release firmware you also ship by serial, set it to the build number SimpleOTA assigned when you uploaded that release, so anti-rollback and the server see the correct build. Set cfg.image_build in simpleota_config_t before simpleota_init() on both Arduino and ESP-IDF.
If you USB-flash a device back to a pre-1.0 client, that client keeps reporting the build number the 1.0.0 client stored in NVS until SimpleOTA installs a newer build. Flashing an old client back while a 1.0 trial is still open is not supported.
Arduino: 0.6.0 to 1.0.0¶
1.0.0 replaces the SimpleOTAClient C++ class with the same C API as ESP-IDF.
#include <simpleota.h>, fill a simpleota_config_t, and call
simpleota_init() early in setup().
0.6.x (SimpleOTAClient) |
1.0.0 (simpleota.h) |
|---|---|
SimpleOTAClient ota(token, chip, ...) |
cfg.token, cfg.chip_family, cfg.device_id, ... then simpleota_init(&cfg) |
ota.begin(interval, onResult, isConnected[, delayMs]) |
cfg.check_interval_s, cfg.event_cb, cfg.is_connected, cfg.initial_delay_ms, then simpleota_start() |
ota.end() |
simpleota_stop() |
ota.checkNow() |
simpleota_check_now() |
ota.check() / ota.apply() |
simpleota_check(&update) / simpleota_apply() |
ota.confirm() |
simpleota_confirm() |
ota.setImageBuild(n) |
cfg.image_build |
ota.setServerUrl(url) |
cfg.base_url or simpleota_set_server_url(url) while stopped |
ota.setCACert(pem) |
cfg.cert_pem (TLS stays on; use cert_pem to pin a CA) |
ota.onEvent(cb) |
cfg.event_cb with simpleota_event_t / SIMPLEOTA_EVENT_* |
OTAResult / OTA_* |
simpleota_result_t / SIMPLEOTA_* |
SECURITY_MODE_BASIC, SECURITY_MODE_SIGNED |
SIMPLEOTA_SECURITY_BASIC, SIMPLEOTA_SECURITY_SIGNED on cfg.security_mode |
setSigningPublicKey / addSigningPublicKey |
cfg.signing_keys / cfg.num_signing_keys |
SimpleOTAClient::setDebug(true) |
esp_log_level_set("simpleota", ESP_LOG_DEBUG) |
Managed mode¶
Before (0.6.0):
After (1.0.0):
#include <simpleota.h>
simpleota_config_t cfg = {};
cfg.token = token;
cfg.is_connected = wifiUp;
cfg.check_interval_s = 3600;
simpleota_init(&cfg);
simpleota_start();
Events instead of begin(onResult)¶
Before (0.6.0):
void onResult(OTAResult r) {
if (r == OTA_FLASH_FAIL) { /* ... */ }
}
ota.begin(3600, onResult, wifiUp);
After (1.0.0):
void onOtaEvent(const simpleota_event_t *e, void *) {
if (e->id == SIMPLEOTA_EVENT_UPDATE_FAILED && e->reason) {
Serial.println(e->reason);
}
}
cfg.event_cb = onOtaEvent;
simpleota_start();
Remove report() and setRollbackEnabled()¶
The library now posts lifecycle status itself, so delete your report() calls, and delete setRollbackEnabled() too, because every apply on Arduino 1.0 runs as a trial.
Confirm and trial helpers¶
Before (0.6.0): confirmRunning(), isTrialInstall()
After (1.0.0): simpleota_confirm(), simpleota_is_trial_boot()
HTTP timeout build flag¶
On 1.0.0 the knob is CONFIG_SIMPLEOTA_HTTP_TIMEOUT_MS (default 15000), set as
a -D compile flag or in ESP-IDF Kconfig. CONFIG_SIMPLEOTA_TASK_STACK_SIZE
(default 12288) sizes the managed task and the short-lived tasks that run
simpleota_check() and simpleota_apply() in polling mode. Map from 0.6.x as
follows:
| 0.6.x | 1.0.0 |
|---|---|
SIMPLEOTA_TIMEOUT_MS |
-DCONFIG_SIMPLEOTA_HTTP_TIMEOUT_MS=... |
SIMPLEOTA_HTTP_TIMEOUT_MS |
-DCONFIG_SIMPLEOTA_HTTP_TIMEOUT_MS=... |
TLS is always verified¶
setCACert(nullptr) on 0.6.x disabled verification. On 1.0.0 TLS stays on; pin
trust with cfg.cert_pem when needed.
Handle SIMPLEOTA_DOWNGRADE¶
After (1.0.0):
bool update = false;
esp_err_t err = simpleota_check(&update);
if (err == ESP_ERR_NO_MEM) { /* try again later */ }
else if (err == ESP_OK && update) {
switch (simpleota_apply()) {
case SIMPLEOTA_OK: break;
case SIMPLEOTA_NO_OFFER: break;
case SIMPLEOTA_FLASH_FAIL: break; // network, URL, flash or state failure; offer cleared unless the apply task could not be created
case SIMPLEOTA_DOWNGRADE: break; // server gets failed / downgrade_refused
}
}
Do not mix polling and managed mode¶
While simpleota_start() runs, simpleota_check() returns
ESP_ERR_INVALID_STATE and simpleota_apply() returns SIMPLEOTA_NO_OFFER.
Call simpleota_stop() before polling, or never call simpleota_start().
simpleota_set_server_url() only when stopped¶
cfg.base_url = "https://simpleota.com"; // before init, or:
simpleota_stop();
simpleota_set_server_url("https://simpleota.com");
http:// URLs are rejected. Firmware downloads follow up to three redirects,
each of which must be https://.
One client instance¶
A second simpleota_init() returns ESP_ERR_INVALID_STATE.
simpleota_stop() waits for the current step¶
simpleota_stop() blocks up to about one minute for the current check,
download, or flash step to finish.
Chip family¶
Before (0.6.0): SimpleOTAClient::CHIP_ESP32S3
After (1.0.0): cfg.chip_family = "esp32s3" (defaults to the build target
when omitted).
ESP-IDF: 0.2.1 to 1.0.0¶
In idf_component.yml, replace xanderwasserman/simpleota with wassie-dev/simpleota.
security_mode is simpleota_security_t¶
Before (0.2.x): security_mode is const char *. The macros are string literals (SIMPLEOTA_SECURITY_BASIC is "basic", SIMPLEOTA_SECURITY_SIGNED is "signed", SIMPLEOTA_SECURITY_TOKEN is "token").
cfg.security_mode = SIMPLEOTA_SECURITY_SIGNED; /* const char * "signed" */
cfg.security_mode = "basic";
After (1.0.0): the field is simpleota_security_t (simpleota.h). SIMPLEOTA_SECURITY_BASIC and SIMPLEOTA_SECURITY_SIGNED still compile, as enum constants. Assigning a string does not compile. SIMPLEOTA_SECURITY_TOKEN is removed. SIMPLEOTA_SECURITY_NONE (the zero default) reports no mode.
base_url must be https:// with a host¶
Before (0.2.1): base_url was stored as given. A plain http:// URL was accepted.
After (1.0.0): omit base_url to use https://simpleota.com, or set an https:// URL that has a host. simpleota_init() returns ESP_ERR_INVALID_ARG otherwise. Plain http:// is rejected as not_https. An empty URL, one with no host, one longer than 256 characters, or one with spaces or control characters is rejected as malformed.
simpleota_config_t cfg = {
.token = "YOUR_PROJECT_TOKEN",
.base_url = "https://simpleota.com", // https:// with a host, or omit
};
ESP_ERROR_CHECK(simpleota_init(&cfg));
simpleota_result_t and polling API¶
Before (0.2.1): polling was not in the public header.
After (1.0.0):
bool update = false;
esp_err_t err = simpleota_check(&update);
if (err == ESP_ERR_NO_MEM) { /* try again later */ }
else if (err == ESP_OK && update) {
simpleota_result_t r = simpleota_apply();
if (r == SIMPLEOTA_DOWNGRADE) { /* refused before download */ }
if (r == SIMPLEOTA_FLASH_FAIL) { /* network, URL, flash or state failure; offer cleared unless the apply task could not be created */ }
}
While the managed task runs, simpleota_check() returns ESP_ERR_INVALID_STATE
and simpleota_apply() returns SIMPLEOTA_NO_OFFER.
Polling mode always needs simpleota_confirm()¶
.manual_confirm applies to managed mode only. In polling mode you must call
simpleota_confirm() after your health checks, on every trial boot.
After (1.0.0):
Software rollback when bootloader rollback is off¶
After (1.0.0): CONFIG_SIMPLEOTA_SOFTWARE_ROLLBACK defaults to y. A reset
after simpleota_init() on the new image (crash, deep sleep, or esp_restart())
before confirm rolls back. Confirm (or managed auto-confirm) before you call
esp_restart() on purpose, or set CONFIG_SIMPLEOTA_SOFTWARE_ROLLBACK=n, and with
bootloader rollback also off, a new image is then accepted on first boot as in 0.2.x.
Software rollback can't catch a crash loop that never reaches simpleota_init(), so turn on CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE on new devices.
simpleota_confirm() return value¶
After (1.0.0):
esp_err_t err = simpleota_confirm();
if (err == ESP_FAIL) {
/* image not marked valid; trial continues; call again */
}
simpleota_deinit() return values¶
After (1.0.0):
esp_err_t err = simpleota_deinit();
if (err == ESP_ERR_TIMEOUT) { /* task did not stop; resources kept */ }
if (err == ESP_ERR_INVALID_STATE) { /* called from event_cb on the managed task */ }
image_build in simpleota_config_t¶
The field sits between version_label and nvs_schema_version, so use designated initializers rather than positional ones.
After (1.0.0):
simpleota_config_t cfg = {
.token = "YOUR_PROJECT_TOKEN",
.version_label = "1.4.0",
.image_build = 15,
.nvs_schema_version = 1,
};
simpleota_set_server_url() and simpleota_last_offered_version()¶
simpleota_set_server_url() follows the same https:// with a host rule as base_url and returns ESP_ERR_INVALID_ARG when the URL does not match. It returns ESP_ERR_INVALID_STATE before init or while the managed task runs.
ESP_ERROR_CHECK(simpleota_set_server_url("https://simpleota.com"));
const char *ver = simpleota_last_offered_version();