Physical Notifications for Claude Code with ESPTimeCast
I use Claude Code all day. It runs refactors, writes tests, scaffolds entire features, sometimes for minutes at a time. The problem: I’d switch to something else while waiting, then forget to check back. Notifications on my laptop are easy to miss when I’m deep in another task.
So I built a physical notification. A small LED matrix clock on my desk that scrolls a message whenever Claude finishes a task. No app switching, no checking the terminal. Just a glowing reminder sitting in my peripheral vision.
The Hardware
ESPTimeCast is an open-source project that turns an ESP32 or ESP8266 and a MAX7219 LED matrix into a WiFi-connected clock with weather display. It supports the full ESP32 family including the S2, C3, and S3 variants. It has a clean web UI, NTP time sync, and most importantly for this project, an HTTP API.
My setup:
- AZ-Delivery ESP32 Dev Board
- 8x32 MAX7219 LED matrix module
- 3D-printed case from Printables (with a single-layer diffuser for the best results)
- Powered via USB
Flashing takes under a minute with their web installer. No Arduino IDE needed. After connecting it to WiFi, the clock was running within five minutes.
A tip on the case: I printed the diffuser layer at just a single layer thickness. This gave the cleanest look, enough to soften the individual LEDs without dimming the display too much. Multiple layers made it too opaque.
The API
ESPTimeCast exposes an /action endpoint that accepts both GET and POST requests. You can send messages, control brightness, set timers, and more, all over your local network.
Sending a message is as simple as:
curl -X POST -d "message=HELLO WORLD" "http://esptimecast.local/action"The display only supports uppercase letters, numbers, and a handful of symbols. It scrolls the text and then returns to showing the time. You can control how long the message stays with seconds or how many times it scrolls with scrolls (which defaults to 0 for infinite):
# Show for 10 secondscurl -X POST -d "message=DONE!&seconds=10" "http://esptimecast.local/action"
# Scroll 3 times then disappearcurl -X POST -d "message=TASK FINISHED&scrolls=3" "http://esptimecast.local/action"Short messages (8 characters or fewer) display static and centered instead of scrolling. That works nicely for quick status indicators.
Claude Code Hooks
Claude Code has a hooks system that lets you run shell commands at specific points in its lifecycle. The event I needed: Stop, which fires whenever Claude finishes responding.
Hooks are configured in ~/.claude/settings.json:
{ "hooks": { "Stop": [ { "matcher": "", "hooks": [ { "type": "command", "command": "curl -s -X POST -d 'message=CLAUDE DONE&scrolls=3' 'http://esptimecast.local/action'" } ] } ] }}That’s the entire setup. Every time Claude finishes a task, it fires a POST request to the clock. The display scrolls “CLAUDE DONE” three times and then goes back to showing the time.
The Flow
Here’s what happens when I give Claude a task:
┌──────────────┐ │ You: "refactor│ │ the auth │ │ module" │ └──────┬───────┘ │ ▼ ┌──────────────┐ │ Claude Code │ │ working... │ │ (you go do │ │ something │ │ else) │ └──────┬───────┘ │ Stop event fires ▼ ┌──────────────┐ HTTP POST ┌──────────────┐ │ Stop hook │ ──────────────► │ ESPTimeCast │ │ runs curl │ │ shows message │ └──────────────┘ └──────┬───────┘ │ after 3 scrolls ▼ ┌──────────────┐ │ Back to clock │ └──────────────┘Going Further
Once you have a programmable display on your desk that responds to HTTP, it’s hard to stop at just one notification. A few ideas:
Different messages for different outcomes. The Stop hook receives JSON on stdin with context about what just happened. You could parse it and send different messages based on whether Claude edited files, ran tests, or hit an error.
{ "hooks": { "Stop": [ { "matcher": "", "hooks": [ { "type": "command", "command": "jq -r '\"CLAUDE: \" + (.stop_reason // \"DONE\") | ascii_upcase' | xargs -I{} curl -s -X POST -d 'message={}&scrolls=3' 'http://esptimecast.local/action'" } ] } ] }}Protected messages for failures. ESPTimeCast supports an interrupt=0 parameter that prevents other messages from overwriting the current one. By default, new messages can interrupt (interrupt=1). Setting it to 0 protects the message, and any other requests get a 409 Conflict until it expires:
curl -X POST -d "message=BUILD FAILED&seconds=30&interrupt=0" "http://esptimecast.local/action"Combine with Home Assistant. ESPTimeCast has native Home Assistant support. You could trigger automations alongside the display notification, like flashing a smart light or sending a push notification to your phone.
What I Like About This Setup
It’s simple. One curl command in a hook config. No daemon, no polling, no websockets. The clock sits on my desk doing its normal clock thing, and it becomes a notification display exactly when I need it.
The physical nature of it matters too. A scrolling LED matrix in your peripheral vision registers differently than yet another software notification competing for screen space. It’s the kind of ambient awareness that screens can’t replicate.
Parts List
| Part | Notes |
|---|---|
| AZ-Delivery ESP32 Dev Board | Any ESPTimeCast-compatible board works |
| MAX7219 8x32 LED matrix | The standard 4-module chain |
| USB cable + power adapter | For power |
| 3D printed case (optional) | Printables or Cults3D (paid STL files) |
Total cost: around €10-15 if you already have a USB power source.
Links
- ESPTimeCast on GitHub
- ESPTimeCast Web Installer
- Claude Code Hooks Documentation
- Building Skills for Claude Code (another way to customize Claude Code)