Aggiornamenti OTA su ESP32: partizioni, rollback, firma e LTE Cat-1

Aggiornamenti OTA su ESP32: partizioni, rollback, firma e LTE Cat-1

Io: Cheap, se domani scopro un bug nel firmware di un sensore montato in cima a un palo, che faccio?

Cheap: Prendi la scala, il cavo USB e un ombrello. Oppure fai come me: mi aggiorno da solo, via radio.

Io: E se la versione nuova è sbagliata?

Cheap: Ho due cassetti. In uno c’è la versione che funziona, nell’altro metto quella nuova. Se al primo risveglio non mi sento bene, torno al cassetto vecchio.

Io: E se qualcuno ti manda un firmware finto?

Cheap: Controllo la firma. Costo due centesimi, mica sono uno sprovveduto.

Io: E dove il Wi-Fi non arriva?

Cheap: Una SIM e un modem sulla seriale. Ogni megabyte però si paga: aggiornamenti sì, capricci no.

Aggiornare il firmware “over the air” (OTA) vuol dire che il dispositivo scarica da solo la nuova versione e la installa, senza cavi e senza scale. Sugli ESP32 il meccanismo è lo stesso su tutta la famiglia; cambiano la sicurezza disponibile, la memoria e le radio da cui il firmware può arrivare. Vediamo come funziona con ESP-IDF, che cosa offre ogni chip, e costruiamo un progetto per ESP32-C6: OTA via HTTPS da un server di casa, rollback con autodiagnosi e, al posto del Wi-Fi, un modem LTE Cat-1 sulla UART.

In breve

Tema In sintesi
Meccanismo due slot (ota_0, ota_1) e una partizione otadata che dice al bootloader quale avviare
API esp_ota_ops per qualsiasi canale, esp_https_ota per HTTPS
Paracadute rollback con autodiagnosi; anti-rollback con contatore negli eFuse
Fiducia TLS, firma (Secure Boot v2), flash encryption, immagini pre-cifrate
Versioni progetto compilato con ESP-IDF 5.4; l’ultima stabile è la 6.1 (27 agosto 2026)

Due cassetti e un segnalibro

Tutto parte dalla tabella delle partizioni, la mappa della flash. Per l’OTA servono almeno due slot applicativi e la partizione otadata (due settori, 8 KB): il “segnalibro”. Le due copie si scrivono una alla volta con un contatore, così un blackout a metà non lo corrompe. Se otadata è vuota il bootloader avvia la partizione factory, se c’è, altrimenti il primo slot.

Mappa della flash da 4 MB: bootloader, tabella partizioni, nvs, otadata, phy_init e due slot OTA da 1,875 MB
La flash del progetto: la nuova versione si scrive sempre nello slot che non è in uso.

Rinunciamo alla factory: lo spazio va ai due slot ed è lo schema richiesto se un giorno si attiva l’anti-rollback. Con 4 MB si sta larghi; un ESP32-C2 da 2 MB, con la stessa zona di sistema, lascerebbe circa 960 KB per slot, e un firmware come il nostro (1 032 KB sul C6) andrebbe snellito.

# Flash da 4 MB: due slot OTA da 1,875 MB, niente partizione factory
# Name,   Type, SubType, Offset,   Size
nvs,      data, nvs,     0x9000,   0x6000
otadata,  data, ota,     0xf000,   0x2000
phy_init, data, phy,     0x11000,  0x1000
ota_0,    app,  ota_0,   0x20000,  0x1E0000
ota_1,    app,  ota_1,   0x200000, 0x1E0000

Le API: dal byte allo slot

Il componente app_update non sa nulla del canale: esp_ota_begin() prepara lo slot libero, esp_ota_write() scrive i dati man mano che arrivano, esp_ota_end() verifica l’immagine (struttura, checksum, SHA-256 in coda) e esp_ota_set_boot_partition() sposta il segnalibro. Wi-Fi, Bluetooth o modem: per lui sono solo byte.

Per HTTPS c’è esp_https_ota: una funzione che fa tutto, oppure l’API “advanced” (begin, get_img_desc, perform, finish, abort) che legge l’intestazione della nuova immagine prima di scrivere. Con partial_http_download l’immagine arriva in più richieste HTTP Range, comodo con poca RAM e collegamenti instabili.

Dalla 5.5 ESP-IDF aggiunge la ripresa di un download interrotto anche dopo un riavvio (ota_resumption) e l’OTA di bootloader e tabella delle partizioni tramite una partizione di appoggio (esp_ota_set_final_partition()). La documentazione lo definisce un aggiornamento “non sicuro”: un’interruzione durante la copia finale può rendere il dispositivo irrecuperabile, e lì il rollback non esiste. Su ESP32-C5, C61 e P4 la ROM offre un recovery bootloader che rende sicura l’operazione; sul C6 no.

Il paracadute: rollback e anti-rollback

Con CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE la nuova versione ha un solo tentativo: il bootloader la segna PENDING_VERIFY e tocca al firmware confermarla. Se si riavvia prima, al giro dopo il bootloader la scarta e torna alla precedente.

Sequenza OTA: manifesto, download, verifiche, cambio slot, bootloader, autodiagnosi con tre esiti
Solo l’autodiagnosi può rendere definitiva la nuova versione.

Il punto delicato è che cosa controllare. Se la diagnosi pretende il server e quel giorno il router è spento, si torna indietro anche con un firmware buono: pazienza, si riproverà. Il contrario è peggio: un firmware confermato che non sa più collegarsi alla rete non si aggiorna più da remoto. Quindi: rete e funzioni essenziali, in fretta, con un timeout generoso.

L’anti-rollback (CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK) impedisce invece di tornare a versioni con un secure_version più basso di quello scritto negli eFuse. Il campo ha 32 bit sull’ESP32 e 16 sugli altri: ogni incremento consuma un bit, per sempre. Nel progetto è spento.

Attenzione: il bootloader non cambia con gli OTA, e un’app compilata con un ESP-IDF molto più nuovo deve conviverci (l’avviso Espressif AR2026-007 descrive un caso così su C6 e H2, corretto nella 6.0.3). Prima di saltare di versione si prova l’OTA dal bootloader che c’è sul campo.

Fidarsi del firmware: firma, Secure Boot e cifratura

Il TLS protegge il viaggio, non dice chi ha preparato il file. Con Secure Boot v2 la ROM verifica il bootloader e il bootloader l’applicazione, a ogni avvio e già durante l’OTA.

Catena di fiducia di Secure Boot v2: chiave privata sul PC, ROM, bootloader, app, eFuse, flash encryption
La chiave privata resta sul PC; negli eFuse c’è solo l’impronta della chiave pubblica.
  • Algoritmi. RSA-3072 (RSA-PSS) o ECDSA (P-256/P-192), secondo il chip; fino a tre chiavi revocabili, una sola su ESP32 e C2.
  • ECDSA e l’avviso AR2026-006. Un problema nella verifica ECDSA della ROM di H2, C5, C61 e P4 (non di C2 e C6) ha portato Espressif a disabilitare, da ESP-IDF 5.5.5 e 6.1, il Secure Boot ECDSA su H2, C5 e P4, raccomandando RSA. La verifica ECDSA fatta dall’app durante l’OTA non è interessata.
  • Firma senza Secure Boot. Con CONFIG_SECURE_SIGNED_APPS_NO_SECURE_BOOT l’app verifica la firma delle nuove immagini senza bruciare eFuse: non ferma chi riscrive la flash col cavo, ma ferma un server compromesso.
  • Flash encryption. XTS-AES (AES-256 sull’ESP32 classico) con una chiave illeggibile dal software; durante l’OTA la cifratura è automatica. In modalità “Release” la seriale non scrive più immagini in chiaro: si aggiorna solo via OTA.
  • Immagini pre-cifrate. esp_encrypted_img (2.7.1) cifra il file con AES-GCM (chiave protetta da RSA-3072 o ECIES-P256) e lo decifra durante il download: riservatezza sì, autenticità no.
  • Delta OTA. esp_delta_ota (1.1.4) scarica solo le differenze rispetto al firmware in esecuzione, compresse, senza partizioni in più. Sul cellulare è la prima ottimizzazione da considerare.

Perché nel progetto è tutto spento? Perché gli eFuse passano da 0 a 1 una volta sola: una chiave persa vuol dire niente più aggiornamenti firmati, un’opzione sbagliata una scheda da buttare. Prima si prova in modalità “Development” su una scheda sacrificabile, con le chiavi al sicuro (e in copia), poi si decide.

Chip per chip

SoC CPU Memoria nel package Radio Secure Boot Flash enc. Note OTA
ESP32 Xtensa LX6, 1-2 core, 240 MHz 4 MB flash o 2 MB PSRAM Wi-Fi 4, BT Classic + LE 4.2 v1; v2 RSA dalla rev. 3.0 AES-256 una chiave; anti-rollback a 32 bit
ESP32-S2 LX7, 1 core, 240 MHz 4 MB flash e/o 2 MB PSRAM Wi-Fi 4 v2 RSA XTS-128/256 download anche via USB OTG
ESP32-S3 LX7, 2 core, 240 MHz fino a 8 MB flash, 16 MB PSRAM Wi-Fi 4, BLE 5 v2 RSA XTS-128/256 USB Serial/JTAG e OTG
ESP32-C2 RISC-V, 120 MHz 2 o 4 MB flash Wi-Fi 4, BLE 5 v2 ECDSA, una chiave XTS-128 slot stretti con 2 MB
ESP32-C3 RISC-V, 160 MHz 4 o 8 MB flash Wi-Fi 4, BLE 5 v2 RSA (rev. 0.3+) XTS-128 USB Serial/JTAG
ESP32-C5 RISC-V, 240 MHz 4 MB flash o 2/8 MB PSRAM Wi-Fi 6 2,4+5 GHz, BLE, 802.15.4 v2 RSA (ECDSA disabilitato) XTS-128 recovery bootloader; preview in IDF 5.4
ESP32-C6 RISC-V, 160 MHz 4 o 8 MB flash Wi-Fi 6, BLE 5.3, 802.15.4 v2 RSA o ECDSA XTS-128 il nostro target
ESP32-C61 RISC-V, 160 MHz 4 MB flash o 2/8 MB PSRAM Wi-Fi 6, BLE v2 solo ECDSA (AR2026-006) XTS-128 recovery bootloader; preview in IDF 5.4
ESP32-H2 RISC-V, 96 MHz 2 o 4 MB flash BLE 5.3, 802.15.4 v2 RSA (ECDSA disabilitato) XTS-128 OTA via Thread/Matter o BLE
ESP32-P4 RISC-V, 2 core, 400 MHz (360 nella rev. 1.x) 16 o 32 MB PSRAM nessuna v2 RSA (ECDSA disabilitato) XTS-128/256 rete da un co-processore

CPU, memorie e radio dai datasheet Espressif 2026; sicurezza dalla documentazione ESP-IDF. ESP32-S31, H4 e H21 sono ancora in anteprima in ESP-IDF 6.1. Approfondimenti: ESP32-C6 ed ESP32-P4.

Da dove arriva il firmware

Tabella dei canali OTA: Wi-Fi, LTE Cat-1 PPP e AT, Bluetooth LE, ESP-NOW, Thread/Matter, co-processore, seriale/USB
Otto strade ufficiali: cambia il trasporto, non il meccanismo degli slot.
  • Bluetooth LE. Il componente ble_ota di esp-iot-solution riceve l’immagine a settori da 4 KB con CRC; Espressif pubblica le app ESP BLE OTA per Android e iOS.
  • ESP-NOW. Nell’esempio ota di esp-now un nodo “initiator” scarica il firmware e lo distribuisce a molti “responder”, con ripresa dopo le interruzioni (vedi il nostro articolo su ESP-NOW). Per le reti mesh Wi-Fi c’è esp-mesh-lite; ESP-MDF è archiviato.
  • Thread e Matter. In esp-matter il dispositivo è un OTA Requestor che chiede l’immagine a un OTA Provider, anche cifrata o delta. Sul Thread Border Router di Espressif l’host aggiorna l’H2 che fa da radio, con due immagini e rollback.
  • Co-processore. L’ESP32-P4 non ha radio: sulla scheda di sviluppo il Wi-Fi arriva da un ESP32-C6 via ESP-Hosted, e il P4 aggiorna il C6 con esp_hosted_slave_ota_begin/write/end/activate(), controllando la compatibilità delle versioni.
  • Servizi. ESP RainMaker offre OTA gestito dal cloud, ESP Insights la diagnostica remota. Noi preferiamo un server nostro: niente abbonamenti.

Sulla UART: OTA con un modem LTE Cat-1

Dove il Wi-Fi non arriva, il firmware può arrivare dalla rete cellulare. Usiamo l’hardware del tutorial sul router LTE con ESP32-C6 (pubblicazione in arrivo): SIMCom A7670 su UART1, GPIO4 verso RXD del modem, GPIO5 da TXD, PWRKEY dal GPIO10 tramite transistor.

Confronto tra OTA via PPP con esp_modem e OTA via comandi AT HTTP del modem
In A il TLS lo fa l’ESP32, in B il modem.

A) PPP con esp_modem (consigliata)

Il componente esp_modem (2.1.0) porta il modem in modalità dati e crea un’interfaccia PPP: per lwIP è una scheda di rete come le altre, e il codice OTA resta identico a quello Wi-Fi, con TLS, certificato e rollback gestiti dall’ESP32. Con CMUX la UART trasporta due canali virtuali, dati e comandi AT: si legge il segnale anche durante il download.

#if CONFIG_OTA_MODEM_CMUX
#define MODO_DATI ESP_MODEM_MODE_CMUX   /* AT e PPP insieme su due canali virtuali */
#else
#define MODO_DATI ESP_MODEM_MODE_DATA   /* solo PPP finché la sessione è aperta */
#endif

La documentazione di esp_modem avverte che l’OTA HTTPS su PPP può soffrire di overflow della UART e suggerisce ISR in IRAM, buffer più grandi, priorità più alta e controllo di flusso: le prime tre sono nel progetto, la quarta chiede i fili RTS/CTS. E c’è la matematica: a 115200 baud passano al massimo 11 520 byte al secondo, quindi la nostra immagine Cat-1 da 707 440 byte richiede almeno un minuto, più PPP, TCP e TLS. Il manuale del modem prevede velocità più alte (AT+IPR).

B) Lo stack HTTP del modem

L’A7670 ha un client HTTP(S) interno: AT+HTTPINIT, AT+HTTPPARA="URL",..., AT+HTTPACTION=0 e AT+HTTPREAD=<offset>,<byte> per leggere a blocchi, oppure AT+HTTPREADFILE nel file system del modem e poi AT+FSREAD. L’ESP32 passa ogni blocco a esp_ota_write(), calcola lo SHA-256 e lo confronta col manifesto prima di esp_ota_end(). Serve a chi ha un microcontrollore senza stack TCP/IP; con un ESP32 è peggio: TLS (fino a 1.2 nel manuale V1.12) e certificati dipendono dal modem, la lettura massima non è documentata e il parsing AT è fragile. Non la implementiamo.

C) Le attenzioni del cellulare

  • Dati. Il manifesto pesa meno di 200 byte più la connessione TLS; l’immagine si scarica solo se serve, dopo aver controllato l’intestazione. Il delta OTA fa il resto.
  • Interruzioni. Richieste Range da 16 KB; in ESP-IDF 5.4 un errore annulla il tentativo e si riprova al giro dopo, dalla 5.5 ota_resumption riparte dal punto raggiunto.
  • APN, segnale, timeout. APN in menuconfig, riconnessione automatica del PPP, 180 secondi per la rete nella diagnosi invece di 60.
  • Alimentazione. L’A7670 vuole VBAT tra 3,4 e 4,2 V con picchi tipici di 2 A: un calo mentre l’ESP32 scrive la flash trasforma l’OTA in un mistero. Alimentazione separata, condensatori vicino a VBAT e niente OTA con la batteria scarica.
  • Verifica prima dello scambio. Versione nell’intestazione, SHA-256 ricalcolato dalla flash, controlli di esp_ota_end(): solo dopo si sposta il segnalibro.

E il bootloader seriale?

La UART serve anche alla via classica: con un pin di strapping basso al reset (GPIO0 su ESP32, S2, S3; GPIO9 su C3, C6, C61, H2; GPIO28 sul C5; GPIO35 sul P4) il chip entra in download mode e esptool scrive la flash; con l’USB Serial/JTAG il reset è automatico. Con la sicurezza attiva resta la Secure Download Mode (pochi comandi), o la si spegne con CONFIG_SECURE_UART_ROM_DL_MODE. Se a programmare è un altro microcontrollore c’è esp-serial-flasher (2.1.0, Apache-2.0: STM32, ESP32, Raspberry Pi Pico, Zephyr, Linux). La ROM recupera anche un firmware rotto ma non ha rollback; l’OTA ha le protezioni ma presuppone un firmware che funzioni. Servono entrambi.

Il progetto: OTA con rollback su ESP32-C6

Il progetto è in progetti/esp-ota/: main.c (avvio e autodiagnosi), ota.c (manifesto e download), rete_wifi.c o rete_cat1.c (scelti nel menu “OTA webappportal”) e, in server/, gli script Python. La configurazione attiva partizioni e rollback e lascia spenta la sicurezza basata sugli eFuse:

# Flash da 4 MB e tabella delle partizioni con due slot OTA
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
# Rollback: al primo avvio la nuova versione deve superare l'autodiagnosi
CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y
# Secure Boot, flash encryption e anti-rollback restano SPENTI di proposito:
# bruciano eFuse irreversibili (vedi l'articolo prima di abilitarli)

L’autodiagnosi

Al primo avvio dopo un aggiornamento il firmware aspetta la rete, poi controlla un contatore in NVS, l’heap libero e la risposta HTTPS di /salute. Solo se tutto passa conferma la versione.

/* Controlli del TUO prodotto: qui un contatore in NVS, l'heap e il server.
 * In un progetto vero aggiungi sensori, attuatori, bus... */
static bool verifica_applicativa(void)
{
    nvs_handle_t h;
    uint32_t avvii = 0;
    if (nvs_open("diagnosi", NVS_READWRITE, &h) != ESP_OK) {
        return false;
    }
    nvs_get_u32(h, "avvii", &avvii);
    bool nvs_ok = nvs_set_u32(h, "avvii", avvii + 1) == ESP_OK && nvs_commit(h) == ESP_OK;
    nvs_close(h);
    uint32_t heap = esp_get_free_heap_size();
    ESP_LOGI(TAG, "Avvio n. %" PRIu32 ", heap libero %" PRIu32 " byte", avvii + 1, heap);
    return nvs_ok && heap > 40 * 1024 && ota_server_raggiungibile();
}

/* Al primo avvio dopo un OTA lo stato è PENDING_VERIFY: decidiamo noi */
static void autodiagnosi(void)
{
    const esp_partition_t *corrente = esp_ota_get_running_partition();
    esp_ota_img_states_t stato;
    if (esp_ota_get_state_partition(corrente, &stato) != ESP_OK ||
        stato != ESP_OTA_IMG_PENDING_VERIFY) {
        return;                         /* avvio normale, niente da confermare */
    }
    ESP_LOGW(TAG, "Primo avvio di questa versione: autodiagnosi");
    bool rete_ok = rete_attendi(pdMS_TO_TICKS(CONFIG_OTA_TIMEOUT_RETE_S * 1000));
    if (rete_ok && verifica_applicativa()) {
        ESP_LOGI(TAG, "Autodiagnosi superata: versione confermata");
        esp_ota_mark_app_valid_cancel_rollback();
    } else {
        ESP_LOGE(TAG, "Autodiagnosi fallita (rete %s): torno alla versione precedente",
                 rete_ok ? "ok" : "assente");
        esp_ota_mark_app_invalid_rollback_and_reboot();
    }
}

Download e verifiche

Se /version.json annuncia una versione più alta parte il download. La funzione controlla che l’intestazione corrisponda al manifesto, salta le versioni che hanno già fallito la diagnosi (altrimenti verrebbero riscaricate all’infinito) e ricalcola lo SHA-256 in flash prima di cambiare slot.

static esp_err_t scarica_e_installa(const char *percorso, const char *versione, const char *sha)
{
    char url[160];
    snprintf(url, sizeof(url), "%s%s", CONFIG_OTA_URL_BASE, percorso);
    esp_http_client_config_t http = {
        .url = url,
        .cert_pem = server_cert_pem,
        .timeout_ms = 30000,
        .keep_alive_enable = true,
    };
    esp_https_ota_config_t cfg = {
        .http_config = &http,
        .partial_http_download = true,              /* richieste Range a blocchi */
        .max_http_request_size = CONFIG_OTA_BLOCCO_HTTP,
    };
    esp_https_ota_handle_t ota = NULL;
    ESP_RETURN_ON_ERROR(esp_https_ota_begin(&cfg, &ota), TAG, "connessione al server fallita");

    /* L'intestazione dell'immagine arriva per prima: controlliamo prima di scrivere */
    esp_app_desc_t nuova;
    esp_err_t err = esp_https_ota_get_img_desc(ota, &nuova);
    if (err == ESP_OK && strcmp(nuova.version, versione) != 0) {
        ESP_LOGE(TAG, "Il file contiene la versione %s, il manifesto dice %s", nuova.version, versione);
        err = ESP_ERR_INVALID_VERSION;
    }
    if (err == ESP_OK && versione_scartata(nuova.version)) {
        ESP_LOGW(TAG, "La %s ha già fallito l'autodiagnosi: la salto", nuova.version);
        err = ESP_ERR_INVALID_STATE;
    }

    int ultimo_log = 0;
    while (err == ESP_OK) {
        err = esp_https_ota_perform(ota);
        if (err != ESP_ERR_HTTPS_OTA_IN_PROGRESS) {
            break;
        }
        err = ESP_OK;
        int letti = esp_https_ota_get_image_len_read(ota);
        if (letti - ultimo_log >= 64 * 1024) {
            ESP_LOGI(TAG, "Scaricati %d KB", letti / 1024);
            ultimo_log = letti;
        }
    }
    if (err == ESP_OK && !esp_https_ota_is_complete_data_received(ota)) {
        err = ESP_ERR_INVALID_SIZE;
    }
    if (err == ESP_OK) {
        err = verifica_sha256(sha);
    }
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Aggiornamento annullato: %s", esp_err_to_name(err));
        esp_https_ota_abort(ota);
        return err;
    }
    /* esp_ota_end() verifica l'immagine, poi esp_ota_set_boot_partition() */
    ESP_RETURN_ON_ERROR(esp_https_ota_finish(ota), TAG, "immagine rifiutata");
    ESP_LOGW(TAG, "Versione %s installata: riavvio", versione);
    vTaskDelay(pdMS_TO_TICKS(1000));
    esp_restart();
    return ESP_OK;
}

Il certificato autofirmato del server è incluso nel firmware: il dispositivo si fida solo di quel server. Per cambiarlo serve un aggiornamento, quindi lo script lo genera con 10 anni di validità. Con un server pubblico e una CA riconosciuta si usa invece il bundle di ESP-IDF (esp_crt_bundle_attach).

La variante LTE Cat-1

Basta un secondo file di configurazione. Dalla rete cellulare però il server di casa non si vede: serve un server pubblico (per esempio una piccola VPS) e il suo URL in menuconfig.

# Variante LTE Cat-1: modem su UART, PPP con esp_modem
CONFIG_OTA_CANALE_CAT1=y
CONFIG_LWIP_PPP_SUPPORT=y
CONFIG_LWIP_PPP_PAP_SUPPORT=y
CONFIG_LWIP_PPP_ENABLE_IPV6=n
# ISR della UART in IRAM: non perde byte mentre l'OTA scrive in flash
CONFIG_UART_ISR_IN_IRAM=y

Compilazione e prova del server

Le tre varianti compilano con ESP-IDF 5.4 senza errori e senza warning nel codice del progetto. Per Cat-1 si usa una cartella di build separata: idf.py -B build-cat1 -D SDKCONFIG=build-cat1/sdkconfig -D SDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.cat1" set-target esp32c6 build.

Variante Target esp_ota.bin Slot libero
Wi-Fi esp32c6 1 057 232 byte 46%
LTE Cat-1, PPP + CMUX esp32c6 707 440 byte 64%
Wi-Fi esp32s3 940 352 byte 52%

Per l’S3 è cambiato solo il target (idf.py set-target esp32s3). Il server si prepara con python genera_cert.py 192.168.1.10 (IP del PC; poi si ricompila) e python server_ota.py percorso/esp_ota.bin: versione e SHA-256 li legge dal file. La prova con curl:

$ curl --cacert cert/server_cert.pem https://127.0.0.1:8443/version.json
{"versione": "1.0.0", "url": "/firmware.bin", "dimensione": 1057232, "sha256": "b8fdbd76d45d49d42f0ae05784430e2eccb40cfff1f09fb42825fc036f23b49d"}
$ curl --cacert cert/server_cert.pem https://127.0.0.1:8443/salute
ok
$ curl --cacert cert/server_cert.pem -I https://127.0.0.1:8443/firmware.bin
HTTP/1.0 200 OK
Content-Length: 1057232
Accept-Ranges: bytes
$ curl --cacert cert/server_cert.pem -r 0-16383 -o blocco.bin -w "%{http_code} %{size_download}\n" https://127.0.0.1:8443/firmware.bin
206 16384

La risposta 206 è quella che esp_https_ota riceve a ogni blocco; senza --cacert curl rifiuta il certificato (errore 60). Un download simulato in Python a blocchi da 16 KB (65 richieste) ricompone un file con lo SHA-256 del manifesto.

Sul banco (da confermare)

Il comportamento sulla scheda è da confermare sul banco. La prova prevista: 1.0.0 via cavo (idf.py -p COMx flash monitor); poi PROJECT_VER a “1.0.1” e server sulla nuova immagine: download in ota_1, riavvio, conferma; una 1.0.2 con password Wi-Fi sbagliata deve tornare alla 1.0.1 senza essere riscaricata; infine Cat-1, staccando l’antenna a metà download.



Per approfondire

Cheap: Riassumo? Due cassetti, un segnalibro, un controllo al risveglio. E una firma, quando saremo pronti a bruciare gli eFuse.

Io: E la SIM?

Cheap: Con giudizio: prima il manifesto da 200 byte, poi, se serve, il resto. Magari solo le differenze.

Io: Nessun abbonamento al cloud?

Cheap: Un server Python e un certificato fatto in casa. Per un robottino da due centesimi è già un lusso.

Riguardo

Imprenditore, maker, MBSE enthusiast e co-autore di Web App Portal. Collegati su LinkedIn.