There are two habits I want to keep: publishing posts here and uploading photos to Unsplash. Reminders, repeating tasks in Things, and widgets could all help of course. But after recently seeing a variety of hardware projects on social media, I decided the reminder had to be a physical object on my desk with an interface I could design.
Selecting hardware
I wanted a battery-powered device to avoid adding a new cable to my desk, and four colors so a nudge could look different from a warning. The official TRMNL device is too large for the spot I had in mind, and an ESP32 board with a panel is too small and needs a cable. The ZecTrix Note 4C devkit sits right in between: a 4.2-inch panel, a battery, and the TRMNL firmware, which let me drive the device from a web server.
How it works
At 7:00 AM, 12:00 PM, 5:00 PM, and 9:00 PM, the device wakes up, asks my server for a PNG, receives it, refreshes the screen (slowly), and goes back to sleep. The server is a small Next.js app on Vercel. It gathers five numbers: the dates of my last post and photo upload, this week’s visitors, my GitHub commits, and my Claude usage. It then lays them out as a web page and flattens that page into a 400 by 300 pixel image. Claude usage is the one number the server does not fetch. A small macOS widget I previously built posts it.
Below is one round trip between the device and the server:
Designing for the physical world
The dashboard has to feel right when I look at it. If it sits on my desk, it must match my aesthetic. The grid and the typography required extensive experimentation.
On track
The device lives in my peripheral vision, so it must never feel interruptive or aggressive, a rule I helped write two decades ago in a paper on evaluating peripheral displays.1
| Signal | Black | Yellow | Red |
|---|---|---|---|
| Days since last post or upload | 0 to 10 | 11 to 14 | 15 and up |
| Claude weekly usage | Under 70% | 70 to 90% | Over 90% |
| Header | Normal | Problem |
Type that survives 120 dpi
I mistakenly assumed I could use Inter for the typeface. At 120 dpi a 12 px Inter stem lands between pixels, and the panel has no gray to anti-alias with. Below is a comparison between Inter Regular, Inter Medium, Departure Mono at its native 11 px, and Departure Mono at 12 px (off-grid).
I quickly switched to Departure Mono, which is both a pixel and monospaced font. Every stroke sits on the pixel grid, and every character takes the same 7 px cell. However, I wanted bold headings for hierarchy, and Departure Mono ships in a single weight. There’s an old trick for that: draw every glyph twice, one pixel apart. But four letters, w, m, W, and M, already fill their entire cell, so they cannot use the double glyph trick.
Below are a few more directions I considered during this journey:
- Make headings two pixels larger: no longer on the grid.
- Ditch bold altogether: clean, but headings and captions become the same voice.
- Uppercase: clear hierarchy, but it breaks my rule of not feeling aggressive.
A second trick I read about: draw the problematic letters by hand. Now we’re talkin’.
w m W M
##.#.## ###.##. ##...## ##...##
##.#.## ##.#.## ##...## ##...##
##.#.## ##.#.## ##...## ###.###
##.#.## ##.#.## ##.#.## ##.#.##
##.#.## ##.#.## ##.#.## ##.#.##
.##.##. ##.#.## ##.#.## ##...##
.##.##. ##...##
.##.##. ##...##
The results:
- Departure Mono at 11 px for text.
- Departure Mono with faux bold for headings and four hand-drawn glyphs.
- Inter Bold at 84 px for the hero numbers and 28 px for the visitor count. I tried a wide variety of Inter and Inter Display weights before settling on Bold because it felt the smoothest on the display. Inter is sufficient at these larger sizes because strokes are much wider; a half-pixel at the edge is nearly invisible. The pixel grid problem only exists where a stroke is one or two pixels wide.
Before: Inter throughout
A sparkline made of whole pixels
My first sparkline was a 1.5 px line with rounded corners, and on the panel it looked ragged; every slope broke into uneven steps. Redrawing it as a 2 px line with square corners, with every point on a whole pixel, made it read as one continuous stroke at a distance. The round dot at the end also felt fuzzy, so it’s now a 5 px square.
Moving from software to hardware
A few hiccups I encountered when working with the device:
- Backing up the factory firmware took 30 minutes. Before swapping to the TRMNL firmware I backed up the device’s existing 16 MB firmware, which took half an hour because the chip’s built-in USB serial transfers at 9 KB/s. It’s the only way back to a stock state, so it was worth the wait. Note: This was handy considering how difficult #2 was to overcome.
- Getting the device to talk to my API was confusing. The device’s web app hides the server URL on an “Advanced” page, and the value is only sent to the device if you tap the “Back to Wi-Fi” button. It took three erase-and-reflash cycles before I figured out the precise path.
- The device loves to fall asleep. This makes transferring data to it a bit frustrating (understatement).
"status": 200is a failure. Once the device was on Wi-Fi it called my server, asked what to show, and went back to sleep without ever downloading the image. No error on the screen, nothing in the logs. The firmware’s display parser treats 0 as success and silently ignores 200 even though the setup endpoint expects 200.- The bright green LED stays on in sleep mode. A light breaks my rule that the device must not distract me from work, so I patched the firmware to turn it off.
Living with it
My goal was to design a glanceable display that gently encourages me to write and shoot, with a backend I built myself. The two large numbers accomplish this, and the panel remains visually quiet. Next I’ll try a few more pixel typefaces and keep tinkering with the color thresholds.
Footnotes
Chat about this?
Send a comment