
很多人第一次看到“Claude Meter with XIAO ePaper DIY Kit (ESP32-S3) EE05”这个项目名第一反应是拿一块电子墨水屏给Claude API做个体检仪表这名字听着就很有意思。说白了这个项目就是用Seeed家的XIAO ESP32-S3小板子加上配套的墨水屏扩展板做一个能显示Claude API调用量、Token消耗、费用情况的桌面小仪表。接上WiFi之后自动拉数据时间、用量、配额、状态一目了然。这块屏幕最吸引人的地方是静态显示不耗电放在桌面上像一张随时更新的印刷小报。适合三类人来玩天天调Claude API想盯着配额花销的开发者想练手ESP32墨水屏的嵌入式新手还有单纯想给桌面添一个极客小玩具的DIY爱好者。我最初是被墨水屏那种纸质般的显示效果吸引的但真正做完后发现这个项目的价值远不止是“有个屏显示数字”。它背后涉及物联网设备如何对接云服务API、如何在低功耗设备上做网络请求、怎么用极少的资源完成JSON解析和排版渲染以及墨水屏这种特殊显示器件驱动的坑。这篇文章我会把从零开始到跑通全流程的经验全部写出来尤其是那些文档里不会写在明面上的细节。1. 项目轮廓与方案选型1.1 这是一个什么样的设备简单说这是一个桌面级API监控仪表。取名“Meter”很贴切因为它干的就是仪表的事采集数据、量化展示。Claude API的使用计量通常包括请求次数、输入Token数、输出Token数、计费金额、当前配额或者剩余额度。这些数据从官方提供的用量查询接口拿经过ESP32处理后渲染到墨水屏上。这种设备解决的核心痛点是API费用是隐性消耗月底账单出来才发现超支。传统做法是打开网页看仪表盘或者写脚本定时查一下。但网页不会一直开着脚本结果也只活在终端里。把它们固化到一块常驻桌面的低功耗屏幕上是这个方案最直接的价值。早上扫一眼屏幕剩余配额清不清楚心里有数这种获取信息的体验是完全不同层级的。做这类设备的思路其实跟做一个智能家居中控面板高度一致设备联网、拉取数据、本地解析、界面渲染、定时刷新。把这套逻辑理清楚了以后做任何信息展示类设备都是同一套方法论换个数据源就能复用。1.2 为什么是XIAO ESP32-S3而不是其他板子选型是这个项目最关键的前置决策。我对比过几套方案Arduino Uno串口屏模块、树莓派ZeroHDMI小屏、ESP32全家桶。Unio性能太弱也没WiFi外接模块连线乱到怀疑人生树莓派Zero一台的价格够买三套这套方案而且开机速度和功耗都不是一个量级。最终锁定XIAO ESP32-S3是有硬理由的。XIAO ESP32-S3这块板子是Seeed XIAO系列里的主力型号。尺寸只有拇指大小却集成了双核240MHz的Xtensa LX7处理器、WiFi 4和BLE 5.0、2.4GHz射频还有8MB Flash和2MB PSRAM。关键是它直接集成了锂电池充放电管理电路可以边充边用也可以电池供电脱离USB线。配合它的墨水屏扩展板规格是2.13英寸250x122分辨率三色显示黑、白、红通过SPI接口驱动。这套组合完完全全就是为桌面信息仪表这种场景设计的所有模块通过排针直插连接不需要面包板和杜邦线。还有个容易被忽略的点这款扩展板在PCB上留了RTC槽位可以插一个RTC芯片给设备提供断电后的时间保持能力。考虑到仪表设备要显示时间戳这个设计很实用。1.3 整体工作流程设备的工作流程可以用一个闭环来概括上电初始化 - 连接WiFi - 请求Claude API用量数据 - 解析JSON - 排版渲染到墨水屏 - 进入低功耗状态等待下一个刷新周期。这里面有一个容易轻视但实际很关键的环节墨水屏的刷新机制。它和LCD、OLED有本质不同墨水屏刷新时需要把整块屏幕置黑、置白、再点墨这个过程在视觉上是明显的闪烁而且一次完整的刷新要耗时一两秒。刷新次数也有寿命限制通常在十万次量级。这对软件设计提出了要求数据没变化时不要频繁刷新能局部更新就不要全刷。我自己在项目里加了脏数据检查机制Token数值和上次一致直接跳过刷新把屏幕寿命和功耗都控制住了。2. 硬件物料与接线要点2.1 物料清单这套DIY Kit的物料非常收敛来回收纳就七个部件物料型号/规格作用主控板XIAO ESP32-S3主控芯片、WiFi通信、数据处理墨水屏模块2.13英寸三色墨水屏信息展示扩展板XIAO ePaper ESP32-S3扩展板承载屏幕、提供电路连接、预留RTC槽位锂电池3.7V聚合物电池无线供电USB线Type-C数据线烧录固件与调试外壳透明亚克力夹板固定与保护RTC芯片选配常见I2C接口RTC模块断电时间保持如果购买的是完整Kit核心部件到手即插即用不需要额外焊接对新手极度友好。不过要注意一点从排针焊接到扩展板这个动作还是绕不开的包装里带的排针需要自己动手焊上去。没有焊接经验的朋友建议先找块废板练手XIAO的焊盘间距小排针固定不牢容易虚焊。我第一次焊这种小而密的焊盘就踩了坑教训是焊台温度不要超过350℃单点焊接时间控制在2到3秒焊完用放大镜检查每个焊点是否有润湿饱满。2.2 接线与电平匹配XIAO ESP32-S3和墨水屏扩展板是通过排母直插的方式连接的。只要方向对准插到底就行不存在交叉接错的可能这一步比面包板方案省心太多。整套系统运行在3.3V逻辑电平上ESP32-S3的GPIO输出就是3.3V和墨水屏控制器的逻辑电平完全兼容不需要额外做电平转换。如果你的RTC模块是自己外接的独立模块需要注意SDA、SCL接到扩展板上对应引脚。XIAO ESP32-S3默认的I2C引脚是GPIO41SDA和GPIO42SCL这一点跟很多ESP32老型号不一样一开始我按惯用习惯接GPIO21、22搞了半天检测不到设备看原理图才发现问题。遇到设备扫描不到的情况第一步永远是用I2C扫描工具确认引脚定义。2.3 上电检查顺序插好屏幕、接好电池、连上USB之后不要急着写代码。先做一轮硬件自检顺序如下先看板载RGB指示灯是否闪烁说明主控正常运行再用I2C扫描确认RTC如果有是否在总线上响应最后检查墨水屏在Arduino示例代码下能否完成一次完整的初始化刷新。这个过程花费五分钟能省下后面好几个小时的排障时间。硬件链路如果没通软件写再多也是白搭这是嵌入式开发里永恒的第一性原理。3. 开发环境与固件实现3.1 Arduino环境搭建这个项目的核心代码用Arduino框架写是最快的。虽然ESP-IDF功能更底层、可控性更强但对于这种规模的应用Arduino的库生态能省掉大量造轮子的时间。我用的是Arduino IDE 2.x版本安装过程分三步。先在“开发板管理器”里添加ESP32的板级支持包。打开偏好设置在“附加开发板管理器网址”中填入官方ESP32索引地址然后在开发板管理器里搜索esp32并安装。这一步网络状况不稳定时容易中断安装完成后最好核对一下版本号我建议选择3.x版本后续的库兼容性更省心。安装完成之后在开发板列表里选XIAO ESP32-S3注意对应关系不要误选成其他ESP32-S3变体导致引脚定义错位。然后是安装项目依赖的库。在库管理器里分别搜索安装以下三个库GxEPD2这是目前最好用的墨水屏驱动库支持大量主流型号ArduinoJson用来解析API返回的JSON数据U8g2_for_Adafruit_GFX这是给墨水屏提供高质量字体渲染的适配库。这三个库装好开发环境的底座就齐了。3.2 核心代码逻辑拆解代码层面核心就干四件事联网、拉取数据、解析、显示。我把关键代码的骨架写出来你可以基于这个框架继续扩展。#include WiFi.h #include HTTPClient.h #include ArduinoJson.h #include GxEPD2_BW.h #include U8g2_for_Adafruit_GFX.h // WiFi凭据 const char* ssid your_ssid; const char* password your_password; // API相关参数 const char* apiKey your_claude_api_key; const char* apiUrl your_usage_endpoint; // 墨水屏对象实例化2.13英寸三色屏 GxEPD2_213_ZS_GDEY021Z98_B72 display(GxEPD2_213_ZS_GDEY021Z98_B72::EPD_SIZE_213_ZS); U8g2_for_Adafruit_GFX u8g2; // 数据缓存 float inputTokens 0; float outputTokens 0; int requestCount 0; float totalCost 0; void setup() { Serial.begin(115200); initDisplay(); connectWiFi(); fetchUsageData(); renderDisplay(); enterSleep(); } void loop() { // 定时唤醒逻辑通过深度睡眠定时器触发 }这里有个关键点loop()里几乎不做事情而是进深度睡眠。设备的刷新周期按小时算几十分钟甚至几小时唤醒一次每次唤醒做完工作继续睡。这是墨水屏仪表和普通智能屏最大的区别功耗量级完全不同。3.3 联网与数据获取模块WiFi连接这块有一个值得优化的细节。直接用WiFi.begin()后加delay()等待连接是最常见的新手写法但可靠性差。我用的是带超时的轮询机制void connectWiFi() { WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); int attempts 0; while (WiFi.status() ! WL_CONNECTED attempts 30) { delay(500); attempts; } if (WiFi.status() WL_CONNECTED) { Serial.println(WiFi connected, IP: WiFi.localIP().toString()); } else { // 失败后直接显示错误信息而不是硬等 displayError(WiFi Failed); } }超时设计为15秒30次每次半秒。这个值是经验值太短在弱信号下容易误报太长又等于把设备拖在阻塞状态里耗电。实测在正常家庭WiFi环境下3到5秒内基本完成连接。然后是HTTP请求。Claude的用量数据通过HTTPS接口返回ESP32-S3需要先加载CA证书否则TLS握手会失败。我把证书以字符串形式直接编进固件里避免依赖文件系统加载。请求头里加上认证信息用GET方式拿到JSON响应。这里给一个完整的最小请求片段void fetchUsageData() { HTTPClient http; http.begin(apiUrl); http.setTimeout(10000); http.addHeader(Authorization, String(Bearer ) apiKey); http.addHeader(Content-Type, application/json); int httpCode http.GET(); if (httpCode 0) { if (httpCode HTTP_CODE_OK) { String payload http.getString(); parseResponse(payload); } else { Serial.printf(HTTP error code: %d\n, httpCode); } } else { Serial.printf(HTTP request failed: %s\n, http.errorToString(httpCode).c_str()); } http.end(); }这里的细节是http.setTimeout(10000)。默认超时值偏短在弱网环境下数据包稍大一点就会读取超时导致整轮刷新失败。设为10秒比较均衡。另外http.end()必须执行否则会占用连接资源下次请求可能报错。3.4 JSON解析与关键字段提取Claude API用量接口返回的JSON结构相对复杂嵌套层级深字段多。用ArduinoJson库解析时一个常见误区是先Serial.print()看原始响应再设计解析代码。省时间的方法是直接用一个在线的JSON格式化工具把样例数据规整化然后对照写解析逻辑。void parseResponse(String payload) { JsonDocument doc; DeserializationError error deserializeJson(doc, payload); if (error) { Serial.print(F(JSON parse failed: )); Serial.println(error.c_str()); displayError(JSON Error); return; } inputTokens doc[data][usage][inputTokens] | 0.0f; outputTokens doc[data][usage][outputTokens] | 0.0f; requestCount doc[data][meta][requestCount] | 0; totalCost doc[data][billing][estimatedCost] | 0.0f; }使用|运算符设置默认值是防御性编程的好习惯API返回结构有调整或者字段缺失时程序不会因为读取空值而崩溃。另外ArduinoJson 7.x版本默认的JsonDocument会动态分配内存实际使用时优先用JsonDocument而不用旧的StaticJsonDocument省去了手动估算缓冲区大小的麻烦。不过要注意解析超大响应时内存占用会升高ESP32-S3的PSRAM在这里发挥了作用。3.5 显示排版与刷新控制墨水屏的显示逻辑是整个项目中视觉体验的核心。2.13英寸三色屏的分辨率是250x122空间有限排版必须克制。我的版面设计是上下三区顶部小字体显示当前时间和日期中间大字号显示累计费用数字作为整个仪表最有感知力的指标底部两行小字分别显示输入Token数、输出Token数以及请求次数。字体渲染用U8g2库有非常丰富的字体资源。这里有个细节同一尺寸下不同字体的实际显示宽度差异很大排版前先在电脑上用U8g2的字体预览工具选中合适的字号而不是在代码里一个个试。我最后选用了12像素高的无衬线字体用于详情20像素高的粗体用于主费用数字视觉层级非常清晰。刷新控制方面我实现了脏检测刷新void renderDisplay() { float lastCost ...; // 从RTC或Flash读取上次的值 if (abs(totalCost - lastCost) 0.001 requestCount lastCount) { Serial.println(No data change, skip refresh); return; } display.setFullWindow(); display.firstPage(); do { drawHeader(); drawCost(); drawDetails(); } while (display.nextPage()); display.display(); saveLastValues(); }数据未变化时直接跳过物理刷新这能显著延长墨水屏寿命也避免不必要的闪烁干扰视线。墨水屏不是LCD每次刷新都有机械颗粒移动的物理过程这个损耗是不可逆的。4. 数据解析与展示优化4.1 数值格式化的处理Token计数和费用金额是浮点数直接渲染到屏幕上会出现一长串小数既不美观也超出屏幕宽度。换算和格式化是必须做的。我的处理方式是这样的费用保留四位小数并带上货币符号Token数分档处理过千显示为k过百万显示为M。举个例子输入Token数是1534200直接显示会撑爆版面格式化为1.53M后又清晰又省空间。String formatLargeNumber(float value) { if (value 1000000) { return String(value / 1000000, 2) M; } else if (value 1000) { return String(value / 1000, 1) k; } return String(value, 0); }有个容易犯的错ESP32的浮点数转字符串时默认精度行为容易产生尾数误差比如4.35被格式化为4.349999。处理方法是格式化时指定小数位数不要依赖默认转换。4.2 时间与时区问题设备显示时间必须考虑时区问题。ESP32默认获取的是UTC时间如果不加时区偏移屏幕上的时间会和北京时间差8小时。最简单的方式是连接WiFi后通过NTP获取时间然后设置正确的Time Zone偏移值。configTime(8 * 3600, 0, ntp.aliyun.com, pool.ntp.org);第一个参数是秒级时区偏移中国时区是8小时即28800秒。如果你只是设置了configTime却没配置setenv(TZ, CST-8, 1)和tzset()部分时间函数仍然会返回UTC时间这个坑很隐蔽。项目里务必三者配合使用。4.3 低功耗与电池续航策略墨水屏仪表的优势在低功耗但低功耗不是白来的需要软件配合。我的策略分三档不休眠、轻度睡眠、深度睡眠。工作状态下设备消耗在80mA左右深度睡眠则能掉到100uA量级。假如每天只唤醒几次每次工作20秒一颗300mAh电池撑上几周完全没有问题。深度睡眠的唤醒方式我选择了定时器esp_sleep_enable_timer_wakeup(refreshIntervalSeconds * 1000000ULL); esp_deep_sleep_start();注意esp_sleep_enable_timer_wakeup的单位是微秒直接填秒会溢出导致唤醒时间完全不对。我第一次写的时候把60秒直接当微秒传进去设备“沉睡”了17个小时才醒来还以为是硬件出了问题。另外唤醒后建议先只初始化必要的外设和WiFi等拿到数据初始化显示部分不需要的外设保持关闭状态。省电的核心就是延迟初始化用多少开多少。5. 常见问题与排错手册5.1 墨水屏显示异常玩墨水屏最常见的问题就是首屏显示花屏、闪烁后内容消失。大多数情况是初始化时序不对导致的墨水屏的控制器对复位脉冲宽度和等待时间有严格要求。我在代码里加入适当的延迟后问题就消失了。另一个高发场景是显示器只显示部分内容这种情况几乎都是因为调用了setPartialWindow()在部分刷新模式下绘制的区域坐标超出实际屏幕尺寸。新手阶段建议直接全屏刷新稳定了再考虑局部刷新优化。5.2 联网不稳定与请求超时设备连不上WiFi、请求超时是第二大问题源。我的排查顺序是先确认2.4G频段ESP32不支持5G频段如果你的路由器开了双频合一建议给设备单独开一个2.4G访客网络再检查信号强度放在金属外壳或者封闭桌面柜里信号衰减严重最后检查证书和API地址。还有一个容易被忽略的点如果路由器开启了AP隔离设备虽然能连上WiFi但无法与外部网络通信表现就是一直连接成功但请求始终无响应。把AP隔离关掉或者给设备设置固定IP问题迎刃而解。5.3 API返回数据解析失败解析失败的典型表现是屏幕显示“JSON Error”或者某个字段显示为0。原因有两类一是API实际返回的结构与代码中假设的路径不一致比如某个层级字段被包在data下面而你直接取顶层我建议先在PC上把响应存成文件格式化后核对层级再写代码二是API接口有鉴权信息过期的情况认证失败时返回的是错误JSON和正常响应结构完全不同代码中应该先判断HTTP状态码对非200响应单独做错误处理。5.4 一张问题速查表现象直接原因处理建议屏幕完全不亮排针方向反或者松动断电重新插拔确认方向首屏花屏初始化时序过短增加复位后延迟到100ms以上能画图但不变色三色屏驱动型号选错核对屏幕型号与GxEPD2构造函数WiFi连不上路由器只开了5G开启2.4G网络请求超时TLS握手失败检查证书或尝试关闭TLS验证电池一天就没电刷新过于频繁或未进休眠检查refreshInterval及深度睡眠参数时间始终不更新NTP无法访问更换国内NTP服务器或配置代理重启后不显示上次数据Flash未存储增加Preferences存储层6. 从仪表到工作台的小拓展6.1 多页面与按键交互基础版本做完之后可以加一个物理按键来切换显示页面。第一个页面显示概览费用第二个页面显示这次结算周期的请求趋势第三个页面显示配置和网络状态。实现思路是在loop()里监听按键电平变化用外部中断唤醒设备或者在服务函数中翻转页面标志。墨水屏的切换记得先display.setFullWindow()全刷一次否则两页内容会叠加残留产生残影。6.2 接入更多数据源这套硬件架构本质上是通用的API监控屏。Claude用量只是第一个数据源你可以把天气、汇率、待办事项、服务器状态、股票价格统统接进来。每次唤醒依次请求多个API再排版渲染。要注意的是请求串行执行总耗时累加。我试过接三个API后单次刷新时间达到45秒体验很煎熬最终还是改为错峰请求每小时只刷新一个数据源降低单次唤醒的工作量。6.3 我给这个项目的实操体会做完这套系统最大感受是墨水屏设备的开发和手机App开发完全是两种思维。手机App追求实时和流畅墨水屏追求的是“恰到好处的更新”。强迫习惯做实时界面的开发者去接受“一小时更新一次还闪烁好几秒”的约束本身就是一种有趣的思维转换。如果让我给后来者一条最核心的建议先把纯显示部分做好再接入网络。单独跑一个显示Demo把布局调到自己满意再一步步加WiFi、加API、加低功耗。不要试图一次集成所有功能“通电能显示”和“工程可用”之间的距离比你想象的远。模块化解耦每个环节都能单独验证出了问题也能准确定位这是整个项目过程中我最深的体会。