ESP-IDF integration¶
The ESP-IDF path uses esp_https_ota for verified, streaming update
installation.
Components¶
esp_http_clientesp_https_otaesp_app_format(foresp_app_get_description()if you want to read the running build number from the binary header)
Check + install flow¶
#include "esp_log.h"
#include "esp_http_client.h"
#include "esp_https_ota.h"
#include "esp_crt_bundle.h"
#include "cJSON.h"
#define SIMPLEOTA_BASE "https://simpleota.com"
#define PROJECT_TOKEN "PASTE_TOKEN_HERE"
#define DEVICE_ID "esp32-idf-0001"
static const char *TAG = "ota";
static esp_err_t do_check(char **out_url, long *out_build) {
char post[512];
int n = snprintf(post, sizeof(post),
"{\"device_id\":\"%s\",\"framework\":\"esp-idf\","
"\"chip_family\":\"esp32\",\"board_id\":\"esp32-devkitc\","
"\"current_build_number\":%d,\"partition_profile\":\"default_4mb\"}",
DEVICE_ID, my_running_build_number());
char auth[256];
snprintf(auth, sizeof(auth), "Bearer %s", PROJECT_TOKEN);
esp_http_client_config_t cfg = {
.url = SIMPLEOTA_BASE "/api/v1/ota/check/",
.method = HTTP_METHOD_POST,
.crt_bundle_attach = esp_crt_bundle_attach,
};
esp_http_client_handle_t c = esp_http_client_init(&cfg);
esp_http_client_set_header(c, "Authorization", auth);
esp_http_client_set_header(c, "Content-Type", "application/json");
esp_http_client_set_post_field(c, post, n);
esp_err_t err = esp_http_client_perform(c);
if (err != ESP_OK || esp_http_client_get_status_code(c) != 200) {
esp_http_client_cleanup(c);
return ESP_FAIL;
}
char body[1024];
int len = esp_http_client_read_response(c, body, sizeof(body) - 1);
body[len > 0 ? len : 0] = 0;
esp_http_client_cleanup(c);
cJSON *root = cJSON_Parse(body);
cJSON *upd = cJSON_GetObjectItem(root, "update_available");
if (!cJSON_IsTrue(upd)) {
cJSON_Delete(root);
return ESP_ERR_NOT_FOUND; // no update available
}
*out_url = strdup(cJSON_GetObjectItem(root, "url")->valuestring);
*out_build = (long)cJSON_GetObjectItem(root, "build_number")->valuedouble;
cJSON_Delete(root);
return ESP_OK;
}
esp_err_t simpleota_tick(void) {
char *url = NULL;
long build = 0;
esp_err_t err = do_check(&url, &build);
if (err == ESP_ERR_NOT_FOUND) return ESP_OK; // up to date
if (err != ESP_OK) return err;
ESP_LOGI(TAG, "installing build %ld from %s", build, url);
esp_http_client_config_t http_cfg = {
.url = url,
.crt_bundle_attach = esp_crt_bundle_attach,
.timeout_ms = 30000,
.keep_alive_enable = true,
};
esp_https_ota_config_t ota_cfg = { .http_config = &http_cfg };
err = esp_https_ota(&ota_cfg);
free(url);
if (err == ESP_OK) {
report_status(build, "success", NULL);
esp_restart(); // reboot into new partition
} else {
report_status(build, "failed", esp_err_to_name(err));
}
return err;
}
Reading the running build number¶
If you encode the SimpleOTA build number into the app version string at
build time, you can read it back via esp_app_get_description():
static int my_running_build_number(void) {
const esp_app_desc_t *desc = esp_app_get_description();
return atoi(desc->version); // however you encoded it
}
For now, encode the build number explicitly into the app version string at build time, as shown above.
Rollback¶
esp_https_ota writes to the inactive OTA partition. After
esp_restart(), the bootloader tries the new partition. If your app
calls esp_ota_mark_app_valid_cancel_rollback() early after a clean
boot (e.g. once it has connected to the network), the new partition is
sealed. If it never marks valid, the bootloader rolls back on the next
power cycle.
This is the platform's built-in safety net. Combine it with SimpleOTA's
/api/v1/ota/status/ reports to see "tried, never marked valid" cases
in the dashboard.