Um recurso muito prático no campo da IoT é a capacidade de programar um microcontrolador conectado, como um ESP32, via WiFi. Esta técnica é designada por Programação Over-The-Air (OTA).
Hardware
- NodeMCU ESP32
- Computador
- Rede WiFi
Princípio de funcionamento
Em princípio, descarrega-se um programa para o ESP32 NodeMCU através de uma ligação em série à porta USB. O computador comunica o programa ao microcontrolador. Quando dois dispositivos estão ligados à mesma rede WiFi, podem comunicar entre si. Assim, é possível carregar o código através da rede WiFi sem ter de se ligar a cada microcontrolador.
É igualmente possível carregar um código no NodeMCU quando este está configurado como ponto de acesso WiFi. Neste caso, o computador utilizado para a programação deve estar ligado à rede do NodeMCU. O código completo é apresentado no final deste tutorial.
Adicionar firmware ArduinoOTA
A biblioteca ArduinoOTA está disponível quando se instala o gestor do ESP32. Para configurar a programação OTA, é necessário instalar o firmware. Para o fazer, pode utilizar o exemplo BasicOTA.ino disponível no IDE Arduino.
Certifique-se de que substitui ssid e password pelos identificadores da sua rede WiFi.
#include <WiFi.h> #include <ESPmDNS.h> #include <WiFiUdp.h> #include <ArduinoOTA.h> const char* ssid = "*******"; const char* password = "********"; unsigned long previousMillis; 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(); } // Port defaults to 3232 // ArduinoOTA.setPort(3232); // Hostname defaults to esp3232-[MAC] //ArduinoOTA.setHostname("esp3232"); // 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_SPIFFS type = "filesystem"; // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() Serial.println("Start updating " + type); }) .onEnd([]() { Serial.println("\nEnd"); }) .onProgress([](unsigned int progress, unsigned int total) { Serial.printf("Progress: %u%%\r", (progress / (total / 100))); }) .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(); }
Anote o endereço IP local do aparelho (aqui: 192.168.1.33).
Quando este código tiver sido carregado através de comunicação série, poderá carregar um código através de Wifi. Prima o botão RST ou EN no seu ESP e reinicie o Arduino IDE.
Modificar o seu programa antes de o carregar
Colocamos o código de inicialização na função initOTA(). Assim, será fácil copiá-lo para outros projectos. E acrescentamos um ecrã na porta série para verificar se o código foi modificado.
ATENÇÃO: O código de inicialização do ArduinoOTA deve estar presente em todos os códigos que carregar, caso contrário perderá a capacidade de efetuar transmissões aéreas.
#include <WiFi.h> #include <ESPmDNS.h> #include <WiFiUdp.h> #include <ArduinoOTA.h> const char* ssid = "******"; const char* password = "********"; unsigned long previousMillis; 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(); } initOTA(); Serial.println("Ready"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); } void loop() { ArduinoOTA.handle(); if (millis() - previousMillis >= 500) { previousMillis = millis(); Serial.println(F("Code has been update")); } } void initOTA() { // Port defaults to 3232 // ArduinoOTA.setPort(3232); // Hostname defaults to esp3232-[MAC] ArduinoOTA.setHostname("ESP32"); // 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_SPIFFS type = "filesystem"; // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() Serial.println("Start updating " + type); }) .onEnd([]() { Serial.println("\nEnd"); }) .onProgress([](unsigned int progress, unsigned int total) { Serial.printf("Progress: %u%%\r", (progress / (total / 100))); }) .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(); }
Una vez que haya modificado su código, en “Herramienta> Puerto”, seleccione su tarjeta entre los puertos de red. (si no aparece el puerto de red, consulte el capítulo siguiente).
Pode então carregar o seu código como faria com a comunicação em série.
Seleccione novamente a porta série e abra o monitor série para verificar se o código foi modificado.
Se a porta de rede não for exibida no IDE do Arduino
Se a porta de rede não estiver presente nas opções do Arduino IDE, terá de fazer um pequeno trabalho. É necessário ir ao Centro de Rede e Partilha
Em seguida, aceda a “Modificar parâmetros do cartão”.
Clique com o botão direito do rato na sua rede e vá para propriedades
Pode então desmarcar a opção “Protocolo Internet versão 6 (TCP)”.
Se voltar ao Arduino IDE depois de o reiniciar, em “Tool > Port” (Ferramenta > Porta) deve ver a opção de porta de rede apresentada.
Pode verificar novamente o IPv6 assim que a sua porta de rede for reconhecida.
Bónus: visualização de mensagens de série OTA
A possibilidade de carregar um programa via Wi-Fi é muito útil, mas é preciso notar que se perde a possibilidade de depurar com o monitor de série. É possível criar uma interface Web que permita visualizar as informações do ESP32 NodeMCU.
Aqui veremos como usar a biblioteca RemoteDebug para se conectar ao microcontrolador via telnet e recuperar as mensagens enviadas.
- Comece por instalar a biblioteca descarregando o ficheiro ZIP ou através do gestor de bibliotecas.
- Criar um objeto RemoteDebug Debug; depois de incluir o objeto
- Inicializar a depuração com Debug.begin(“ESP32”);
- Em seguida, substitua todos os Serial.print por Debug.print
#include <WiFi.h> #include <ESPmDNS.h> #include <WiFiUdp.h> #include <ArduinoOTA.h> #include <RemoteDebug.h> const char* ssid = "*******"; const char* password = "*******"; RemoteDebug Debug; unsigned long previousMillis; 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(); } // init remote debug Debug.begin("ESP32"); initOTA(); Serial.println("Ready"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); } void loop() { ArduinoOTA.handle(); Debug.handle(); if (millis() - previousMillis >= 500) { previousMillis = millis(); Debug.println(F("Code has been update")); } } void initOTA() { // Port defaults to 3232 // ArduinoOTA.setPort(3232); // Hostname defaults to esp3232-[MAC] ArduinoOTA.setHostname("ESP32"); // 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_SPIFFS type = "filesystem"; // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() Serial.println("Start updating " + type); }) .onEnd([]() { Serial.println("\nEnd"); }) .onProgress([](unsigned int progress, unsigned int total) { Serial.printf("Progress: %u%%\r", (progress / (total / 100))); }) .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(); }
Para visualizar as mensagens enviadas via RemoteDebug, vamos utilizar o software PuTTy que já vimos várias vezes. Para esta aplicação, vamos configurar o PuTTy no telnet usando o endereço IP obtido do monitor serial.
Bónus 2: Programação OTA em modo de ponto de acesso
É possível utilizar a rede criada pelo ESP32 NodeMCU em modo Access Point. Para isso, pode descarregar o código seguinte através da porta série. Em seguida, é necessário ligar o computador à rede do NodeMCU para poder descarregar um novo código via WiFi.
#include <WiFi.h> #include <ESPmDNS.h> #include <WiFiUdp.h> #include <ArduinoOTA.h> const char *ssid = "AC-ESP32"; const char *passphrase = "123456789"; IPAddress local_IP(192,168,4,22); IPAddress gateway(192,168,4,9); IPAddress subnet(255,255,255,0); unsigned long previousMillis; void setup() { Serial.begin(115200); Serial.println("Booting"); //AP config Serial.print("Setting soft-AP configuration ... "); Serial.println(WiFi.softAPConfig(local_IP, gateway, subnet) ? "Ready" : "Failed!"); Serial.print("Setting soft-AP ... "); Serial.println(WiFi.softAP(ssid,passphrase) ? "Ready" : "Failed!"); initOTA(); Serial.println("Ready"); Serial.print("IP address: "); Serial.println(WiFi.localIP()); } void loop() { ArduinoOTA.handle(); if (millis() - previousMillis >= 500) { previousMillis = millis(); Serial.println(F("Code has been update via Wifi")); } } void initOTA() { // Port defaults to 3232 // ArduinoOTA.setPort(3232); // Hostname defaults to esp3232-[MAC] ArduinoOTA.setHostname("ESP32"); // 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_SPIFFS type = "filesystem"; // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() Serial.println("Start updating " + type); }) .onEnd([]() { Serial.println("\nEnd"); }) .onProgress([](unsigned int progress, unsigned int total) { Serial.printf("Progress: %u%%\r", (progress / (total / 100))); }) .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(); }