E-Ink Dashboard Integration for Home Assistant

Release early, release often!

Hello,

I’ve just started with Home Assistant. I’m still in the evaluation phase,
figuring out what I actually want to do with it. One thing I wanted early on
was a “glanceable” dashboard I could check before leaving the house. Checking
if a window is still open? What does the weather look like? Is waste collection
tomorrow or is the cellar flooded?

The solutions I found were not satisfying. They either ran a headless browser
to take screenshots or were somehow duct-taped together. Home Assistant already
uses Pillow Pillow (PIL Fork) 12.2.0 documentation , so all the primitives
are right there, no external tools are needed.

So I started writing a new integration.

The hardware story

I got a TRMNL device recently. They are a nice little e-ink display designed
exactly for this kind of glanceable information. But after I set it up I
remembered that I have old Kindle e-readers lying around that I replaced with
newer models and never found a use for. My old Kindle Paperwhite 2 has a
better display than the TRMNL OG - 758×1024 at 16 grayscale levels versus
800×480 black-and-white.

After some research I found that you can repurpose a jailbroken Kindle as an
always-on display. There is an online screensaver around, but outdated and
not really energy efficient. I started a new project
kndl-online-screensaver.
A modern replacement for the old online screensaver projects, also covered
with tests.
It took me a while to figure out why the online screensaver corrupts the FAT
partition, but that is another anecdote ;-).

So now I had two different devices that both needed images pushed or
pulled from Home Assistant. That shaped the integration.

What it does

The integration renders your dashboard layout to a PNG image using Pillow
directly in Home Assistant - no Chromium, no ImageMagick, no Node.js. It
exposes the image via an HA image entity and an unauthenticated HTTP endpoint
that devices can poll on their own schedule.

For TRMNL devices, HA can push the rendered image via webhook after each
render. For Kindles (and any other device that can fetch a URL), you point the
device at the HTTP endpoint and let it pull on its own schedule, using ETag
headers to skip the download and e-ink refresh when nothing has changed.

The WYSIWYG Lovelace card lets you edit the layout on a canvas preview that
matches your device’s exact pixel dimensions. You can drag, resize, and
configure widgets and immediately see what the rendered output will look like.
Clicking Show rendered image fetches the actual Pillow-rendered PNG for a
pixel-exact comparison.

Current state

The configuration flow is working well and I have images being pushed to my
TRMNL and pulled by my Kindle Paperwhite 2. It is ready for an initial alpha
release. The widget set will need a lot of improvements but maybe someone is
interested in helping.

Expect bugs, this is an early release.

The project is at: GitHub - cryptomilk/hass-eink-dashboard: WIP · GitHub

Features

  • Multiple e-ink displays - create a separate dashboard for each device
    with its own layout, resolution, and refresh interval
  • Device presets - built-in profiles for Kindle 4/5, Paperwhite 1–4,
    Oasis 2/3, TRMNL OG/X/RGB, or enter a custom resolution
  • Portrait and landscape - rotation is handled automatically based on the
    device preset and chosen orientation
  • WYSIWYG Lovelace editor - drag, resize, and configure widgets on a
    canvas preview that matches your device’s exact pixel dimensions
  • Pull and push delivery - devices can fetch the image on their own
    schedule (Kindle) or have HA push it via webhook (TRMNL)
  • E-ink optimization - optional post-processing pipeline: autocontrast,
    sharpness, contrast adjustment, and grayscale quantization (2/4/16/256
    levels with Floyd-Steinberg dithering)
  • Jinja2 templates - text widgets support Home Assistant templates
    (e.g. {{ now().strftime('%H:%M') }})
  • ETag support - conditional HTTP responses so devices skip the download
    and e-ink refresh when the image has not changed
  • Webhook rate limiting - push targets are throttled to one push per 5
    minutes with a 5 MB size cap

How to contribute

This is alpha software - expect rough edges!

  • Please create detailed bug reports when something doesn’t work
  • Be friendly in communication
  • Help to improve the documentation
  • Create Pull Requests improving the code or widget designs

Cheers!

2 Likes

Interesting project, I got a TRMNL recently too and used a different solution to grab a dashboard page screenshot every hour. Your approach seem a little more flexible.

I also have an old Tolino Shine laying around and wanted to do something with this too. Hopefully this project works with it. Looking forward

Hi cryptomilk,

thanks for your work! I started my journey a month ago.

For that, I´m using the “Lovelace Kindle Screensaver”-App which is a similar solution. It can render images from predefined dashboards. Combined with Kiosk-Mode from HACS, it gives a perfect image for my Kindle.
Maybe you will take a look onto that solution, to grab some more ideas.

My problem was, that I picked up the wrong Kindle, which does not support the Screensaver extension. So I had to write my own updater, which kills the battery quite fast.
But what I saw from TRMNL seems to be an even better solution!

Thanks for your efford so far, and I´ll follow this project :slight_smile:

Hey HerrRiebmann,

I know that app. It is the standalone component which I talked about earlier. It connects to HA to create a screenshot with a headless browser, exactly what I don’t want to have :wink:

1 Like

Also this probably works fine on a Kindle with has a nice display, but not with a dithered display like the TRMNL has.

I've rewritten the rendering pipeline. Before I had one in python (backend) and one in the frontend (javascript). So you implemented each widget twice and needed to make sure they are in sync. That actually sucks. I thought about using a different language for designing widget. CSS wouldn't work or anything like that.

The solution is SVG. SVG is actually just XML. To make it replace stuff we can use Jinja and have .svg.j2 templates. We just need an endpoint (websockt) to get the final rendered SVG to display then in the frontend. For the device we can rasterize the SVG to a png and do eink optimizations afterwards.

I think this is the way to move forward. I will release 0.2.0 soon and we can start polishing widgets and add new ones.

I also have a small tool which helps to design the widgets.

I've released version 0.2.0. Time for bug fixing and adding new widgets.

[0.2.0] - 2026-05-15

Added

  • SVG rendering pipeline: all widgets are now rendered as Jinja2 SVG templates
    rasterised by resvg-py, replacing the PIL drawing infrastructure.
  • WebSocket commands eink_dashboard/render_widget and
    eink_dashboard/render_widgets for fetching server-rendered SVG previews
    from the Lovelace editor.
  • Shared SVG macro library (_macros.svg.j2) for card borders, dividers, and
    other reusable layout primitives.
  • mdi_svg and weather_svg Jinja2 filters for inlining MDI and weather
    icons directly into SVG templates.
  • State circle indicator on status_icons widget, controlled by the new
    show_icon and show_state options.
  • Card container (card_style) support for the text widget.
  • Standalone design tool (tools/design_widget.py) for iterating on widget
    layouts outside of Home Assistant.
  • CI job publishing JUnit test results to GitHub Actions.

Changed

  • Editor canvas preview replaced by server-rendered SVGs; drag and resize
    interactions rewritten accordingly.
  • status_icons widget redesigned from pill chips to icon-and-text labels.
  • Config flow screen-portion selection step restricted to TRMNL devices only.
  • TRMNL documentation URL standardised to trmnl.com.
  • Build tooling migrated from tox to uv.

Removed

  • LINE widget type.
  • PIL drawing infrastructure (render.py PIL helpers).
  • cairosvg icon build pipeline.

Fixed

  • Widget SVG viewport clipped to the widget's content dimensions, preventing
    overflow into adjacent widgets.
  • Card border inset now included in the device_battery natural-width
    calculation.
  • Weather forecasts are now fetched in the SVG preview WebSocket handlers so
    the editor shows live forecast data.
  • Config flow orientation indicator shown in the screen-portion step title.

Version 0.3.0 is out with the following changes:

[0.3.0] - 2026-05-16

Added

  • Media Source platform exposing rendered dashboard PNGs in HA's Media Browser,
    enabling delivery to screens running OpenDisplay.
  • TRMNL battery level support in the device_battery widget.
  • Snap-to-widget-edges in the Lovelace editor: hold Shift while dragging a
    widget to align it to the nearest edge of any other widget.
  • Design tool: interactive resize handles on the Raw SVG preview panel.
    Corner handles resize width and height; left/right edge handles resize
    width only. A dashed blue outline tracks the widget bounds, and handles
    snap to the widget's actual rendered content area.

Changed

  • Editor resize model: corner handles (nw/ne/sw/se) resize width and
    height; left/right edge handles (w/e) resize width only. Font size
    scales proportionally on corner resize for text and weather widgets.
  • Bumped resvg-py to 0.3.2 for fontdb caching support.
  • Weather widget: main temperature is now rendered in bold.

Fixed

  • Spurious RuntimeError logged during HA shutdown when the image
    refresh executor is already stopped is now suppressed.
  • device_battery widget resize box now matches the rendered content
    width instead of a hardcoded 200 px default.
1 Like

Ok, we are entering beta quality. The core is sound, I've started to create useful widgets now. I think with the current set you can already do some nice things :slight_smile:

[0.4.1] - 2026-05-21

Fixed

  • Fixed release pipeline

[0.4.0] - 2026-05-21

Added

  • Sensor widget: single-entity sensor card with optional sparkline history
    graph (graph: "line"), configurable hours_to_show, detail level, and
    fixed Y-axis limits.
  • Entities widget: multi-entity list card with optional title, inline
    divider rows (type: "divider"), and section rows (type: "section").
  • Entity widget: large-value single-entity card showing state and unit,
    mirroring HA's Entity card.
  • Heading widget: section heading with optional MDI icon and entity badges,
    superseding the deprecated Text widget.
  • Locale-aware number formatting: per-device decimal and thousands
    separator override (decimal comma, decimal point, or HA default) settable
    in the integration options.
  • Visibility conditions: all widgets now support a visibility list to
    show or hide based on entity state or other HA conditions.
  • Editor drag-and-drop reordering: widget order can be changed by dragging
    rows in the widget list, replacing the previous up/down arrow buttons.
  • Editor live SVG preview scaling: the preview panel scales
    proportionally while a widget is being resized.

Changed

  • Row dividers in Entities, Waste Schedule, and Weather widgets now render in
    light gray (#b4b4b4) instead of medium gray, improving visual hierarchy on
    16-level grayscale displays. No change on 2-level (TRMNL) displays.
  • Entity and Sensor widget header icon enlarged for better legibility on
    e-ink screens.
  • Design tool default dashboard (no --widget flag) now previews all key
    widget types: Weather, Tile, Entity, Sensor (with sparkline), Waste
    Schedule, and Device Battery.

Removed

  • Deprecated widget types text (use heading), sensor_rows, and
    status_icons removed from the renderer and widget picker. Existing
    configs that reference these types will no longer render.

Fixed

  • Entity and Sensor header icon circle now has consistent padding from the
    top card border (previously the circle clipped the border line).
  • Heading widget had double padding below the title under
    card_style: "border".
  • Weather and Device Battery widgets: content soft-padding now applied
    consistently when card_style: "none".
  • Widget picker showed raw MDI icon strings instead of glyphs for Entities
    and Sensor card types.
  • Waste Schedule, Tile, and Heading icon circle strokes are now widened on
    2-level (TRMNL) displays to prevent dithering artifacts.
  • recorder integration declared in after_dependencies so history data is
    available when the Sensor widget fetches it at startup.
  • Translation selector keys for date format settings corrected to lowercase.
2 Likes

This seems like the best option I can see.

But I am to stupid to use it.
I have setup my display with opendisplay and integrated via Bluetooth and opendisplay integration in HA.
I have installed your integration and created a "picture".

How can I now connect the bluetooth display with your integration, so that the image is displayed?

I got some ESP32 e-ink displays and wonder if this solution can be easily used with esphome?

I have OpenDisplay lying around here, but couldn't integrate it yet.

However as far as I know you need to select in the media browser.

I've released a new version.

[0.5.0] - 2026-06-30

Added

  • Graph widget: time-series line chart for one or more numeric entities,
    with configurable hours_to_show, smoothing, axis labels, grid lines,
    group_by aggregation, extrema markers, min_bound_range, and
    multi-entity overlay support.
  • Bar chart mode for the Graph widget (chart_type: "bar").
  • Color threshold styling for the Graph widget: segments change color
    based on configurable value thresholds.
  • Gauge widget: arc-style gauge for a single numeric entity with
    configurable min, max, and optional color thresholds.
  • Frame widget: decorative card with rounded corners for visual grouping,
    with no entity data of its own.
  • Calendar widget: upcoming-events list sourced from HA calendar entities.
  • Custom sensor overrides for the Weather widget: replace the built-in
    temperature, humidity, or wind sensors with arbitrary entity IDs.
  • hide_icon option on the Entity and Tile widgets to suppress the icon
    circle entirely.
  • hide_fill and hide_state options on the Sensor widget sparkline.
  • Seeed reTerminal E1001 and E1003 device presets in the config flow.

Changed

  • Widget SVG backgrounds are now transparent, enabling correct compositing
    when widgets overlap or when the dashboard background shows through.

Fixed

  • Config flow: grayscale_levels is now coerced to int before validation,
    preventing a type error when the value arrives as a string.

Performance

  • Frontend editor skips redundant SVG renders instead of queuing them,
    reducing unnecessary server round-trips during rapid widget edits.
1 Like