---
title: An E-Ink Truck Dashboard That Admits When It Doesn't Know
description: "An e-ink dashboard for my Rivian, built so missing data never looks like zero: six screen states, a frozen-screen trap, and webhook rate-limit math."
image: https://willcurran.com/hubfs/will-curran-brand/eink-truck-dashboard-featured.png
---

[Skip to content](https://willcurran.com/blog/eink-truck-dashboard#main)

[Will Curran](https://willcurran.com/)

[Home](https://willcurran.com/) [Portfolio](https://willcurran.com/portfolio) [Press](https://willcurran.com/media) [Blog](https://willcurran.com/blog) [Favorites](https://willcurran.com/favorites) [Speaking](https://willcurran.com/speaking) [Consulting](https://willcurran.com/consult) 

[Hire Will](https://willcurran.com/hire)

[Writing](https://willcurran.com/blog) Builds — An E-Ink Truck Dashboard That Admits When It Doesn't Know [Subscribe](https://willcurran.com/blog/subscribe)

Builds

# An E-Ink Truck Dashboard That Admits When It Doesn't Know

I built an e-ink dashboard for my Rivian whose main job is to say plainly when it doesn't know something, because missing data that shows up as 0% is a false alarm. The build log covers the six screen states, the bugs where empty values turned into warnings, the frozen-screen ...

![](https://willcurran.com/hs-fs/hubfs/will-curran-brand/will-curran-avatar.jpg?width=44&height=44&name=will-curran-avatar.jpg)

Will Curran Jul 30, 2026 · 7 min read

<https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fwillcurran.com%2Fblog%2Feink-truck-dashboard>

On this page

![An abstract e-ink panel in its no-data state: a dash where the charge number would be, an empty bar and a row of outlined question marks, above a dashed line drawing of a pickup, beside the title Missing is not zero.](https://willcurran.com/hs-fs/hubfs/will-curran-brand/eink-truck-dashboard-featured.png?width=1600&height=900&name=eink-truck-dashboard-featured.png)

In July I built an e-ink dashboard for my Rivian in one day. It sits on a TRMNL and shows charge, range, cabin temperature and anything left open. Most of the work went into one question: what should the screen do when it doesn't know?

## A readout that lies is worse than no signal

I spent years in live events. The readouts I trusted at a show all said so when the feed dropped. A meter that freezes on its last value, or falls to zero, looks exactly like a real reading. A console that says "no signal" sends you to check the cable. Mid-show, the second one is the one you want.

A dashboard on a wall has the same problem with lower stakes. If the data stopped arriving hours ago, the screen should look different from a healthy one, from across the room.

## Six states, one small screen

The data comes from Home Assistant, so only my own hardware talks to Rivian. Home Assistant pushes a small payload to TRMNL, and TRMNL draws it. I designed the screen around six states:

- **Quiet.** Everything is fine. The checks are a row of ticks and the truck diagram is a plain outline.
- **Open.** The open panel fills solid, drawn opening the way it really opens. The failing check inverts, and open items are listed in words.
- **No data.** Numbers become a dash. Checks become an outlined question mark, which can't be mistaken for a pass or a fail. The truck outline goes dashed, so the picture stops implying every door is closed, and a badge says there is no data.
- **Stale.** A filled badge says how long since the last update, and the timestamp always carries the date.
- **Low.** A real 0% still warns. A missing value never does.
- **Degraded charging.** When the live charge rate isn't available, the kW figure is dropped instead of shown as zero. Energy added is estimated and marked with a tilde, so an estimate never passes for a meter reading.

![Six e-ink screen renders in a two-by-three grid, all with invented sample data. Quiet/charging: 64% with a row of ticks and a plain truck outline. Something open: 41%, Locked and Closed checks inverted, the open panels filled solid and listed in words. No data: dashes for every number, outlined question marks for every check and a dashed truck outline. Stale: 72% with a No update 3d badge and a dated timestamp. Degraded charging: 64% with the kW figure dropped. Low charge: 9% with a Low charge badge.](https://willcurran.com/hs-fs/hubfs/will-curran-brand/eink-truck-dashboard-state-grid.png?width=1440&height=1768&name=eink-truck-dashboard-state-grid.png)

## Missing is not zero

Home Assistant reports a sensor that didn't answer as unknown or unavailable. The common template shortcut turns anything that isn't a number into 0. That shortcut was in the first version, and it was wrong in the worst way: a dead feed showed 0% charge, a low-charge warning, and a clean row of passing checks. The screen looked urgent and confident about a truck nobody was talking to.

The fix is a rule every field follows. A value is a value, an explicit "no" is a no, and nothing stays nothing all the way to the pixels.

Two more bugs had the same shape. A missing lock value drew a failed lock check, and missing tire flags drew tire problems. The template language's default filter treats false as blank, so it can't tell "no" from "nothing". The only safe test is an explicit check for an empty value. Separately, the lock sensor's on and off turned out to be inverted: on meant unlocked.

The tests had passed through all of it. They checked that missing data raised no warning, and it didn't. It drew passing checks instead, and the tests had been written to accept that. Now they check both directions: an empty payload must produce neither a pass nor a fail, and a genuine 0% must still warn.

## A frozen screen looks exactly like a live one

TRMNL skips data that hasn't changed before it renders. That is sensible for e-ink, and it means that if the feed dies, the last image stays on the wall forever, identical to a live one, until the payload changes.

No template logic fixes that, because the template never runs. So the fix has two parts. While Home Assistant is up but the truck has gone quiet, it also sends how long since anything reported, in coarse steps. A healthy feed always sends the same value, so nothing redraws needlessly; a dying feed sends a new one, which gets through, and the stale badge appears. If Home Assistant itself is down, nothing can redraw, so the last image has to be unambiguous on its own. A timestamp with the date on it reads correctly a minute later or two weeks later.

## Twelve posts an hour, plus one

TRMNL accepts 12 webhook posts an hour on the free tier and 30 on TRMNL+. Go over and it answers with a 429. The payload limit is 2 kB; this one sends about 1 kB.

The obvious throttle is a minimum gap between sends. The catch is that a gap of `g` seconds allows `floor(3600/g) + 1` sends in any rolling hour, because a one-hour window can catch a send at both ends. A 290-second gap looks safe and allows 13, which earned 429s. At 330 seconds the math gives 11, one spare under the cap. On TRMNL+ the gap can drop to 130 seconds.

![Two rows of send ticks across one hour. With a 290 second gap the hour holds 13 sends, the 13th at 58:00, one over the dashed limit line at 12, so it gets a 429. With a 330 second gap it holds 11 sends; the next would come at 60:30, outside the hour, leaving one to spare.](https://willcurran.com/hs-fs/hubfs/will-curran-brand/eink-truck-dashboard-rate-limit.png?width=1440&height=966&name=eink-truck-dashboard-rate-limit.png)

Every send goes through one queue that holds the gap, so the 30-minute heartbeat uses one of the 11 slots instead of adding a twelfth. Anything you would act on (something opened, the truck unlocked, charging started, the feed died) waits its turn. Routine drift, like charge ticking up a point, is dropped and rides along on the next send.

One more trap: turning on TRMNL's diagnostic logging for a plugin raises the limit for a while. Tune your interval with it on and everything works, until it expires a day later and the 429s start with nothing changed on your side.

## The $20 you find out about late

A private plugin that receives a webhook needs TRMNL's Developer add-on. It is a one-time $20, and it is per device, so two TRMNLs showing the truck cost $40. Know that before you wire anything up.

## The parts that were not magic

Rivian removed a field from its API in June. Because the integration asks for the whole vehicle state at once, one dead field took down every entity. A community fix exists for the integration. That is exactly the failure the no-data screen was built for.

As of October the automations are on and Home Assistant is pushing. The truck's sensors in Home Assistant are unavailable, so its live readings arrive empty instead of as 0. For a dashboard built around this exact failure, that is the right answer.

## If you are about to try this

- **Design the no-data screen first.** It is the state most likely to fool you.
- **Carry empty values all the way to the display.** Any shortcut that turns missing into zero will eventually raise a false alarm.
- **Test in both directions.** Missing data must not warn, and it must not pass either.
- **Put the date in the timestamp,** and do the rate-limit math with the plus one, from the published limits.

## Build it yourself

What it does: an e-ink dashboard for an electric vehicle that shows charge, range, cabin temperature and anything open, and looks clearly different when its data is missing or old. The parts:

- **A TRMNL** with the Developer add-on ($20 one-time, per device), so it can run a private webhook plugin.
- **Home Assistant** with an integration that reports your vehicle's state.
- **A template on each side.** Home Assistant builds a small JSON payload where missing fields stay empty. The plugin template draws each field as a value, an explicit no, or unknown.
- **One queued send script** in Home Assistant that holds the minimum gap, lines up urgent changes and drops routine ones.

How the pieces fit: Home Assistant builds the payload, including a coarse "time since last report" value, and pushes it through the one queue. TRMNL renders whatever arrives.

Give this to your AIBuild me a private TRMNL webhook plugin that shows my electric vehicle's charge, range, cabin temperature and any open doors or panels, fed from Home Assistant. Every field must be one of three things: a value, an explicit false, or null. Never coerce unknown or unavailable to 0. In the template, test with explicit nil, true and false comparisons, not a default filter. Null numbers render as a dash, null checks as an outlined question mark, and if closure data is missing the vehicle outline goes dashed. A real 0% charge still warns; a null never does. Because TRMNL skips unchanged payloads, include a coarse "minutes since last report" bucket so a dead feed changes the payload and shows a stale badge, and always print the date in the timestamp. Send everything through one queued script with a minimum gap g where floor(3600/g)+1 stays under the hourly webhook limit. Urgent changes queue; routine ones are dropped. Write tests that fail if missing data shows either a pass or a fail.

If you build one, or you have a readout of your own that lies to you, I would like to hear about it.

NoteWritten October 2026, about work from July 2026.

Filed under [Builds](https://willcurran.com/blog/tag/builds)

![](https://willcurran.com/hs-fs/hubfs/will-curran-brand/will-curran-avatar.jpg?width=64&height=64&name=will-curran-avatar.jpg)

Will Curran

I spent sixteen years building one company, and I have been in the event industry since 2007. These days I write about the technology I actually use — trucks, radios, home infrastructure, and the tools that survive past the first invoice.

[All posts by Will →](https://willcurran.com/blog/author/will-curran)

Get the next one by email

One email per post. No digest, no sponsors.

[← Previous We Brought a Weather Bot to a Festival. It Died on Day Three.](https://willcurran.com/blog/festival-weather-bot) [Next → How I Rebuilt willcurran.com With Claude Design and Claude Code](https://willcurran.com/blog/how-i-rebuilt-my-website-with-ai)

## Related reading

[Builds How I Rebuilt willcurran.com With Claude Design and Claude Code 8 min read](https://willcurran.com/blog/how-i-rebuilt-my-website-with-ai)

[Will Curran](https://willcurran.com/)

[LinkedIn — 29,339+ followers](https://www.linkedin.com/in/willcurran) [GitHub](https://github.com/rancur) [YouTube](https://www.youtube.com/channel/UCJgscc27l4fpkWx1SeK05xg/) [Spotify](https://open.spotify.com/user/rancur) [SoundCloud DJ mixes](https://www.soundcloud.com/thewillsee) [Twitch](https://www.twitch.tv/thewillsee) [Last.fm](https://www.last.fm/user/default) [Trakt — movies & TV](https://trakt.tv/users/willcurran) [Goodreads](https://www.goodreads.com/user/show/55302838-will-curran) [Will See Music](https://www.thewillsee.com) [Arizona Meshtastic](https://azmsh.net) [Electric Forest Meshtastic](https://efmesh.com)

© 2026 Will Curran — All rights reserved. [RSS](https://willcurran.com/blog/rss.xml) [Back to top](https://willcurran.com/blog/eink-truck-dashboard#main)

```json
{
  "@context" : "https://schema.org",
  "@type" : "BlogPosting",
  "author" : {
    "@type" : "Person",
    "name" : "Will Curran",
    "url" : "https://willcurran.com/blog/author/will-curran"
  },
  "dateModified" : "2026-10-21T22:42:28.444Z",
  "datePublished" : "2026-07-30T16:00:00.000Z",
  "headline" : "An E-Ink Truck Dashboard That Admits When It Doesn't Know",
  "image" : [ "https://willcurran.com/hubfs/will-curran-brand/eink-truck-dashboard-featured.png" ],
  "mainEntityOfPage" : {
    "@id" : "https://willcurran.com/blog/eink-truck-dashboard",
    "@type" : "WebPage"
  },
  "publisher" : {
    "@type" : "Organization",
    "logo" : {
      "@type" : "ImageObject",
      "url" : "https://willcurran.com/hubfs/image-1.png"
    },
    "name" : "Will Curran LLC"
  }
}
```