* Checks for firmware updates over HTTP, applies them with rollback support.
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_http_client.h"
#include "esp_app_desc.h"
#include "esp_partition.h"
static const char *TAG = "ota_node";
/* ---- Configuration ---- */
#define WIFI_SSID "YOUR_WIFI_SSID"
#define WIFI_PASS "YOUR_WIFI_PASSWORD"
#define OTA_SERVER_IP "192.168.1.100"
#define OTA_SERVER_PORT "8080"
#define OTA_FIRMWARE_URL "http://" OTA_SERVER_IP ":" OTA_SERVER_PORT "/ota_sensor_node.bin"
#define OTA_VERSION_URL "http://" OTA_SERVER_IP ":" OTA_SERVER_PORT "/version.txt"
#define OTA_CHECK_INTERVAL 60 /* seconds between OTA checks */
#define SELF_TEST_TIMEOUT 15000 /* ms to wait for Wi-Fi during self-test */
/* DHT22 data pin (reuse from previous lessons) */
#define DHT22_GPIO GPIO_NUM_4
static EventGroupHandle_t s_wifi_event_group;
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static int s_retry_count = 0;
static void wifi_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_count < MAX_RETRY) {
ESP_LOGI(TAG, "Retrying Wi-Fi connection (%d/%d)", s_retry_count, MAX_RETRY);
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
static void wifi_init(void)
s_wifi_event_group = xEventGroupCreate();
esp_event_loop_create_default();
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_event_handler_instance_t any_id;
esp_event_handler_instance_t got_ip;
esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
&wifi_event_handler, NULL, &any_id);
esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
&wifi_event_handler, NULL, &got_ip);
wifi_config_t wifi_config = {
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
esp_wifi_set_mode(WIFI_MODE_STA);
esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
static bool wifi_wait_connected(uint32_t timeout_ms)
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdMS_TO_TICKS(timeout_ms));
return (bits & WIFI_CONNECTED_BIT) != 0;
/* ---- Simplified DHT22 Read (for self-test) ---- */
* This is a minimal DHT22 read for the self-test.
* Replace with your actual DHT22 driver from Lesson 2.
static esp_err_t read_dht22(float *temperature, float *humidity)
gpio_set_direction(DHT22_GPIO, GPIO_MODE_INPUT_OUTPUT_OD);
gpio_set_level(DHT22_GPIO, 1);
vTaskDelay(pdMS_TO_TICKS(100));
/* Send start signal: pull low for 20ms, then release */
gpio_set_level(DHT22_GPIO, 0);
vTaskDelay(pdMS_TO_TICKS(20));
gpio_set_level(DHT22_GPIO, 1);
/* Wait for DHT22 response (pull low then high) */
while (gpio_get_level(DHT22_GPIO) == 1 && timeout > 0) {
ESP_LOGW(TAG, "DHT22 not responding, using dummy values for self-test");
/* For self-test, accept if sensor is not wired up */
/* Full DHT22 protocol read would go here.
* For brevity, return placeholder values.
* In production, use a proper DHT22 driver. */
/* ---- Self-Test ---- */
static bool run_self_test(void)
ESP_LOGI(TAG, "=== Running self-test ===");
/* Test 1: Wi-Fi connectivity */
ESP_LOGI(TAG, "Self-test: checking Wi-Fi...");
if (!wifi_wait_connected(SELF_TEST_TIMEOUT)) {
ESP_LOGE(TAG, "Self-test FAILED: Wi-Fi did not connect within %d ms",
ESP_LOGI(TAG, "Self-test: Wi-Fi OK");
/* Test 2: Sensor read */
ESP_LOGI(TAG, "Self-test: reading sensor...");
if (read_dht22(&temp, &hum) != ESP_OK) {
ESP_LOGE(TAG, "Self-test FAILED: sensor read error");
ESP_LOGI(TAG, "Self-test: sensor OK (%.1f C, %.1f %%)", temp, hum);
ESP_LOGI(TAG, "=== Self-test PASSED ===");
/* ---- OTA Version Check ---- */
* Fetches version.txt from the server and compares with current version.
* Returns true if a new version is available.
* Copies the new version string into new_ver (must be at least 32 bytes).
static bool check_for_update(char *new_ver, size_t new_ver_len)
const esp_app_desc_t *app_desc = esp_app_get_description();
ESP_LOGI(TAG, "Current firmware version: %s", app_desc->version);
esp_http_client_config_t config = {
esp_http_client_handle_t client = esp_http_client_init(&config);
ESP_LOGE(TAG, "Failed to init HTTP client");
esp_err_t err = esp_http_client_open(client, 0);
ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
esp_http_client_cleanup(client);
int content_length = esp_http_client_fetch_headers(client);
if (content_length <= 0 || content_length >= (int)new_ver_len) {
ESP_LOGE(TAG, "Invalid version file (length: %d)", content_length);
esp_http_client_close(client);
esp_http_client_cleanup(client);
int read_len = esp_http_client_read(client, new_ver, new_ver_len - 1);
esp_http_client_close(client);
esp_http_client_cleanup(client);
ESP_LOGE(TAG, "Failed to read version file");
/* Trim whitespace and newlines */
new_ver[read_len] = '\0';
char *end = new_ver + strlen(new_ver) - 1;
while (end > new_ver && (*end == '\n' || *end == '\r' || *end == ' ')) {
ESP_LOGI(TAG, "Server firmware version: %s", new_ver);
if (strcmp(app_desc->version, new_ver) == 0) {
ESP_LOGI(TAG, "Firmware is already up to date");
ESP_LOGI(TAG, "New firmware available: %s (current: %s)",
new_ver, app_desc->version);
/* ---- OTA Download and Flash ---- */
static esp_err_t perform_ota_update(void)
ESP_LOGI(TAG, "Starting OTA update from %s", OTA_FIRMWARE_URL);
const esp_partition_t *update_partition = esp_ota_get_next_update_partition(NULL);
if (update_partition == NULL) {
ESP_LOGE(TAG, "No OTA partition available");
ESP_LOGI(TAG, "Writing to partition: %s (offset 0x%"PRIx32", size 0x%"PRIx32")",
update_partition->label, update_partition->address, update_partition->size);
esp_http_client_config_t http_config = {
esp_http_client_handle_t client = esp_http_client_init(&http_config);
ESP_LOGE(TAG, "Failed to init HTTP client");
esp_err_t err = esp_http_client_open(client, 0);
ESP_LOGE(TAG, "HTTP open failed: %s", esp_err_to_name(err));
esp_http_client_cleanup(client);
int content_length = esp_http_client_fetch_headers(client);
ESP_LOGI(TAG, "Firmware size: %d bytes", content_length);
esp_ota_handle_t ota_handle;
err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &ota_handle);
ESP_LOGE(TAG, "esp_ota_begin failed: %s", esp_err_to_name(err));
esp_http_client_close(client);
esp_http_client_cleanup(client);
/* Download and write in chunks */
char *buffer = malloc(4096);
ESP_LOGE(TAG, "Failed to allocate download buffer");
esp_ota_abort(ota_handle);
esp_http_client_close(client);
esp_http_client_cleanup(client);
while ((read_len = esp_http_client_read(client, buffer, 4096)) > 0) {
err = esp_ota_write(ota_handle, buffer, read_len);
ESP_LOGE(TAG, "esp_ota_write failed: %s", esp_err_to_name(err));
esp_ota_abort(ota_handle);
esp_http_client_close(client);
esp_http_client_cleanup(client);
/* Progress logging every 64 KB */
if (total_read % (64 * 1024) < 4096) {
ESP_LOGI(TAG, "Written %d bytes...", total_read);
esp_http_client_close(client);
esp_http_client_cleanup(client);
ESP_LOGI(TAG, "Total written: %d bytes", total_read);
ESP_LOGE(TAG, "HTTP read error");
esp_ota_abort(ota_handle);
err = esp_ota_end(ota_handle);
ESP_LOGE(TAG, "esp_ota_end failed: %s", esp_err_to_name(err));
/* Set the new partition as boot partition */
err = esp_ota_set_boot_partition(update_partition);
ESP_LOGE(TAG, "esp_ota_set_boot_partition failed: %s", esp_err_to_name(err));
ESP_LOGI(TAG, "OTA update successful. Rebooting in 2 seconds...");
vTaskDelay(pdMS_TO_TICKS(2000));
return ESP_OK; /* Never reached */
/* ---- OTA Check Task ---- */
static void ota_task(void *pvParameter)
/* Wait for Wi-Fi to be connected */
if (!wifi_wait_connected(30000)) {
ESP_LOGE(TAG, "Wi-Fi not connected, OTA task exiting");
if (check_for_update(new_version, sizeof(new_version))) {
esp_err_t err = perform_ota_update();
ESP_LOGE(TAG, "OTA update failed: %s", esp_err_to_name(err));
/* If OTA succeeded, we rebooted already.
* If it failed, wait and try again next cycle. */
ESP_LOGI(TAG, "Next OTA check in %d seconds", OTA_CHECK_INTERVAL);
vTaskDelay(pdMS_TO_TICKS(OTA_CHECK_INTERVAL * 1000));
/* Print firmware info */
const esp_app_desc_t *app_desc = esp_app_get_description();
ESP_LOGI(TAG, "========================================");
ESP_LOGI(TAG, " OTA Sensor Node");
ESP_LOGI(TAG, " Version: %s", app_desc->version);
ESP_LOGI(TAG, " Project: %s", app_desc->project_name);
ESP_LOGI(TAG, " Compiled: %s %s", app_desc->date, app_desc->time);
ESP_LOGI(TAG, " IDF: %s", app_desc->idf_ver);
ESP_LOGI(TAG, "========================================");
/* Print partition info */
const esp_partition_t *running = esp_ota_get_running_partition();
ESP_LOGI(TAG, "Running from partition: %s (offset 0x%"PRIx32")",
running->label, running->address);
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
/* Check rollback status */
esp_ota_img_states_t ota_state;
if (esp_ota_get_state_partition(running, &ota_state) == ESP_OK) {
if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) {
ESP_LOGW(TAG, "Firmware is pending verification (first boot after OTA)");
ESP_LOGI(TAG, "Self-test PASSED, confirming firmware");
esp_ota_mark_app_valid_cancel_rollback();
ESP_LOGE(TAG, "Self-test FAILED, rolling back to previous firmware");
esp_ota_mark_app_invalid_rollback_and_reboot();
ESP_LOGI(TAG, "Firmware state: validated (no rollback pending)");
/* Start OTA check task */
xTaskCreate(&ota_task, "ota_task", 8192, NULL, 5, NULL);
/* Main loop: normal sensor operation */
if (read_dht22(&temp, &hum) == ESP_OK) {
ESP_LOGI(TAG, "Sensor: %.1f C, %.1f %% RH", temp, hum);
vTaskDelay(pdMS_TO_TICKS(10000)); /* Read sensor every 10 seconds */
Comments