Overview

The Visual Timer.

The Visual Timer is a configurable, glanceable desk timer built on a high-contrast e-ink display. Rather than displaying digits counting down to zero, it tells time through art: it ships with 101 distinct animated story-scene faces, each one a full-screen 1-bit scene whose subject embodies the countdown — a tower crane building a high-rise floor by floor, a piggy bank filling with coins, a kite climbing its string, a retro boss whose HP bar drains, a wrecking ball levelling a tower.

The subject itself reaches its climax exactly as the timer hits zero, with many faces playing a bespoke "time's-up" finale. Its stark 1-bit aesthetic keeps it distraction-free on the desk. The firmware runs four timer modes (Countdown, Pomodoro, Stopwatch, Interval/HIIT), an on-device settings menu, a soft power button, an aquarium screensaver, optional Wi-Fi clock and weather, and deep-sleep standby. It is at v1.0 — shipped.

At A Glance

Scaling Embedded Content

The Visual Timer runs on the Elecrow CrowPanel ESP32‑S3 2.13" E‑Paper HMI (122×250, SSD1680). The interesting problem was not the firmware but the scale: 101 distinct full-screen C++ animations. The project builds a host-side render mirror and a multi-agent design-and-audit pipeline so that each face can be drawn, rendered, critiqued, and integrated without flashing hardware on every iteration.

Visuals

101 Animated Stories

Each face is one orientation-aware function that lays out proportional to the panel size and draws a 1-bit scene — a crane raising a high-rise, a kite climbing, a submarine diving to a treasure chest, a lighthouse beam guiding a boat to dock. The edit→compile→flash loop is impractical at 101 faces, so the work is iterated on a desktop render mirror first, then ported.

Pipeline

1-Bit Render Mirror

A Python/Pillow shim re-implements the ESP32 GFX primitives so a face's draw code renders pixel-faithfully on the desktop at 250×122, 1-bit, anti-aliasing off. The pipeline writes C++, renders a verification strip, and tests the output against a strict rendering contract.

Quality Gate

Per-Face Audit Fleet

One reviewer agent per face grades its strip against the contract — recognizable, embodied, sensible, dynamic, legible — and returns a ranked fix list. The first pass scored 84/101; the 17 flagged faces were redesigned and re-audited until no face scored below 4/5.

Animated Aquarium Timer rendering
Deterministic Workflows

Deterministic Integration.

Giving the pipeline the ability to render and critique its own output is what makes the scale tractable. Each face is built by an autonomous agent that renders a 3-row verification strip — Row A is the scene swept across the countdown in landscape, Row B the same sweep in portrait, and Row C samples per-second motion to check the scene keeps moving — then critiques it against the contract and iterates.

Once a scene passes, deterministic, idempotent Python integration scripts splice the verified C++ into the firmware: adding the enum id, name, scene function, finale, and dispatcher case. They validate before applying and are safe to re-run on partial batches — the place where hand-splicing dozens of functions into a 38k-line codebase would otherwise introduce silent errors.

Visual Timer animated story-scene countdown face
Animation Model

One Face, 30 s to 90 min

A single progress value looks frozen on a 90-minute timer and starved on a one-minute one. Every face is instead driven by three independent inputs — a "two-clock" model — so the same scene reads well across the whole range.

frac

What finishes at zero

The remaining fraction (1→0) drives the one thing that must complete exactly at zero — the crane's roof flag, the kite reaching the top, the boat docking.

elapsedSec

What keeps moving

Free-running elapsed seconds drive continuous motion at a constant, watchable pace regardless of the total duration, so the scene never looks frozen between the things that matter.

totalSec

How much content

The total duration scales the amount of stuff in the scene. Splitting "what finishes" from "what moves" from "how much" is what lets one face work from 30 s to 90 min.

Finales

61 Bespoke Time's-Up Animations

"Time's up" deserves a payoff. 61 of the 101 faces have a custom finale, dispatched per-face from Finale.cpp via runFaceFinale(); faces without one fall back to a chosen generic alarm animation (Flash, Rings, Checker, Text, Wipe, or Border).

Scale

Code & Flash Footprint

~38k lines of firmware across the sketch, with the 101-face library and finales dominating. The app image is ≈ 2.08 MB — roughly 58% of one ~3.6 MB OTA slot — on a custom 8 MB dual-OTA partition that keeps rollback. Panel is 250×122, 1-bit.

Power Engineering

The E-Ink Lifecycle

Graceful Sleep

Deep-Sleep Park

When idle, paused, finished, or soft-powered-off (hold EXIT ~1.5 s), the ESP32-S3 enters true deep sleep and any of the 5 buttons wakes it via EXT1. The bistable e-paper holds its last frame with no power, so aggressive sleep is free. The board has no battery-sense calibration, so no measured standby current is claimed — soft-off standby is weeks, not months, because the USB-serial chip and regulator still draw; a hardware switch on the battery lead is the path to true zero-drain.

Hardware Quirks

Driver Profiling

Identical part numbers don't guarantee identical glass. On a replacement board the SSD1680 panel ghosted on the original GxEPD2 profile. The fix was a one-line controller-class swap to GxEPD2_213_GDEY0213B74 — same 250×122 SSD1680 controller, different waveform — a reminder to confirm driver waveforms against the actual glass batch.