首页 / 正文

用 M5Paper 做一个「墨水屏 Agent 看板」:躺沙发也能盯着 Claude Code / Codex 跑到哪了

Mooko
发布于 2026-04-27 · 5分钟阅读
614 浏览
0 点赞 暴击点赞!

用 M5Paper 做一个「墨水屏 Agent 看板」

你是不是也有这种瞬间:

  • Agent 在跑,你在做饭/洗澡/躺床上刷手机
  • 突然想起来“它是不是又在等我审批了?”
  • 然后你冲回电脑前,发现它卡了 20 分钟 😅

把状态搬到墨水屏上就舒服了。

这个方案的目标很简单:

  • 在家里任何角落看见 Claude Code / Codex 的运行状态
  • 有需要时能看到“等待审批”的提示
  • 不依赖桌面端,更不挑设备

灵感来自 Claude 的实体 buddy 项目。原版偏向 M5StickCPlus + 桌面端。我这里把它改成适配 M5Paper(墨水屏),并改成只靠 Claude Code 插件 + 本地服务就能跑。


你会做出什么效果

M5Paper 屏幕上显示一个很像“工单看板”的界面:

  • 当前活跃 Agent:Claude Code / Codex
  • 状态:Running / Waiting approval / Done / Error
  • 当前步骤/任务名(短文本)
  • 最近心跳时间
  • 可选:进度条(伪进度也行)、错误摘要

墨水屏的爽点:

  • 不刺眼,晚上看很舒服
  • 低功耗,放桌上几天不用管
  • 刷新慢但足够用(状态看板不需要 60fps)

整体架构(别怕,很直)

核心就三块:

  1. Claude Code / Codex → 插件:捕获运行状态(开始、结束、等待审批、报错…)
  2. 本地状态服务(Status Hub):存一份“最新状态”,提供 HTTP API(也可 MQTT)
  3. 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 告诉我,我按你的栈给你配齐。😄

OpenClaw
OpenClaw
木瓜AI支持养龙虾啦
木瓜AI龙虾专供API,限时领取免费tokens
可在 OpenClaw接入全球顶尖AI大模型
立即领取