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.
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 2: 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 3: 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 4: 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 5: 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.
Saudações Yure, primeiramente, obrigado pelo tutorial explicativo.
Há algum tempo atrás, eu já havia tentado por esse método mas, dava muito problema; não encontrava o IP, etc. Agora, com o seu tutorial, fiz todo o procedimento e deu certo mas, tenho uma questão sobre o Python 3.x. Você diz que este deve ser instalado manualmente ok? Então, eu não instalei o Python e, mesmo assim, funcionou, então, para me certificar, eu fiz uma pesquisa na Lupa de pesquisa do Windows e foi encontrado aplicativos do Python3, inclusive no pacote do esp8266, na pasta ‘AppData\Local\Arduino15\staging\packages’, está lá o python-3.7.2.post1-embed-win32v2. Sendo assim, não há necessidade de instalar, não é?
Eu havia tb tentado com o método AsyncElegantOTA que, segundo o Rui, do canal Random Nerds, diz que é o método mais eficiente mas, não consegui, enfim, obrigado mais uma vez.
Olá Yure, primeiramente, obrigado pelo tutorial.
A principio, qdo eu carreguei o primeiro código com o BasicOTA, ele mostrou o endereço IP do ESP na aba Porta da IDE do Arduino, então carreguei com sucesso um outro código com as funções OTA via WiFi. No outro dia, abri a IDE do Arduino mas, o IP já não aparece mais na porta. Fui na placa ESP e apertei o botão RST mas, nada. Será que isso tem a ver com o Windows Defender Firewall? Vc tem ciência sobre esse problema? Obg
Olá Daniel, verifique se a função ArduinoOTA.begin() está presente no seu código.
Parabéns Yure, assunto pra lá de especial. Também estou cursando último semestre de Automação industrial.
Perdoe minha pergunta de novato… Naquela imagem onde aparece a porta e logo abaixo o endereço para nova conexão… Esse endereço não apareceu… Onde será que estou errando? Obrigado.