用 M5Paper 做一个「墨水屏 Agent 看板」
你是不是也有这种瞬间:
- Agent 在跑,你在做饭/洗澡/躺床上刷手机
- 突然想起来“它是不是又在等我审批了?”
- 然后你冲回电脑前,发现它卡了 20 分钟 😅
把状态搬到墨水屏上就舒服了。
这个方案的目标很简单:
- 在家里任何角落看见 Claude Code / Codex 的运行状态
- 有需要时能看到“等待审批”的提示
- 不依赖桌面端,更不挑设备
灵感来自 Claude 的实体 buddy 项目。原版偏向 M5StickCPlus + 桌面端。我这里把它改成适配 M5Paper(墨水屏),并改成只靠 Claude Code 插件 + 本地服务就能跑。
你会做出什么效果
M5Paper 屏幕上显示一个很像“工单看板”的界面:
- 当前活跃 Agent:Claude Code / Codex
- 状态:Running / Waiting approval / Done / Error
- 当前步骤/任务名(短文本)
- 最近心跳时间
- 可选:进度条(伪进度也行)、错误摘要
墨水屏的爽点:
- 不刺眼,晚上看很舒服
- 低功耗,放桌上几天不用管
- 刷新慢但足够用(状态看板不需要 60fps)
整体架构(别怕,很直)
核心就三块:
- Claude Code / Codex → 插件:捕获运行状态(开始、结束、等待审批、报错…)
- 本地状态服务(Status Hub):存一份“最新状态”,提供 HTTP API(也可 MQTT)
- M5Paper:每隔 N 秒拉一次 API,刷新墨水屏
为什么要中间这个 Status Hub?
- M5Paper 不适合直接连一堆工具
- 你想加第二个屏、加手机页面、加 Home Assistant,都只用接这一个 Hub
准备清单
硬件
- M5Paper(推荐)
- 2.4G Wi‑Fi 环境(ESP32 只支持 2.4G)
- 一根数据线
软件
- 你电脑上有 Claude Code / Codex 的使用环境
- Node.js(用来跑本地 Status Hub,当然你也可以用 Python)
- Arduino IDE 或 PlatformIO(用来刷 M5Paper)
Step A:先把「状态服务」搭起来(5 分钟出 API)
我们做个最小可用版:
POST /update插件推状态GET /status墨水屏读状态
1)新建一个 Node.js 服务
创建 status-hub/server.js:
import express from "express";
const app = express();
app.use(express.json());
// 内存里存最新状态。想更稳就写到 sqlite/json 文件。
let latest = {
source: "unknown", // claude_code / codex
state: "idle", // running / waiting / done / error / idle
title: "",
detail: "",
updatedAt: Date.now(),
};
app.get("/status", (req, res) => {
res.json(latest);
});
// 简单口令,防止局域网里被乱写
const TOKEN = process.env.HUB_TOKEN || "change-me";
app.post("/update", (req, res) => {
const token = req.header("x-hub-token");
if (token !== TOKEN) return res.status(401).json({ ok: false });
latest = {
...latest,
...req.body,
updatedAt: Date.now(),
};
res.json({ ok: true });
});
const port = process.env.PORT || 8787;
app.listen(port, "0.0.0.0", () => {
console.log(`Status Hub running on http://0.0.0.0:${port}`);
});
安装依赖并运行:
cd status-hub
npm init -y
npm i express
node server.js
2)快速验证
curl -X POST http://localhost:8787/update \
-H 'content-type: application/json' \
-H 'x-hub-token: change-me' \
-d '{"source":"claude_code","state":"running","title":"Refactor auth","detail":"editing files..."}'
curl http://localhost:8787/status
能返回 JSON 就稳了。
Step B:让 Claude Code / Codex「把状态吐出来」
你这里有两种做法,选你顺手的。
方案 1:Claude Code 插件直接 POST(推荐)
思路:遇到关键事件就请求 POST /update。
事件你至少要抓住这些:
- Agent 开始跑(running)
- 需要你确认/审批(waiting)
- 完成(done)
- 报错(error)
插件怎么写取决于你用的框架,这里给一个“你照着改就能用”的通用伪代码:
// pseudo code
const HUB = process.env.HUB_URL || "http://192.168.1.10:8787";
const TOKEN = process.env.HUB_TOKEN;
async function push(state) {
await fetch(`${HUB}/update`, {
method: "POST",
headers: {
"content-type": "application/json",
"x-hub-token": TOKEN,
},
body: JSON.stringify(state),
});
}
onAgentStart((ctx) => push({
source: "claude_code",
state: "running",
title: ctx.taskTitle,
detail: ctx.stepSummary,
}));
onNeedApproval((ctx) => push({
source: "claude_code",
state: "waiting",
title: ctx.taskTitle,
detail: ctx.question,
}));
onAgentDone((ctx) => push({
source: "claude_code",
state: "done",
title: ctx.taskTitle,
detail: "done",
}));
onAgentError((ctx) => push({
source: "claude_code",
state: "error",
title: ctx.taskTitle,
detail: ctx.errorMessage,
}));
方案 2:你懒得写插件,就“看日志推送”
适合临时用。
- Claude Code / Codex 运行时一般有日志输出
- 你用一个脚本 tail 日志,匹配关键词,然后 POST
这方案不优雅,但能跑,胜在快。
Step C:M5Paper 刷一个「墨水屏看板」
M5Paper 是 ESP32 + 960x540 墨水屏。
关键点:
- 别太频繁刷新(墨水屏会闪、也伤寿命)
- 刷新节奏建议:
- Running:30~60 秒一刷
- Waiting approval:10~20 秒一刷(你想更快也行)
- Done/Idle:2~5 分钟一刷
下面示例用 Arduino + M5EPD 库(思路清晰,能改)。
1)安装库
Arduino IDE 里装:
- M5EPD(M5Paper 官方库)
2)示例代码(HTTP 拉取 + 画文本)
#include <M5EPD.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASS = "YOUR_PASS";
// 你的 Status Hub 局域网地址
const char* HUB_STATUS_URL = "http://192.168.1.10:8787/status";
M5EPD_Canvas canvas(&M5.EPD);
String state = "idle";
String title = "";
String detail = "";
String source = "";
unsigned long updatedAt = 0;
void drawUI() {
canvas.createCanvas(960, 540);
canvas.fillCanvas(15);
canvas.setTextSize(4);
canvas.setTextColor(0);
canvas.setCursor(40, 40);
canvas.printf("Agent 看板");
canvas.setTextSize(3);
canvas.setCursor(40, 120);
canvas.printf("Source: %s", source.c_str());
canvas.setCursor(40, 170);
canvas.printf("State : %s", state.c_str());
canvas.setCursor(40, 230);
canvas.printf("Task : %s", title.c_str());
canvas.setCursor(40, 290);
canvas.printf("Info : %s", detail.c_str());
canvas.setCursor(40, 470);
canvas.setTextSize(2);
canvas.printf("Updated: %lu", updatedAt);
canvas.pushCanvas(0, 0, UPDATE_MODE_DU4);
canvas.deleteCanvas();
}
bool fetchStatus() {
HTTPClient http;
http.begin(HUB_STATUS_URL);
int code = http.GET();
if (code != 200) {
http.end();
return false;
}
String payload = http.getString();
http.end();
StaticJsonDocument<512> doc;
DeserializationError err = deserializeJson(doc, payload);
if (err) return false;
source = doc["source"].as<String>();
state = doc["state"].as<String>();
title = doc["title"].as<String>();
detail = doc["detail"].as<String>();
updatedAt = doc["updatedAt"].as<unsigned long>();
return true;
}
unsigned long nextIntervalMs() {
if (state == "waiting") return 15000;
if (state == "running") return 45000;
if (state == "error") return 20000;
return 180000;
}
void setup() {
M5.begin();
M5.EPD.SetRotation(0);
M5.EPD.Clear(true);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(300);
}
fetchStatus();
drawUI();
}
void loop() {
if (fetchStatus()) {
drawUI();
}
delay(nextIntervalMs());
}
你会发现它很“朴素”。
够用就行。等你真用起来,再去美化 UI:加图标、加大号红色“WAITING”、加二维码跳回电脑审批页面。
让它更像个成熟玩意(建议你加的三件事)
1)Waiting 状态强提醒
墨水屏不响、也不闪灯怎么办?
- 让 M5Paper 蜂鸣器/震动(取决于你的外设)
- 或者把 Waiting 时刷新频率调高
- 或者让 Status Hub 同时发个手机推送(企业微信/Telegram/PushDeer 都行)
2)加一个“审批入口”
最实用的方式:
- Status Hub 在
status里带一个approveUrl - M5Paper 显示二维码
- 你手机一扫,直接打开审批页面
3)掉线自愈
现实很残酷:Wi‑Fi 会抽风。
你至少要做到:
- 连不上 Hub 时,屏幕显示
OFFLINE - 连不上 Wi‑Fi 自动重连
- N 次失败后自动重启(ESP32 很常见的保命招)
避坑清单(踩过的人才会写)
- 墨水屏刷新别太勤:别做成“实时监控”,你会得到闪屏 + 体验崩坏。
- Hub 地址别写 localhost:M5Paper 访问的是你电脑在局域网的 IP。
- 2.4G Wi‑Fi:很多人家里开了双频,ESP32 连不上 5G 就开始怀疑人生。
- 加个 token:不然同一局域网里谁都能 POST 改你状态,家里有爱折腾的朋友你就懂了。
- JSON 长度控制:墨水屏显示不下长文本。detail 记得截断,比如最多 80 字。
你可以怎么扩展(玩起来就停不下了)
- 同时显示多个 Agent(队列)
- 加“今日已跑任务数/节省时间”这种统计
- 接入 Home Assistant:Waiting 时让客厅灯变黄
- 用 MQTT 替代 HTTP:推送更舒服,M5Paper 也不用轮询
一句话复盘
你要做的事其实就两步:
- 让 Claude Code / Codex 把关键状态发出来(插件/日志都行)
- 让 M5Paper 低频读状态、刷新墨水屏
做完以后,你会发现它特别“居家”:屏幕往餐桌一放,饭都能吃得踏实点。
如果你想让我把这一套整理成可直接跑的开源仓库结构(Status Hub + M5Paper 固件 + 简单插件模板),也可以把你现在用的是 Claude Code 还是 Codex、以及你更偏 Arduino 还是 UIFlow 告诉我,我按你的栈给你配齐。😄