devicescript-watchdog-estabilidad

Watchdog and Stability in DeviceScript

  • 5 min

A watchdog is a timer that resets the system if the software fails to renew it within the expected time.

What happened? Probably a voltage spike, a transient memory fault, or a bug in the code that left the processor stuck in an infinite loop.

It’s a last line of defense against a complete lockup, not a guarantee of availability. It does not detect by itself that an application is still running but has stopped performing its function.

How a Watchdog Works

The Watchdog is essentially an independent counter (a timer) inside the chip. It works like this:

  1. The counter starts at a high number (e.g., 5 seconds) and counts down.
  2. The main program is obliged to reset that counter (“feeding the dog” or kicking the dog) periodically.
  3. If the program hangs and forgets to reset the counter, it reaches ZERO.
  4. The watchdog barks: It triggers a Hardware Reset of the microcontroller.

It’s the electronic “dead man’s switch”.

The Watchdog in DeviceScript

If you come from low-level ESP32 C++ programming, you know you have to configure the WDT and feed it manually.

In DeviceScript, the Virtual Machine (VM) does this for us.

  • The DeviceScript Runtime automatically feeds the ESP32/Pico’s hardware watchdog as long as the system runs fine.
  • This protects us from internal firmware faults.

But, what happens if we write bad code?

The Danger of Blocking Loops

DeviceScript uses cooperative concurrency. If a function hogs the CPU, the runtime cannot advance other tasks or its internal maintenance.

Look at this killer code:

// ❌ DANGEROUS CODE
// This loop has no 'await delay()'.
// It hogs 100% of the CPU.
while(true) {
   let a = 1 + 1
}
Copied!

If you upload this to your board, you’ll see the device reset itself after a few seconds. Why?

  1. The infinite loop blocks the Event Loop.
  2. The DeviceScript Runtime cannot execute.
  3. No one feeds the hardware watchdog.
  4. The ESP32 detects the lockup and resets itself to recover.

Do not use an eventual reset as a normal control mechanism. Avoid blocking from the design phase and validate the watchdog behavior on the actual board.

Supervising Application Logic

The hardware watchdog protects us from total CPU lockups. But there are more subtle hangs: Logical Hangs.

Example: The device has plenty of CPU, the LED is blinking… but the WiFi connection has gone “dumb” and hasn’t sent data for 2 hours. The system is technically not hung, but functionally it’s a brick.

For this, we implement our own “Software Watchdog”.

Registering the Last Successful Operation

We can record when the main operation last completed successfully. Another task checks that timestamp and applies a subsystem-specific recovery, such as closing and recreating an MQTT client.

The following snippet shows the pattern. You must implement ejecutarOperacionPrincipal() and recuperarSubsistema() with the actual operations of your project.

import * as ds from "@devicescript/core"

// Variable to know when we last "did something useful"
let ultimoExito = ds.millis()

// Maximum time without success before recovery (e.g., 1 hour)
// For the example we'll use 60 seconds
const TIEMPO_MAXIMO_SIN_EXITO = 60 * 1000 

async function tareaPrincipal() {
    while(true) {
        // Execute the application's useful operation.
        // Update the timestamp only if it completes successfully.
        await ejecutarOperacionPrincipal()
        ultimoExito = ds.millis()

        await ds.delay(5000)
    }
}

async function vigilante() {
    console.log("Starting watch...")
    while(true) {
        const ahora = ds.millis()
        const tiempoSinExito = ahora - ultimoExito

        console.log(`Time without success: ${tiempoSinExito / 1000} sec`)

        if (tiempoSinExito > TIEMPO_MAXIMO_SIN_EXITO) {
            console.error("TIMEOUT! The system appears unstable.")
            console.warn("Attempting subsystem recovery...")

            await recuperarSubsistema()
            ultimoExito = ds.millis()
        }

        // Check every 10 seconds
        await ds.delay(10000)
    }
}

// Launch both processes in parallel
tareaPrincipal()
vigilante()
Copied!

What This Pattern Detects

If the main operation fails, tareaPrincipal stops updating ultimoExito.

  1. 10, 20, 50 seconds will pass…
  2. Upon reaching 60, the vigilante will detect that something is wrong.
  3. It will attempt to recover the affected subsystem.
  4. If the recovery works, the task will start updating the success timestamp again.

This supervisor shares the same event loop. It cannot execute if another function completely blocks the runtime; for that case, we need the hardware watchdog.

Power Supply and Brownout

Sometimes, the reset isn’t the code’s fault, but the electricity’s. If you power an ESP32 with a depleted battery or an inadequate power supply, the voltage can drop during a current spike.

This triggers the chip’s Brownout Detector, causing a reset to prevent the CPU from processing corrupted data.

If resets occur when activating WiFi or a load, check the power source, wiring, and decoupling. DeviceScript cannot compensate for a poor power supply.