Hola,
Os paso un ejemplo así como preliminares de qué hay que instalar en Arduino IDE:
Necesitamos 2 cosas instaladas en el IDE:
El primero sirve para poder decodificar el error y que te diga en qué linea ha sido. Lo guarda en la EEPROM.
El segundo sirve para poder enviar el programa (sketch) por la WiFi pero también puede enviar otros datos como imágenes, ficheros html, etc directamente a la wemos. Si queremos enviar ficheros, dentro de nuestra carpeta de proyecto donde tengamos el .ino deberemos tener una carpeta "data" donde meteremos diferentes cositas que necesitamos. por ejemplo yo la uso para poner el index.html, favicon.ico, etc etc aunque en el ejemplo que os paso no hago uso de estas funcionalidades.
Tened en cuenta que yo suelo usar la ESP8266 incluso como servidor FTP donde guardo ficheros de logs etc.
Una vez que lo tengamos instalados los dos plugins, en el menú del Arduino IDE debe aparecer en Herramientas así:
Observad que están los dos instalados, el ESP Exception Decoder y el ESP8266 Sketch Data Upload.
Para el .ino que os paso, hago uso de watchdog por lo que si no lo conocéis aquí paso un link que sin mirar mucho he encontrado en internet: watchdog
Lo primero que hacemos es pasar este .ino de ejemplo a la wemos por su cable usb (puerto serie) como cualquier otro.
La primera vez hemos de hacerlo así, después ya podemos usar OTA:
Perdón, antes de pasarlo hay que cambiar los datos de la WiFi así como la IP y puerta de enlace por alguna vuestra. para este ejemplo NO he usado DHCP, lo siento: no es mi cometido enseñar aqui sobre esp y wifi o redes.
Una vez pasado, en el monitor serie aparecerá que ya está funcionando:

ahora abriremos la página según la IP con /index al final.
En mi caso abro http://10.0.0.123/index.html
y aparecerá esto:
Habrá que "formatear" la eeprom por lo que daremos al link que dice "borrar o inicializar la eeprom para guardar crash".
Esto lo tendremos que hacer la primera vez y siempre que nos de un error es conveniente hacerlo para que pueda guardar otro nuevo dado que no suele tener espacio para más de 1 error (a veces me ha guardado más pero no os lo recomiendo).
Le daremos atrás al navegador para ir otra vez a index.html.
¿cómo subir un nuevo sketch?
Fácil, ahora cuando lo modifiquemos, por ejemplo he añadido esta línea en el index.html (linea 199 aprox):
clienteWeb.print(F("<H1>Test ESP SaveCrash y otros</H1>")); ///añadida!
Si queremos subir el sketch por la wifi daremos buscaremos el nombre que tiene. más o menos en la línea 64:
//activar el actualizador OTA (over the air)
ArduinoOTA.setHostname("testOTA"); //mi nombre que mostrará el IDE de Arduino
En el Arduino IDE aparece un nuevo puerto así:
Y ya tendremos siempre ese puerto (cuando la wemos esté activa, claro).
Una vez transmitido, operar como siempre.
¿como usar savecrash?
pues si accedemos a la web index.html (ver anteriores capturas), tenemos dos ejemplos.
Si fuerzo el de nullpointer, la Wemos se resetea y en la web index.html cambia el mensaje inicial que decía que era "external system" por este otro:
Y si damos sobre "ver reporte último crash":
Seleccionamos y cortamos el texto que aparece en el recuadro rojo.
Vamos al arduino IDE (si lo teníamos cerrado hemos de abrir el .INO y compilarlo primero).
Y damos a ESP Exception Decoder que está en el menú Herramientas:
y pegamos con Control+V el texto recortado de la página web:
Vemos el volcado de la pila de llamadas donde el primero es el Loop del fichero otaespsavecrash.ino en la línea 245 que en mi caso es esta:
*b=a; //esto nos dará una excepción al escribir en la zona de memoria reservada
El .INO de ejemplo que he realizado (lo he llamado otaespsavecrash.ino) se puede mejorar pero no tengo mucho tiempo:
/* OTA ESPSaveCrash test - Dan_X3
*
* Descripción de proyecto:
* Este proyecto es unicamente para poder realizar pruebas de:
* OTA (Over The Air) actualizar por la wifi el sketch
* ESPSaveCrash Para poder probar la funcionalidad
*
* Creado con Arduino 1.8.16
* Sobre placa Wemos D1 mini
* Compilar, en mi caso, como Generic ESP8266 Module,
* Upload speed: 115200
* Cpu freq.: 80mhz
* Cristal freq.: 25mhz
* Flash size: 4M (3M LittleFS) ///atencion!!! si no se utiliza 4M/3MLittleFS, no subirá por OTA !!!
* Flash mode: DOUT (compatible)
* Flash freq.: 40mhz
* Reset Method: "ck"
* Debug port: disable
* Debug level: none
* IwIP Variant: v2 lower memory
* VTables: flash
* Exceptions: disabled
* Build in led: 2
* Erase flash: only sketch
* Espressif FW: nonos sdk 2.2.1 (legacy)
* SSL support: all ssl chipers (most compatible)
*
* Revisiones:
* 26/09/2022 - Versión inicial
*
*/
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <EEPROM.h>
#include <ArduinoOTA.h> //para poder actualizar el .ino del wemos sin conectar al usb (por la wifi)
#include <EspSaveCrash.h> //en caso de excepcion que se pueda recuperar el error de la EEPROM y trazarlo
const char* ssid = "el nombre de mi wifi"; //SSID de la WiFi a conectarse
const char* passwordwifi = "la contraseña de mi wifi"; //Password de la wifi
int mip[4]={10,0,0,110}; //ip que tendrá la wemos (para las pruebas poner una ip fija mejor)
int mgateway[4]={10,0,0,1}; //puerta de enlace(IP del router)
int msubnet[4]={255,0,0,0}; //máscara de red de la wemos (normalmente 255.255.255.0, en mi caso uso clase A, 255.0.0.0)
int mdns[4]={10,0,0,1}; //ip del servidor dns (normalmente la IP del router)
const unsigned int serverport=80; //puerto TCP por donde se accederá por web a la placa Wemos
WiFiServer servidorWeb(serverport); //servidor web de WeMos por el puerto 80
String peticionWeb =""; //cadena donde se guarda la petición web (url invocada)
int wstate=0; //estado de la WiFi (connected/failed,..)
int wifidisconnected=0;
int counter=0; //aqui llevamos el contador de reintentos de conexión que hace SETUP()
int counterb=0; //idem al anterior (usados para imprimir puntitos por el serial para ver la conexión
//savecrash para poder monitotizar excepciones por la web
EspSaveCrash SaveCrash; //manejador del ESP SaveCrash donde se guardarán los errores
//establecer la configuración de pines, redes, etc...
void setup()
{
//Inicializar puerto serie
Serial.begin(115200);
Serial.println("\n\n"); //limpiar salida serie
//activar el actualizador OTA (over the air)
ArduinoOTA.setHostname("testOTA"); //mi nombre que mostrará el IDE de Arduino
//ArduinoOTA.setPassword((const char *)"w"); //si tiene password, no puede subir los ficheros al sketch (html, etc etc) por un bug, creo
ArduinoOTA.onStart([]() { Serial.println(F("Comienzo OTA.")); }); //cuando empieza transmisión OTA (hacer las tareas necesarias)
ArduinoOTA.onEnd([]() { Serial.println(F("\nFin OTA.")); }); //idem cuando finaliza OTA (por ejemplo formatear, guardar datos en EEPROM, etc)
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total)
{
Serial.print(F("Progreso OTA: "));
Serial.print((progress / (total / 100)));
Serial.println("\n");
});
ArduinoOTA.onError([](ota_error_t error) //gestionar errores OTA
{
Serial.print(F("Error "));
Serial.print(String(error));
Serial.print(":");
if (error == OTA_AUTH_ERROR) Serial.print(F("Error contraseña"));
else if (error == OTA_BEGIN_ERROR) Serial.print(F("Fallo al iniciar"));
else if (error == OTA_CONNECT_ERROR) Serial.print(F("Fallo al conectar"));
else if (error == OTA_RECEIVE_ERROR) Serial.print(F("Fallo al recibir"));
else if (error == OTA_END_ERROR) Serial.print(F("Fallo de finalización"));
});
ArduinoOTA.begin(); //activamos OTA: esto hace que desde Arduino IDE ya pueda aparecer el dispositivo
//inicializar el watchdog
ESP.wdtEnable(5000); // 5 segundos de watchdog. para Test es correcto, para productos finales hay que estudiar las necesidades
ESP.wdtFeed();
Serial.println(""); //limpiar la salida serie del wemos
Serial.flush();
Serial.println(F("Product:OTA ESP SaveCrash ejemplo v1.0"));
Serial.println(F("Release:26/09/2022 Dan_X3"));
Serial.flush();
Serial.println("Mensaje anterior reset:"+ESP.getResetInfo()); //porqué se reinicó antes !!! muy útil para saber si fue por una excepción o no
// Conectar WiFi
char HostName[32];
sprintf(HostName, "testOTAySC-%06X", ESP.getChipId()); //poner el nombre que tendrá en la red wifi (como ocurre con los PC's)
wifi_station_set_hostname(HostName);
WiFi.hostname(HostName);
ESP.wdtFeed(); //watchdog para evitar reset
// IP estatica si se necesita:
IPAddress ip(mip[0],mip[1],mip[2],mip[3]);
IPAddress gateway(mgateway[0],mgateway[1], mgateway[2], mgateway[3]);
IPAddress subnet(msubnet[0], msubnet[1], msubnet[2],msubnet[3]);
IPAddress dns(mdns[0],mdns[2],mdns[2],mdns[3]);
IPAddress dns2('8','8','8','8'); //se necesita 2 DNS para el NTP, si no, no funciona con con ip estática (bug NTP)
WiFi.config(ip, gateway, subnet,dns,dns2); //configurar la WiFi
WiFi.begin(ssid, passwordwifi); //conectar a la WiFi
counter=0;
//esperamos a la WiFi que conecte
wstate=WiFi.status();
while (wstate != WL_CONNECTED)
{
ESP.wdtFeed();
delay(250);
if (wstate==WL_NO_SHIELD) Serial.println("No tiene chip WiFi?");
if (wstate==WL_IDLE_STATUS) Serial.println("Chip WiFi no responde?");
if (wstate==WL_NO_SSID_AVAIL) Serial.println("WiFi no encuentra SSID?");
if (wstate==WL_SCAN_COMPLETED) Serial.println("WiFi busqueda completada?");
if (wstate==WL_CONNECT_FAILED) Serial.println("WiFi fallo de conexion?");
if (wstate==WL_CONNECTION_LOST) Serial.println("WiFi perdida conexion?");
//if (wstate==WL_DISCONNECTED) Serial.println("WiFi desconectada");
ArduinoOTA.handle();
counter++;
if (counter==20)
{
counter=0;
Serial.print(".");
counterb++;
if (counterb==8)
{
ESP.restart(); //demasiados reintentos, reiniciamos ESP !!!
}
}
wstate=WiFi.status();
ESP.wdtFeed(); //watchdog que no se olvide
}
//comenzar servidor http:
Serial.println(F("Activando servidor HTTP..."));
servidorWeb.begin();
//todo listo, finalizamos configuración
ESP.wdtFeed();
Serial.println(F("Configuración SETUP terminada. Servidores iniciados."));
} //fin setup()
/* loop principal */
void loop()
{
WiFiClient clienteWeb = servidorWeb.available(); //manejador del cliente web conectado
//dar tiempo a los manejadores:
yield();
ESP.wdtFeed(); //watchdog !!! siempre en el loop
yield();
ArduinoOTA.handle(); //por si hay que actualizar por OTA
yield();
wstate=WiFi.status(); //asegurar que hay WiFi conectada:
//comprobar que la WiFi siga conectada
if (wstate != WL_CONNECTED)
{
Serial.println(F("WiFi desconectada?"));
wifidisconnected++;
if (wifidisconnected>100) ESP.restart(); //demasiado tiempo sin WiFi, reiniciar el ESP
}
else
{
wifidisconnected=0;
}
if (clienteWeb)
{
peticionWeb = clienteWeb.readStringUntil('\r'); //guardamos la url invocada
Serial.println(peticionWeb);
if (peticionWeb.indexOf("index.htm")>0) //aparece index.html o index.htm?
{
clienteWeb.println(F("HTTP/1.1 200 OK"));
clienteWeb.println(F("Content-Type: text/html"));
clienteWeb.println(""); //no olvidar mandar una linea en blanco !
clienteWeb.print(F("<!DOCTYPE HTML><html><head><title>Test ESP SaveCrash y otros</title></head><body>"));
//clienteWeb.print(F("<script type=\"text/javascript\">setTimeout(function(){window.location=\"test.html\";}, 180000);</script>"));
clienteWeb.print(F("<H1>Test ESP SaveCrash y otros</H1>")); ///añadida!
clienteWeb.print(F("<br><hr><br>"));
clienteWeb.print(F("Memoria libre (bytes):<br>"));
clienteWeb.print(ESP.getFreeHeap());
clienteWeb.print(F("<br><hr><br>"));
clienteWeb.print("Mensaje reset anterior:<br>"+ESP.getResetInfo());
clienteWeb.print(F("<br><hr><br>"));
clienteWeb.print(F("<a href='reporte.txt'>ver reporte del último crash</a>"));
clienteWeb.print(F("<br><hr><br>"));
clienteWeb.print(F("<a href='borrarcrash.txt'>borrar o inicializar la eeprom para guardar crash</a>"));
clienteWeb.print(F("<br><hr><br>"));
clienteWeb.print(F("<a href='forzarcrash1.txt'>forzar un 'crash' de puntero nulo</a>"));
clienteWeb.print(F("<br><hr><br>"));
clienteWeb.print(F("<a href='forzarcrash2.txt'>forzar un 'crash' por watchdog</a>"));
clienteWeb.print(F("<br><hr><br>"));
clienteWeb.println(""); //no olvidar mandar una linea en blanco
}
if (peticionWeb.indexOf("reporte.txt")>0) //volcar el reporte
{
clienteWeb.println(F("HTTP/1.1 200 OK"));
clienteWeb.println(F("Content-Type: text/plain"));
clienteWeb.println(""); //no olvidar mandar una linea en blanco
clienteWeb.println(F("Crash Report (cortar y pegar en el IDE desde el 'begin' al 'end'):"));
clienteWeb.println(F("===========begin"));
clienteWeb.println(""); //no olvidar mandar una linea en blanco
SaveCrash.print(clienteWeb);
clienteWeb.println(""); //no olvidar mandar una linea en blanco
clienteWeb.println(F("===========end"));
clienteWeb.println(""); //no olvidar mandar una linea en blanco
}
if (peticionWeb.indexOf("borrarcrash.txt")>0) //petición de borrado del último savecrash?
{
clienteWeb.println(F("HTTP/1.1 200 OK"));
clienteWeb.println(F("Content-Type: text/plain"));
clienteWeb.println(""); //no olvidar mandar una linea en blanco
clienteWeb.println("Registro de excepciones borrado.");
SaveCrash.clear();
clienteWeb.println(""); //no olvidar mandar una linea en blanco
}
if (peticionWeb.indexOf("forzarcrash1.txt")>0) //petición de crash por división por cero?
{
clienteWeb.println(F("HTTP/1.1 200 OK"));
clienteWeb.println(F("Content-Type: text/plain"));
clienteWeb.println(""); //no olvidar mandar una linea en blanco
clienteWeb.println("intentando realizar excepción...");
clienteWeb.println(""); //no olvidar mandar una linea en blanco
clienteWeb.flush(); //forzamos envío y cierre cliente
int a=0;
int *b;
b=(int*)0;
*b=a; //esto nos dará una excepción al escribir en la zona de memoria reservada
}
if (peticionWeb.indexOf("forzarcrash2.txt")>0) //petición de crash por no llamar al watchdog?
{
clienteWeb.println(F("HTTP/1.1 200 OK"));
clienteWeb.println(F("Content-Type: text/plain"));
clienteWeb.println(""); //no olvidar mandar una linea en blanco
clienteWeb.println("intentando división por cero...");
clienteWeb.println(""); //no olvidar mandar una linea en blanco
clienteWeb.flush(); //forzamos envío y cierre cliente
while (1){} //morir aqui
}
clienteWeb.flush(); //forzamos envío y cierre cliente
Serial.println(F("Cliente WEB desconectado"));
}
} //loop fin
Saludos.