Are you the author? Sign in to claim
Open-source ESP32 dashboard for monitoring Claude Code and Codex usage, limits, and status in real time.
Portuguese / Português do Brasil
Local token and limit dashboard for an ESP32 2.8-inch TFT touch display. The firmware shows local Codex usage and Claude Code status by polling a small HTTP bridge running on your computer.
The ESP32 does not call OpenAI, Anthropic, Codex, or Claude APIs directly. Personal Codex and Claude Code accounts do not expose a complete public per-user usage API, so this project reads local data sources:
~/.codex/state_5.sqlite.statusLine output written to server/claude_status.json./api/codex, /api/claude, /api/summary, and /api/health.statusLine installer for live 5-hour / 7-day status when available.Recommended hardware:
Board purchase link used for this build:
Confirm the seller listing, pinout, display driver, and touch controller before buying, because similar ESP32 display boards can use different wiring.
Optional printed support:
Check the MakerWorld model page for print files, fit, license, attribution, and remix rules before printing or sharing modified parts.
statusLine.
+- src/
| +- main.cpp # ESP32 firmware, LVGL UI, Wi-Fi, HTTP polling
| +- logo_assets.c # Generated LVGL image assets
+- imgs/
| +- project-logo.png # AI-generated project logo
| +- codex.png
| +- claudecode.png
+- include/
| +- config.h # Defaults and local override hook
| +- config.example.h
| +- lv_conf.h
+- scripts/
| +- load_env.py # Injects .env values into PlatformIO build flags
| +- install_claude_statusline.py
| +- claude_statusline.ps1
| +- generate_logo_assets.py
+- server/
| +- run_server.py # Local HTTP bridge
| +- config.example.json
+- platformio.ini
+- README.md
+- README.pt-BR.md
Local files intentionally ignored by Git:
.env
.pio/
.vscode/
server/config.json
server/claude_status.json
include/config.local.h
__pycache__/
*.pyc
~/.codex/state_5.sqlite exists.Quick checks:
py -3 --version
& $env:USERPROFILE\.platformio\penv\Scripts\platformio.exe --version
If PlatformIO is not installed, install it through the VS Code extension or with Python:
py -3 -m pip install platformio
Install Claude Code:
irm https://claude.ai/install.ps1 | iex
If the installer says C:\Users\YOUR_USER\.local\bin is not in your PATH, add it:
$claudeBin = Join-Path $env:USERPROFILE ".local\bin"
$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
if (($userPath -split ";") -notcontains $claudeBin) {
[Environment]::SetEnvironmentVariable("Path", "$userPath;$claudeBin", "User")
}
$env:Path += ";$claudeBin"
Test it:
claude --version
claude
Do not run Claude Code with --bare or --safe-mode for this project, because those modes can ignore customizations such as statusLine.
From the project root:
cd C:\Users\YOUR_USER\Documents\codes\codex-claude-project
py -3 scripts\install_claude_statusline.py
The installer updates:
C:\Users\YOUR_USER\.claude\settings.json
It installs a command similar to:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "...\scripts\claude_statusline.ps1"
Open Claude Code normally and send any message. Then verify that the local status file exists:
Test-Path .\server\claude_status.json
Get-Content .\server\claude_status.json
If it does not exist, restart the terminal, run claude, send a simple message, and run the installer again if needed.
Create a local .env file in the project root. Do not commit it.
Example:
WIFI_SSID=your-network
WIFI_PASSWORD=your-password
CODEX_BRIDGE_URL=http://192.168.1.50:8787/api/codex
CLAUDE_BRIDGE_URL=http://192.168.1.50:8787/api/claude
USE_CODEX=1
USE_CLAUDE=1
USAGE_WINDOW_DAYS=7
REFRESH_INTERVAL_MS=5000
CODEX_TOKEN_LIMIT=0
CLAUDE_TOKEN_LIMIT=0
DEVICE_NAME=TokenMeter
Use the IPv4 address of the computer running the bridge. Do not use localhost in ESP32 firmware, because localhost would point to the ESP32 itself.
Find your PC IP:
ipconfig
The .env file is loaded by scripts/load_env.py during the PlatformIO build. Rebuild and upload the firmware after changing .env.
Start the bridge:
py -3 server\run_server.py
It listens on:
http://0.0.0.0:8787
Endpoints:
http://localhost:8787/api/codex
http://localhost:8787/api/claude
http://localhost:8787/api/summary
http://localhost:8787/api/health
Test locally:
irm http://localhost:8787/api/codex
irm http://localhost:8787/api/claude
irm http://localhost:8787/api/summary
Test the IP used by the ESP32:
irm http://192.168.1.50:8787/api/summary
If localhost works but the LAN IP does not, check Windows Firewall and confirm that the ESP32 and PC are on the same network.
Run the bridge in the background:
Start-Process -WindowStyle Hidden -FilePath py -ArgumentList @("-3", "server\run_server.py") -WorkingDirectory (Get-Location)
Stop duplicate bridge processes:
Get-CimInstance Win32_Process |
Where-Object { $_.CommandLine -match "run_server.py" } |
ForEach-Object { Stop-Process -Id $_.ProcessId -Force }
By default the server reads:
C:\Users\YOUR_USER\.codex\state_5.sqlite
Expected healthy response:
irm http://localhost:8787/api/codex
{
"id": "codex",
"available": true,
"status": "ok",
"tokens_used": 123456
}
If Codex has never been used on that machine, the endpoint can return no local data.
Claude Code writes live status data to:
server/claude_status.json
The bridge also scans Claude local history under:
C:\Users\YOUR_USER\.claude\projects
C:\Users\YOUR_USER\.claude\sessions
Expected healthy response:
irm http://localhost:8787/api/claude
{
"id": "claude",
"available": true,
"status": "5h",
"display_value": "2%",
"percent_used": 2,
"limit_label": "5h reset 06:10"
}
If the response says available: false or sem dados, generate a new Claude Code response and check server/claude_status.json.
Copy the example config only if you need manual paths or manual values:
Copy-Item server\config.example.json server\config.json
Example manual Claude status:
{
"bind": "0.0.0.0",
"port": 8787,
"providers": {
"claude": {
"enabled": true,
"percent_used": 62,
"display_value": "62%",
"limit_label": "5h reset 18:00",
"status": "manual"
}
},
"paths": {
"codex_state_db": null,
"claude_status_json": null
}
}
server/config.json is ignored by Git.
Build:
& $env:USERPROFILE\.platformio\penv\Scripts\platformio.exe run -e esp32-2432S028R
List serial ports:
& $env:USERPROFILE\.platformio\penv\Scripts\platformio.exe device list
Upload:
& $env:USERPROFILE\.platformio\penv\Scripts\platformio.exe run -e esp32-2432S028R -t upload --upload-port COM4
Serial monitor:
& $env:USERPROFILE\.platformio\penv\Scripts\platformio.exe device monitor -b 115200 --port COM4
If upload fails with a boot mode error:
BOOT.Connecting..., press and release EN/RST once while holding BOOT.BOOT when writing starts.Use a full erase when the display shows leftovers from old firmware or the board behaves strangely:
& $env:USERPROFILE\.platformio\penv\Scripts\platformio.exe run -e esp32-2432S028R -t erase --upload-port COM4
& $env:USERPROFILE\.platformio\penv\Scripts\platformio.exe run -e esp32-2432S028R -t upload --upload-port COM4
Current TFT configuration in platformio.ini:
-D ILI9341_DRIVER=1
-D TFT_WIDTH=240
-D TFT_HEIGHT=320
-D TFT_MISO=12
-D TFT_MOSI=13
-D TFT_SCLK=14
-D TFT_CS=15
-D TFT_DC=2
-D TFT_RST=-1
-D TFT_RGB_ORDER=TFT_RGB
-D TFT_BL=21
-D TFT_BACKLIGHT_ON=HIGH
-D SPI_FREQUENCY=20000000
-D SPI_READ_FREQUENCY=16000000
-D SPI_TOUCH_FREQUENCY=2500000
Touch defaults in include/config.h:
#define TOUCH_CS 33
#define TOUCH_IRQ 36
#define TOUCH_MOSI 32
#define TOUCH_MISO 39
#define TOUCH_CLK 25
For a different touch calibration, create include/config.local.h:
#pragma once
#define TOUCH_SWAP_XY 1
#define TOUCH_INVERT_X 1
#define TOUCH_INVERT_Y 0
#define TOUCH_MIN_X 250
#define TOUCH_MAX_X 3800
#define TOUCH_MIN_Y 250
#define TOUCH_MAX_Y 3800
irm http://YOUR_PC_IP:8787/api/summary
Check:
.env is the PC LAN IP.8787..env.Test-Path .\server\claude_status.json
Get-Content .\server\claude_status.json
irm http://localhost:8787/api/claude
Then open Claude Code normally and send a message. Avoid --bare and --safe-mode.
Test-Path $env:USERPROFILE\.codex\state_5.sqlite
irm http://localhost:8787/api/codex
Use Codex on this machine to generate local history.
The project currently uses:
-D TFT_RGB_ORDER=TFT_RGB
If red and blue are inverted on your board, try:
-D TFT_RGB_ORDER=TFT_BGR
Then rebuild and upload again.
Before publishing:
git init
git add .
git status
Make sure these files are not staged:
.env
.pio/
.vscode/
server/config.json
server/claude_status.json
include/config.local.h
Then commit and push:
git commit -m "Initial ESP32 token meter"
git branch -M main
git remote add origin https://github.com/YOUR_USER/YOUR_REPO.git
git push -u origin main
statusLine values are updated only when Claude Code executes the status line command.191 agents, 155 skills, and 82 plugins cross-compatible with Claude Code, Cursor, and Codex
Project management using GitHub Issues + Git worktrees for parallel agent execution
Core skills library for Claude Code with 20+ battle-tested skills including TDD, debugging, and brainstorming
Coding agent session manager supporting Claude Code, Gemini CLI, Codex, and more