
我每天都用 Claude,而我最常问的问题并不是关于代码。而是”我这周还剩多少额度?“Claude Code 的 /usage 界面能回答,但前提是我得停下来手动输入。我希望答案就摆在桌上,就像时钟显示时间那样。
M5Stack Basic 很合适。它是一台成品设备:2 英寸 320×240 屏幕、三个按键、一块电池,以及封装在 54 mm 外壳里的 ESP32。无需面包板,无需焊接。
本文介绍这个仪表盘:它显示什么、数据如何从 Claude 传到屏幕、设备如何免配置连接,以及自己动手搭建的步骤。UI 是在 Velxio 中的 M5Stack Core 上设计完成的,之后才接触真实硬件——如果你想自己做一个,我也推荐这条路线。你也可以在浏览器中运行这个固件,使用演示数据,无需设备。
设备上的仪表盘:在 YouTube 上观看。
所有代码——固件和 PC 桥接程序——都以 MIT 许可证发布在 GitHub 上:velxio/m5claude。下文”逐部分解读代码”一节会逐一说明每个文件的作用。
硬件

这块板子是 M5Stack Basic Core IoT Development Kit v2.7:
- ESP32-D0WDQ6-V3,双核 240 MHz。
- 520 KB SRAM,没有 PSRAM。这一限制塑造了固件的设计。
- 16 MB flash。
- 2 英寸 320×240 IPS 面板,ILI9342C 控制器,v2.7 上覆盖玻璃。
- 三个按键(A、B、C),分别接 GPIO 39、38 和 37。
- CH9102 USB 转串口芯片,支持 Wi-Fi 和蓝牙 LE。
- GPIO 25 上的 1 W 扬声器。
- IP5306 电源芯片,配 110 mAh 电池,可通过 I2C 读取。
不需要其他东西:仪表盘只用外壳里已有的部件。这块板子没有 IMU、麦克风、LED 或实时时钟,所以仪表盘用上了其余的一切:屏幕和按键、用于提醒的扬声器、用于省电的背光,以及用于显示电量的电源芯片。
屏幕上显示什么
这个仪表盘支持两类 Claude 账户,而且每类都可以有多个:
- Pro 和 Max 订阅。 这类账户有用量限额:5 小时会话窗口、每周限额,以及按模型划分的每周限额。屏幕把每一项显示为百分比,并附上重置时间。
- API 组织。 这类账户按 token 付费,所以屏幕显示的是花费:今天和最近七天的美元金额。
对两类账户,它都会显示最近一周的每日 token 数,拆分为输入、输出、缓存读取和缓存写入,并按模型分类。
共有四个视图。B 切换到下一个视图,A 和 C 在账户之间切换,长按 B 向 PC 请求最新数据,长按 C 打开设置。下面是所有屏幕,以及每个视图打开时播放的动画:

总览。 每个账户一张卡片。布局会根据账户数量自适应:一个账户铺满屏幕,两个账户使用更高的卡片并多出一行今日用量,三个或更多则变成可滚动的列表。


限额。 每周限额显示为圆环,旁边是会话和按模型的限额。模型名称直接来自 Claude 本身,所以这些行会跟随你套餐实际拥有的限额。颜色在 60%(琥珀色)和 85%(红色)时变化。

Token。 今天、最近七天和美元金额,然后是每日 token 数,并用虚线标出平均值。对于订阅账户,美元金额是同样多的 token 按 API 标价计算的花费。它不是实际扣费,但能让你直观看到自己使用套餐的强度。


明细。 本周 token 按类型和按模型的分布。使用 Claude Code 时,缓存读取通常占总量的大部分。


时钟旁的小圆点表示连接状态:数据新鲜时为绿色,过期时为琥珀色,没有 PC 连接时为红色。照片显示的是我自己的账户。截图使用的是演示账户。
电池、声音与设置
时钟旁边是电池,画法与手机一致:电量,以及充电时不断上升的闪电填充。IP5306 电源芯片只报告四档(25、50、75 和 100%),所以数字以 25 为步进变化。

扬声器用于提醒。当会话或每周限额超过 90%(如果你选择,也可以是 80%)时,设备会播放一段简短的双音提示,并显示是哪个账户、哪项限额。限额重置时,它会播放一段上扬的提示音。来自新电脑的审批请求也会发出声音。按键点击音是可选的。
长按 C 打开设置。A 和 C 移动,B 修改数值,长按 B 关闭屏幕。设置保存在设备上。

- 语言。 英文或西班牙文。屏幕上的所有字符串都放在一张表里,所以添加一种语言只需再加一个条目。
- 亮度。 25、50、75 或 100%。
- 声音。 关闭、仅提醒,或提醒加按键点击音。
- 提醒阈值。 80%、90% 或关闭。
- 电池供电时息屏。 从不、一分钟后或五分钟后。任意按键都能唤醒,而唤醒的那次按键不会触发其他操作。在 110 mAh 电池下,这一点很重要。
- 夜间调暗。 使用 PC 的时钟,在 22:00 到 07:00 之间降低背光。
- 已配对电脑。 显示已批准的数量,B 在确认后将其遗忘。


让 110 mAh 撑得更久
最初的固件在不到一小时内就耗尽了电池。CPU 运行在 240 MHz,屏幕每秒重绘,无线电以全功率发射,连接间隔为 15–30 ms。而显示一个每分钟才变化一次的数字,根本不需要这些。在电池供电时,固件现在会这样做:
- CPU 运行在 80 MHz,这是蓝牙支持的最低速度。接 USB 供电时恢复到 240 MHz。
- 背光:无按键操作 20 秒后调暗,并在设置中选择的时间后关闭。
- 待机:屏幕关闭 15 秒后,无线电关闭,ESP32 进入轻度睡眠。RAM 得以保留,所以任意按键都能立即带回屏幕并显示最后的数据,桥接程序也会在几秒内重新连接。每 10 分钟,一个定时器会唤醒它最多 45 秒,此时屏幕仍然关闭。它会获取最新数据,如果有某项限额越过了提醒阈值,它会点亮屏幕并播放提醒。
- 无线电:使用 0 dBm 而非 +9,每 0.5–1 秒广播一次,连接间隔为 100–200 ms 并启用从机延迟。每分钟 600 字节的快照需要的并不多。
- 重绘只在分钟变化或新数据到达时发生,电池供电时没有空闲动画。
如果要在离开桌面的情况下全天使用,可以把 Battery Module 13.2 叠在 Basic 下面,增加 1500 mAh。
工作原理
设计原则是设备本身一无所知。它不保存任何 Claude 凭据,也没有 Wi-Fi 设置。PC 上的一个小程序——桥接程序——负责收集所有数据,并把一份快照发给设备去绘制。这样密钥就不会落在摆在桌上的设备里,而且更换电脑或网络也无需重新配置。
数据来源
桥接程序约 1,100 行 Python,只有两个依赖(pyserial 和 bleak)。它读取三个来源:
- 套餐限额,来自 Claude Code 的登录信息。 每个 Claude Code 配置目录(
~/.claude、~/.claude-work等)保存着一个账户的登录信息。桥接程序会找到所有这些目录,并用各自的 token 读取 Claude Code 的/usage界面所显示的同一份限额。它只读取 token,从不刷新,因为刷新会轮换 token,导致 Claude Code 被登出。 - Token,来自 Claude Code 的会话记录。 Claude Code 收到的每个响应都会写入
projects/下的一个 JSONL 文件,其中包含 token 用量和模型。桥接程序按天累加这些数据,去除重复项(恢复的会话会把先前的消息复制到新文件中),并用 API 价格表计算费用。 - API 组织的花费,来自 Usage and Cost Admin API。
usage_report/messages提供按天和按模型的 token 数。cost_report提供真实花费。两者都需要 Admin API 密钥(sk-ant-admin01-...)。
在真实账户上运行后得到的两点经验:
- 限额端点有速率限制。 我在测试时,它开始返回不带
Retry-After的 HTTP 429。现在桥接程序每五分钟读取一次限额,而不是每分钟,并把最后一次成功的结果保存在磁盘上,在临时错误期间继续显示它。Token 仍然每分钟更新,因为它们来自本地文件。 - 会话记录会变得很大。 我的有 665 MB。桥接程序只读取最近八天内修改过的文件,并且只在文件大小或修改时间变化时重新解析。一次刷新大约需要一秒。
快照
每次刷新都会生成一行约 600 字节的 JSON:
{"v":1,"ts":1792770195,"tz":3600,"acc":[
{"n":"Personal","p":"Max 20x","k":"sub","ok":1,
"s":[42,1792778235],"w":[67,1793037195],"x":[["Opus",54,1793037195],["Sonnet",31,1793037195]],
"td":2840000,"tk":[2310000,1480000,15900000,1120000],"c":84.2,"ct":11.35,
"d":[1900000,3400000,2600000,4100000,3300000,2670000,2840000],"m":[["Opus",61],["Sonnet",34],["Haiku",5]]}]}
s、w 和 x 分别是会话、每周和按模型的限额,格式为 [百分比, 重置时间]。d 是每日 token 数。ts 和 tz 是 PC 的时钟和 UTC 偏移。M5Stack Basic 没有实时时钟,也没有时区数据库,所以设备从快照中获取这两者,并从那里开始计秒。这足以显示“2 小时 14 分钟后重置”和“周二 04:00”。
无需配置的连接
我希望完全不需要设置。在只有三个按钮的设备上输入 Wi-Fi 密码很不愉快。在设备上保存 PC 的 IP 地址,一旦更换电脑就会失效。所以设备支持两种连接方式,都不需要任何设置。
USB。 插上数据线即可。桥接程序会查找 USB 芯片为 M5Stack 所用型号(CH9102 或 CP2104)的串口。它在打开端口时不切换 DTR 和 RTS,因此 ESP32 不会复位,然后等待问候消息。固件每两秒发送一次 {"hello":"m5claude",...},直到收到数据。如果几秒内没有收到问候消息,桥接程序会释放端口并在一段时间内不再碰它,这样其他 ESP32 开发板和烧录工具就不会受到影响。
蓝牙 LE。 固件广播一个 GATT 服务,包含两个特征:一个供 PC 写入数据行,另一个供设备发送通知。桥接程序扫描该服务 UUID 并连接。没有 Windows 配对对话框,也不需要从列表中选择任何东西。
由于范围内的任何电脑都能发现该设备,每台新 PC 都需要在设备上批准一次:

桥接程序在 ~/.m5claude/pc_id 中保存一个随机 id。设备在 NVS 中最多存储四台已批准 PC 的 id。已知的 PC 会立即被接受,开机时按住 A 会忘记所有已批准的 PC。已经通过 USB 连接的设备会跳过蓝牙,因此不会要求同一台 PC 批准两次。
在桥接程序运行之前,设备会显示该怎么做:

固件
固件是一个 Arduino sketch,使用 arduino-cli 在 arduino-esp32 2.0 核心上构建。它使用三个库:
- LovyanGFX 用于绘制屏幕。
- NimBLE-Arduino 用于蓝牙。NimBLE 使用的 RAM 约为默认 Bluedroid 协议栈的一半。
- ArduinoJson 7 用于解析快照。
UI 代码不依赖硬件。它绘制到一个 LovyanGFX 目标中,并读取三个按钮。所有与开发板相关的内容(面板、USB、BLE、NVS)都在 sketch 中。
一些让它看起来像产品而非演示的细节:
- 抗锯齿文字。 字体是 Inter,由一个小 Python 脚本转换为 LovyanGFX 的平滑 VLW 格式,共六种字号。脚本只包含每种字号用到的字符,总共约 77 KB。
- 抗锯齿仪表。 圆环逐像素绘制:覆盖率根据到圆环边缘的距离计算,两端为圆角。条形和卡片使用 LovyanGFX 的平滑圆角矩形。
- 短动画。 每次视图切换时,条形和数字在半秒内以缓出曲线增长到目标值。
在没有 PSRAM 的情况下绘制,以及斜条纹
16 位色的完整 320×240 帧为 150 KB。在蓝牙运行的情况下,ESP32 最大的空闲内存块比这更小。因此 UI 每帧分四次绘制到一个 320×60 的条带(38 KB)中。每一遍都按条带的偏移量向上平移绘制所有内容,超出条带的部分会被裁剪。
第一次在开发板上运行时,屏幕显示斜条纹,批准提示的按钮出现了两次,分别在顶部和底部。我首先怀疑的是面板配置。我根据 M5Stack 的文档手工重写了它,但没有任何变化。原因在别处:
LovyanGFX 通过 DMA 在后台把 sprite 发送到面板。我的循环在上一个条带仍在发送时就开始把下一个条带绘制到同一个缓冲区,于是面板收到了一半来自一个条带、一半来自下一个条带的内容。修复方法是轮流使用两个缓冲区,并在推送前等待上一次传输完成:
for (int y = 0, k = 0; y < 240; y += 60, k ^= 1) {
LGFX_Sprite& band = bands[k];
band.fillScreen(BG);
draw_ui(band, /*offset_y=*/y); // runs while the other buffer is still being sent
lcd.waitDMA(); // the other buffer's transfer has finished
band.pushSprite(&lcd, 0, y); // starts this buffer's transfer
}
lcd.waitDMA();
代价是多用一个 38 KB 的缓冲区,而空闲 RAM 总共约 275 KB。
代码,逐部分讲解
用 git clone https://github.com/velxio/m5claude.git 下载代码,或从仓库的 Code 按钮下载 ZIP。它由两半组成,除了协议之外互不共享:bridge/ 在 PC 上运行,firmware/ 和 src/ 在 M5Stack 上运行。
在 M5Stack 上
firmware/m5claude/m5claude.ino:所有与开发板相关的内容。
- 面板配置:VSPI 上的 ILI9342C、其引脚、旋转方向和颜色反转。
- USB 链路。它读取串口并写入回复。
- 蓝牙链路。它是一个 NimBLE GATT 服务,带有一个 PC 写入的特征和一个设备通知的特征。NimBLE 回调中收到的字节会进入流缓冲区,并在
loop()中处理,因此其余代码绝不会在蓝牙任务上运行。 - 已批准 PC 的列表和设置,用
Preferences保存在 NVS 中。 - 电池:每两秒通过 I2C 读取 IP5306 的电量、充电中和充电完成位。
- 扬声器:一个小型音序器用低占空比的 LEDC 方波播放警报、重置和配对音调,随后将引脚拉低,这样扬声器在两次声音之间不会发出嘶嘶声。
src/app/app.cpp:协议,与链路无关。
- 每条链路(USB 或蓝牙)都有自己的行缓冲区和状态。它在每个换行符处解析一行,并每两秒重复一次问候,直到收到数据。
- 不受信任的链路需要经过批准:设备询问 UI,记住答案并回复
pair: ok或denied。 - 它把长按 B 转换为
{"req":"refresh"}。 - 它保留上一份快照的副本,这样 UI 就能判断某个限额何时越过警报阈值或重置。
- 它维护时钟:最后一份快照的时间加上自其到达以来经过的秒数。
src/model/:数据。
model.h描述一个账户:套餐、以百分比表示的限额和重置时间、每日 token 数、花费以及模型。parse.cpp用 ArduinoJson 从快照填充模型。它解析到一份单独的副本中,只有当整行都有效时才替换当前数据,因此损坏的行绝不会让屏幕处于半更新状态。mock.cpp包含本文截图中使用的演示账户,在设计时很有用。
src/ui/:屏幕。
ui.cpp绘制四个视图、设置和批准提示。它处理按钮:单击,或 650 毫秒后的长按。它运行半秒动画,决定背光级别(选定的亮度、夜间调暗、电池供电空闲时关闭),触发限额警报,并用两个 DMA 缓冲区按条带渲染。i18n.h保存屏幕上的所有字符串,包括英文和西班牙文,以及星期名称和桥接程序的错误消息。gfx.h保存颜色和绘图辅助函数:圆角条、抗锯齿圆环,以及让同一绘图代码填充任意条带的偏移量。fonts_gen.h包含 Inter 字体。它们由tools/make_fonts.py生成。
在 PC 上
桥接程序位于 bridge/m5claude_bridge/。它唯一的依赖是 pyserial 和 bleak。HTTPS 调用使用标准库。
__main__.py:命令行和主循环。
- 命令有
run、snapshot、accounts、init、add-api和autostart。 - 运行前,它会获取一个锁(localhost 上的监听套接字),因此同一时间只有一个桥接程序在运行。
run每 60 秒采集一次快照,或在设备请求时立即采集,并将其交给 hub。
autostart.py 注册桥接程序在登录时无窗口启动:Windows 上是带 pythonw 的 Run 注册表项,macOS 上是 LaunchAgent,Linux 上是 systemd 用户单元。
config.py:账户。
- 它检测每个已登录的 Claude Code 配置目录。
- 或者,它读取
~/.m5claude/config.json,你可以在其中命名账户并添加 API 组织。
sources/:每个数据源一个文件。
subscription.py读取账户的 Claude Code 登录信息和其套餐限额。local_logs.py读取 Claude Code 的转录记录。它增量解析、去除重复消息,并按天和按模型汇总 token。admin_api.py读取 API 组织的 Usage and Cost Admin API,并跟随分页。
pricing.py 保存按模型系列划分的 API 标价。它估算订阅用量的价值。
snapshot.py:构建快照。
- 它并行采集每个账户,因此一个慢账户不会拖慢其余账户。
- 它应用限额策略:每五分钟一次,最后一次成功的结果缓存在磁盘上,并在临时错误期间保留。
hub.py 保留最新的快照并将其提供给每个已连接的设备。慢速链路只会拿到最新的那一份,绝不会积压。
transports/:每条链路一个文件。
usb_serial.py查找 M5Stack USB 芯片,并且只有当固件向它问候时才保留该端口。ble.py扫描、连接、等待批准,并按协商 MTU 能容纳的块大小写入。tcp.py是用于开发的本地链路。
仓库的 README 以表格形式提供了同样的导览,外加故障排除。
自己动手做一个
我推荐的顺序是:先在 Velxio 里设计界面,然后给板子刷固件,最后连接桥接程序。
1. 在 Velxio 里运行并修改 M5Stack Core
Velxio 模拟 M5Stack Core,包括它的 320×240 屏幕、A、B、C 三个按键和电池芯片。在 2 英寸的屏幕上排版需要大量微调(标签低了两个像素、数字放不进卡片),而在浏览器里每次调整只需几秒,不必经历一轮刷写。
这个仪表盘就在 Velxio 的示例库中,源文件与仓库中的完全一致:
- 打开 M5Stack Core: Claude usage dashboard (m5claude) 并点击播放按钮。无需注册。它会以本文中的演示账户启动。
- 点击 A 和 C 选择账户,点击 B 切换视图。模拟器运行固件的速度比板子慢,所以要打开设置,请按住 C 几秒钟。
- 想看到你自己的数据,就在 PC 上运行
m5claude-bridge snapshot,把它打印出的那一行粘贴到串口监视器中。草图的第一条注释里有一行示例,你可以直接修改。 - 在
ui.cpp中修改界面并重新运行。看起来满意后,再刷写到板子上。
与板子有两处不同:Velxio 没有蓝牙,因此这条链路被省略,串口监视器是唯一的连接方式;扬声器代码使用的是较新的 ESP32 Arduino 核心的调用。每处改动都在草图中做了标记。
想从空白屏幕开始搭建自己的仪表盘,M5Stack Core: Hello LCD + Buttons 是最小的示例。它使用 M5Stack 的 M5GFX,后者基于 LovyanGFX 构建并共享其绘图 API,因此代码可以在两者之间迁移。
2. 刷写固件
git clone https://github.com/velxio/m5claude.git && cd m5claude
arduino-cli core install esp32:esp32
arduino-cli lib install LovyanGFX NimBLE-Arduino ArduinoJson
./firmware/build.sh /dev/ttyUSB0 # Linux / macOS
.\firmware\build.ps1 -Upload # Windows, finds the COM port by itself
这些脚本为 M5Stack Core 构建,使用的分区方案为大型应用留出了空间(PartitionScheme=no_ota)。固件大约 820 KB。有两个设置让我踩了坑:
- C++17。 arduino-esp32 2.0 核心默认按 C++11 编译,而代码用到了 C++14 和 C++17 的特性。请添加
--build-property "compiler.cpp.extra_flags=-std=gnu++17"。 - 面板。 我显式配置了 ILI9342C(VSPI,MOSI 23,MISO 19,SCK 18,CS 14,DC 27,RST 33,背光 32,
offset_rotation = 3),而不是使用 LovyanGFX 的板卡自动检测。v2.7 的 IPS 面板需要反色。它的检测方式与 M5Stack 自家库相同:通过复位线的回读方式来判断。
3. 运行桥接程序
pip install -e bridge # from the repository root; installs pyserial and bleak
m5claude-bridge accounts # which accounts it found
m5claude-bridge autostart on # start now, and at every login
autostart on 会注册桥接程序,使其在你登录时无窗口启动。在 Windows 上,它会在用户的 Run 注册表项下添加一个启动 pythonw 的值。在 macOS 上它会安装一个 LaunchAgent,在 Linux 上则是一个 systemd 用户服务。autostart status 会显示它是否在运行,以及日志的最后几行,日志位于 ~/.m5claude/bridge.log。同一时间只能运行一个桥接程序:第二个会争抢 USB 端口和蓝牙连接,因此它会打印一条消息后退出。单独运行 m5claude-bridge 会让它保持在前台,日志输出到终端。
Pro 和 Max 账户是从 Claude Code 的配置目录中找到的。要添加第二个订阅,请在其自己的目录下登录:
$env:CLAUDE_CONFIG_DIR = "$HOME\.claude-work"; claude # then /login
API 组织需要一个 Admin API 密钥,可在 Claude Console 的 Settings → Admin keys 中获取:
m5claude-bridge add-api "Acme API" --key-env ACME_ADMIN_KEY
4. 连接
插上 USB 线,或者把设备放在桌上让蓝牙找到它。首次通过蓝牙连接时,在设备上按 C 来批准这台 PC。数据会在几秒内出现。
注意事项
- 套餐限额接口不是公开 API。 它是 Claude Code 的
/usage界面所使用的接口,可能会发生变化。桥接程序会回退到较旧的字段名,但如果格式改变则需要更新。 - 登录会过期。 Claude Code 会在你使用时刷新其令牌。如果你有几个小时没用过某个账户,它会显示“Login expired”,直到 Claude Code 再次使用该账户。
- 订阅的美元金额是按 API 标价估算的。 API 组织则显示来自成本报告的真实花费。
下一步
- PC 上的托盘图标,显示连接状态。
- microSD 卡上的历史记录,用于展示超过七天的图表。
- Grove 端口上的一个 RGB LED,超过 90% 时变红,隔着房间也能看到。
- 可选的 Wi-Fi 连接,通过 USB 或蓝牙从桥接程序配置,这样设备就可以放在另一个房间。
代码在 github.com/velxio/m5claude。欢迎提交 issue 和 pull request。
感谢 M5Stack 提供了一块开箱即用的板子,也感谢 LovyanGFX、NimBLE-Arduino、ArduinoJson 和 bleak 的作者们。
本页中指向 M5Stack 商店的链接是联盟链接:如果你通过它们购买,Velxio 会获得少量佣金,对你没有额外费用,这有助于让模拟器保持免费。