Keeping a Wearable Alive: Watchdogs and Boot-Spin Guards
A pet wearable has one job above all others: be awake when something happens. A dog doesn't schedule its zoomies, and a collar that has quietly wedged itself into an infinite loop is, for all practical purposes, dead, it just hasn't fallen off the dog yet. The cruel part is that these hangs rarely look like crashes. There's no smoke, no error code; the device simply stops making progress while the battery keeps draining. At Hoomanely, we treat liveness, the guarantee that firmware keeps making forward progress, as a first-class feature, not a happy accident. This post is about the small, unglamorous habits that keep an always-on collar responsive: bounding every wait, yielding inside every tight loop, and respecting the watchdog that's quietly judging us.
The concept: liveness is a feature you build
Most embedded bugs that take a device offline aren't logic errors, they're progress errors. The code is technically correct but waits forever for something that will never arrive, or hogs the CPU so completely that nothing else runs.
Three failure shapes account for almost all of them. The unbounded wait: a loop that retries an operation with no exit, spinning until the end of time on a peripheral that isn't there. The CPU monopoly: a tight loop that does real work but never lets lower-priority tasks, including the system's idle housekeeping, get a turn. And the panic spiral: code that responds to a transient failure by rebooting, only to hit the same failure again and reboot forever. Designing against all three is what "liveness engineering" actually means in firmware.

Why it matters
For a consumer gadget, a hang means an annoyed user power-cycles it. For a continuous health monitor, a hang means a gap in the medical record, a missing afternoon of activity, an undetected event, a trend line with a hole in it. Worse, the device gives no signal that it failed; it looks online while silently recording nothing.
The watchdog is the safety net of last resort. A task watchdog is a timer that expects to be "fed" regularly by the system's lowest-priority idle task; if the idle task never gets to run for several seconds, the watchdog assumes the system is stuck and forces a reset. That's a blunt instrument, a reset is data loss, so the real goal is never to need it. The watchdog should be the thing that catches a bug we missed, not a routine part of normal operation.
Four habits that keep us alive
1. Never reboot your way out of a transient failure. Our hardest-won lesson came from the wireless advertising path. An early version called a full system restart whenever it failed to start advertising. Under heavy connect/disconnect churn from a phone, that failure was transient, the radio controller's memory pool was briefly full, but the restart wiped all in-RAM state and hit the same condition again, producing an endless reboot loop. The fix was to bound the retries and fail gracefully instead of fatally:
for (int i = 0; i < 20; i++)
{
rc = ble_gap_adv_set_fields(&fields);
if (rc == 0)
break;
ESP_LOGW(TAG, "Error setting advertisement fields: %d, attempt %d/20", rc, i + 1);
vTaskDelay(pdMS_TO_TICKS(500));
}
if (rc != 0)
{
ESP_LOGE(TAG, "Could not set advertisement fields (rc=%d) — staying silent until next disconnect cycle", rc);
return; // Do NOT esp_restart(); next disconnect will retry advertising.
}Notice the vTaskDelay between attempts, the loop sleeps, giving the radio time to recover and other tasks time to run, rather than busy-spinning. And on persistent failure it simply logs and returns; the next natural disconnect event will try again. A bounded retry with a clean give-up beats an aggressive reset every time.

2. Give every wait an exit. The same discipline applies to hardware that may simply be absent. When the device tries to mount its external storage, it doesn't loop forever hoping the chip appears, it tries, falls back to an internal partition, and if even that fails, it gives up cleanly with a loud log line:
// Step 2: if the external mount didn't take, fall back to the internal
// "storage" partition declared in partitions.csv.
if (active_label == NULL)
{
if (mount_fatfs(INTERNAL_FALLBACK_PARTITION_LABEL))
{
active_label = INTERNAL_FALLBACK_PARTITION_LABEL;
active_partition_label = INTERNAL_FALLBACK_PARTITION_LABEL;
ESP_LOGI(TAG, "Mounted internal fallback partition at %s", MOUNT_POINT);
}
else
{
ESP_LOGE(TAG, "All flash mount attempts failed.");
flash_mounted = false;
return;
}
}The device boots and runs in a degraded-but-alive state instead of hanging at startup. A monitor that comes up with reduced storage still captures data; one that spins forever at boot captures nothing. Boot must always terminate, into success or into a known, logged fallback.
3. Yield inside every tight loop. Bounded waits keep us from hanging forever; cooperative yielding keeps us from starving everything else right now. Our flash-spill task can, during an audio burst, receive frames faster than it writes them, and a naive while loop draining that queue will peg a CPU core at 100% for seconds, which is exactly what trips the watchdog. So the loop yields on a fixed cadence:
spill_one_frame(buf, got);
frames_written++;
if ((frames_written % YIELD_EVERY_N) == 0) {
// 1-tick delay = portTICK_PERIOD_MS (default 10 ms) on
// this build. Long enough for IDLE to run, short enough
// that we still drain the ring well above the producer
// rate (8 frames per 10 ms = 800 fps headroom).
vTaskDelay(1);
}The trick is calibrating the yield so the watchdog is satisfied while throughput stays far above the data rate, here, draining headroom of roughly 800 frames per second against a producer doing about 95.

4. Feed the watchdog even when you're busy. Sometimes a loop genuinely needs to do heavy, sustained math. The pattern is the same, a single deliberate yield per iteration hands the scheduler a window to run the idle task and reset the watchdog timer:
void intensiveCPUTask()
{
while (1)
{
for (int i = 0; i < 1000000; i++)
{
volatile int result = sqrt(i) * log(i);
}
vTaskDelay(1); // Small delay to prevent watchdog timer from resetting
}
}One line, vTaskDelay(1), is the difference between a busy task and a hung device. The rule we apply everywhere: if a loop can run longer than the watchdog timeout, it must contain a yield.
Why it matters at Hoomanely
The word "continuous" is a promise, and liveness is how we keep it. An AI engine can only find the early warning signs of pain, anxiety, or illness if the edge device beneath it is reliably awake and recording. Every bounded retry, every yield, every refusal to reboot into a loop is a small guarantee that the data stream never silently goes dark.

Key takeaways
Liveness is a designed property, not a default, since most "dead" devices are simply stuck making no progress while looking online. Bound every wait, a retry loop needs a maximum count and a clean give-up path. Never reboot out of a transient error, since a reset that wipes state and re-hits the same failure becomes an infinite reboot loop. Yield inside tight loops, so if a loop can outlast the watchdog timeout, it must contain a deliberate yield. And treat a watchdog reset as a bug, not a feature, it's the last line of defense, never part of the normal flow.
Costume preparation becomes easier when measurements, fabric and accessories are reviewed together. The size chart should be checked against current body measurements. For event preparation, Space Ereshkigal cosplay costume(スペースエレシュキガル コスプレ衣装) provides the relevant costume reference. This makes it easier to separate the main outfit from optional pieces. Comfort, movement and fabric care are as important as the visual design. Delicate decorations may need protective wrapping between uses.
Al revisar guía para elegir una camiseta de selección, conviene empezar por los colores, el escudo y los detalles del diseño. Como comprobación final, conviene asegurarse de que las imágenes muestren frontal, espalda, escudo y cuello.