Some time ago I ordered an Inkplate 6 which basically is a recycled e-paper screen from an Amazon Kindle enriched with an ESP32 chip. So it has Wifi and is programmable using the Arduino IDE. The goal was to create a wireless dashboard for my living room to display upcoming events, weather etc. After tinkering around with the Arduino IDE and C++ I created a solution that pulls necessary data off a restful API that my Home Assistant provides and displays them on the screen.

But I was not really happy with the solution: It felt too complicated aligning texts and icons on the screen. I mean, everytime you change some positions you have to compile and upload the whole thing to the ESP32. This takes some time and is annoying. Also I was struggling with german umlauts which I was not able to display.

The idea

Wouldn’t it be nice if the changes made to the screen would immediately be visible to the developer? So I came up with the following idea: I want to develop my dashboard screen with web-technologies like HTML, CSS and Javascript and see any change in the web-browser. In production the Inkplate device just downloads a screenshot of this website and displays it - nothing more.

The solution

My solution consists of three components:

  • A webapp that fetches data from a restful API and displays them.
  • A minimal restful API that takes screenshots of that webapp and provides them for a download.
  • The Inkplate client that calls the API, loads the screenshot and displays it.

Dashboard Webapp

The dashboard is a simple Angular webapp that fetches the data from a custom API. The API provided data about upcoming events, weather and a garbage collection calendar. As the Inkplate is only capable to show monochrome colors, everything in the webapp is displayed in black & white colors.

Webapp Screenshot

Source: Github

Server for screenshots

The dashboard server provides an endpoint /dashboard/:id that will be called by the Inkplate client. By using an :id parameter it is able to handle more than one dashboard - so maybe my living room dashboard will get a sister sometime.

Different dashboards can be configured through the config.json. Each entry contains the dimensions of the dashboard screen and a list of screens. This means that a dashboard can handle different screens: Maybe you want to show a different page in the morning than in the evening? Also a time-to-sleep can be configured using the sleep or sleepUntil property.

config.json

{
  "livingroom": {
    "width": 800,
    "height": 600,
    "screens": [
      {
        "url": "http://localhost:4200/dashboard",
        "from": "08:00",
        "sleep": 30
      }, {
        "url": "http://localhost:4200/dashboard-night",
        "from": "22:15",
        "sleepUntil": "08:00"
      }
    ]
  }
}

The configuration shown above will show the page http://localhost:4200/dashboard starting from 8:00am and the Inkplate should sleep for 30 minutes afterwards.
When the sleep time has passed everything starts over until 22:15 (10:15pm): After this time the next screen http://localhost:4200/dashboard-night will be called and the Inkplate should sleep until next morning 8:00am.

So, when the dashboard-endpoint is requested internally a Chrome session is started using puppeteer. It takes a screenshot of the given screen-url and stores it in an accessible folder. Also it keeps the previous screenshot - why? We come to that later.

Source: Github

Inkplate client

The code for the Inkplate is very simple and basically does the following

  1. Call the endpoint /dashboard/livingroom of my backend.
  2. Load the resulting screenshot and the screenshot taken before.
  3. Get the time-to-sleep from the backend.
  4. Draw the screenshot.
  5. Go to sleep.

The reason why we download always the current and the previous taken screenshot is, that the Inkplate will looses its memory when going to deep sleep. To perform a partial screen update without flickering the previous image is taken to make the Inkplate recall what is written to the e-ink display.

Inkplate Dashboard in a picture frame

Source: Github

Sources