Atualização de software OTA (Over-The-Air) no ESP32 Deixe um comentário

Em se tratando de Internet das Coisas e Industria 4.0, uma coisa é fundamental existir: atualização remota de software. Por melhor que seja o projeto de software, sempre há algo a se mudar: seja uma correção de software, adição de nova funcionalidade ou otimização. Ao se utilizar redes sem fio para para fazer tal atualização remota, cria-se a chamada atualização de software OTA (Over-The-Air). Portanto, atualização OTA é fundamental pois muitas vezes dispositivos desta categoria estão em locais de difícil acesso ou muito distantes para que sua atualização de software seja feita de modo físico. Este post vai te ensinar a fazer atualização OTA para o ESP32 de modo que você consiga atualizá-lo remotamente, sem fio.

Material necessário

Para fazer o projeto deste post, você precisará de qualquer placa de desenvolvimento que utilize um ESP32 e um cabo micro-USB para usar na programação e alimentação do ESP32. Como sugestão de placa de desenvolvimento, recomendo o uso do Módulo WiFi ESP32 Bluetooth. Uma foto deste módulo pode ser vista na figura 1.

Módulo WiFI ESP32 Bluetooth (Imagem 1)
Figura 1 – Módulo WiFi ESP32 Bluetooth

Neste projeto, utilizaremos a Arduino IDE para desenvolver os softwares e programar o ESP32. Se você não sabe como fazer isso, recomendo fortemente a leitura deste nosso artigo aqui do blog antes de prosseguir.

Por que pensar em atualização remota de software?

Atualmente, é cada vez mais comum lermos notícias sobre falhas de segurança e falhas de funcionamento em dispositivos vendidos em massa, como roteadores e câmeras IP, por exemplo. Uma falha em um dispositivo desse tipo significa, muitas vezes, milhões de usuários expostos em falhas de segurança e/ou com equipamentos com funcionamento ruim.
Para entender o tamanho da importância de atualização remota de software neste contexto, imagine-se dono de uma empresa que tem milhões de dispositivos vendidos no mercado e descobre, subitamente, que há falhas no software de seus equipamentos que os deixam inseguros e que podem prejudicar a experiência de uso. Neste caso, para preservar a imagem da empresa perante o mercado e garantir que seus consumidores tenham um bom produto, há somente três saídas:

  1. Fazer um recall dos produtos: isso significa pedir para cada um dos seus milhões de consumidores sair de suas casas em dias e horários pré-definidos, levar o equipamento até um ponto autorizado e, além disso, mobilizar muitas pessoas de sua empresa para arrumarem ou substituírem os equipamentos dos consumidores por novos.
    Além do enorme custo envolvido em mão-de-obra e em equipamentos e peças para reparo, você precisa tirar seus consumidores dos seus lares (numa data e horário muitas vezes não conveniente) para que eles tenham algo que lhes sempre foi de direito: um bom produto. Isso causa uma imagem muito negativa para sua empresa. Ainda, há a chance de uma parcela dos consumidores morarem tão distantes dos pontos autorizados que simplesmente é impossível fazer o recall dos equipamentos deles, certamente significando clientes a menos para você no futuro.
  2. Enviar equipamentos novos para cada um de seus consumidores via Correio (ou qualquer outra transportadora): bom, enviar equipamentos novos (e funcionando corretamente, claro) para a casa de seus consumidores fará com que eles não tenham que sair de seus lares. Porém, já imaginou o custo dessa solução? Isso forçará sua empresa a manter uma base de dados com os endereços de cada um de seus clientes. E se clientes se mudarem e não atualizarem seus endereços na sua base de dados? E se clientes se negarem a fornecer os endereços, por razões de segurança? Pior ainda: e se for proibido por lei manter tais dados sigilosos de seus clientes em uma base de dados corporativa (como prevê a Lei Geral de Proteção dos Dados)?
    Trágico, não?
  3. Atualizar remotamente o software de seus equipamentos: assumindo que o equipamento tenha conectividade com a Internet (hoje em dia isso é algo muito plausível), a empresa pode disparar uma atualização remota de software em massa. Logo, basta o equipamento estar conectado na Internet que ele poderá se atualizar em poucos minutos, a qualquer hora do dia ou da noite, deixando seus consumidores satisfeitos e com um bom produto em seus lares. Além disso, seus custos operacionais para esse tipo de operação com os equipamentos de seus milhões de consumidores é quase zero.

Obviamente, você escolheria a saída 3, certo? Logo, você percebeu porque a atualização remota de software é algo bom para todos os lados e, obrigatoriamente, você deve ao menos pensar na possibilidade de ter este recurso em seus projetos e produtos (se quiser estar preparado para quando problemas acontecerem, claro).

Atualização OTA do ESP32 – maneiras possíveis

Basicamente, há duas maneiras de se fazer uma atualização de software OTA no ESP32:

  1. Via Arduino IDE: dessa forma, a Arduino IDE consegue programar o ESP32 via rede. Para isso, ao invés de ser selecionada uma interface serial na Arduino IDE para a programação, é acessado o IP do ESP32 na rede e a Arduino IDE se encarrega de atualizar o software do ESP32 via rede sem fio.
  2. Via web updater: aqui, um web server é criado no ESP32, permitindo que via browser (navegador de Internet) usando de seu computador, smartphone ou tablet seja possível enviar o software ao ESP32, de forma que ele se atualiza sozinho ao final do processo de envio.
    Ainda, neste caso é possível colocar uma autenticação (usuário e senha), permitindo que somente as pessoas que tenham estas credenciais consigam fazer atualização de software no ESP32.

Neste post será ensinado a fazer a atualização OTA utilizando o método 2 (via web updater). É importante ressaltar que o processo aqui mostrado é válido para ESP32 presente na mesma rede que seu computador, tablet ou smartphone. Para atualização com ESP32 em outras redes, é necessário o uso de VPNs (conteúdo não abordado nesta postagem).

Como funciona o processo de atualização OTA no ESP32 (com web updater)?

O processo de atualização OTA no ESP32 com web updater funciona da seguinte maneira:

  1. Inicialmente, para o processo funcionar, é necessário que o software que está rodando no ESP32 suporte atualização OTA via web updater.
  2. Uma vez que o software do ESP32 possua suporte a atualização OTA via web updater, será possível acessar o recurso de atualização pelo browser (através do IP do ESP32 na rede).
  3. Pelo browser é enviado o novo software ao ESP32 e, após o upload, o ESP32 será reiniciado e já fará o boot com o software novo. É importante lembrar que, para futuras atualizações OTA, este novo software também precisa suportar atualização OTA via web updater. Caso contrário, será impossível atualizar este software via OTA.

Em resumo: antes de fazer atualização OTA, é preciso que ambos os softwares (software atual / corrente e o novo software) suportem este recurso.

Primeiro passo: fazendo o primeiro software com suporte a atualização OTA

Segue abaixo o código-fonte do primeiro software a ser gravado no ESP32 (já com suporte a atualização OTA). Não se esqueça de substituir as credenciais wi-fi (nome da rede e senha da rede wi-fi) nas variáveis ssid e password (linhas 10 e 11).
Observe que este software implementa o web server e já contém os códigos-fonte das páginas de autenticação / login e upload de software “embedadas” (já programadas internamente) em si.

Importante: Leia todos os comentários do código-fonte para total entendimento do mesmo.

/*  Programa para ESP32 antes da atualização OTA */
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>

/* Constantes - conexão wi-fi e webserver */
const char* host = "esp32";
const char* ssid = " "; /* coloque aqui o nome da rede wi-fi que o ESP32 deve se conectar */
const char* password = " "; /* coloque aqui a senha da rede wi-fi que o ESP32 deve se conectar */

/* Variáveis globais */
int contador_ms = 0;

/* Webserver para se comunicar via browser com ESP32  */
WebServer server(80);

/* Códigos da página que será aberta no browser 
   (quando comunicar via browser com o ESP32) 
   Esta página exigirá um login e senha, de modo que somente 
   quem tenha estas informações consiga atualizar o firmware
   do ESP32 de forma OTA */
const char* loginIndex = 
 "<form name='loginForm'>"
    "<table width='20%' bgcolor='A09F9F' align='center'>"
        "<tr>"
            "<td colspan=2>"
                "<center><font size=4><b>ESP32 - identifique-se</b></font></center>"
                "<br>"
            "</td>"
            "<br>"
            "<br>"
        "</tr>"
        "<td>Login:</td>"
        "<td><input type='text' size=25 name='userid'><br></td>"
        "</tr>"
        "<br>"
        "<br>"
        "<tr>"
            "<td>Senha:</td>"
            "<td><input type='Password' size=25 name='pwd'><br></td>"
            "<br>"
            "<br>"
        "</tr>"
        "<tr>"
            "<td><input type='submit' onclick='check(this.form)' value='Identificar'></td>"
        "</tr>"
    "</table>"
"</form>"
"<script>"
    "function check(form)"
    "{"
    "if(form.userid.value=='admin' && form.pwd.value=='admin')"
    "{"
    "window.open('/serverIndex')"
    "}"
    "else"
    "{"
    " alert('Login ou senha inválidos')"
    "}"
    "}"
"</script>";
 
const char* 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'>"
        "<input type='submit' value='Update'>"
    "</form>"
 "<div id='prg'>Progresso: 0%</div>"
 "<script>"
  "$('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('Progresso: ' + Math.round(per*100) + '%');"
  "}"
  "}, false);"
  "return xhr;"
  "},"
  "success:function(d, s) {"
  "console.log('Sucesso!')" 
 "},"
 "error: function (a, b, c) {"
 "}"
 "});"
 "});"
 "</script>";

void setup(void) 
{
    Serial.begin(115200);

    /* Conecta-se a rede wi-fi */
    WiFi.begin(ssid, password);
    Serial.println("");

    while (WiFi.status() != WL_CONNECTED) 
    {
        delay(500);
        Serial.print(".");
    }
    
    Serial.println("");
    Serial.print("Conectado a rede wi-fi ");
    Serial.println(ssid);
    Serial.print("IP obtido: ");
    Serial.println(WiFi.localIP());

    /* Usa MDNS para resolver o DNS */
    if (!MDNS.begin(host)) 
    { 
        //http://esp32.local
        Serial.println("Erro ao configurar mDNS. O ESP32 vai reiniciar em 1s...");
        delay(1000);
        ESP.restart();        
    }
  
    Serial.println("mDNS configurado e inicializado;");
  
    /* Configfura as páginas de login e upload de firmware OTA */
    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);
    });
  
    /* Define tratamentos do update de firmware OTA */
    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) 
        {
            /* Inicio do upload de firmware OTA */
            Serial.printf("Update: %s\n", upload.filename.c_str());
            if (!Update.begin(UPDATE_SIZE_UNKNOWN)) 
                Update.printError(Serial);
        } 
        else if (upload.status == UPLOAD_FILE_WRITE) 
        {
            /* Escrevendo firmware enviado na flash do ESP32 */
            if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) 
                Update.printError(Serial);      
        } 
        else if (upload.status == UPLOAD_FILE_END) 
        {
            /* Final de upload */
            if (Update.end(true))             
                Serial.printf("Sucesso no update de firmware: %u\nReiniciando ESP32...\n", upload.totalSize);
            else
                Update.printError(Serial);
        }   
    });
    server.begin();
}

void loop() 
{
    server.handleClient();
    delay(1);

    contador_ms++;

    if (contador_ms >= 1000)
    {    
        Serial.println("Programa antes da atualizacao OTA");
        contador_ms = 0;
    }
}

Salve (em uma pasta de fácil acesso / conhecida) o código-fonte como “esp32_antes_ota.ino”, compile e grave-o no ESP32 usando a Arduino IDE. Feito isso, observe os seguintes pontos:

  • Quando o software se inicia no ESP32, no serial monitor é escrito o endereço IP que o módulo pegou na rede wi-fi.
  • No serial monitor (com baudrate de 115200 baud) será escrito continuamente a frase “Programa antes da atualizacao OTA”. 

Ambos os pontos estão em destaque na figura 2.

Atualização OTA no ESP32 - antes (Imagem 2)
Figura 2 – saída do serial monitor (esp32_antes_ota.ino)

Segundo passo: verificando o funcionamento do web server para atualização OTA

Usando um browser qualquer no seu computador, acesse o IP do ESP32 (informado no serial monitor). Você verá a página de login do web updater, conforme mostrado na figura 3.

Login Web Server (Imagem 3)
Figura 3 – página de login do web updater

O login e senha para acesso é admin e admin, respectivamente. Isso pode ser mudado alterando-se as palavras “admin” na seguinte linha do código-fonte:

"if(form.userid.value=='admin' && form.pwd.value=='admin')"

Ao se identificar, você será direcionado a página de upload de firmware (conforme mostra figura 4), onde é possível enviar um software para ser atualizado via OTA no ESP32.

Upload do Software (Imagem 4)
Figura 4 – página de upload de software (web updater)

Terceiro passo: fazendo o segundo software com suporte a atualização OTA

Segue abaixo o código-fonte do segundo software a ser gravado no ESP32 (também com suporte a atualização OTA). Não se esqueça de substituir as credenciais wi-fi (nome da rede e senha da rede wi-fi) nas variáveis ssid e password (linha 10 e 11)
Observe que ele é muito similar ao código anterior. Sua única diferença é que ao invés de escrever continuamente no serial monitor a frase “Programa antes da atualizacao OTA”, agora é escrita continuamente a frase “Programa depois da atualizacao OTA”. Isso servirá como evidência de que o software do ESP32 foi realmente foi atualizado.

Importante: Leia todos os comentários do código-fonte para total entendimento do mesmo.

/*  Programa para ESP32 depois da atualização OTA */
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>

/* Constantes - conexão wi-fi e webserver */
const char* host = "esp32";
const char* ssid = " "; /* coloque aqui o nome da rede wi-fi que o ESP32 deve se conectar */
const char* password = " "; /* coloque aqui a senha da rede wi-fi que o ESP32 deve se conectar */

/* Variáveis globais */
int contador_ms = 0;

/* Webserver para se comunicar via browser com ESP32  */
WebServer server(80);

/* Códigos da página que será aberta no browser 
   (quando comunicar via browser com o ESP32) 
   Esta página exigirá um login e senha, de modo que somente 
   quem tenha estas informações consiga atualizar o firmware
   do ESP32 de forma OTA */
const char* loginIndex = 
 "<form name='loginForm'>"
    "<table width='20%' bgcolor='A09F9F' align='center'>"
        "<tr>"
            "<td colspan=2>"
                "<center><font size=4><b>ESP32 - identifique-se</b></font></center>"
                "<br>"
            "</td>"
            "<br>"
            "<br>"
        "</tr>"
        "<td>Login:</td>"
        "<td><input type='text' size=25 name='userid'><br></td>"
        "</tr>"
        "<br>"
        "<br>"
        "<tr>"
            "<td>Senha:</td>"
            "<td><input type='Password' size=25 name='pwd'><br></td>"
            "<br>"
            "<br>"
        "</tr>"
        "<tr>"
            "<td><input type='submit' onclick='check(this.form)' value='Identificar'></td>"
        "</tr>"
    "</table>"
"</form>"
"<script>"
    "function check(form)"
    "{"
    "if(form.userid.value=='admin' && form.pwd.value=='admin')"
    "{"
    "window.open('/serverIndex')"
    "}"
    "else"
    "{"
    " alert('Login ou senha inválidos')"
    "}"
    "}"
"</script>";
 
const char* 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'>"
        "<input type='submit' value='Update'>"
    "</form>"
 "<div id='prg'>Progresso: 0%</div>"
 "<script>"
  "$('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('Progresso: ' + Math.round(per*100) + '%');"
  "}"
  "}, false);"
  "return xhr;"
  "},"
  "success:function(d, s) {"
  "console.log('Sucesso!')" 
 "},"
 "error: function (a, b, c) {"
 "}"
 "});"
 "});"
 "</script>";

void setup(void) 
{
    Serial.begin(115200);

    /* Conecta-se a rede wi-fi */
    WiFi.begin(ssid, password);
    Serial.println("");

    while (WiFi.status() != WL_CONNECTED) 
    {
        delay(500);
        Serial.print(".");
    }
    
    Serial.println("");
    Serial.print("Conectado a rede wi-fi ");
    Serial.println(ssid);
    Serial.print("IP obtido: ");
    Serial.println(WiFi.localIP());

    /* Usa MDNS para resolver o DNS */
    if (!MDNS.begin(host)) 
    { 
        //http://esp32.local
        Serial.println("Erro ao configurar mDNS. O ESP32 vai reiniciar em 1s...");
        delay(1000);
        ESP.restart();        
    }
  
    Serial.println("mDNS configurado e inicializado;");
  
    /* Configfura as páginas de login e upload de firmware OTA */
    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);
    });
  
    /* Define tratamentos do update de firmware OTA */
    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) 
        {
            /* Inicio do upload de firmware OTA */
            Serial.printf("Update: %s\n", upload.filename.c_str());
            if (!Update.begin(UPDATE_SIZE_UNKNOWN)) 
                Update.printError(Serial);
        } 
        else if (upload.status == UPLOAD_FILE_WRITE) 
        {
            /* Escrevendo firmware enviado na flash do ESP32 */
            if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) 
                Update.printError(Serial);      
        } 
        else if (upload.status == UPLOAD_FILE_END) 
        {
            /* Final de upload */
            if (Update.end(true))             
                Serial.printf("Sucesso no update de firmware: %u\nReiniciando ESP32...\n", upload.totalSize);
            else
                Update.printError(Serial);
        }   
    });
    server.begin();
}

void loop() 
{
    server.handleClient();
    delay(1);

    contador_ms++;

    if (contador_ms >= 1000)
    {    
        Serial.println("Programa depois da atualizacao OTA");
        contador_ms = 0;
    }
}

Salve (em uma pasta de fácil acesso / conhecida) o código-fonte como “esp32_depois_ota.ino” e compile-o.
Aqui vem algo diferente: como será feita a atualização OTA (usando wi-fi), é preciso enviar pelo web updater (conforme figura 4) um arquivo com o programa do ESP32 já compilado. A este arquivo dá-se o nome de binário.
Para gerar este binário, siga o procedimento abaixo:

  1. Na Arduino IDE, clique sobre Sketch, e depois sobre Export compiled Binary.
  2. Aguarde o processo de compilação e exportação do arquivo.
  3. Será criado na mesma pasta que você salvou o código fonte (esp32_depois_ota.ino) um arquivo chamado esp32_depois_ota.ino.esp32.bin. Este é o binário que iremos utilizar na página de upload de software no web updater (figura 4).

Quarto passo: atualizando o software do ESP32 via OTA com web updater

Por fim, é chegada a hora de atualizarmos o software no ESP32. Com o primeiro programa (esp32_antes_ota.ino) rodando no seu ESP32, utilize o IP do mesmo para acessar a página de login (figura 3) e se identifique (com o login e senha contidos no código, sendo o padrão admin / admin).

Você irá ser direcionado para a página de upload de software (figura 4). Nela, clique sobre o botão “Escolher arquivo” e, na janela que surgir, escolha o binário do segundo software gerado (esp32_depois_ota.ino.esp32.bin). A página de upload de software ficará conforme mostra a figura 5.

Upload do Software (Imagem 5)
Figura 5 – binário selecionado na página de upload de software

Clique no botão “Update” e aguarde o upload do binário, cujo progresso do upload é indicado na página no campo “Progresso”. Este processo pode levar algo em torno de 20 segundos. Após o progresso atingir 100%, o binário enviado será gravado e o ESP32 reiniciará, já bootando com o software novo.
Para comprovar que o software foi atualizado, abra o Serial Monitor e observe que agora está sendo impresso continuamente a frase “Programa depois da atualizacao OTA”, conforme mostra a figura 6.

Atualização OTA no ESP32 - depois (Imagem 6)
Figura 6 – Serial Monitor após a atualização OTA

Desta forma, o software do ESP32 foi atualizado OTA, usando WiFi!

Referências e Conclusão

Conforme vimos, a atualização remota de software é algo importantíssimo, pois garante que dispositivos em campo / operação possam passar por atualização de software sem intervenção humana e sem precisar retirar o equipamento de operação por muito tempo, poupando custos e diminuindo o tempo total de regularização do dispositivo. Ainda, a atualização remota de software OTA vai além, fazendo tudo isso utilizando uma rede sem fio (mais comumente, o wi-fi).

Neste post, você aprendeu a como fazer atualização OTA no ESP32 utilizando wi-fi, a partir do browser de um computador na mesma rede que o ESP32. Para casos de atualização OTA em que o computador esteja em uma rede diferente da utilizada pelos ESP32, devem ser usados outros recursos de acesso remoto à rede destes dispositivos, tais como o uso de VPN (Virtual Private Networks), por exemplo.

Para fazer este post, utilizei como referência este excelente artigo do Rui Santos: https://randomnerdtutorials.com/esp32-over-the-air-ota-programming/

Gostou deste post sobre atualização OTA no ESP32? Deixe seu comentário logo abaixo. Em caso de dúvidas, caso queira trocar uma ideia, ou até mesmo dividir seu projeto, acesse nosso Fórum!

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *