DEV Community

Kamil Marut
Kamil Marut

Posted on • Originally published at kamilmarut.com

Setting up an e-paper dashboard with OpenDisplay

reTerminal E1001 with my custom dashboard

I've been eyeing TRMNL for some time now, but due to the price tag, I simply couldn't justify the purchase. After all, I sit at a computer all day and could just pin a calendar in a browser tab or use a desktop widget for weather forecasts. Spending 120 EUR (plus probably the extra 20 EUR for the clarity kit) on a gadget that is more of a nice-to-have just doesn't make sense, especially since it feels like something I could build myself. I don't even have to think about how to build it too much as TRMNL has a BYOD/S guide with all the components you need laid out for you. I was ready to give it a go with a device I found suitable for my needs which is the reTerminal E1001, but while I was waiting for the device to arrive, I stumbled upon this fresh partnership between the Open Home Foundation and OpenDisplay and found myself thinking that OpenDisplay was the perfect fit for my need: very simple specification, open firmware, and a straightforward Python SDK to create your own integrations. I wanted to get to work immediately!

Installing OpenDisplay firmware

This is fairly straightforward with probably just a few Linux-specific (or perhaps Arch-specific?) caveats that I've run into.

After getting the hardware of your choice and then plugging it to your computer via USB, you want to open up the OpenDisplay Toolbox in a Chromium-based browser. Unfortunately, Firefox does not support the required WebUSB and Web Bluetooth APIs 1.

You then most likely want to set up BLE encryption, otherwise anybody can just connect to your device if they're in range!

If you also have a ESP32-based hardware like I do, unless you are going to keep it plugged in at all times, you probably want to set the "deep sleep between updates" option. I have it set to 6 hours as I want to maximize battery life and I don't require frequent updates. If you're not familiar with what does "deep sleep" exactly mean, there's a fairly good explanation on the ESPHome website. What it means in the context of the OpenDisplay firmware is that the device will power off nearly every component and "wake up" on a given schedule. So if you set it to a 1-hour interval, the device, after turning on will wake up for a short time, advertise itself over BLE for a short time (about 2 minutes on my hardware) and then go to sleep - during this time you will be unable to connect to the device. After an hour of deep sleep, the device will wake up again and repeat the process. If you have any physical buttons on your device, the keypresses will only be registered while the device is awake and the only way to wake it up manually is to turn it off and then turn it back on.

Now you're ready to install the firmware! Press the "Install firmware" button on the page and select the (most likely) ttyUSB0 device from the prompt. If you're running Linux and you get a failed to execute 'open' on 'SerialPort': Failed to open serial port. error, you most likely need to set the correct ACL on the serial port. You can do this by running the following command and trying again:

sudo setfacl -m u:$USER:rw /dev/ttyUSB0
Enter fullscreen mode Exit fullscreen mode

Firmware installed? Good! Now proceed to the Bluetooth configuration. You should be able to connect to the device using the Bluetooth settings on your computer and then press the "Configure over Bluetooth" button. I ran into another error here: Auto-install failed: Cannot read properties of undefined (reading 'requestDevice'). I was running this in Chromium so maybe it works out-of-box on Chrome, but I had to enable the "Experimental Web Platform features" flag in Chromium to get it to work.

Experimental Web Platform features flag in Chromium

After that, you should automatically be redirected to the BLE Tester where you can test your device connectivity and some OD features.

Creating a dashboard server

Depending on what it is you want to display and your refresh rate, you might want to write a simple script that just uploads your data once or you want something that keeps your dashboard up-to-date (or as much up-to-date as you can within your deep sleep interval). I knew that I wanted to be able to display a dashboard page with the weather forecast as well as my calendar. I also wanted to be able to view the calendar full-size if I wanted to.

No matter what it is you want to build, you want the py-opendisplay Python library to communicate with the display. It is very easy to work with and provides great examples for getting you started.

For my use case, I wanted a daemon-style app that sits on my homelab server and updates the display as soon as it wakes up from deep sleep. After it receives the data from the device, it extracts the battery level and starts fetching the relevant data:

I wanted to avoid having to implement a scheduler that runs the app periodically so the fetching has fairly short timeouts to avoid missing the wake-up window. All requests happen asynchronously to avoid blocking each other using the niquests library.

Using Pillow it generates a static image with the dashboard and calendar content. I also use libcairo to draw SVG icons for the Weather forecast.

One thing I'd recommend you to implement is a way to preview your images locally before pushing them to the display. Even if you don't have a deep sleep interval enabled, the long refresh times of the e-paper display make it cumbersome for proper testing. Here's a preview generated locally showing a just-for-fun HTTP Cats screen. Since the screen is monochrome, I generate all images in grayscale.

HTTP Cats screen preview

You can find the full source code of my app on my Forgejo server. It's not necessarily made in mind to be extensible by other people, but I believe it's fairly straightforward to understand and modify for your own use!

Running the server in Docker

Since everything in my homelab is Dockerized, I just couldn't make an exception for this project! Fortunately it's fairly simple to run the whole thing Dockerized with a few extra steps:

  1. You want to set up your Dockerfile and (probably) a Docker Compose file like usual.
  2. The Dockerfile runtime image should have the bluez and dbus packages installed. This is required for the OpenDisplay SDK to work and communicate with the Bluetooth in the host system.
  3. In your Docker Compose file, you want to set up a bind mount for the D-Bus to access host's Bluetooth device:
volumes:
  - /var/run/dbus:/var/run/dbus
Enter fullscreen mode Exit fullscreen mode
  1. You want to add the extra capabilities to the Docker Compose file to allow the container to access the host's Bluetooth device:
cap_add:
  - NET_ADMIN
  - NET_RAW
Enter fullscreen mode Exit fullscreen mode
  1. And of course, you want to have bluez and dbus installed on your host's machine, as well as your user in the bluetooth group to be able to use the Bluetooth device.
sudo usermod -aG bluetooth $USER
Enter fullscreen mode Exit fullscreen mode

Remember to run a sanity check on your host machine first with bluetoothctl scan on - if it finds your device, then it's most likely the Docker container is the problematic part.

Summary

I'm in the process of finding the dashboard a spot on my IKEA SKÅDIS board in my office. In hindsight, in the time it took me to write the server along with figuring out the Docker setup in my homelab, I could've just invested in the TRMNL instead. But this was way more fun, I got to learn more about Bluetooth connections and now I have a truly custom dashboard that I am happy with.


  1. I can't really understand why not though. After reading a bit into it, some users mention security concerns about implementing such APIs. IMO as long as the API requires user confirmation, how does it differ from the browser having access to your camera/microphone? So far this is the second time I've ever run into somebody using this API (the other being the GrapheneOS WebUSB installer) but I think it's such a neat thing that it'd be nice to have in Firefox. 

Top comments (0)