MCPcopy Create free account
hub / github.com/HermannBjorgvin/Clawdmeter

github.com/HermannBjorgvin/Clawdmeter @main

Chat with this repo
repository ↗ · DeepWiki ↗ · + Follow
526 symbols 1,334 edges 113 files 174 documented · 33% updated 12d ago★ 2,18418 open issues

Browse by type

Functions 503 Types & classes 23
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Clawdmeter

A small ESP32 dashboard I made for my desk to keep an eye on Claude Code usage.

It runs on a Waveshare ESP32-S3-Touch-AMOLED-2.16 as well as a few other alternative boards and pairs over Bluetooth, the splash screen plays pixel-art Clawd animations that get busier when your usage rate climbs. The two side buttons send Space and Shift+Tab over BLE HID for Claude Code's voice mode and mode-toggle shortcuts.

Usage meter

Screens

The device boots into the splash. Tap the screen anywhere to switch to the Usage view; tap again to flip back to the splash.

Splash Usage
Splash Usage
Splash; touch-toggle anytime Session and weekly utilization

While the splash is up, the middle (PWR) button cycles animations. Hold the power button for 3 seconds, then release, to put the device into pairing mode — this clears the saved Bluetooth bond and re-advertises. The firmware also auto-rotates animations every 20 s within the current usage-rate group, so a long stretch on the splash isn't just one Clawd on loop.

Hardware

Boards supported out of the box:

Please check if a pull request exists for your alternative hardware port before opening a new one, providing QA feedback and testing on the same hardware is more valuable than duplicate pull requests.

Porting to another board: the firmware is a thin HAL with per-board folders under firmware/src/boards/. Drop in a new folder and a new PlatformIO env — main.cpp, ui.cpp, and splash.cpp never need to change. See docs/porting/adding-a-board.md for the walk-through and docs/porting/hal-contract.md for the interfaces a port must implement.

Prerequisites

  • Linux (tested on Ubuntu), macOS, or Windows 10/11
  • PlatformIO CLI
  • Linux: curl, bluetoothctl, busctl, dbus-monitor (BlueZ Bluetooth stack), python3, setsid, stdbuf (util-linux / coreutils), systemctl (systemd user services)
  • macOS: python3 (the installer sets up a venv with bleak and httpx)
  • Windows: python3 3.11+ (the installer sets up a venv with bleak, httpx, and pystray)
  • Claude Code with an active subscription

macOS installation

The macOS host pieces — Python daemon, LaunchAgent, and flash helper — were ported by Chris Davidson (@lorddavidson). Thanks Chris!

Flash the firmware

./flash-mac.sh waveshare_amoled_216                       # ESP32-S3 2.16" (auto-detects /dev/cu.usbmodem*)
./flash-mac.sh waveshare_amoled_216_c6                    # ESP32-C6 2.16" variant
./flash-mac.sh waveshare_amoled_18  /dev/cu.usbmodem1101  # ESP32-S3 1.8" (or pass an explicit USB serial port)

The board env name is required. Run ./flash-mac.sh with no args to see the available envs (scraped from firmware/platformio.ini).

Pair the device

After flashing, open System Settings → Bluetooth and click Connect next to "Clawdmeter". The daemon only ever connects to the peripheral this Mac is paired/connected to — it never scans for a nearby device — so once it's connected here the daemon picks it up on its next poll (~60 s).

Install the daemon

The daemon reads your Claude OAuth token from the macOS Keychain (service Claude Code-credentials), polls usage every 60 s, and pushes it to the display over BLE.

./install-mac.sh

The installer creates a Python venv in daemon/.venv/, installs bleak and httpx, renders a LaunchAgent into ~/Library/LaunchAgents/com.user.claude-usage-daemon.plist, and loads it. The first run is launched interactively so macOS prompts for Bluetooth permission.

Useful commands:

launchctl list | grep claude-usage                                          # check it's running
tail -F ~/Library/Logs/claude-usage-daemon.out.log                          # live logs
launchctl unload ~/Library/LaunchAgents/com.user.claude-usage-daemon.plist  # stop
launchctl load -w ~/Library/LaunchAgents/com.user.claude-usage-daemon.plist # start

Linux installation

Flash the firmware

./flash.sh waveshare_amoled_216                  # ESP32-S3 2.16" (defaults to /dev/ttyACM0)
./flash.sh waveshare_amoled_216_c6               # ESP32-C6 2.16" variant
./flash.sh waveshare_amoled_18  /dev/ttyACM1     # ESP32-S3 1.8" (or pass an explicit USB serial port)

The board env name is required. Run ./flash.sh with no args to see the available envs (scraped from firmware/platformio.ini).

Pair the device

After flashing, the device advertises as "Clawdmeter". Pair it once:

# Scan for the device
bluetoothctl scan le

# When "Clawdmeter" appears, pair and trust it
bluetoothctl pair F4:12:FA:C0:8F:E5    # use your device's MAC
bluetoothctl trust F4:12:FA:C0:8F:E5

To re-pair later, hold the power button for 3 seconds then release — the device clears its saved bond and re-advertises.

Install the daemon

The daemon polls your Claude usage every 60 seconds and sends it to the display over BLE.

./install.sh
systemctl --user start claude-usage-daemon

Check status: systemctl --user status claude-usage-daemon

View logs: journalctl --user -u claude-usage-daemon -f

To change the poll interval, set poll_interval = <seconds> in ~/.config/claude-usage-monitor/config (see daemon/config.example). The daemon picks it up without a restart. Polling slower than ~80s is fine: between polls the daemon replays the last payload every heartbeat_interval seconds (default 60) with the reset countdowns aged, so the firmware's 90s freshness window never lapses and no reflash is needed.

Windows installation

Runs natively on Windows — no WSL required. A system-tray app polls your usage and pushes it over BLE, and starts automatically at login.

Prerequisites

  • Native Windows (not WSL).
  • Python 3.11+ from python.org — check "Add python.exe to PATH" during install.
  • Claude Code installed, with claude login completed. The token is read from %USERPROFILE%\.claude\.credentials.json (falling back to %LOCALAPPDATA%\Claude\ then %APPDATA%\Claude\).
  • The repo on a native Windows path (e.g. %USERPROFILE%\Clawdmeter), not a \\wsl$ share — the installer refuses a WSL path.

Flash the firmware

pio run -d firmware -e waveshare_amoled_216 -t upload --upload-port COM5   # use your device's COM port

Run pio run -d firmware with no env to see the available board envs.

Pair the device

The device is a bonded BLE HID keyboard, so pair it once: Settings → Bluetooth & devices → Add device → Bluetooth, then select "Clawdmeter". Pairing is required — it enables the physical buttons and keeps a persistent connection (the device keeps showing your last-synced usage even after the daemon quits). To undo, use Remove device (this disables the buttons).

Install the daemon (recommended)

From the repo root in PowerShell:

powershell -ExecutionPolicy Bypass -File install-windows.ps1

This creates a venv, installs bleak/httpx/pystray/Pillow from the in-repo requirements (no internet downloads), registers a per-user login-autostart entry (HKCU\…\Run, no admin needed), and launches the tray app headlessly (no console window).

Run manually instead (optional)

python -m venv .venv
.venv\Scripts\Activate.ps1        # if blocked: Set-ExecutionPolicy -Scope CurrentUser RemoteSigned, then retry
pip install -r daemon\requirements-windows.txt
python daemon\claude_usage_daemon_windows.py        # runs in the foreground; Ctrl+C to stop

Tray icon and menu

The icon's corner bubble shows state — green Connected, amber Scanning, red Error — and hovering shows the status (Connected · last update HH:MM). A notification fires once when it enters Error (e.g. an expired token). Right-click for the menu:

  • Status header — live state + last sync time.
  • Start at login — toggle autostart on/off.
  • Quit — stops the daemon cleanly; leaves the Windows pairing intact (device keeps its last reading).

Logs and troubleshooting

Get-Content $env:LOCALAPPDATA\Clawdmeter\daemon.log -Tail 30        # view logs
reg delete "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v Clawdmeter /f   # remove autostart
Symptom Fix
Device not found Power on the device; make sure it's in range and paired.
token expired toast / API HTTP 401 Re-run claude login, then restart the daemon.
Connection failed Toggle Windows Bluetooth off/on in Settings.
Warning: running under Linux/WSL Run from a native PowerShell window, not a WSL shell.

How it works

  1. The daemon reads your Claude Code OAuth token — from the macOS Keychain (service Claude Code-credentials) on macOS, or from ~/.claude/.credentials.json on Linux (%USERPROFILE%\.claude\.credentials.json on Windows).
  2. It makes a minimal API call to api.anthropic.com/v1/messages — one token of Haiku, basically free.
  3. The usage numbers come straight out of the response headers (anthropic-ratelimit-unified-5h-utilization and friends).
  4. The daemon connects to the ESP32 over BLE and writes a JSON payload to the GATT RX characteristic.
  5. The firmware parses it and updates the LVGL dashboard.
  6. The firmware also tracks the rate of change of session % over a 5-minute window and picks splash animations from the matching mood group.
  7. The two side buttons are independent of all of this — they send Space and Shift+Tab as BLE HID keyboard input to the paired host directly.

Physical buttons

The board has three side buttons. Left and right send HID keys; the middle (PWR) button cycles splash animations and, held for 3 seconds, triggers pairing mode.

Button GPIO Function
Left GPIO 0 Hold to send Space (Claude Code voice-mode push-to-talk)
Middle (PWR) AXP2101 PKEY On splash: cycle animations. Hold 3s + release: pairing mode
Right GPIO 18 Press to send Shift+Tab (Claude Code mode toggle)

Space and Shift+Tab go out as standard BLE HID keyboard reports, so they trigger in whatever window has focus on the paired host — not just Claude Code.

BLE protocol

The device advertises a custom GATT service alongside the standard HID keyboard service:

UUID
Data Service 4c41555a-4465-7669-6365-000000000001
RX Characteristic (write) 4c41555a-4465-7669-6365-000000000002
TX Characteristic (notify) 4c41555a-4465-7669-6365-000000000003
HID Service 00001812-0000-1000-8000-00805f9b34fb

JSON payload format (written to RX):

{ "s": 45, "sr": 120, "w": 28, "wr": 7200, "st": "allowed", "ok": true }

Fields: s = session %, sr = session reset (minutes), w = weekly %, wr = weekly reset (minutes), st = status, ok = success flag.

Development

  • Desktop simulator — iterate on the UI without hardware: an SDL2 window runs the full firmware loop with scenario playback (pio run -d firmware -e sim, then cd firmware && .pio/build/sim/program). See SIM-USAGE.md for controls, scenarios, and headless screenshots.
  • Splash animations — Anthropic's official Clawd sprites, archived with provenance notes in research/clawd-official/; node tools/convert_official_clawd.js regenerates firmware/src/splash_animations.h. See tools/README.md.
  • Icons — Lucide PNGs convert to LVGL C arrays with tools/png_to_lvgl.js. See tools/README.md.
  • Fonts — the pre-compiled LVGL fonts and the LVGL-9 patching they need: docs/fonts.md.
  • Portingdocs/porting/adding-a-board.md and docs/porting/hal-contract.md.

Credits

  • P

Core symbols most depended-on inside this repo

browse all functions →

Shape

Function 484
Method 19
Class 16
Enum 7

Languages

C++49%
Python44%
C4%
TypeScript2%

Modules by API surface

daemon/tests/test_windows_reconnect.py39 symbols
daemon/claude_usage_daemon.py36 symbols
daemon/claude_usage_daemon_windows.py30 symbols
firmware/src/ble.cpp28 symbols
firmware/src/ui.cpp25 symbols
daemon/tests/test_windows_tray.py23 symbols
daemon/tests/test_macos_multidir.py21 symbols
firmware/src/es8311.c19 symbols
daemon/tests/test_windows_poll.py19 symbols
firmware/src/splash.cpp14 symbols
daemon/tests/test_windows_token.py14 symbols
daemon/tray_windows.py12 symbols

For agents

$ claude mcp add Clawdmeter \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page