Thanks to the daylight saving time zealots*, it’s dark when I get up. There’s been an unwelcome chill in the air as I unlock the Voi bike and zoom off to work. Rainier times beckon. Maybe I should start taking the train? I’m in no position to make this decision so early in the morning, squinting at the weather map, before I’ve had the first of several coffees.

I’ve always loved Richard’s bicycle barometer - a dial with a needle that swings between bike and train. Like Richard himself, it’s inspiring in so many ways. I wonder if he’s still using it? I wanted a bit more context than a needle though - I’m not ready to give up my whole decision-making to the bot. Yet.

Like Richard, I didn’t want a screen - his is a repurposed flea market clockface. We have enough glowing rectangles in the house. And I think a tablet on the wall would give airport departures board - all shouty and green and red. Neither did I want a circuit board with a ribbon cable and a USB lead trailing down to a socket.

I wanted something I could glance at on the way to the kettle, but that didn’t take up too much of my headspace. Then a colleague showed me their Xteink. It’s a cheap ebook reader, smaller than a paperback, with an e-ink screen, wifi, a battery that lasts for weeks and - this is the important bit - an ESP32 inside that I could reflash with my own firmware.

I bought one on the spot.

This was my plan. The device itself should do as little as possible. No weather API calls, no rules, no fonts, no layout. All of that happens somewhere else, once a day, and the device just wakes up, downloads a picture, paints it and goes back to sleep. It’s a bit more complicated than that - it has to connect to wifi and do an ntp call to get the correct time, but not much.

Pictures of clouds in the cloud somewhere. No VPS, obviously - regular readers know how I feel about that. And, reader, I remain too mean to pay for anything.

So, GitHub Actions, because that’s my computer now. Twice a day a workflow fetches the forecast from Open-Meteo, decides whether I’m cycling, draws the dashboard, and publishes it to GitHub Pages as a single file. The device fetches that file at four in the morning (a quick update before I get up) and at six in the evening (showing the next day’s possibilities). A bit of Elm to do the rendering, a bit of Ruby to do the fetching. Bob’s your uncle.

The dashboard for a wet Thursday: 22 degrees, rain, a dense scatter of rain drops across the commuting hours, and the verdict TRAIN both in and back, with departure times from Cambridge South

The picture, on a bad day. Train in, train back, and a dry remark at the bottom.

And it worked, kind of. I had three issues that turned this from a thirty minute experiment to a couple of hours of frustration, relief, frustration and then relief again.

First, there’s a pin on the device that acts as a battery cut-off, the ominous-sounding GPIO13. Before it goes to sleep, the community firmware pulls this low, the device looks like it’s asleep - it always looks like it’s asleep - but it’s in fact shut off and my timer never fires to wake it up again. It took me a while to figure this out, with the help of the bot. You have to hold that pin high and tell the chip to keep holding through the deep sleep.

The second thing was GitHub Actions’ cron. It’s best-effort, and by “best effort” they mean “best avoided”. My workflow was scheduled for a quarter past, the device woke on the hour, and the picture simply wasn’t there yet. So I schedule it early, on odd minutes - like a cicada waking up on prime years - and take four shots at it. Well done GitHub - a scheduler that fails to schedule, very Microsoft.

One thing I added in all this messing around was a diagnostics screen. If I hold down one of the side buttons on the device when powering on, then instead of the weather you get a bunch of stats: boot count, why it woke, battery, wifi signal, what time it thinks it is, what the server said. I had to add fonts, and rendering and all kinds of stuff to my firmware to make this happen but I think it’s worth it for the bug-hunting. I should have done it earlier.

The firmware has the network name and password baked in at compile time, and I wasn’t keen on that. I think my TV and printer are probably bigger threats but I didn’t make those. There is probably a way to better secure it but, as a simple solution, the device got its own guest network with nothing else on it. The hardest thing about this was digging the router out of the cupboard (where it had over time been buried under strata of stuff) to get the admin password. I took the opportunity to give it a static IP address at the same time so it didn’t have to do DHCP negotiations.

It’s all a bit Heath Robinson - there’s C and Elm, Ruby, Python and headless Chromium, GitHub Actions and network retries up the wazoo. But it works.

And there is now a finished-looking paperback-sized thing on my wall, no wires, that tells me whether to cycle. It has a little battery icon in the corner - another late addition to the firmware - and should go months between charges.

The Xteink e-reader in its cream case, showing SATURDAY, 14 degrees, drizzle, and NO COMMUTE in large serif capitals

The real thing. Saturday, drizzle, and the bike having a lie-in.

It shows the day’s forecast as a scatter of rain drops leaning in the direction of the wind. It refuses to have an opinion on Fridays, which is a rule I’m thinking of applying more widely. And every day it has one random silly line of its own: “Even the ducks have cancelled.” “Fen sun. Rarer than a free punt.” “Scrape the saddle.” I spent as much time choosing fonts as I did anything else. Fraunces, if you’re curious.

The dashboard for a partly cloudy Monday in Fraunces: CYCLE in, CYCLE with waterproofs back, showers possible 45%

Fraunces doing the heavy lifting on a better day.

The proof will be in the next week’s commute - I’ll let you know how it goes.

* I quite like daylight saving time and it’s only in place for another week or so. It’s the zealots that bug me.