Come aggiornare il codice sorgente tramite la funzione OTA
Introduzione
I sistemi embedded hanno raggiunto un livello tecnologico e di complessità impensabili fino a qualche anno fa, e di conseguenza anche le applicazioni che si possono realizzare.
Alla luce di queste considerazioni e dalla richiesta del mercato di una maggiore flessibilità delle applicazioni, sono state sviluppate delle architetture che consento l’aggiornamento del software con estrema semplicità tramite la connessione “wireless”.
Anche il nostro smartphone, periodicamente ci richiede di eseguire degli aggiornamenti al sistema operativo e/o alle applicazioni installate, per correggere degli errori e/o ampliare le funzionalità, e l’aggiornamento solitamente è eseguito tramite la rete Internet, senza nessun cavo di connessione.
In questi ultimi anni ha riscosso un notevole successo l’architettura Software Radio, molto usata nei ponti radio della telefonia mobile, con questo sistema, l’operatore che gestisce l’infrastruttura, può decidere se aggiornare o addirittura aggiungere funzionalità al sistema di trasmissione e ricezione.
In questo scenario, anche il microcontrollore ESP32, grazie alla partizione della sua memoria di programma, può abilitare la funzione OTA (Over The Air Updates) cioè tramite un Web-server è possibile caricare all’interno di una delle partizioni della memoria, precedentemente configurata, il codice sorgente nel formato binario.
In questo modo possiamo aggiornare il sistema, con nuove funzionalità e/o correggere degli errori della precedente versione senza la connessione USB alla scheda.
Questa funzione è molto utile poiché in alcuni casi la scheda non è accessibile e quindi l’aggiornamento del codice potrebbe di difficile implementazione.
Partizione della memoria
Per poter utilizzare la funzione OTA, la memoria codice del microcontrollore deve essere partizionata in due aree distinte e usate singolarmente, pertanto se carichiamo il codice sorgente nella prima parte di memoria, l’altra deve essere lasciata libera fino a quando non si esegue un aggiornamento tramite la funzione OTA.
Al riavvio, il microcontrollore, in automatico, esegue un controllo della partizione del codice aggiornato, se il codice è stato correttamente scritto in memoria, il microcontrollore esegue lo swap dell’area di memoria e questo processo si ripete ogni volta che si aggiorna la memoria codice.
Sono presente due partizioni identificate: “OTA_0” e OTA_1, per la memorizzazione del codice sorgente e lo swap come descritto sopra.
È evidente che la prima programmazione del microcontrollore ESP32, deve essere eseguita tramite il cavo USB poiché’ la funzione OTA deve essere attiva.
Ad ogni successivo aggiornamento eseguito tramite webserver, per garantire gli aggiornamenti futuri, nel nuovo codice deve essere sempre integrata la funzione OTA.
Il codice sorgente che deve essere aggiornato tramite il webserver deve essere prima convertito in file binario.
Nell’ambiente di sviluppo di Arduino è presente un comando che svolge questa funzione, come descritto dall’immagine animata riportata di seguito:
Per aggiornare il codice bisogna connettersi all’IP del webserver (ESP32), stampato sul terminale della seriale all’avvio.
Digitare su un browser, di un dispositivo connesso alla stessa rete WIFI a cui è connesso l’ESP32.
Dobbiamo inserire le credenziali, che nel nostro caso sono User ID = “admin” e Password = “admin” e poi cliccare su login. Le credenziali possono essere cambiate e ve lo descriverò nel paragrafo dove descrivo il codice.
Cliccando sulla finestra “choose file” si selezione il file, che si vuole aggiornare sul dispositivo.
Codice
di seguito e’ riportato il codice sorgente
#include <WiFi.h> #include <WiFiClient.h> #include <WebServer.h> #include <ESPmDNS.h> #include <Update.h> #include "page.h" const char* host = "esp32"; const char* ssid = "WIFI ID"; const char* password = "Password"; WebServer server(80); /* setup function */ void setup(void) { Serial.begin(115200); // Connect to WiFi network WiFi.begin(ssid, password); Serial.println(""); // Wait for connection while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.print("Connesso a "); Serial.println(ssid); Serial.print("Indirizzo IP: "); Serial.println(WiFi.localIP()); /*use mdns for host name resolution*/ if (!MDNS.begin(host)) { Serial.println("Errore durante la configurazione del MDNS!"); while (1) { delay(1000); } } Serial.println("mDNS e' partito"); /*pagina principale */ server.on("/", HTTP_GET, []() { server.sendHeader("Connection", "close"); server.send(200, "text/html", loginIndex); }); server.on("/serverIndex", HTTP_GET, []() { server.sendHeader("Connection", "close"); server.send(200, "text/html", serverIndex); }); /*pagina che aggiorna il codice */ server.on("/update", HTTP_POST, []() { server.sendHeader("Connection", "close"); server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); ESP.restart(); }, []() { HTTPUpload& upload = server.upload(); if (upload.status == UPLOAD_FILE_START) { Serial.printf("Update: %s\n", upload.filename.c_str()); if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size Update.printError(Serial); } } else if (upload.status == UPLOAD_FILE_WRITE) { /* flashing firmware to ESP*/ if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { Update.printError(Serial); } } else if (upload.status == UPLOAD_FILE_END) { if (Update.end(true)) { Serial.printf("aggiornamento terminato con successo: %u\nRebooting...\n", upload.totalSize); } else { Update.printError(Serial); } } }); server.begin(); } void loop(void) { server.handleClient(); delay(1); }
Collegamento alla rete WIFI:
Caricamento delle pagine Web e gestione di eventuali errori.
Il processo di aggiornamento del codice e’ gestito dalle seguenti funzioni:
Di seguito e’ riportato il codice delle pagine HTML del server
/* Style */ String style = "<style>#file-input,input{width:100%;height:44px;border-radius:4px;margin:10px auto;font-size:15px}" "input{background:#f1f1f1;border:0;padding:0 15px}body{background:#3498db;font-family:sans-serif;font-size:14px;color:#777}" "#file-input{padding:0;border:1px solid #ddd;line-height:44px;text-align:left;display:block;cursor:pointer}" "#bar,#prgbar{background-color:#f1f1f1;border-radius:10px}#bar{background-color:#3498db;width:0%;height:10px}" "form{background:#fff;max-width:258px;margin:75px auto;padding:30px;border-radius:5px;text-align:center}" ".btn{background:#3498db;color:#fff;cursor:pointer}</style>"; /* Login page */ String loginIndex = "<form name=loginForm>" "<h1>ESP32 Login</h1>" "<input name=userid placeholder='User ID'> " "<input name=pwd placeholder=Password type=Password> " "<input type=submit onclick=check(this.form) class=btn value=Login></form>" "<script>" "function check(form) {" "if(form.userid.value=='admin' && form.pwd.value=='admin')" "{window.open('/serverIndex')}" "else" "{alert('Error Password or Username')}" "}" "</script>" + style; String serverIndex = "<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>" "<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>" "<input type='file' name='update' id='file' onchange='sub(this)' style=display:none>" "<label id='file-input' for='file'> Choose file...</label>" "<input type='submit' class=btn value='Update'>" "<br><br>" "<div id='prg'></div>" "<br><div id='prgbar'><div id='bar'></div></div><br></form>" "<script>" "function sub(obj){" "var fileName = obj.value.split('\\\\');" "document.getElementById('file-input').innerHTML = ' '+ fileName[fileName.length-1];" "};" "$('form').submit(function(e){" "e.preventDefault();" "var form = $('#upload_form')[0];" "var data = new FormData(form);" "$.ajax({" "url: '/update'," "type: 'POST'," "data: data," "contentType: false," "processData:false," "xhr: function() {" "var xhr = new window.XMLHttpRequest();" "xhr.upload.addEventListener('progress', function(evt) {" "if (evt.lengthComputable) {" "var per = evt.loaded / evt.total;" "$('#prg').html('progress: ' + Math.round(per*100) + '%');" "$('#bar').css('width',Math.round(per*100) + '%');" "}" "}, false);" "return xhr;" "}," "success:function(d, s) {" "console.log('success!') " "}," "error: function (a, b, c) {" "}" "});" "});" "</script>" + style;
Per cambiare le credenziali bisogna modificare la seguente riga di codice:
Per qualsiasi dubbio non esitate a contattarmi tramite il form di seguito..