Tesserae: self-hosted dashboard for Inky panels

I built Tesserae, an open-source dashboard for e-ink panels. Compose dashboards once in the browser, render for each bound device, so an Inky Impression 13.3 in the kitchen and an Inky Impression 7.3 by the desk both paint from the same composition, each at its own resolution.

On the Inky side: a Pi runs a small MQTT client that listens for frame announces, fetches the PNG, and paints via Pimoroni’s official inky.auto library. Spectra 6 13.3 is supported, with the colour quantisation and dithering done server-side so the Pi just pushes pixels at the panel. The rack in the photo also has a Kindle (via TRMNL’s BYOS protocol) and ESP32 panels driving from the same server, so the model isn’t Inky-only.

Plugin model: drop a folder under plugins/, get a new widget. Bundle includes weather, calendar, news, Home Assistant, Spotify, and GitHub stats, with a community catalog for more.

Runs as a Home Assistant Add-on or as a standalone Docker container. AGPL-3 licensed.

Repo + screenshots: GitHub - dmellok/tesserae: E-ink dashboard companion. Compose tile-based dashboards, render headless, push to Pi and ESP32 panels over MQTT. · GitHub

Happy to answer any questions or help people get things set up.

3 Likes

Great project, went directly to my todo list. I have a lot of these panels and also some software that is basically the same (server part and client part), but far from being as polished as your solution.

Do you plan to create a pico-client as well? I am running all my Inky-Impressions with a Pico instead of a Pi, and there is also the Inky-Frame product line.

Thank you! Actually I’m looking to do the same, I’ve picked up a Pico to Pi HAT and a Pico Plus 2 to drive the 13.3" Impression, just haven’t started on the code yet. If you have a repo I could take a look at that’d be fantastic.

I’ll definitely be building a Pico client, would you be open to testing it?

I mainly code in CircuitPython, see e.g. GitHub - bablokb/pi-e-ink-daily: Display Daily Agenda on an E-Ink Display · GitHub

It would be great if your server would have a nice REST-API, this would allow a client implementation without being bound to a specific client (maybe it already has, I did not read through all of the docs).

If you code in C, you should also have a look at the low-power implementation from Pimoroni for the Badger2350. This would basically also work for a Pico Plus2 driving an Inky-Impression. The Inky-Frames have low-power management built into the hardware, which is even more efficient.

Certainly. I have an Inky-Impression 4", and one of the new Inky-Impression Spectras to play with.

Thanks, pi-e-ink-daily looks like a great starting point for adapting drivers to the Pico.

Tesserae already supports a plugin architecture and partially covers REST via the existing TRMNL compatibility, so a new REST transport should be pretty straightforward to add. I’ll put it on my backlog. MQTT stays the main transport because of the power-consumption win, but a REST sibling gives the community a simpler path for building firmware on top.

Definitely looking to maximize power efficiency on this, so thanks for the pointers.

Awesome, I’ll ping you here once there’s something to try.

I am actually using a custom PCB for this: GitHub - bablokb/pcb-pico-power-switch: Suport PCB to switch battery power - designed for a Raspberry Pi Pico · GitHub
So I am doing it in hardware like the Inky-Frames. With the RP2350 you can go as low as about 150µA in deep-sleep, so a hardware solution is not really necessary anymore.

In action:

This is the backside of an Inky-Impression, with a Pi-to-Pico adapter (also a custom PCB) and then the power-switch PCB. All within a 3D printed frame. I am updating my daily agenda automatically once a day. The lipo runs for many month without recharging.

1 Like

Damn that’s a fantastic build. Mine admittedly are held together with tape on the back.

I have all of my dashboards updating between every 5-15 minutes so battery life on my end is nowhere near comparable haha.

I’m glad to know that the RP2350 is so power efficient in deep sleep. I was planning C for efficiency but I’ve talked myself into CircuitPython since your circuitpython-inky driver already covers the 7.3" Spectra 6.

I’ll target 7.3" first so you can test, then try to tackle the 13.3" Spectra 6 driver.

Still MQTT-first (Tesserae has a built-in broker if needed). REST API after.

I don’t think CircuitPython is ready for that. The 13.3" uses two chips internally, so you have two CS lines and some commands/data go to one chip, one to the other. This would require some new logic in the CircuitPython core. The core-developers are always open for PRs, but I don’t think the changes will be trivial.

@bablokb you were right that the 13.3" is the tricky one. The dual-controller split is exactly what made it awkward, so I went C on the Pico SDK instead of CircuitPython, with a good bit of help from Claude. It’s painting reliably now on a Pico Plus 2 W.

First test firmware: tesserae-device-pico-bin

It reads the HAT EEPROM and picks a per-panel driver. The 13.3" is verified on hardware; the 7.3" Spectra 6 and 4.0" drivers are ported from the Inky sources but untested, so they should work in theory, I just can’t confirm them. Your 4" and the new Spectra would be ideal for shaking those out (it prints [UNTESTED driver] for anything unverified).

MQTT client with RP2350 POWMAN deep sleep (thanks for the badger2350 pointer, that was just what I needed) and a captive-portal setup flow. REST sibling still on the backlog. Would love any feedback once you get a chance.

I’ve been hitting some issues with the built-in MQTT broker and have decided to implement a REST API. This is now mostly implemented in Tesserae, but I’ll have some new firmware that incorporates it shortly.

I will give it a try next week. Since I don’t have an active docker host, I will probably try to install the server in a container environment (PXE or Micro-Cloud) as a first step, then I proceed to client install.

Perfect. that gives me plenty of time to smooth out some of the rough edges.

@bablokb the REST firmware’s done and painting reliably on my end (13.3" verified on hardware). Test whenever, no rush. :)

Separately, I also put together a bare-minimum standalone EL133 (13.3") example for anyone who just wants to drive this panel on a Pico without the full stack:

A short status update: I started the server using Github Codepages and the manual install method. Very simple and fast. Nothing for productive use of course, but it is a simple throw away environment perfect for testing if you don’t have the (docker) infrastructure at hand. In fact this could be fully automated by adding a .devcontainer.json file.

The wizard looks really nice. What I have seen of your code: high quality, the same holds true for the documentation.

I am currently a bit unsure which client to try. I am tempted to create a generic CircuitPython client. I already have a basic framework for this kind of application (wake, poll, update, sleep) and I only have to implement the data-provider and the ui-provider. The latter will be simple, since it would only display a png as I understand and the data-provider will fetch it using the REST API.

Since CircuitPython runs on 400+ devices with all kinds of displays (builtin or not), this would open up Tesserae to the complete world of microcontrollers/SBCs/PCs with a single codebase. Not as fast as native clients, but as long as the update speed is limited by the display this should actually not matter.

My suggestions: we could switch to Tesserae’s discussion board within Github, so this is also visible for other Tesserae users.

Thanks for the careful test drive. You called the right thing with .devcontainer.json: I’ve added one and a Codespaces badge, and after a couple of iterations, the launch now drops you straight into a running production server with nothing to type. Prebuilds bake on every main push, so badge clicks should be ~30 seconds to a usable container.

On the UI/Docs feedback, thank you. Worth being upfront that both lean on Claude for the rote work; structural decisions are mine, but a fair share of the polish is LLM.

For the client: try device-pico-bin first as a working reference. Battery-powered Pico Plus 2 driving a Spectra 6 panel, wake / fetch / paint / sleep over retained MQTT, exactly the shape you described, just in C++. The generic CircuitPython client you’re proposing is honestly the most exciting thing anyone’s offered to build; one codebase covering 400+ boards is real leverage.

Small note: native clients fetch a packed bin frame for battery/bandwidth, but the in-tree pi_png renderer means PNG works end to end. Almost certainly the right format for CircuitPython, given how imageload already handles decode.

I wrote up a full client protocol spec covering REST, MQTT, pairing, frame formats, and the steady-state loop, so you have something concrete to code against: Client protocol spec - Tesserae

And yes, GitHub Discussions makes sense. Opened a collab thread for the CircuitPython client work specifically, follow-up over there: Generic CircuitPython client + client protocol spec · dmellok/tesserae · Discussion #24 · GitHub

:)