把 Claude 运行状态做成桌面摆件:从 API 状态到实体灯效

2026-06-11 24 预计阅读时间: 1 分钟
来源: oschina.net AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:10 分钟

开源中国造物社区最近搞了个有意思的活动——「AI 造物大赏」,面向硬件创客征集 AI 加持的实体项目。不限硬件品牌、不限开发平台,核心要求只有一个:你的作品得让 AI 从屏幕里"走出来",变成摸得到、看得见的东西。

其中「码克助手」这个方向尤其吸引我:把 Claude 的运行状态做成桌面摆件。想象一下——你桌上有个小屏幕或一组 LED,Claude 在思考时灯光缓缓呼吸,回答完成时亮起绿色,出错时闪烁红色。不用切回终端看日志,一眼就知道模型在干什么。

下面拆解一下这个项目的关键环节,并给出可以直接跑的代码。

状态从哪来?

Claude 的运行状态本质上来自两个地方:

  1. API 响应阶段——请求发出后,模型在生成回答的过程中,Anthropic 的 streaming API 会持续返回 content_block_delta 事件;生成结束会返回 message_stop
  2. 本地进程状态——如果你用 CLI 或自建前端调用 Claude,进程本身就有"正在等待响应 / 已收到 / 出错"的状态变化。

对于桌面摆件来说,最实用的方案是写一个轻量中间层:本地 Python 脚本调用 Claude API,监听 streaming 事件流,把每个阶段翻译成硬件指令,推给串口或 MQTT 通道。

状态到灯效的映射

把 API 事件映射成硬件行为,逻辑很直接:

API 事件 硬件行为
请求发出(message_start 黄灯呼吸动画——"正在思考"
内容流式返回(content_block_delta 绿灯稳定亮——"正在输出"
生成完成(message_stop 绿灯闪两下后熄灭——"回答完毕"
请求出错(HTTP 5xx / 超时) 红灯快闪——"出问题了"
空闲(无请求) 蓝灯微光——"待命"

这个映射表可以根据自己的审美调整。有人喜欢用小 OLED 屏显示文字状态,有人喜欢纯灯效——都可以,关键是状态和视觉之间有明确对应。

实操:Python 中间层 + ESP32 灯效

下面给一个最小可跑的方案:Python 脚本监听 Claude streaming 事件,通过串口把状态码发给 ESP32;ESP32 收到状态码后切换灯效。

Python 端:监听 Claude 流式响应并推送状态

依赖:anthropic SDK(pip install anthropic),pyserialpip install pyserial)。

#!/usr/bin/env python3
"""claude_desktop_monitor.py
监听 Claude streaming 事件,通过串口推送状态码给 ESP32。

状态码约定:
  0 = 空闲(蓝)
  1 = 思考中(黄呼吸)
  2 = 输出中(绿)
  3 = 完成(绿闪两下)
  4 = 出错(红快闪)

运行前修改: API_KEY, SERIAL_PORT, 提问内容
"""

import serial
import time
import anthropic

API_KEY = "sk-ant-xxxxx"          # ← 替换成你的 key
SERIAL_PORT = "/dev/ttyUSB0"      # ← Linux; macOS 常见 /dev/tty.usbmodemxxxx; Windows COM3
BAUD = 9600

STATE_IDLE = 0
STATE_THINKING = 1
STATE_OUTPUTTING = 2
STATE_DONE = 3
STATE_ERROR = 4


def push_state(ser: serial.Serial, code: int):
    """向 ESP32 发送单字节状态码"""
    ser.write(bytes([code]))
    print(f"[状态推送] -> {code}")


def ask_claude_stream(prompt: str, ser: serial.Serial):
    """调用 Claude streaming API,实时推送状态"""
    client = anthropic.Anthropic(api_key=API_KEY)

    push_state(ser, STATE_THINKING)

    try:
        with client.messages.stream(
            model="claude-sonnet-4-20250514",
            max_tokens=1024,
            messages=[{"role": "user", "content": prompt}],
        ) as stream:
            got_content = False
            for event in stream:
                # 首次收到内容块,切换到"输出中"
                if event.type == "content_block_delta" and not got_content:
                    got_content = True
                    push_state(ser, STATE_OUTPUTTING)

            # 流结束
            push_state(ser, STATE_DONE)

    except anthropic.APIError as e:
        print(f"[API 错误] {e}")
        push_state(ser, STATE_ERROR)

    # 3 秒后回到空闲
    time.sleep(3)
    push_state(ser, STATE_IDLE)


def main():
    ser = serial.Serial(SERIAL_PORT, BAUD, timeout=1)
    time.sleep(2)  # 等 ESP32 重置完成

    push_state(ser, STATE_IDLE)

    # 示例提问——改成你想问的任何内容
    ask_claude_stream("用三句话解释量子纠缠", ser)

    ser.close()


if __name__ == "__main__":
    main()

运行方式:

pip install anthropic pyserial
python3 claude_desktop_monitor.py

ESP32 端:接收状态码并切换灯效

用一块 ESP32 开发板 + 一颗 RGB LED(WS2812/NeoPixel 最方便),接线:LED 数据脚接 GPIO 4。

Arduino 代码如下(需安装 Adafruit NeoPixel 库,Arduino IDE 库管理器直接搜):

// esp32_claude_lamp.ino
// ESP32 + WS2812 RGB LED: 串口接收状态码,切换灯效
// 状态码: 0=空闲(蓝) 1=思考(黄呼吸) 2=输出(绿) 3=完成(绿闪) 4=出错(红闪)

#include <Adafruit_NeoPixel.h>

#define LED_PIN   4
#define LED_COUNT 1

Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);

int currentState = 0;
int breathVal   = 0;   // 呼灯亮度 0~255
int breathDir   = 1;   // 1=变亮, -1=变暗
int flashCount  = 0;   // 完成闪烁计数

void setup() {
  Serial.begin(9600);
  strip.begin();
  strip.show();  // 全灭
}

void loop() {
  // 读取串口状态码(单字节)
  if (Serial.available()) {
    int incoming = Serial.read();
    if (incoming >= 0 && incoming <= 4) {
      currentState = incoming;
      flashCount = 0;
      breathVal  = 0;
      breathDir  = 1;
    }
  }

  // 根据状态切换灯效
  switch (currentState) {
    case 0: // 空闲 - 蓝微光
      strip.setPixelColor(0, strip.Color(0, 0, 40));
      strip.show();
      break;

    case 1: // 思考 - 黄呼吸
      breathVal += breathDir * 5;
      if (breathVal >= 255) breathDir = -1;
      if (breathVal <= 0)   breathDir = 1;
      strip.setPixelColor(0, strip.Color(breathVal, breathVal * 3 / 4, 0));
      strip.show();
      delay(30);
      break;

    case 2: // 输出 - 绿稳定
      strip.setPixelColor(0, strip.Color(0, 200, 0));
      strip.show();
      break;

    case 3: // 完成 - 绿闪两下
      if (flashCount < 4) {
        bool on = (flashCount % 2 == 0);
        strip.setPixelColor(0, on ? strip.Color(0, 200, 0) : strip.Color(0, 0, 0));
        strip.show();
        flashCount++;
        delay(200);
      } else {
        // 闪完熄灭,等 Python 推下一个状态
        strip.setPixelColor(0, strip.Color(0, 0, 0));
        strip.show();
      }
      break;

    case 4: // 出错 - 红快闪
      bool redOn = (millis() / 150) % 2 == 0;
      strip.setPixelColor(0, redOn ? strip.Color(255, 0, 0) : strip.Color(0, 0, 0));
      strip.show();
      break;
  }

  delay(10);
}

ESP32 烧录后,插上 USB,Python 脚本就能通过串口控制灯效了。整个链路:提问 → Claude streaming → Python 解析事件 → 串口发状态码 → ESP32 切灯

进阶方向

上面是最小骨架,实际做摆件可以往几个方向扩展:

  • 换 OLED 屏显文字:ESP32 驱动一块 0.96 寸 SSD1306 OLED,除了灯效还显示"Thinking…"、"Done"等文字,信息密度更高。
  • 加 MQTT 无线通道:把串口换成 MQTT(ESP32 跑 WiFi),Python 端发 MQTT 消息,摆件不用插线,放在桌面任何位置。
  • 多模型支持:中间层同时监听 Claude、GPT、Gemini 的 API,不同模型用不同颜色区,一眼看出谁在工作。
  • 外壳设计:3D 打印一个小房子或机器人外壳,LED 藏在"眼睛"位置,摆件感立刻拉满。

上手清单

如果你也想做一个类似的 AI 桌面摆件,准备这些东西就够了:

物品 备注
ESP32 开发板 任意型号,带 USB 即可,十几块钱
WS2812 RGB LED 一颗就行,也可以用 RGB 共阴模块替代
USB 数据线 注意是数据线不是纯充电线
3D 打印外壳(可选) 没打印机就用纸盒先凑合

风险提示:串口通信在不同操作系统上端口名不同,首次跑建议先 ls /dev/ttyUSB*ls /dev/tty.usbmodem* 确认设备路径。WS2812 的数据脚建议加 300–500Ω 电阻做保护,虽然一颗 LED 大概率没事,但长期跑还是稳妥点好。


开源中国造物社区的「AI 造物大赏」本质上是给这类"把 AI 变成实体"的项目一个展示舞台。码克助手这个方向——让大模型的状态从日志行变成桌面上的光——不复杂,但每天用的时候确实能省下不少"切窗口看状态"的注意力。一块 ESP32、一颗 LED、三十行代码,今晚就能亮起来。


相关推荐