A tiny physical dashboard that shows your Claude.ai usage limits on a 0.96" OLED. Built for the Waveshare ESP32-S3-Zero — a 23.5×18 mm board — driving a two-color (yellow/blue) SSD1306. Shows the current 5-hour session usage, 7-day cap, a live countdown to reset, and an animated face that reacts to how close you are to your limit. No API key required.
| Component | Notes |
|---|---|
| ESP32-S3-Zero | Waveshare, ESP32-S3FH4R2: 4 MB flash, 2 MB PSRAM (PSRAM unused), native USB-C |
| SSD1306 OLED 128×64, I2C | Two-color variant: top 16 px yellow, bottom 48 px blue (a plain mono SSD1306 works too) |
| USB-C cable | Flashing + power |
| Breadboard + 4 jumper wires | No resistors — SSD1306 breakout modules have onboard I2C pull-ups |
The two-color panel is electrically identical to a monochrome SSD1306 — the yellow/blue split is just a dye band on the glass, with a small unlit seam at the boundary (~rows 14–16). The layout is designed around that seam so nothing important lands in it.
OLED ESP32-S3-Zero
──── ─────────────
VCC → 3V3
GND → GND
SDA → GPIO 8
SCL → GPIO 7
- GPIO 8 / GPIO 7 are used for I2C. GPIO 9 is avoided (flash-adjacent on this module) and GPIO 21 is reserved for the onboard WS2812 LED (not used by this firmware).
- Pins are centralized in
src/config/config.h— changePIN_SDA/PIN_SCLthere if you wire differently.
Each breadboard row of 5 holes is one shared electrical node, so you can run 4 direct point-to-point jumpers with no power rails at all:
- Seat the S3-Zero across the center gap, USB-C port overhanging one edge.
3V3→ OLEDVCC,GND→ OLEDGND,GPIO8→ OLEDSDA,GPIO7→ OLEDSCL.- Verify each pin by the silkscreen label, not just position — header order varies by seller/batch. Cross-check against the espboards.dev pinout.
- Only power on over USB-C once all four jumpers are seated (avoids shorting VCC/GND through a half-placed wire).
Live capture from GET /api/screen, rendered in the panel's two colors.
- Yellow strip: centered
CLAUDE USAGEtitle + a full-width progress bar for your primary metric. - Blue area: the large primary percentage (5-hour if your plan has one, else 7-day), the animated face top-right, then the 7-day cap with its days countdown, then the live reset countdown.
Every row is individually toggleable in the web portal under Display Settings.
A small face lives top-right in the blue area. It winks every 7 seconds (left eye, then right, then they reopen in the same order), and its mouth follows your usage — same metric as the primary %:
| < 30% | 30–60% | 60–80% | > 80% |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
| Smile | Open | Flat | Sad |
If the face looks sad, you're about to hit your cap.
- VS Code with the PlatformIO extension
- PlatformIO downloads all library dependencies automatically on first build
- Clone or download this repo and open the folder in VS Code (PlatformIO detects
platformio.iniautomatically). - Click Build (checkmark) — first build downloads the toolchain (~5 min).
- Connect the S3-Zero via USB-C.
- Click Upload (arrow).
The S3-Zero has no auto-reset-into-bootloader circuit. If flashing can't connect, or the board rapidly connects/disconnects (audible USB "beep-beep" loop), put it in download mode manually:
Hold
BOOT, tapRESET, then releaseBOOT.
Then click Upload again. After a successful flash it resets into the app normally.
Reflashing an already-configured device: if an update changes the settings storage format (noted in that update's changelog/commit), the device wipes its saved settings once on first boot after the flash — you'll redo First-Time Setup below. This is a one-time reset per format change, not a routine reflash behavior.
After flashing, the device starts a WiFi access point named ESP32-Claude-Dashboard. The OLED shows ! ERROR / Set WiFi SSID until configured — that's expected.
Do this before connecting to the device AP, while on your normal WiFi:
- Open claude.ai in Chrome, logged in.
- Open DevTools (
F12) → Network tab. - Refresh, click any request to
claude.ai. - In Request Headers, find Cookie and copy its entire value.
Cookie: sessionKey=sk-ant-...; __cf_bm=...; other=...
^^^^^ copy this entire value
The ESP32 impersonates a logged-in browser session. The cookie is valid as long as you stay logged in to claude.ai. If you log out or it expires, repeat this step.
- Connect to WiFi ESP32-Claude-Dashboard (password:
dashboard1). - Open
http://192.168.4.1. - Under Connection Settings → Add a Network, click Scan for Networks, click your network in the dropdown, and enter its password when prompted — it's saved immediately as a known network. (Hidden network? Type its name + password into the fields below the scan button instead.)
- Paste your Claude cookie under Claude Cookie Header.
- Click Save Settings.
The device connects and fetches live data within ~10 seconds. The OLED updates automatically.
The device remembers up to 5 WiFi networks (like a phone) and auto-connects to whichever one is actually in range — no need to reconfigure every time you move it.
- Add more networks anytime: connect to the
ESP32-Claude-DashboardAP (it's always reachable, regardless of which — if any — known network is currently in range) and repeat the scan-and-add step above for each new location. - Bring it to a known network: power it on there and it reconnects on its own, usually within 30 seconds — nothing to do.
- Forget a network: click Forget next to it in the Known WiFi Networks list in the portal.
Under the hood, the device always keeps its own hotspot in plain AP mode by default, and only switches into AP+STA mode when a scan actually confirms a saved network is in range — so a stale or out-of-range network can never make the portal unreachable. Retries happen on a bounded 30-second schedule, never continuously.
Connect to the ESP32-Claude-Dashboard AP and open http://192.168.4.1.
| Section | Description |
|---|---|
| Live Preview | Canvas rendering of the current OLED layout, updates every 30 s |
| Display Settings | Toggle each row on/off |
| Connection Settings | Known WiFi networks (add/forget, up to 5), session cookie, refresh interval, AP password |
| Refresh Data | Force an immediate fetch |
| Reset Defaults | Wipe all settings back to factory defaults |
Bonus:
GET /api/screenreturns the raw 1024-byte SSD1306 framebuffer — the exact pixels currently on the OLED.
| Setting | Default | Description |
|---|---|---|
| Refresh interval | 30 000 ms | How often to poll claude.ai |
| AP password | dashboard1 |
Password for the device WiFi AP |
| Session cookie | (empty) | Full Cookie header from claude.ai DevTools |
| Known WiFi networks | (empty, up to 5) | Auto-connects to whichever is in range — see "Taking the Device Somewhere Else" |
Board rapidly connects/disconnects ("beep-beep" loop), flash won't take
- Enter download mode manually: hold
BOOT, tapRESET, releaseBOOT, then Upload.
Boot loops with partition table / Failed to verify partition table on serial
- The board definition defaulted to an 8 MB partition scheme but this chip has 4 MB. This repo already pins the fix in
platformio.ini(board_upload.flash_size = 4MBandboard_build.partitions = default.csv) — make sure those lines are present.
OLED shows nothing
- Check wiring: SDA → GPIO 8, SCL → GPIO 7, VCC → 3V3 (not 5V).
- Verify the I2C address is
0x3C; some modules use0x3D— editconfig.h.
Content clipped at the yellow/blue boundary
- That's the physical seam (~rows 14–16). The layout avoids it by design; if you re-position elements, keep the yellow content at y ≤ 13 and blue content at y ≥ 17.
OLED shows Set WiFi SSID / Set session key
- Configure the device at
http://192.168.4.1(see First-Time Setup).
Hotspot appears in WiFi lists but won't let you in / 192.168.4.1 won't load
- Fixed as of the multi-network update: earlier firmware kept retrying a single fixed SSID indefinitely in the background, which could destabilize the AP's own radio channel if that network was out of range or misspelled. The device now only attempts STA connections on a bounded 30-second schedule, gated by an actual scan confirming a known network is present — the AP itself should now always be reachable regardless of WiFi state. If you're still on older firmware, reflash to pick up the fix.
OLED shows API Error
- The session cookie likely expired — repeat Step 1. Open the Serial Monitor (115200 baud) for HTTP error codes.
Build fails with cc1plus.exe: CreateProcess: No such file or directory
- Corrupted toolchain download. Run in PowerShell, then rebuild:
Remove-Item -Recurse -Force "$env:USERPROFILE\.platformio\packages\toolchain-xtensa-esp-elf"
The firmware compiles for any ESP32-family board. Typically only three things change: the board line, the PSRAM/USB build flags, and the I2C pins.
| Board | board = value |
build_flags notes |
|---|---|---|
| ESP32 classic (Wemos D1 Mini32, NodeMCU-32S) | esp32dev |
remove -DARDUINO_USB_CDC_ON_BOOT=1 |
| ESP32-S3 (8 MB+ devkit, PSRAM) | esp32-s3-devkitc-1 |
add -DBOARD_HAS_PSRAM; drop the 4 MB partition lines |
| ESP32-S3-Zero (this build) | esp32-s3-devkitc-1 |
keep board_upload.flash_size = 4MB + board_build.partitions = default.csv |
| ESP32-S2 | esp32-s2-saola-1 |
— |
| ESP32-C3 | esp32-c3-devkitm-1 |
remove -DARDUINO_USB_CDC_ON_BOOT=1 |
| ESP32-C6 | esp32-c6-devkitc-1 |
remove -DARDUINO_USB_CDC_ON_BOOT=1 |
-DBOARD_HAS_PSRAM— only add it if your module has PSRAM and you need it. This firmware doesn't, so it's omitted even though the S3-Zero's chip physically has 2 MB.- Partition sizing is the #1 gotcha on small-flash boards: if your chip's flash is smaller than the board definition assumes, the bootloader rejects the partition table and crash-loops. Pin
board_upload.flash_sizeandboard_build.partitionsto match.
#define PIN_SDA 8 // ← change to match your wiring
#define PIN_SCL 7 // ← change to match your wiringAny free GPIO works. Common defaults: ESP32 classic 21/22, ESP32-S3/S2 8/9, ESP32-C3 8/9, ESP32-C6 6/7.
- ESP8266 — different
WiFiClientSecureAPI (TLS often fails against Cloudflare), no NVSPreferences, andWIFI_AP_STAbehaves differently. - Non-ESP MCUs (Uno, STM32, etc.) — the project needs WiFi + HTTPS/TLS, ~50 KB free RAM, and the Arduino framework. Boards like the Uno R4 WiFi or RP2040 W are possible in principle but require porting the WiFi/TLS/storage layers.
claude.ai ──HTTPS──> ESP32-S3-Zero ──I2C──> OLED
(session cookie)
|
└──WiFi AP──> Browser (192.168.4.1)
The ESP32 calls claude.ai/api/organizations/{org}/usage directly using your browser session cookie. No backend server, no API key, no cloud service. The org UUID is auto-discovered on first fetch and cached in flash (NVS).
- Change the default AP password. Ships as
dashboard1; anyone in WiFi range who knows it can open the portal and read/replace your cookie. Set a strong one under Connection Settings → AP Password. - No TLS certificate verification. The device uses
setInsecure()(loading a full CA bundle costs too much flash), so a machine-in-the-middle on your local network could intercept the cookie. Low risk on a trusted network. - Credentials live only on the device. Session cookie, WiFi password, and AP password are stored in the ESP32's NVS — never in source or config files.
- The settings portal is HTTP, not HTTPS. Use it only while connected to the device's own AP.
MIT




