This is a fork of HermannBjorgvin/Clawdmeter. The original relies on a companion script (a daemon / tray app) running on your computer: it reads your Claude token, polls the API, and pushes the numbers to the device over Bluetooth. I didn't want that — a machine that has to stay on running a background process. So this fork makes the device fully autonomous: it joins your WiFi and talks to the Anthropic API by itself. Nothing runs on your PC.
What this fork changes
- No host daemon — the device polls the Anthropic API directly over WiFi and refreshes its own login token (~every 8 h).
- On-device setup — first boot opens a
Clawdmeter-setupWiFi hotspot with a web form to enter WiFi and sign in with your Claude account (OAuth). No cable, no app, no PC.- Re-open setup anytime with a triple-click on the BOOT button — change WiFi / clock / login without wiping.
- Idle wall-clock when plugged in and idle (configurable time zone + style).
- Top button sends Enter to validate Claude Code prompts (was Space).
- Bluetooth kept only for the side-button HID keyboard.
- Extra usage views — additional pace / history-sparkline / active-limit pages.
Everything below documents this autonomous build. To pull future changes from the original, see Keeping in sync with upstream.
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. It is fully standalone: it joins your WiFi, queries the Anthropic API directly, and refreshes its own login — no PC, no companion app, no background daemon. The splash screen plays pixel-art Clawd animations that get busier when your usage rate climbs, and the side buttons act as a Bluetooth keyboard for Claude Code shortcuts.
| Usage meter | Clawd animation screen |
|---|---|
![]() |
![]() |
The Clawd animations come from claudepix, @amaanbuilds's library of pixel-art Clawd sprites, check it out, it's lovely.
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; touch-toggle anytime | Session and weekly utilization |
While the splash is up, the middle (PWR) button cycles animations. 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.
When plugged into USB and idle (Claude "Ready", no active usage for ~1 min), the Usage view can switch to an idle clock instead — see Idle clock.
Boards supported out of the box:
- Waveshare ESP32-S3-Touch-AMOLED-2.16 — 480×480
- Waveshare ESP32-S3-Touch-AMOLED-1.8 — 368×448
PSRAM is required. Because the device queries the Anthropic API itself, every refresh runs a TLS handshake whose buffers (~32 KB) live in PSRAM — without it the handshake runs out of internal RAM and usage never loads. Only PSRAM-equipped ESP32-S3 boards are supported. (An earlier ESP32-C6 port was dropped when the firmware went WiFi-autonomous, since the C6 has no PSRAM.)
Please check if a pull request exists for your alternative hardware port before opening a new one — 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. A viable target needs:
- an ESP32-S3 with OPI PSRAM (the TLS/usage path depends on it — see above);
- a QSPI AMOLED panel with a driver in GFX Library for Arduino (CO5300, SH8601, NV3041A, …);
- an I2C touch controller (any driver you can compile);
- at least the BOOT button (a second button and an AXP2101 PMU / QMI8658 IMU / IO expander are optional).
See docs/porting/adding-a-board.md for the walk-through and docs/porting/hal-contract.md for the interfaces a port must implement.
- PlatformIO CLI on Linux, macOS, or Windows — only needed to build and flash the firmware.
- A 2.4 GHz WiFi network (the ESP32 has no 5 GHz radio).
- A phone or laptop with a browser, for the one-time setup.
- A Claude account with an active subscription — you sign in on the device during setup.
That's it. There is no host-side software to install or keep running.
The board env name is either waveshare_amoled_216 or waveshare_amoled_18.
# macOS (auto-detects /dev/cu.usbmodem*, or pass an explicit port)
./flash-mac.sh waveshare_amoled_216
./flash-mac.sh waveshare_amoled_18 /dev/cu.usbmodem1101
# Linux (defaults to /dev/ttyACM0, or pass an explicit port)
./flash.sh waveshare_amoled_216
./flash.sh waveshare_amoled_18 /dev/ttyACM1# Windows — use your device's COM port
pio run -d firmware -e waveshare_amoled_216 -t upload --upload-port COM5Run the helper script (or pio run -d firmware) with no env to list the available boards.
On its first boot (nothing provisioned yet) the device starts an open WiFi access point called Clawdmeter-setup and shows a "Setup" hint on screen.
- Join the AP. Connect your phone or laptop to
Clawdmeter-setup. A captive page should pop up automatically; if not, openhttp://192.168.4.1/. - WiFi. Enter your 2.4 GHz network name and password, then Save & connect. The device joins your WiFi (it stays an AP at the same time so the page keeps working).
- Sign in with Claude. Click Open the Claude login page, sign in on Anthropic's own site, approve access, then copy the code it shows you and paste it back into the form. Your password / SSO / MFA happen entirely on Anthropic's pages — the device never sees them. It receives its own OAuth tokens and from then on refreshes them itself.
- Idle clock (optional). Enable it and pick a time zone + style if you want a clock when the device is plugged in and idle.
Once WiFi and sign-in are both done the portal closes by itself, the screen switches to the Usage view, and it refreshes every 60 seconds.
The sign-in uses the same OAuth flow as
claude login(Authorization Code + PKCE). The endpoints and client id are undocumented, so a future Anthropic change could require a firmware update.
To change WiFi, sign in again, or adjust the clock on an already-configured device, triple-click the BOOT button (three quick presses). That re-opens the same setup portal without erasing anything. Triple-click again — or just wait for the safety timeout — to close it.
Two side notes: while the portal is open the Bluetooth keyboard is temporarily off (its RAM is reused for the secure connection), and because each BOOT press is also an Enter, a quick triple-click sends three Enters to whatever has focus.
To wipe the stored WiFi + tokens and start over, open the USB serial console at 115200 baud and send wipe. (Clock and brightness settings are kept.)
The side buttons reach your computer as a Bluetooth HID keyboard. Pair it once from your OS Bluetooth settings — it advertises as Clawdmeter. This is completely separate from the usage data (which travels over WiFi), so the dashboard works whether or not anything is paired. To clear the pairing and re-advertise, hold the PWR button for 3 seconds then release.
When the device is on USB power and Claude has been idle ("Ready", no active usage) for about a minute, the Usage view can swap to a clock. Enable it and configure it in setup step 4:
- Time zone — the dropdown is preselected from your browser's zone; DST is handled on the device (it stores a POSIX TZ rule).
- Style — Time only, Time + date, or Time + creature (a small sleeping Clawd).
It reverts to live usage the moment activity resumes, and never shows on battery (so it can't keep the panel lit overnight).
The buttons are a standard BLE HID keyboard, so the keystrokes land in whatever window has focus on the paired host — not just Claude Code. Layout depends on the board:
| Button | GPIO | Function |
|---|---|---|
| Top (BOOT) | GPIO 0 | Press → Enter (submit Claude prompts). Triple-click → open/close setup portal. |
| Middle (PWR) | AXP2101 PKEY | On splash: cycle animations. On usage: cycle brightness. Hold 3 s + release: clear BLE pairing. |
| Right | GPIO 18 | Press → Shift+Tab (Claude Code mode toggle). 2.16 boards only. |
The AMOLED-1.8 has only the BOOT and PWR buttons (no third button).
- On first boot the device hosts a WiFi setup portal (SoftAP
Clawdmeter-setup). You enter your WiFi and sign in with your Claude account over OAuth — the login runs on Anthropic's pages, so your password never touches the device. It stores its own access + refresh tokens in flash (NVS). - Every 60 s it makes a minimal call to
api.anthropic.com/v1/messagesover your WiFi — one token of Haiku, basically free. The usage numbers come straight out of the response headers (anthropic-ratelimit-unified-5h-utilizationand friends). - It refreshes its own OAuth access token (~every 8 h, or immediately on a 401), so it stays autonomous indefinitely with no PC and no re-login.
- It tracks the rate of change of session % over a few-minute window and picks splash animations from the matching mood group; the status line shows playful "working" words during active usage and Ready when idle.
- Bluetooth is used only for the HID keyboard — the side buttons send keystrokes directly to the paired host.
TLS note: the Arduino libs build mbedTLS with internal-only allocation, which can't fit the handshake alongside WiFi + BLE, so the firmware pins Anthropic's root CAs and routes the TLS buffers to PSRAM. Relevant if you port to a board without PSRAM.
BLE now carries only the standard HID keyboard service (0x1812, advertised as Clawdmeter); usage data travels over WiFi, so the old custom GATT data service is gone. Pair the device once from your OS Bluetooth settings to enable the side buttons.
The firmware/src/font_*.c files are pre-compiled LVGL bitmap fonts.
npm install -g lv_font_convGenerate each one (one at a time — lv_font_conv doesn't like loop-driven invocations) with --no-compress (required for LVGL 9):
# Tiempos Text (titles, 56px)
lv_font_conv --font assets/TiemposText-400-Regular.otf -r 0x20-0x7E \
--size 56 --format lvgl --bpp 4 --no-compress \
-o firmware/src/font_tiempos_56.c --lv-include "lvgl.h"
# Styrene B (large numbers 48, panel labels 28, small text 24, minimal 20)
for size in 48 28 24 20; do
lv_font_conv --font assets/StyreneB-Regular.otf -r 0x20-0x7E \
--size $size --format lvgl --bpp 4 --no-compress \
-o firmware/src/font_styrene_${size}.c --lv-include "lvgl.h"
done
# DejaVu Sans Mono (32px, with spinner Unicode chars)
lv_font_conv --font assets/DejaVuSansMono.ttf \
-r 0x20-0x7E,0xB7,0x2026,0x2722,0x2733,0x2736,0x273B,0x273D \
--size 32 --format lvgl --bpp 4 --no-compress \
-o firmware/src/font_mono_32.c --lv-include "lvgl.h"Important: lv_font_conv v1.5.3 outputs LVGL 8 format. Each generated file must be patched for LVGL 9 compatibility:
- Remove
#if LVGL_VERSION_MAJOR >= 8guards aroundfont_dscand the font struct - Remove the
.cachefield fromfont_dsc - Add
.release_glyph = NULL,.kerning = 0,.static_bitmap = 0to the font struct - Add
.fallback = NULL,.user_data = NULLto the font struct
Without these patches, fonts compile but render as invisible.
firmware/src/font_cjk_16.c covers the full CJK Unified Ideographs basic
block (U+4E00–U+9FFF, ~20k glyphs) plus ASCII, CJK punctuation, and
halfwidth/fullwidth forms. Generated from Noto Sans CJK SC
(SIL OFL 1.1) at 16px, 2bpp:
lv_font_conv --font NotoSansCJKsc-Regular.otf --size 16 --bpp 2 \
--no-compress --format lvgl --lv-include 'lvgl.h' \
-r '0x20-0x7E,0xB7,0x2014,0x2018-0x2019,0x201C-0x201D,0x2026,0x3000-0x303F,0x4E00-0x9FFF,0xFF00-0xFFEF' \
-o firmware/src/font_cjk_16.cThen apply the four LVGL 9 patches above. Because the font has >65k of
glyph bitmap data, the build needs -DLV_FONT_FMT_TXT_LARGE=1 in
platformio.ini build flags so font descriptor offsets switch from
16-bit to 32-bit.
The CJK font is used for the Activity screen's user-prompt row and todo
content rows. The headline (28pt Styrene B) and titles stay ASCII-only
to preserve the brand font — Chinese text in those slots renders as
empty boxes. Add a font_cjk_28.c if full coverage is needed (~1MB
more flash).
The UI uses a small set of Lucide icons (bluetooth + battery states) converted to RGB565 / RGB565A8 C arrays for LVGL.
node tools/png_to_lvgl.js assets/icon_bluetooth_48.png icon_bluetooth_data ICON_BLUETOOTH_WIDTH ICON_BLUETOOTH_HEIGHTDefault tint is white (0xFFFFFF); Lucide PNGs ship as black-on-transparent and would render invisible against the dark UI without it. Pass --no-tint for pre-coloured artwork like the logo. Battery icons use RGB565A8 (alpha plane) so they blend cleanly over the splash; the rest are baked RGB565 over the panel colour. Paste the converter output into firmware/src/icons.h.
The animations come from claudepix.vercel.app,
a library of Clawd sprites. tools/scrape_claudepix.js evaluates the
site's JavaScript in a Node VM to pull out frame data and palettes, then
tools/convert_to_c.js turns everything into RGB565 C arrays and writes
firmware/src/splash_animations.h.
To re-pull (e.g. when the source library updates):
node tools/scrape_claudepix.js
node tools/convert_to_c.js
pio run -d firmware -t uploadSee tools/README.md for details.
This repo is a fork of HermannBjorgvin/Clawdmeter. To pull later changes from the original into your fork:
git remote add upstream https://github.com/HermannBjorgvin/Clawdmeter.git # once
git fetch upstream
git merge upstream/main # or: git rebase upstream/main- Pixel-art Clawd animation by @amaanbuilds, sourced from claudepix.vercel.app. Frame data and palettes scraped + converted by the tooling in
tools/. - Lucide icon set (lucide.dev, MIT) for bluetooth and battery UI glyphs.
- macOS flash helper (
flash-mac.sh) ported by Chris Davidson (@lorddavidson). Thanks Chris! - Anthropic brand fonts (Tiempos Text, Styrene B) — see licensing warning below.
The software in this repository uses and adheres to the Anthropic brand guidelines and uses the same proprietary fonts that Anthropic has a license for but this software uses without permission as well as using assets from Anthropic such as the copyrighted Clawd mascot so even though the code in this repo is non-proprietary I will not license it myself under a copyleft license since this repo includes proprietary fonts and copyrighted assets. Please be aware of this if you fork or copy the code from this repo. You have been warned!



