Quando estamos desenvolvendo projetos utilizando ESP8266 ou mesmo ESP32 podemos nos deparar com alguns obstáculos na forma de fazer upload do código, por exemplo no Post sobre o Arduino Mega com WiFi tínhamos que configurar diversas chaves para alternar entre o Atmega2560 e o ESP8266 para fazer o upload. Em casos assim podemos utilizar uma forma de upload sem fio conhecida como OTA. Existem diferentes formas de fazer este tipo atualização nas placas ESP, neste post trataremos da mais comum durante o desenvolvimento que é utilizando o Arduino IDE.
O que é OTA
OTA é o acrônimo de Over-the-air que se refere a uma forma de atualização de software sem fio e de forma remota. OTA é um excelente recurso para aplicações IoT, pois os dispositivos podem ter firmwares e/ou configurações alterados sem a necessidade de serem recolhidos nem desmontados para serem reprogramados. Apesar desse recurso ser extremamente útil, é papel do desenvolvedor garantir que a fonte do novo firmware é confiável.
Os ESP8266 e ESP32 tem suporte para esse recurso, e não só isso, também já existe uma biblioteca com exemplos para o framework Arduino.
Existem 3 formas possíveis de atualização OTA:
- OTA via Arduino IDE: É a forma mais simples porque o upload é feito diretamente da Arduino IDE.
- OTA via upload de Imagem: O ESP mantém um pequeno servidor web e o usuário faz o upload dentro da interface desse servidor diretamente pelo navegador web.
- OTA via Servidor HTTP: Neste método existe um servidor HTTP que Hospeda o firmware e sempre que a alteração deste o ESP faz o download do firmware.
Durante processo de upload OTA o ESP cumpre 4 etapas:
- O novo sketch será armazenado no espaço entre o sketch antigo e a spiff;
- Na próxima reinicialização, o “eboot” bootloader verifica os comandos;
- O novo sketch agora é copiado “sobre” o antigo;
- O novo sketch é iniciado;
Figura 1: Divisão da memoria durante o upload OTA.
Mãos à obra
Nesse post utilizarei o Arduino Mega com WiFi mas você pode utilizar outras placas baseadas no ESP8266 como Wemos D1 Mini, placa Mega 2560 Wifi R3 ou placas baseadas no ESP32 também como a Wemos D1 ESP32.
Também é necessário ter o Arduino IDE na versão 1.6.7 ou mais recente devidamente configurado para as placas ESP, veja no Guia sobre GPIO do ESP32 e no post sobre Mega WiFi como configurar a IDE para trabalhar essas placas. Todos os códigos e testes desse post foram feitos utilizando a versão 1.8.12. Também é necessário o Python 3.x instalado e estar incluído na variável path. Isso é feito durante a instalação no Windows selecione “Adicionar python ao path”.
Figura 2: Instalação do Python.
Primeiro upload – USB
Seja qualquer a forma de update OTA, o primeiro upload de firmware deve ser feito através de uma porta serial.
O código abaixo funcionará tanto para ESP8266 quanto para ESP32, ele é baseado no exemplo BasicOTA.ino.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | #ifdef ESP8266 #include <ESP8266WiFi.h> #include <ESP8266mDNS.h> #else #include <WiFi.h> #include <ESPmDNS.h> #endif #include <WiFiUdp.h> #include <ArduinoOTA.h> const char* ssid = ".........."; const char* password = ".........."; void setup() { Serial.begin(115200); Serial.println("Booting"); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); while (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("Connection Failed! Rebooting..."); delay(5000); ESP.restart(); } #ifdef ESP8266 // Port defaults to 8266 // ArduinoOTA.setPort(8266); // Hostname defaults to esp8266-[ChipID] // ArduinoOTA.setHostname("myesp8266"); #else // Port defaults to 3232 // ArduinoOTA.setPort(3232); // Hostname defaults to esp3232-[MAC] // ArduinoOTA.setHostname("myesp32"); #endif // No authentication by default // ArduinoOTA.setPassword("admin"); // Password can be set with it's md5 value as well // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3 // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3"); ArduinoOTA.onStart([]() { String type; if (ArduinoOTA.getCommand() == U_FLASH) { type = "sketch"; } else { // U_FS type = "filesystem"; } // NOTE: if updating FS this would be the place to unmount FS using FS.end() Serial.println("Start updating " + type); }); ArduinoOTA.onEnd([]() { Serial.println("\nEnd"); }); ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { Serial.printf("Progress: %u%%\r", (progress / (total / 100))); }); ArduinoOTA.onError([](ota_error_t error) { Serial.printf("Error[%u]: ", error); if (error == OTA_AUTH_ERROR) { Serial.println("Auth Failed"); } else if (error == OTA_BEGIN_ERROR) { Serial.println("Begin Failed"); } else if (error == OTA_CONNECT_ERROR) { Serial.println("Connect Failed"); } else if (error == OTA_RECEIVE_ERROR) { Serial.println("Receive Failed"); } else if (error == OTA_END_ERROR) { Serial.println("End Failed"); } }); ArduinoOTA.begin(); Serial.println("Ready"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); } void loop() { ArduinoOTA.handle(); } |
Faça o upload do código e após a conclusão verifique o endereço ip impresso no Monitor Serial.
Figura 3: Saída do serial monitor.
Pronto agora seu ESP está pronto a receber futuras atualizações via OTA.
Segundo upload – OTA
Nesse exemplo vamos abrir duas instancias da Arduino IDE. Usaremos uma janela para fazer o upload e outra para observar as mensagens impressas no Monitor Serial.
Abra uma janela e verifique se o ESP aparece na opções de porta de rede. Caso não encontre a porta de rede, feche e abra novamente a Arduino IDE.
Figura 4: Porta de rede.
Abra uma outra janela da Arduino IDE e abra o Monitor Serial por ela. Usaremos uma janela para fazer o upload e outra para o observar as mensagens impressas no Monitor Serial.
Nesse segundo exemplo vamos alterar um pouco o primeiro código, vamos adicionar o blink como exemplo para esse post.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | #ifdef ESP8266 #include <ESP8266WiFi.h> #include <ESP8266mDNS.h> #else #include <WiFi.h> #include <ESPmDNS.h> #endif #include <WiFiUdp.h> #include <ArduinoOTA.h> #define LED_BUILTIN 2; const char* ssid = ".........."; const char* password = ".........."; void setup() { pinMode(LED_BUILTIN, OUTPUT); Serial.begin(115200); Serial.println("Booting"); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); while (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("Connection Failed! Rebooting..."); delay(5000); ESP.restart(); } #ifdef ESP8266 // Port defaults to 8266 // ArduinoOTA.setPort(8266); // Hostname defaults to esp8266-[ChipID] // ArduinoOTA.setHostname("myesp8266"); #else // Port defaults to 3232 // ArduinoOTA.setPort(3232); // Hostname defaults to esp3232-[MAC] // ArduinoOTA.setHostname("myesp32"); #endif // No authentication by default // ArduinoOTA.setPassword("admin"); // Password can be set with it's md5 value as well // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3 // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3"); ArduinoOTA.onStart([]() { String type; if (ArduinoOTA.getCommand() == U_FLASH) { type = "sketch"; } else { // U_FS type = "filesystem"; } // NOTE: if updating FS this would be the place to unmount FS using FS.end() Serial.println("Start updating " + type); }); ArduinoOTA.onEnd([]() { Serial.println("\nEnd"); }); ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { Serial.printf("Progress: %u%%\r", (progress / (total / 100))); }); ArduinoOTA.onError([](ota_error_t error) { Serial.printf("Error[%u]: ", error); if (error == OTA_AUTH_ERROR) { Serial.println("Auth Failed"); } else if (error == OTA_BEGIN_ERROR) { Serial.println("Begin Failed"); } else if (error == OTA_CONNECT_ERROR) { Serial.println("Connect Failed"); } else if (error == OTA_RECEIVE_ERROR) { Serial.println("Receive Failed"); } else if (error == OTA_END_ERROR) { Serial.println("End Failed"); } }); ArduinoOTA.begin(); Serial.println("Ready"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); } void loop() { digitalWrite(LED_BUILTIN, HIGH); delay(500); digitalWrite(LED_BUILTIN, LOW); delay(500); ArduinoOTA.handle(); } |
Observe no console da primeira janela o andamento do upload.
Figura 5: Console com o progresso do upload.
Observe no Monitor Serial o progresso do upload. Essas são as mensagens que configuramos nos callbacks da primeira versão do código.
Figura 6: Mensagens de status do update OTA.
Vale destacar que para manter a funcionalidade OTA, as futuras versões devem obrigatoriamente trazer as mesmas funções e Callbacks OTA contidas no primeiro código.
Entendendo o Código
A seguir uma breve explanação sobre as partes do primeiro código.
Inclusão das bibliotecas
Aqui fazemos a inclusão das bibliotecas que necessitamos. Note que temos as diretivas #ifdef e #else para incluir somente as bibliotecas compatíveis para cada tipo de processador.
1 2 3 4 5 6 7 8 9 10 | #ifdef ESP8266 #include <ESP8266WiFi.h> #include <ESP8266mDNS.h> #else #include <WiFi.h> #include <ESPmDNS.h> #endif #include <WiFiUdp.h> #include <ArduinoOTA.h> |
Credenciais de rede
1 2 | const char* ssid = ".........."; const char* password = ".........."; |
Função setup()
Aqui encontramos a maior parte das configurações e funções para upload OTA.
Inicialização da porta Serial
1 | Serial.begin(115200); |
Conexão com a rede por WiFi
Aqui inicializamos o ESP no modo estação. Logo após ele tentará se conectar a rede que informamos anteriormente. Caso não consiga, o ESP será reiniciado.
1 2 3 4 5 6 7 | WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); while (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("Connection Failed! Rebooting..."); delay(5000); ESP.restart(); } |
Segurança(Opcional)
Esta é uma etapa opcional neste exemplo, mas você pode escolher algums parâmetros como o hostname e a porta que será utilizada no upload. Ainda poderá configurar uma senha para upload, podendo ser simples ou utilizando hash MD5.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #ifdef ESP8266 // Port defaults to 8266 // ArduinoOTA.setPort(8266); // Hostname defaults to esp8266-[ChipID] // ArduinoOTA.setHostname("myesp8266"); #else // Port defaults to 3232 // ArduinoOTA.setPort(3232); // Hostname defaults to esp3232-[MAC] // ArduinoOTA.setHostname("myesp32"); #endif // No authentication by default // ArduinoOTA.setPassword("admin"); // Password can be set with it's md5 value as well // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3 // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3"); |
Callbacks do processo OTA
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | ArduinoOTA.onStart([]() { String type; if (ArduinoOTA.getCommand() == U_FLASH) { type = "sketch"; } else { // U_FS type = "filesystem"; } // NOTE: if updating FS this would be the place to unmount FS using FS.end() Serial.println("Start updating " + type); }); ArduinoOTA.onEnd([]() { Serial.println("\nEnd"); }); ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { Serial.printf("Progress: %u%%\r", (progress / (total / 100))); }); ArduinoOTA.onError([](ota_error_t error) { Serial.printf("Error[%u]: ", error); if (error == OTA_AUTH_ERROR) { Serial.println("Auth Failed"); } else if (error == OTA_BEGIN_ERROR) { Serial.println("Begin Failed"); } else if (error == OTA_CONNECT_ERROR) { Serial.println("Connect Failed"); } else if (error == OTA_RECEIVE_ERROR) { Serial.println("Receive Failed"); } else if (error == OTA_END_ERROR) { Serial.println("End Failed"); } }); ArduinoOTA.begin(); |
Função loop()
Espere por um upload
1 2 3 | void loop() { ArduinoOTA.handle(); } |
Por hoje é só, aguardo você no próximo post. Qualquer dúvida ou sugestão deixe registrado no campo de comentários.