Un watchdog es un temporizador que reinicia el sistema si el software deja de renovarlo dentro del plazo previsto.
¿Qué ha pasado? Probablemente un pico de tensión, un fallo de memoria puntual o un bug en el código que ha dejado al procesador en un bucle infinito.
Es una última línea de defensa frente a un bloqueo total, no una garantía de disponibilidad. No detecta por sí solo que una aplicación siga ejecutándose pero haya dejado de cumplir su función.
Cómo funciona un watchdog
El Watchdog no es más que un contador (un temporizador) independiente dentro del chip. Funciona así:
- El contador empieza en un número alto (ej: 5 segundos) y cuenta hacia atrás.
- El programa principal tiene la obligación de reiniciar ese contador (“dar de comer al perro” o kicking the dog) periódicamente.
- Si el programa se bloquea y se olvida de reiniciar el contador, este llega a CERO.
- El Watchdog ladra: Provoca un Reset por Hardware del microcontrolador.
Es el “interruptor de hombre muerto” de la electrónica.
El Watchdog en DeviceScript
Si venís de programar ESP32 en C++ a bajo nivel, sabréis que hay que configurar el WDT y alimentarlo manualmente.
En DeviceScript, la Máquina Virtual (VM) hace esto por nosotros.
- El Runtime de DeviceScript alimenta al Watchdog de hardware del ESP32/Pico automáticamente mientras el sistema funcione bien.
- Esto nos protege de fallos internos del firmware.
Pero, ¿qué pasa si escribimos mal nuestro código?
El peligro de los bucles bloqueantes
DeviceScript usa concurrencia cooperativa. Si una función acapara la CPU, el runtime no puede avanzar con otras tareas ni con su mantenimiento interno.
Mirad este código asesino:
// ❌ CÓDIGO PELIGROSO
// Este bucle no tiene 'await delay()'.
// Acapara el 100% de la CPU.
while(true) {
let a = 1 + 1
}Si subís esto a vuestra placa, veréis que a los pocos segundos el dispositivo se reinicia solo. ¿Por qué?
- El bucle infinito bloquea el Event Loop.
- El Runtime de DeviceScript no puede ejecutarse.
- Nadie alimenta al Watchdog de hardware.
- El ESP32 detecta el bloqueo y se reinicia para salvarse.
No uses un reinicio eventual como mecanismo normal de control. Evita el bloqueo desde el diseño y valida el comportamiento del watchdog en la placa real.
Supervisar la lógica de la aplicación
El Watchdog de hardware nos protege de bloqueos totales de CPU. Pero hay otro tipo de cuelgues más sutiles: los Cuelgues Lógicos.
Ejemplo: El dispositivo tiene CPU de sobra, el LED parpadea… pero la conexión WiFi se ha quedado “tonta” y lleva 2 horas sin enviar datos. El sistema técnicamente no está colgado, pero funcionalmente es un ladrillo.
Para esto, implementamos nuestro propio “Perro Guardián de Software”.
Registrar el último funcionamiento correcto
Podemos registrar cuándo terminó correctamente la operación principal. Otra tarea comprueba ese instante y aplica una recuperación específica del subsistema, como cerrar y volver a crear un cliente MQTT.
El siguiente fragmento muestra el patrón. Debes implementar ejecutarOperacionPrincipal() y recuperarSubsistema() con las operaciones reales de tu proyecto.
import * as ds from "@devicescript/core"
// Variable para saber cuándo fue la última vez que "hicimos algo útil"
let ultimoExito = ds.millis()
// Tiempo máximo sin éxito antes de reiniciar (ej: 1 hora)
// Para el ejemplo usaremos 60 segundos
const TIEMPO_MAXIMO_SIN_EXITO = 60 * 1000
async function tareaPrincipal() {
while(true) {
// Ejecutamos la operación útil de la aplicación.
// Actualizamos el instante únicamente si termina correctamente.
await ejecutarOperacionPrincipal()
ultimoExito = ds.millis()
await ds.delay(5000)
}
}
async function vigilante() {
console.log("Iniciando vigilancia...")
while(true) {
const ahora = ds.millis()
const tiempoSinExito = ahora - ultimoExito
console.log(`Tiempo sin éxito: ${tiempoSinExito / 1000} seg`)
if (tiempoSinExito > TIEMPO_MAXIMO_SIN_EXITO) {
console.error("¡TIEMPO EXCEDIDO! El sistema parece inestable.")
console.warn("Intentando recuperar el subsistema...")
await recuperarSubsistema()
ultimoExito = ds.millis()
}
// Comprobamos cada 10 segundos
await ds.delay(10000)
}
}
// Lanzamos ambos procesos en paralelo
tareaPrincipal()
vigilante()Qué detecta este patrón
Si la operación principal falla, tareaPrincipal deja de actualizar ultimoExito.
- Pasarán 10, 20, 50 segundos…
- Al llegar a 60, el
vigilantedetectará que algo va mal. - Intentará recuperar el subsistema afectado.
- Si la recuperación funciona, la tarea volverá a actualizar el instante de éxito.
Este supervisor comparte el mismo event loop. No puede ejecutarse si otra función bloquea por completo el runtime; para ese caso necesitamos el watchdog de hardware.
Alimentación y brownout
A veces, el reinicio no es culpa del código, sino de la electricidad. Si alimentamos un ESP32 con una batería agotada o una fuente inadecuada, el voltaje puede caer durante un pico de consumo.
Esto activa el Brownout Detector del chip, que provoca un reinicio para evitar que la CPU procese datos corruptos.
Si aparecen reinicios al activar el WiFi o una carga, revisa la fuente, el cableado y el desacoplo. DeviceScript no puede corregir una alimentación deficiente.