A from-scratch firmware for the LilyGO T-Watch 2020: an aviation chronograph face, "time circuits" 7-segment displays straight out of Back to the Future, weather and real-time bus departures, iPhone notifications, a network engineer's toolbox on the wrist (Wi-Fi analyzer, ping, traceroute, LAN scan, speed test…), a TV remote, a world clock and a sun & moon almanac. ESP32, LVGL 8.3, and a macOS simulator that builds exactly the same interface.
| Faces | Aviator chronograph (smooth hands, battery / minutes / steps sub-dials, date window, weather complication, notification badge) · Time circuits (three 7-segment rows: next alarm, present time, last sync) · Night (time only, dark red, dimmed backlight). Swipe sideways between faces. |
| Apps | Stopwatch, timer, alarms, world clock, weather + buses, sun & moon, 7-day step history, level, torch, network toolbox, TV remote, system monitor, settings — a launcher of 3 × 3 pages drawn like sub-dials, swiped sideways. |
| Network toolbox | Wi-Fi analyzer (2.4 GHz channel map, every network with channel, signal and security) · Link (SSID, BSSID, signal, address, gateway, DNS, MAC, public IP and provider) · Ping with an RTT history and loss · Traceroute (ICMP, TTL 1–24) · DNS lookup · Port check (16 common TCP services) · LAN scan (ARP sweep of the /24, private MACs spotted) · Speed test (10 s download) · Bluetooth scan (BLE devices and their makers) · Subnet calculator (offline). Wi-Fi only stays on while the toolbox is open. |
| World & sky | Twelve cities from Honolulu to Auckland with summer time, day / night and day shift, plus Zulu time · sunrise, sunset, civil twilight, day length and its daily change, the Moon's phase drawn to scale and the next full moon — computed offline. |
| Hardware tools | TV remote over the infrared LED (Samsung, LG, Sony, Philips; power, volume, channels, mute, input, and "all TVs") · Level from the accelerometer, zeroed on any surface · Torch in white or red at full backlight · System: battery voltage and current from the AXP202, its temperature, RAM and PSRAM, CPU, frame rate, uptime, reset cause. |
| Weather | Open-Meteo (no key needed): conditions, feels-like, the next 6 hours, vector pictograms; age of the reading or "offline". |
| STM buses | Real-time departures at up to 3 chosen stops, from the STM GTFS-realtime feed (~1 MB, parsed as a stream on the watch, pinned certificate). Optional API key. |
| iPhone | Notifications over ANCS (Bluetooth Low Energy): a card with vibration on arrival, a list, and "Clear" or "Decline" sent back to the iPhone. Pairing protected by a 6-digit passkey. |
| Time | On-demand NTP (Wi-Fi on for a few seconds, then off), PCF8563 RTC as a fallback. |
| Power | Light sleep with the screen off; wake on button, wrist raise or double tap (BMA423); alarms and timers fire on time while asleep. |
Fonts are OFL-licensed and keep their accented glyphs, so notifications in any Latin-1 language display correctly. Hardware missing from a board revision (GPS, SD card…) is detected at boot and never shows up in the interface.
LilyGO T-Watch 2020 V1 (ESP32-D0WDQ6, 16 MB flash, 4 MB PSRAM, 240 × 240 ST7789 display, AXP202, BMA423, PCF8563, FT6336, vibration motor, MAX98357A speaker, infrared). V2 and V3 environments exist in platformio.ini, but only the V1 has been tested.
brew install platformio sdl2 cmake
git clone --recurse-submodules https://github.com/storagebits/kronograf.git && cd kronograf
cp include/secrets.example.h include/secrets.h # optional: Wi-Fi, STM key, OTA passwordcmake -S sim -B sim/build -DCMAKE_BUILD_TYPE=Release && cmake --build sim/build -j8
sim/build/kronograf_sim # ×3 window, the mouse is your finger
KG_TIME=10:09:30 sim/build/kronograf_sim --shot shot.png # headless PNG screenshot
KG_NET_REAL=1 KG_APP=net:trace sim/build/kronograf_sim # the toolbox, for real, from the Mac
tools/make_screens.sh # screenshots + the overview sheet
tools/make_demo_gif.sh # re-records the demo GIF (ffmpeg)The simulated HAL is driven by environment variables: KG_TIME, KG_FACE, KG_APP (chrono, weather, notifs, net:ping…), KG_PAGE, KG_BATT, KG_STEPS, KG_REV, KG_WX_CODE, KG_WX_TEMP, KG_BUS_STOPS, KG_BLE, KG_NOTIF_CAT, KG_ALERT, KG_NET, KG_NET_REAL, KG_ACCEL… The toolbox's demo data only uses documentation addresses.
Prebuilt firmware: every release ships binaries built by GitHub Actions, with no credentials inside, and flashing instructions. Wi-Fi and the STM key are then set up on the watch.
From source:
# First of all: back up the original flash (16 MB, a few minutes)
tools/backup_flash.sh
pio run -e twatch-v1 -t upload --upload-port /dev/cu.usbserial-XXXXUse /dev/cu.usbserial-* rather than cu.SLAB_USBtoUART.
Workshop mode. With the watch on USB and Wi-Fi configured, it stays connected, accepts OTA updates and opens a TCP console. Unplugged, Wi-Fi is off.
pio run -e twatch-v1-ota -t upload # OTA to kronograf.local
tools/kg.py status # console: TCP when the watch is reachable, serial otherwiseinclude/secrets.h is ignored by git; include/secrets.example.h is the template. An empty value cleanly disables the matching feature.
| Key | Purpose |
|---|---|
KG_WIFI_SSID, KG_WIFI_PASS |
Default network (it can also be picked on the watch: Settings → Wi-Fi) |
KG_STM_API_KEY |
Key from the STM developer portal for bus departures — it can also be entered on the watch (Settings → STM buses) or over the console (tools/kg.py bus key <key>) |
KG_OTA_PASS |
Password for OTA updates (recommended) |
Bus stops are entered on the watch (Settings → STM buses). The weather location lives in src/data/kg_weather.h (downtown Montréal by default).
Pairing an iPhone. iOS does not list generic BLE accessories in Settings → Bluetooth. For the first connection, open an app such as nRF Connect, tap "Connect" next to "Kronograf", then type the code shown on the watch. From then on the iPhone reconnects by itself.
tools/kg.py <command>, over the serial port or over TCP in workshop mode:
status memory, FPS, battery, cause of the last reboot
screenshot f LVGL capture → PNG lcdtest R | G | B test pattern outside LVGL
battlog battery-life log power <min> current measured on USB, simulated battery
weather [refresh] bus [refresh | stops <codes> | key <key>] ble [on | off | forget]
net [<tool> [target]] network toolbox: ping, trace, dns, ports, lan, speed, public, ble
sys system information accel accelerometer in g
ir [<brand> <key>] sends a TV code app net:<tool> opens a network tool
sleep | wake | bl <n> | timeout <s> | face <n> | app <name> | timer <s> | coredump | reboot
src/ui/ LVGL interface, identical on watch and simulator (faces, apps, theme, fonts)
src/data/ plain C, host-tested: Open-Meteo, GTFS-RT (streamed protobuf), ANCS, network probes and
toolbox runners (BSD sockets), time zones, sun and moon, infrared codes
src/hal/hal.h the only hardware interface the UI sees
src/hal/device/ T-Watch: display (esp_lcd, DMA), AXP202, BMA423, network, BLE, safeguards
src/hal/sim/ simulated implementation
sim/ SDL2 harness, PNG screenshots, scripted demo recording
tests/ parser tests (cc -I src tests/test_data.c src/data/kg_*.c)
tools/ backup and block flashing, console, fonts, screenshots and overview, demo GIF, screen inlay
A few structural choices, detailed in DECISIONS.md:
- One UI, two targets. The interface only knows
hal.h; the simulator compiles the same files. Every screenshot in the repository comes from one or the other. - The ESP32 core voltage is never touched. Unlike some firmwares, Kronograf never adjusts the AXP202's DCDC3 and never cuts the ESP32's power rail.
- Network jobs. A background task chains NTP sync, weather and buses over a single connection, then turns Wi-Fi off.
- One toolbox, two targets. The network tools are plain C over BSD sockets, so the simulator runs the very same ping, traceroute or speed test from the Mac. On the watch, the toolbox holds a Wi-Fi lease that lapses by itself once the screen is off.
- Memory. The LVGL heap and the large TLS buffers live in PSRAM, keeping more than 30 % of internal RAM free with BLE, Wi-Fi and HTTPS all active.
- Safeguards. An RTC watchdog with "breadcrumbs" in RTC memory, coredumps in flash, and a safe mode: after three early crashes, BLE is no longer started and OTA stays reachable.
Hardware issues met along the way, and how they were worked around, are logged in BLOCKERS.md.
Development went in stages, each one flashed and verified on the watch.
- ✅ 0–3: hardware diagnostics, faces, sleep and wake-ups, launcher, stopwatch, timer, alarms, steps, settings, sounds, vibration.
- ✅ 4: weather and STM buses.
- ✅ 5: iPhone notifications.
- 🧪 6: infrared TV remote — built and simulated, to be checked on the watch.
- 🧪 8: network toolbox, world clock, sun & moon, level, torch, system monitor — built, simulated, network tools run for real from the simulator; on-watch check pending.
- 🔜 7: polish and a battery-life campaign.
Known limitations. This board has no 32 kHz crystal, so light sleep is impossible while BLE is active: battery life with notifications is being measured (D41). The development unit's micro-USB connector is unreliable (B2), hence OTA updates and block flashing.
Kronograf's code is released under the MIT licence. Dependencies and assets:
- LVGL 8.3 (MIT), NimBLE-Arduino (Apache 2.0), XPowersLib and SensorLib (MIT), stb_image_write (public domain / MIT).
- Barlow Condensed and Allerta Stencil fonts, under the SIL Open Font License (
assets/fonts/OFL-*.txt). - My-TTGO-Watch and the LilyGO library served as references for the pinout and init sequences, without reusing their code (see
docs/AUDIT.md). - Weather: Open-Meteo (CC BY 4.0). Buses: STM open data.

