A Casio-style hourly chime for the Pebble Time 2, written completely in Zig.
Like the hourly time signal on a Casio F-91W, Beep plays a short, high beep at the top of every hour — through the Pebble Time 2's speaker. The chime keeps sounding in the background after you close the app, thanks to a Pebble background worker. The beep is a ~2 kHz square wave (MIDI note 96), close to the F-91W's piezo pitch.
- Hourly chime at :00, played by a background worker so it works even when the app isn't open.
- Four styles (cycle with UP):
- Single – one short beep
- Double – the classic Casio "beep-beep" (default)
- Triple – three quick beeps
- Strike hour – beeps once per hour, like a striking clock (3 pm → 3 beeps, 12-hour)
- Quiet hours (toggle with DOWN): silence the chime overnight (22:00–06:59). On by default.
- On/off with a confirmation beep, and a Test action to preview the current style.
- Settings persist across reboots and are shared between the app and its worker.
| Button | Action |
|---|---|
| SELECT | Turn the chime on / off |
| UP | Cycle the chime style |
| DOWN | Toggle overnight quiet hours |
| hold SELECT | Test the current chime now |
| BACK | Leave the app (the worker keeps chiming) |
There is no C source and no @cImport anywhere in this project. Every Pebble
API the app uses is declared by hand as a plain extern with the C ABI in
src/pebble.zig; the real implementations come from the SDK's
libpebble.a at link time. The struct layouts, enum values and signatures were
taken from the Pebble Time 2 SDK headers so the hand-rolled ABI matches the
firmware exactly.
| File | Role |
|---|---|
src/pebble.zig |
Pure-Zig bindings to the Pebble C API (incl. the Speaker API) + the shared chime |
src/main.zig |
Foreground configuration app (UI, buttons, settings, launches the worker) |
worker_src/worker.zig |
Background worker that plays the chime at the top of each hour |
wscript |
Compiles the Zig to ARM objects and hands them to the Pebble SDK's linker |
package.json |
Pebble app manifest |
The chime uses the speaker via speaker_play_notes() — interestingly, the
speaker_* (and vibes_*) functions are omitted from pebble_worker.h, but the
symbols live in the shared libpebble.a and the syscalls work from the background
worker, so the hand-written extern declarations let both binaries use them.
The new (Core Devices, 2025) Pebble Time 2's hardware codename is obelix.
In the SDK, every obelix hardware revision builds as the emery platform
(200×228, 64-colour, rectangular, with a speaker), so package.json targets
emery. Emery apps are compiled for Cortex-M3, Thumb, soft-float,
position-independent, which is why the Zig object is built with:
-target thumb-freestanding-eabi -mcpu cortex_m3 -fPIC -fno-unwind-tables -O ReleaseSmall
You need the Zig compiler (built with 0.16.0) and the Core Devices Pebble SDK.
# One-time SDK setup (see the link above for platform deps):
uv tool install pebble-tool
pebble sdk install latest
# Tell the build where Zig is (defaults to /home/kanna/.local/zig/zig):
export ZIG=/path/to/zig
# Build the .pbw:
pebble buildThe bundle is written to build/Beep.pbw.
In the emulator:
pebble install --emulator emery
pebble logs --emulator emery # shows the "chime ... started=1" line each time it beepsOn a real Pebble Time 2 (phone app paired, developer connection enabled):
pebble install --phone <phone-ip>- Verified in the
emeryemulator: the app and worker install and run, the UI and buttons work, andspeaker_play_notes()returnstrue(logged asstarted=1) both from the foreground and from the background worker at a minute tick — so the worker really can drive the speaker. QEMU has no audible output, so the actual tone can only be heard on hardware. - Two linker warnings — "missing .note.GNU-stack section" (from the toolchain's
own
crtn.o) and "LOAD segment with RWX permissions" — are normal for every Pebble app and are harmless; Pebble apps are loaded into a single RWX RAM region. - Persistent storage is shared between the app and its worker (same app UUID), so changing a setting in the app is picked up by the worker on its next tick.
