Hacking with Claude on a $27 Smart Watch
Hacking with Claude on a $27 Smart Watch
By Mike Kasberg
Husband, Father, and Staff Software Engineer at Strava
⌚ The Project
The PineTime is an affordable, $27 smart watch designed for those who love open-source firmware. For a couple of years, mine had been gathering dust in a drawer. However, after seeing a tweet from @steveruizok regarding the use of Claude to hack ESP32 devices, I felt a surge of inspiration to dust it off and test the limits of AI-driven development.
"if you know what a coding agent is then go buy this or something very similar..." — Steve Ruiz
The Goal: A Custom Aesthetic
I found the stock watch faces to be adequate boring. After seeing @levelsio create a custom Casio-style face for the Apple Watch, I wondered: Could Claude help me replicate that classic look on the PineTime?
🛠️ The Technical Approach
While I refer to the AI as "Claude," the bulk of the heavy lifting happened within OpenCode using various open-weight models.
The Workflow
The process followed a specific sequence of events:
Development Steps
- Clone the
InfiniSimrepository (including theInfiniTimesubmodule). - Establish a working build process via Claude.
- Feed the AI a reference photo of the Casio face.
- Use existing watch face code as a template.
- Orchestrate sub-agents for specific development tasks.
📉 Iteration & Refinement
The first attempt was... rough. The AI struggled with spatial awareness, leading to a layout where text elements overlapped and were largely illegible.
| Version | Approach | Result |
|---|---|---|
| v1.0 | Pure code generation | |
| v2.0 | Isolated, element-by-element feedback | |
| v3.0 | Hybrid (Static Image + Dynamic Code) |
To save tokens and time, I stopped relying on the AI for every pixel. Instead of having the AI code every static line and border, I decided to create a fullscreen background image. This meant the code only had to handle the dynamic elements (like the time).
⚡ Hardware Constraints
Once I moved from the simulator to the actual device, I hit the physical limits of the hardware.
- Transfer Speed: Sending the pixel image over Bluetooth took approximately 10 minutes.
- Performance: Swiping to refresh the screen takes about seconds.
- Memory: The watch cannot load the entire image into RAM; it must stream it directly from the file system.
// Conceptual logic for the watch face
void updateWatchFace() {
drawBackgroundImage(); // Streamed from filesystem
drawDynamicTime(); // Updated via code
drawBatteryLevel(); // Updated via code
}
🏁 Final Thoughts
I've uploaded the completed code to GitHub for anyone who wants to use it or build upon it. I also had Claude generate an AGENTS.md file to summarize the lessons learned, which should help others avoid the pitfalls I encountered.
Working in a "low-stakes" environment—away from my professional production code at Strava—was incredibly refreshing. It allowed me to iterate rapidly and experience the tangible reward of holding a physical device that I programmed.
Years ago, I lacked the motivation to dive into the InfiniTime codebase. Today, thanks to AI, I built a custom face in just a few hours.
☕ Support & Connect
I enjoy writing about Linux, open source, 3D printing, and programming. If you found this helpful:
- Follow me on X or LinkedIn.
- Share this post with your network.
- Buy me a coffee to support more content!
Related Reading:
- Vibe Coding From My Phone with OpenClaw
- Dotfiles Secrets in Chezmoi
- My $500 Developer Laptop