InkyDesk

An e-ink desk calendar

A little e-ink desk calendar has been one of those things I’ve been wanting to put together for years. I’ve tried a few times and I never managed to get any momentum.

The screen is usually the problem. I have an old Kindle (4th gen) laying in a drawer, and I’ve even jailbroken it and got a custom script running on it. But the built in OS fought me at every step. WiFi would disconnect randomly and it would periodically go to sleep and stop updating the screen.

Then a bunch of podcasters and YouTubers started talking about the TRMNL and I decided to give this another shot.

Even after all of this, I still want a TRMNL.

The Hardware

Rather than attempting to use an old Kindle for this (though it supposedly is possible), I decided to do this correctly and I bought a Pimoroni InkyWHAT (red/black/white) from MicroCenter[1].

The screen itself is very nice, though also very limited. It’s e-ink, so the viewing angles are perfect and it’s not at all distracting to have sitting below my main monitor since it gives off no light. But it’s also rather low resolution (400x300 at 4.2 inches) and can only show black, white, or red. There’s no grayscale. This would prove to be an issue later…

But before that, powering the screen is a Raspberry Pi Zero 2 WH[2]. I had originally planned on using an old Pi 3, but before I could get the screen it got roped into some other use. So I bought the cheapest Pi that would connect to the screen. As you’ll see later, the software stack means I don’t actually need the Pi to do much of anything.

The stand is something I threw together in OpenSCAD and printed myself. It’s clunky and my crappy printer fought me a lot for this particular print, but it does the job. I also bought a down-angle[3] micro USB cable so I don’t have to fight to get the power cable going the right way.

The Software

The software is broken into two parts.

The client is a simple Python script that get’s the image from the server and displays it on the screen. There’s also a simple shell script that sets up the correct virtual environment before running the Python script. I know that’s not necessary, but I haven’t used Python since college and wanted to keep this understandable to me. I added two crontab entries to run the shell script hourly and after a reboot.

The server is where the real work is done. It’s an ASP.NET Core API (using the Minimal APIs pattern) that pulls events from various ICS URLs, gets some weather info, and then generates an image for the screen.

Funny enough, I was able to re-use my ICS parsing code from previous attempts at this. Most of my effort was spent figuring out a good way to render the image. That proved to be a challenge.

First I had the server generate a webpage, then use PuppeteerSharp to load up a headless browser and screenshot the page. That option was nice because CSS is awesome and made styling and layout easy. But my preferred way of hosting local web apps, running them in Docker on my Synology, made that approach impossible. Chrome headless just doesn’t work in Docker without lots of extra effort.

My next approach was to use QuestPDF to render the “document” as an image. This actually worked very well. The layout engine that QuestPDF uses is fairly intuitive and made the rendering easy. Plus it runs in Docker!

Then the screen’s limitations came into play. No grayscale means that the image get’s dithered by the Python library that renders to the screen. Antialiased text that get’s dithered looks… crunchy.

So I finally gave up and used ImageSharp to just render the image directly. Text is still crunchy, but now I can disable antialiasing which helps a lot. ImageSharp takes some getting used to, and the lack of documentation is very annoying, but I’ve gotten the code to a pretty good place and the output looks rather nice. Layout is all done manually and with plenty of (questionable) math, but it’s surprisingly robust.

Fonts are still giving me trouble/ It’s hard to tell in advance if a font will look good when rendered without antialiasing. I’ve gone through several fonts and am still not happy with anything.

The code is public on GitHub, but be aware I haven’t gotten around to writing up a readme to explain how to get it all set up, yet. I’ll get that done soon.


  1. Initially I wanted the black/white option because it can refresh the screen faster. I ended up with the red/black/white version because that’s what MicroCenter had. I’m actually very happy with the red since it really pops. ↩︎

  2. That’s the Zero 2 with wireless and pre-soldered GPIO headers. ↩︎

  3. Yes, down-angle cables are a thing. Right-angle cables are pretty normal, but fun fact: the “right” doesn’t refer to the angle, but the direction it turns. Thus, you can find left-angle, up-angle, and down-angle cables for almost any connector out there. ↩︎