Building Photoface
My Pebble Time 2 preorder finally showed up, and after playing around with it for a bit I went looking for a watchface that would rotate through a handful of my own pictures the same way my Apple Watch does. Nothing on the app store did quite what I wanted. So I built it myself. Photoface is live on the app store right now, you can grab it here.
Getting the tooling working again
The first hurdle wasn't even code, it was getting a dev environment stood up at all. The Pebble SDK doesn't run natively on Windows anymore, so the recommended path these days is WSL. Simple enough in theory. In practice my machine didn't have virtualization turned on, so the install hung halfway through until I dug into the firmware settings and flipped that on, then rebooted. After that it was smooth, install Ubuntu, grab the community maintained pebble tool, pull down the SDK, and I finally had pebble build working again.
The rotation bug that made no sense at first
Once I had a basic watchface going, just two images swapping on a fifteen minute timer plus a tap to force it early, I ran into something odd. Tap your wrist right after the fifteen minute swap happened and the picture would flip right back to the one before it. Took me a minute to see why. The timer based swap and the tap based swap were sharing the same debounce logic but only one of the two paths was actually updating the timestamp that debounce checked against. So the tap thought way more time had passed than it had, and happily flipped the image again immediately. Once I moved that timestamp update into the one place that actually changes the picture, both paths stayed in sync and the flip back went away.
Wanting to swap photos without rebuilding the app
The whole reason I started this project was to avoid the "recompile every time I want a new picture" workflow, so the real goal was a settings page you open from your phone to upload photos directly. This is where things got genuinely interesting from an engineering standpoint.
Turns out the watch itself only gets 4KB of storage that actually survives a reboot. A single full color photo at the watch's resolution is over 45KB. Not even close. So there's no saving your uploaded photos onto the watch permanently, not at any reasonable quality anyway. I ended up designing around that instead of fighting it: the phone hangs onto your uploaded photos, and the watch just asks for whatever picture it needs, right when it needs it, over bluetooth. Everything lives in memory on the watch and gets refetched fresh each time the watchface comes back into focus. A bit unusual as a design but it fits the hardware honestly better than trying to force persistence that isn't really there.
A few bugs that took some real digging
A handful of these were the kind of thing where the symptom and the cause were pretty far apart.
Sending a photo from the phone to the watch chunks it up over a bunch of small messages. Early on the settings page would just crash the moment you picked a photo. Turned out I was reading the entire original file into memory as a giant base64 string before doing any resizing, and modern phone photos are a good ten to twenty megabytes. The fix was using a blob URL instead so the browser never has to hold the whole file as text in one place.
Then there was a proper watch crash, the kind where the fault log just shows the program counter as zero, which is about as unhelpful as it gets. Eventually traced it to memory. Building a new picture on the watch meant briefly holding the old picture, the new one being built, and the incoming data all in memory at once, and that pushed past what the watch had available. Freeing the old image before building the new one instead of after fixed it outright.
Last one worth mentioning, the watch has to ask the phone two separate small questions on every load, does this slot have a photo and here's a request for the current one. I was sending those as two separate messages back to back, and it turns out you can only have one message in flight at a time. The second one was silently getting dropped more often than I'd like to admit. Merging them into a single message solved it completely.
The part I'm actually proud of
Since the watch has no persistent way to know which of the ten photo slots have anything uploaded, I had it ask the phone for a quick yes or no on each slot every time it loads. That way the rotation just skips straight past any slot you haven't filled in yet instead of sitting there with a loading message forever. Paired with a two stage loading screen, first just "loading", then a nudge to check your bluetooth or upload something if it's been a few seconds, the whole thing feels a lot more polished than the plumbing behind it probably deserves.
Wrapping it up
You can find Photoface here
If you end up trying it out, let me know what you think.