ESP8266 programar OTA.

INTRODUCCIÓN ¿QUÉ ES OTA?

Se trata la actualización por vía aire (OTA) es una característica de los micro-controladores que tengan habilitado el WiFi en lugar de un puerto serie como los ESP8266, NodeMCU, etc. Es la capacidad que permite actualizar / cargar un nuevo programa al ESP8266 usando Wi-Fi (de forma inalámbrica) o Bluetooth. Para realizar la actualización del nuevo programa, la funcionalidad se vuelve útil cuando el acceso físico al módulo es limitado o nulo. Por eso OTA es extremadamente útil en caso de no tener acceso físico al módulo ESP.

La gestión del OTA se puede hacer usando:

· Arduino IDE
· Navegador web
· Servidor HTTP

La primera opción es usar OTA principalmente para la fase de desarrollo del software, para subir los proyectos desde el IDE Arduino, la primera carga de firmware se realizará como hasta ahora a través de un puerto serie. Si hemos implementado las rutinas OTA correctamente en el boceto, entonces todas las subidas posteriores se pueden realizar cómodamente por aire.

Las opciones del navegador web así como el servidor HTTP, se verán más útiles después de la primera implementación, facilitando las actualizaciones del módulo como una alternativa más rápida a la carga de un nuevo boceto.

SEGURIDAD BÁSICA.

Normalmente el módulo estará expuesto de forma inalámbrica en el momento de actualizarlo con una nueva versión o boceto, esto nos plantea un inconveniente ya que el módulo puede ser hakeado intencionadamente y cargarlo con un código fraudulento. El dilema es que en la gestión del OTA no existe una opción integrada de fábrica para la seguridad, es decir, la responsabilidad es del desarrollador, garantizar que las actualizaciones sólo se permitan realizar desde fuentes legítimas o autorizadas.

El programador hará un análisis de riesgos dependiendo de la aplicación y decidirá qué funciones de biblioteca implementar. Es tarea del desarrollador asegurarse de que, la aplicación se ejecuta en el módulo, que se cierra el módulo y reinicia de manera segura una vez completada la actualización.

En este apartado de seguridad, téngase en cuenta que actualmente sistemas de protección por contraseña y verificación MD5 se consideran de seguridad muy débil. Para reducir en lo posible ser hackeado, proteger sus subidas con una contraseña, considere la implementación de otros medios de protección contra la piratería seleccionando cierto puerto OTA, exponga el módulo para las cargas de acuerdo con unas condiciones concretas, que para «activar» la opción OTA, si hay coincidencia con un código dedicado a actualizar, etc.

En la librería ArduinoOTA, cierta funcionalidad de protección ya está incorporada y no requiere ninguna codificación adicional por parte del desarrollador. No obstante, verifique que esta librería para mejorar la seguridad contiene las siguientes funciones:

void setPort(uint16_t port);
void setHostname(const char* hostname);
void setPassword(const char* password);

Cuando se procede al OTA, éste acapara el ancho de banda y los recursos del ESP mientras se carga. Posteriormente se reiniciará el módulo y empezará el nuevo boceto, es el momento de analizar y comprobar la funcionalidad entre el boceto existente y el nuevo.

Puesto que el ESP se ubicará en un lugar remoto y además controlará varios dispositivos, se debe poner una atención añadida por lo que suceda si un proceso de actualización se interrumpe inesperadamente. Por este y otros motivos debe poner el módulo en un estado seguro antes de iniciar la actualización.

Supongamos un caso concreto, el módulo controla un sistema de alarma en secuencia del perímetro de la vivienda. Si dicha secuencia no se apaga correctamente y queda un sensor activo, la alarma se accionará sin posibilidad de parar su llamada, si no se cierra al finalizar OTA y se reinicia el módulo, con las consecuencias que genera.

La librería ArduinoOTA nos proporciona unas funciones diseñadas para lograr la funcionalidad de nuestra aplicación durante etapas especificas OTA, veamos en caso de si se produce un error de OTA:

void onStart(OTA_CALLBACK(fn));
void onEnd(OTA_CALLBACK(fn));
void onProgress(OTA_CALLBACK_PROGRESS(fn));
void onError(OTA_CALLBACK_ERROR (fn));

Usted como usuario debe hacer su propio análisis de riesgos como se ha dicho y dependiendo de la aplicación, decida qué funciones de biblioteca implementar. Si es necesario, considere la implementación de otros medios de protección contra la piratería, por ejemplo: ponga el módulo a la carga solo de acuerdo con un horario, después de pulsar un botón, etc.

En consecuencia y visto lo expuesto, se recomienda que el tamaño del flash del chip tenga un tamaño 2 veces el tamaño del boceto. Ya vimos en funcionalidad SPIFFS las capacidades de los diferentes chips de la familia ESP.

Fig. 1

Para más información sobre seguridad y actualizaciones vea más abajo las referencias.

EL IDE ARDUINO.

Durante el desarrollo del firmware como el medio más rápido en la carga en serie desde el IDE Arduino especialmente adaptada a este escenario de los ESP, donde el ESP y la computadora estarán conectados a la misma red.

Arduino a partir de la versión 1.6.4, permite la instalación de paquetes de plataformas de terceros mediante el Administrador de tarjetas. Tienen paquetes disponibles para Windows, Mac OS y Linux (32 y 64 bits). Yo instale la versión actual del IDE de Arduino 1.8.7. La versión actual está en el sitio web de Arduino.

Como siempre inicie Arduino y abra la ventana de Preferencias y en el campo URLs adicionales de Board Manager ingrese: https://arduino.esp8266.com/stable/package_esp8266com_index.json. En el Administrador de tablas desde el menú Herramientas> Placa e instale la plataforma ESP8266. Luego seleccione su placa ESP8266 en el menú Herramientas.

Además de Arduino, se pueden utilizar otros medios como LUAespota.py, pero se ha hecho más popular por su sencillez, a la mayoría de los usuarios el IDE de Arduino. Veamos los pasos que necesitamos realizar con el IDE de Arduino para programar la primera vez, porque hay que realizar una primera carga en el ESP y así dar soporte para cargar y preparar las sucesivas actualizaciones del ESP8266 por medio de OTA.

1.- Instalación del sistema Python.

Descargue el Python 2.7 (no instale Python 3.5 no es compatible). Si usted es usuario de Windows a la hora de la instalación debe seleccionar la opción «Agregar python.exe a la ruta«, ya que no viene seleccionada por defecto. Al ejecutar el instalador y siga el asistente de instalación.

Descargue el Python 2.7 (no instale Python 3.5 no es compatible). Si usted es usuario de Windows al ejecutar el instalador, siga el asistente de instalación, en la sección Personalizar Python 2.7.X, asegúrese de habilitar que la última opción Agregar python.exe, que normalmente no viene habilitada.

Fig. 2

2.- Instalación de la librería OTA en Arduino.

Si no dispone de la librería y las herramientas para OTA haga clic aquí. Ahora para instalar la librería vaya a Programa/Incluir librería/Gestor de librerías y navegue hasta el archivo ESP8266_ota.zip y siga la instalación, si es necesario reinicie Arduino.

Este es un paso imprescindible actualizar inicialmente el firmware, para poder realizar las siguientes actualizaciones o subidas de forma inalámbrica. Carguemos la rutina OTA en serie, el ESP8266 no tiene una actualización OTA de fábrica, por lo tanto, primero se debe cargar el firmware OTA en el ESP8266 a través de la interfaz serial.

Vaya en Arduino a Archivo/Ejemplos y elija el archivo BasicOTA como se muestra en la figura 3.

Fig. 3

Hagamos unos cambios en el archivo BasicOTA y sigamos, suba el boceto:

Fig. 4

El procedimiento más básico es sencillo de implementar. El siguiente boceto contiene lo mínimo necesario (la configuración de la red WiFi no se detalla, pues es algo común al uso del ESP en general).

Archivo mínimo OTA.

Para subir el primer archivo OTA, la configuración de la red WiFi no se detalla.

// Simple OTA
#include "ESP8266WiFi.h" 
#include "ESP8266mDNS.h"
#include "ArduinoOTA.h"

void setup() {
 WiFi.mode(WIFI_STA);
 WiFi.begin("ssid", "password");
 while (WiFi.waitForConnectResult() != WL_CONNECTED) {
  Serial.println("Connection Failed! Rebooting...");
  ESP.restart();
 }
 ArduinoOTA.begin();
}

void loop() {
  ArduinoOTA.handle();
}

Básicamente, como en otros muchos módulos, se configura el sistema en el setup() usando el método ArduinoOTA.begin() y en cada iteración del bucle de eventos, con el método ArduinoOTA.handle(), se comprueba si hay algún programador intentando cargar un nuevo boceto. «La librería se encarga de todos los detalles relacionados con la programación, por lo que no sería necesario hacer nada más«, esta es una visión muy simplista, nosotros necesitamos ir más allá en nuestro camino para aprender y adquirir nuevos conocimientos.

El siguiente es un programa bastante más elaborado, aunque siempre se puede añadir funciones en la medida que se necesiten, sin perder de vista como ya se ha comentado, el tamaño del código.

Archivo BasicOTA.

Subir el primer archivo OTA.

/* BasicOTA.ino
*/

#include "ESP8266WiFi.h"
#include "ESP8266mDNS.h"
#include "WiFiUdp.h"
#include "ArduinoOTA.h"

#ifndef STASSID
#define STASSID "your-ssid"
#define STAPSK "your-password"
#endif

const char* ssid = STASSID;
const char* password = STAPSK;

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 8266
// ArduinoOTA.setPort(8266);

// Hostname defaults to esp8266-[ChipID]
// ArduinoOTA.setHostname("myesp8266");

// 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 v 1.0");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}

void loop() {
ArduinoOTA.handle();
}

Ahora abrimos el monitor serie y si todo ha ido bien nos mostrará la dirección IP de nuestro enrutador, anote la IP. Según la documentación de la librería, cuando cargas el programa con la gestión de OTA, en unos segundos aparecerá la opción de un puerto OTA en el IDE como se aprecia en la figura 5.

nuevo-puerto-ota
Fig. 5

Ahora nuestro ESP8266 está preparado para la carga de la nueva versión del programa vía aire (OTA). De modo que una vez cargado un primer programa como cargador OTA, nuestro ESP está predispuesto a ser actualizado por vía aire (OTA) .

3.- Carga de un nuevo boceto por el aire (vía WIFI).

Una vez hayamos terminado de subir el anterior archivo BasicOTA, ha llegado el momento de subir un nuevo archivo de forma inalámbrica. Recordemos que siempre debemos agregar el código OTA en cada boceto que carguemos, de lo contrario se perderá la capacidad de OTA y no podrán realizar las siguientes subidas por aire.

Ya disponemos de la IP de la red (que hemos guardado) y disponemos del puerto que hemos seleccionado (por defecto, 8266), por supuesto es necesario que estemos conectados de algún modo a la misma red.

Nuevo Archivo BasicOTA.

Para la prueba, es el mismo archivo OTA. Se ha modificado y activa un LED.

// Nuevo_Archivo_BasicOTA.ino
#include "ESP8266WiFi.h"
#include "ESP8266mDNS.h"
#include "WiFiUdp.h"
#include "ArduinoOTA.h"

const char* ssid = "..........";
const char* password = "..........";

// * nuevo
// variables para blinking un LED con Millis
const int led = D0; // ESP8266 Pin a bordo al que está conectado el LED
unsigned long previousMillis = 0; // guardará la última vez que se actualizó el LED
const long interval = 1000; // intervalo en el cual parpadear (milisegundos)
int ledState = LOW; // utiliza ledState para configurar el LED
// *

void setup() {
pinMode(led, OUTPUT); // * nuevo

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 8266
// ArduinoOTA.setPort(8266);

// Hostname defaults to esp8266-[ChipID]
// ArduinoOTA.setHostname("myesp8266");

// No authentication by default
// ArduinoOTA.setPassword("admin");

// La contraseña se puede establecer con su valor md5 también
// 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);
});
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 v 2.0");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}

void loop() {
ArduinoOTA.handle();

// * nuevas
// bucle para parpadear sin demora (delay)
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
// * Guarda la última vez que parpadeó el LED.
previousMillis = currentMillis;
// * Si el LED está apagado, enciéndalo y viceversa:
ledState = not(ledState);
// * configurar el LED con el ledState de la variable:
digitalWrite(led, ledState);
}

}

Adviertase que, en esta ocasión en este programa no utilizamos el retardo delay() para que parpadee el LED, debido a que el modulo ESP8266 detiene el programa durante el delay(), de manera que si en la próxima solicitud de OTA se produce mientras Arduino está en pausa esperando que pase el delay(). el programa perderá dicha solicitud.

Cuando haya cargado el nuevo boceto a su IDE Arduino, iremos a la opción Herramientas>Puerto encontrará un puerto similar a: esp8266-xxxx xxxx at su_esp_ip_address, como se aprecia en la figura 6, súbalo al ESP y verá que el LED integrado en al ESP empieza a parpadear.


Vídeo

Nota: Para poder cargar su boceto una y otra vez utilizando OTA, debe insertar rutinas OTA en su interior. Eso es todo por ahora, en un próximo tutorial se tratará con más ejemplos esta posibilidad de OTA.

Ayúdenos a mantener la comunidad en lo positivo y útil. 
Sobre el tema, sea respetuoso con todas las edades y niveles
con la habitual responsabilidad. 
Sea amable y no haga Spam - ¡Gracias!
Referencias.

https://arduino-esp8266.readthedocs.io/en/latest/ota_updates/readme.html

https://github.com/esp8266/Arduino/blob/master/doc/ota_updates/ota_updates.md

https://github.com/esp8266/Arduino/tree/master/libraries/ArduinoOTA

Esto es todo, por este simple tutorial.

2 comentarios sobre «ESP8266 programar OTA.»

  1. Muchas gracias por este interesante articulo, completamente útil cuando se está pensando en hacer una red de sensores. Sería interesante también ver cuales son las buenas prácticas en términos de seguridad o los métodos que se pueden emplear hoy en día en el ESP866 y el ESP32 para conseguir un buen nivel de seguridad. También me pregunto como se puede hacer para no actualizar todo el código sino el valor algunas variables(que supongo que no tiene nada que ver con OTA).
    Saludos,

    1. Hola Didier Herrera.

      Gracias por tus palabras.

      La tuya es una pregunta muy buena. Cuando tratamos sobre seguridad, tenemos que partir de que en la actualidad, nada que esté en la red está fuera del alcance de un ataque perpetrado por un interesado en entrar en una aplicación.

      Por lo tanto, tenemos que cuidar mucho los términos y sobre todo lo que cada uno consideramos necesario para acceder a nuestra aplicación, y las condiciones que ha de tener antes de iniciar su acceso, es decir, nunca es suficiente. Pero no por eso debemos bajar la guardia.

      La primera opción debe ser utilizar una clave fuerte, tratar de utilizar una especie de AND y procurar una respuesta que permita su acceso, aplicar una muestra MD5 que aunque es algo vulnerable al menos haga trabajar al atacante. Como verás hay que tomar todas las precauciones que tengas a mano y no ponerlo fácil.

      En cuanto a lo de actualizar solo algunas variables, efectivamente nada que ver con OTA, es una cuestión de iniciativa al programar. Una opción en este caso sería actualizar las variables en un archivo y que el programa principal lea en dicho archivo cuando las circunstancias lo requieran.

      Espero haber dado respuesta aclaratoria a tu consulta.

      Saludos.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.