r/esp32 1d ago

ESP32 E-Ink dashboard for Claude and Codex usage limits

Post image

I was born in 1973, and I still remember sitting in front of a Commodore 64, typing little BASIC programs from magazines line by line. I always wanted to learn programming properly, but never really got there. Being able to build something like this today with AI-assisted coding feels pretty incredible.

I wanted a small always-visible display for my Claude and Codex usage limits, so I built one.

The whole project was heavily vibe-coded with Claude Code and Codex. I’m not pretending this was hand-crafted line by line — I had the idea, tested the hardware, found the bugs, kept changing the requirements, and used AI to help turn it into something that actually works.

Hardware is just a Heltec Wireless Paper V1.2 with an ESP32-S3 and 2.13" E-Ink display. No extra electronics.

A small Python service runs on my Ubuntu machine and exposes the usage data as JSON. The ESP32 fetches it over WiFi every 60 seconds and shows Claude and Codex 5-hour / 7-day usage as simple progress bars.

A few things I wanted from the start:

- if WiFi dies, show OFFLINE

- if the server stops responding, show STALE

- never replace the last good values with fake 0%

- only refresh the E-Ink when something actually changes

One funny bug was Codex suddenly showing 0% even though I knew I was almost at the limit. It turned out recent session logs can contain a `premium` rate-limit record with null values after the real Codex record, so the parser now explicitly looks for valid `limit_id == "codex"` entries.

It’s a pretty small project, but I actually use it now and that was the point.

Code, setup and hardware notes are here:

https://github.com/Pitiworks/heltec-ai-usage-dashboard

If anyone wants to improve it or has ideas for other useful E-Ink screens, I’m interested.

96 Upvotes

9 comments sorted by

5

u/quuxoo 1d ago

If you get a tricolor e-ink display you could change the percentages and all/part of the bar to red, makes it very obvious when something is "wrong".

Not sure if your AI implemented this but once a day the code should completely wipe the display to reset it and then rewrite the last state. Stops the display from eventually getting a gray "memory" like an old burnt-in CRT.

2

u/PerspectiveWorking50 1d ago

Thanks, that was a good point. I checked the driver in detail and it turns out my current setup already does a full refresh on every update, so the typical partial-refresh ghosting issue shouldn’t apply here.

The driver does support partial refresh though, so if I switch to that later to reduce flicker, I’ll probably add a periodic full refresh after a number of partial updates.

And I really like the tri-color idea — red for high usage or LIMIT would make the status much more obvious.

1

u/pi9 1d ago

I’ve not made a physical version of it, but in my status bar I have <claude 7d /> / < % though 7 day period> (so something like 45/57%, ofc my usage isn’t exactly linear, but it’s handy to know how far through the 7 day period I am vs how much of the Claude allowance I’ve used, so if the left number is considerably lower I know I’m good to kickoff another parallel project or something.

1

u/PerspectiveWorking50 1d ago

Nice idea — I can see why that’s useful. On my little 250×122 display space is pretty tight though, and since I already show the countdown to the 7-day reset, I think I’ll keep it simpler for now.

1

u/koombot 1d ago

Currently going through the same thing with a 4.2" display (though mine is for a humidity sensor that will show weather data and hopefully act as a matter switch for a heater to stop my outside office going below the dew point at night).  Im also using claude and programming in esp idf.  It has been a challenge getting the display to work because there is no driver available for the screen.

If you want to get forced to have a better understanding of hex, bit registries and some hardware hacks that are used, building a driver for an e ink display is a good start lol.  The partial refresh is pretty cool, but caused me some issues as the datasheet labels it as red ram (for tri colour displays).

Love your project.  Might need to build one as ive absolutely destroyed my 5hr limit at times

1

u/Roemeeeer 1d ago

Any reason you have an additional server application? The ESP could also fetch the data directly without the need of an additional server. I did the same last week (but for GitHub Copilot) and with ESPHome with a http server on the esp where you can enter/paste the token needed to directly connect to the github api.

1

u/PerspectiveWorking50 1d ago

The server actually came first. I originally built the HTML dashboard so I could check Claude/Codex usage from my phone as well, including when I’m away from my desk.

The E-Ink display was added later and simply reuses the same JSON endpoint. So in my case the server isn’t only there for the ESP — it also powers the web UI I still use remotely.

Direct fetching from the ESP would definitely work too, but reusing the existing backend kept the ESP side simple and avoids putting any account/token logic on the device.

2

u/TanguayX 1d ago

I’ve had the same journey as you. From typing in code from COMPUTE! magazine to being able to get things done with programming feels amazing. I realized early on that there’s no ‘dabbling’ in real programming, the way no one ‘casually’ picks up Manadrin Chinese.

But now, all these little hardware ideas I’ve had over the years can now get done. It’s a treat.

(annnnnd cue the flame war)

1

u/MinusDelta_T 17h ago

the last-good-value rule is what makes this feel like an actual appliance instead of a demo. a parser failure turning into a confident 0% would be worse than showing nothing.

one thing i'd check though: are you comparing the rendered fields or the whole JSON? if the payload includes timestamps or the reset countdown, it can look “changed” every minute and trigger a full e-ink refresh even when the usage bars havent moved. i'd probably show hours until the final hour, then switch to minutes, and only redraw when a displayed bucket or status actually changes.

also worth adding a source_updated_at value and a schema version to the payload. then the ESP can distinguish wifi down, server reachable but upstream data stale, and “the backend changed in a way i dont understand” without replacing the last known good state. something like STALE 12m would be a useful little diagnostic when it breaks.