How I Turned an E-Ink Reader Into a Victron Battery Monitor
A wall display that updates once an hour, keeps the Victron system closed, and never touches your phone
I built my own house out of straw and clay, and I run entirely on solar batteries for power. Off-grid living turns you into someone who checks state of charge the way other people check their phone battery. So at some point you stop wanting an app for that and start wanting an object.
Victron already gives you a dashboard, VRM (their cloud monitoring portal), and a phone app, and both are fine for actually diagnosing something. But when I walk past the electrical panel and just want a number right now, state of charge, voltage, what the solar is producing, pulling out my phone and opening an app is already too much friction. I wanted something on the wall that shows that number without me doing anything.

Why the Phone App Wasn't Enough
The obvious candidate was e-ink. It stays readable without backlight, the image stays etched on the screen even powered off, and the power draw is close to nothing. I picked up a Xteink X3, which is really just an e-reader. Good screen, cheap, and completely uninterested in being a smart display.
The catch: when the X3 goes to sleep, it cuts Wi-Fi entirely. It's not listening to anything. So how do you get a device that spends most of its life asleep, with no network connection, to show a state that refreshes itself once an hour without me ever touching it?
That's the whole problem this build had to solve. Not writing another Victron app. Making a device whose entire selling point is "off" behave like something is watching continuously.
You can't push data to a device that's asleep. You can only make it check.
The short answer: flip the model. Instead of the screen listening for updates, the screen wakes itself up on a timer, goes and fetches a state, draws it, and goes back to sleep. This isn't some background agent quietly plotting on its own, Skynet it is not. It's an e-reader that naps until an alarm tells it to check in. E-ink holds the last image with zero power, so between wake cycles nothing needs to run at all.
3 Small Pieces, Not a Single App
Collecting the Victron state. Victron's GX device (the small onboard computer that tracks the battery, inverter, solar input, and load) already knows everything. I query it on the local network first. If it's unreachable, I fall back to VRM, read-only. The GX itself is never exposed to the internet. It's the one reaching out to the cloud, not the other way around.
Serving a plain JSON. The screen doesn't need to know anything about Victron, no API token, no installation ID. It makes a single HTTPS request to a tokenized address and gets back a short payload: state of charge, voltage, solar, load, a timestamp, and 48 points for the chart.
A firmware that knows how to wake itself up. The X3's stock firmware already knows how to go into deep sleep and paint a static sleep screen. It doesn't know how to wake up on its own, fetch anything, or repaint a live display. That's the entire gap I had to fill.
If you're looking for a first project to actually get hands-on with AI coding rather than just prompting a chatbot for snippets, this is a solid shape for one: a real constraint, a small firmware, and a result you can look at on your wall.
The 3 Spots I Touched
I didn't write a new device. I took the X3's existing firmware and touched 3 places: what causes the wake, the timer set right before it sleeps, and a module that paints the Victron screen. Everything else (the reader, the saved Wi-Fi, the display driver, the SD card) stays untouched. Splitting the change into small isolated pieces instead of one blob is the same approach behind letting Claude Code run the boring parts of an automation build, you scope each piece tight enough that nothing else can break.
A single file on the SD card, and that's it. Monitor mode only turns on if a small text file exists on the card, with one line: the HTTPS address of the JSON. No file, no behavior change at all, the X3 acts exactly like it did out of the box. A second, optional file sets the interval, anywhere from 5 minutes to 24 hours. Default is 1 hour.
Telling the button apart from the timer. On boot, the processor knows why it woke up.
- Power button: open the reader, same as always.
- Timer: skip the UI entirely, repaint the Victron screen, go straight back to sleep.
- USB plugged in during sleep: go back to sleep, but re-arm the timer so the hourly rhythm doesn't drift.
The button isn't sacrificed anywhere in this. You can still turn the thing on and read a book. The monitor only lives on the timer path.
Arming the Timer Before It Sleeps
The stock firmware only wakes on the button (and, on some boards, USB). There's no internal alarm at all. So right before entering deep sleep, I arm a processor timer in microseconds. If the address file is missing, that timer never gets armed, zero change in behavior. If it's there, the firmware schedules the next wake (3,600 seconds by default), kills Wi-Fi, puts the display to sleep, and halts the processor.
That's the single real addition on the shutdown side: a single line that says wake me up in an hour.
// Only arm the hourly wake if a Victron endpoint is configured.
// Without this file present, behavior is 100% stock.
if (config_has_victron_url()) {
set_wake_timer(interval_seconds); // default 3,600
wifi_off();
display_sleep();
} else {
display_default_sleep_screen();
}
deep_sleep();
The Hourly Routine, Start to Finish
Every timed wake runs the same small module, in order.
- Read the HTTPS address off the card. Missing means stop, this isn't a monitor.
- Load the last known JSON, if one exists, as a fallback.
- Join the already-saved Wi-Fi network, starting with the last one used. Roughly 20 seconds before giving up. Miss the handshake and it's a straight "you died" moment: no retry loop, no drama, just the last known state back on the wall.
- Download the JSON, a few kilobytes, no Victron token anywhere on the device.
- Parse it. Invalid data means show an error and leave the old cache alone.
- Write the new JSON to the card, then paint the screen: title, a large state of charge, voltage, solar, load, a chart of the last 48 hours, a QR code, date and time.
The chart isn't a local history log. Every hour I get up to 48 points and redraw the whole thing, bars, line, labels. The QR code is generated on the device, not a stored image.
\
E-ink does a full refresh here, not a partial one, so the image holds afterward with zero current draw.
Skipping the Old Sleep Screen
When you press the button to turn the X3 off, it normally paints its stock sleep screen, whatever image or cover is set. If the Victron file is present, I skip that paint entirely. The same module the timer uses runs instead, so the first Victron screen doesn't wait an hour. It shows up the moment the device goes to sleep, and the hourly timer gets armed right after.
What Ends Up on the Wall
Top of the screen: battery level, then the state of charge in very large text. Underneath: voltage, solar production, load. Center: the 48-hour charge curve. Bottom right: a QR code. To its left: the date and time of the last update.
Nothing else. No menu, no interactive chart, just a glance and you move on.
What I Left Out on Purpose
- No opening the GX to the internet. It talks out, nothing talks in.
- No Victron secret stored on the screen itself.
- No backlit display running 24 hours a day.
- No alert firing every hour. If the GX goes quiet, the update just gets skipped, no noise generated.
- No dependency on the file being present. Remove it and the X3 is a stock e-reader again, nothing lost.
Breaking the firmware work into 3 small, isolated changes instead of a single big rewrite is what made this a weekend build instead of a multi-week one. If you want the full step-by-step method for taking something like this from a rough idea to an actual working object, that's the exact territory covered in Vibe Coding, For Real. It's roughly the same shift I wrote about when I went from vibe coding to something that actually ships.
The best dashboard is the one you don't have to open.
The screen does one thing, and it does it well. It sleeps, wakes on its own once an hour, pulls a state, draws it, goes back to sleep. No app to open, no display burning power all day, and the Victron install stays exactly as closed as it was before I touched it.
So what would you put on yours?
Sources
- Victron Energy's VRM portal and GX documentation
- Xteink X3 e-ink reader (base hardware)
This post may contain affiliate links. If you click them, I might earn a small commission — costs you nothing, and helps me keep shipping quality articles every day for your reading pleasure.
Building real hardware that talks to production systems means treating it like a product from day one, not a prototype. The demo-vs-product checklist in the welcome kit covers the 8 concrete criteria that separate 'it works on my wall' from 'it actually ships' (staging, rate limiting, error recovery, auth, monitoring, tests).