设计制造一个机器人桌宠
让我们先冷静一下。在终端中点击“Y”或“N”来批准AI工具的执行,这种操作实在无聊。作为开发者,我们整天盯着文本编辑器和命令行提示符。当你使用Anthropic的Claude Code CLI时,感觉这应该拥有更具体、更实际的东西。
如果,你桌面上的物理四轴工业机械臂不是在 sterile 的终端提示中请求文件系统访问,而是用它那象征性的“脚步”敲击着,等待你的实际认可呢?如果它在你赋予它一项困难任务时庆祝一下,而在闲置时就进入睡眠状态呢?
进入 MyPalletizer Buddy。
在这个项目中,我使用了一台 Elephant Robotics 的 MyPalletizer 260 和一台 M5Stack,将其改造成了一个类似 Tamagotchi 的实体桌面伴侣。更重要的是,它还充当了 Claude Code 的安全、人机协同(HITL)硬件网关。当 Claude Claude Claude 想要运行某个工具时,会向 M5Stack 发送一个 BLE 数据包。屏幕会亮起显示请求内容,用户需通过按压一个实体的硬件按钮来批准或拒绝该操作。机械臂则根据 AI 的状态进行物理动作模拟。
这不仅仅是一个玩具,而是一个真正实用、高度安全的物理隔离空间,专为AI代理执行设计,并融入了大量桌面游戏化元素。让我们来打造它吧。
硬件配置:将M5Stack与ARM结合
MyPalletizer生态系统的优点在于,M5Stack设计为可直接插入机器人的底部。M5Stack负责Wi-Fi/BLE通信和用户界面,同时通过内部串口与机械臂上的舵机进行通信。
1 使用专用电源为MyPalletizer供电。
2 通过 USB-C-C 将 Basic Basic 连接到开发机进行刷机。
环境配置与不可避免的陷阱
在我们开始看代码之前,先省去你大约四个小时的调试时间。
Gotcha #1:“草图过大”
我们正在将用户界面、ASCII动画引擎、BLE堆栈和JSON解析功能全部集成到ESP32上。如果你尝试在默认的分区方案下编译,将会因“程序太大”错误而彻底失败。
修复方法:在Arduino IDE中,进入“工具”>“分区方案”,选择“大型应用(3MB,无OTA/1MB SPIFFS)”。对于这款桌面宠物设备,你不需要空中更新,但确实需要足够的闪存空间。
Gotcha #2:NVS垃圾数据漏洞
我们使用ESP32的非易失性存储(NVS)来保存宠物的属性数据(等级、经验值、情绪状态),以便在重启后持续保存。当新ESP32首次上电时,NVS空间并未被清零,而是包含随机的垃圾数据。如果直接读取这些数据,你的宠物将从第36级、492点开始启动,但情绪状态会损坏。
修复方案:实现一个魔术字哨兵。我使用 0xB5。在启动时,代码会检查特定的 NVSS 地址是否包含 0xB5。如果未找到,则清除统计块,初始化一个一级宠物,并写入 0xB5 以完成封存。
代码与逻辑:幕后真相
该系统依赖于Claude Code CLI(一个本地的Python守护进程)与ESP32固件之间的精密协作。以下是使其正常运行的关键技术要点。
1. Claude Code与IPC绕过
Claude Code Code 允许你定义 PreToolUse 和 PostToolUseUse 回调函数。但默认情况下,如果某个工具需要审批,Claude 会在终端中暂停。我们希望绕过终端界面,将决策推送到我们的物理硬件上。
为此,我们配置 Claude 的钩子来执行一个轻量级客户端脚本。该脚本通过 Unix Unix Unix 域套接字(IPC)与在后台运行的持久化 Python BLE 代理进行通信。
最关键的部分:你的钩子脚本必须返回一个特定的嵌套JSON结构给Claude,告诉它:“我已经处理了审批,跳过终端提示。”
如果你不返回这个确切的结构,克洛德仍然会停留在终端中等待“Y/N”键入,完全违背了硬件按钮的初衷。
2. BLE NUSUS 双向 JSON 与 20 字节限制
使用 Nordic UART Service (NUS)) 配置的蓝牙低功耗(BLE)非常出色,但标准的最大传输单元(MTU)将数据包限制为 20 字节。我们发送的数据包含工具名称、参数以及宠物状态更新等大容量 JSON 数据体。
你不能仅仅通过BLE写入一个200字节的JSON字符串就指望它能正常工作。必须在Python端处理分块写入,并在ESP32端重新组装缓冲区。
我实现了以换行符(\n)分隔的帧格式。Python 代理将 JSON 字符串分割成 20 字节的数据块,然后逐个发送。ESP32 的 BLE BLE 回调程序会将接收到的字节追加到一个 std::stringstring 缓冲区中。一旦检测到换行符,就会将缓冲区数据发送给 ArduinoJsonJson 进行反序列化,并清空缓冲区。
3. 游戏化:代币计数与宠物属性
宠物是如何“升级”的?当然是通过消耗你的API令牌。
Python 服务进程会监控 Claude 的会话日志(a.jsonl 文件)。每次更新时解析整个文件效率极低。因此,我们采用字节偏移缓存的方式:服务进程记录上次文件大小,仅在 Claude Claude 向日志写入新内容时,才读取新增的行。
它提取令牌消耗数据并输入给宠物。
••• 联邦/层级指标:根据消耗的原始代币数量增加。
••• 情绪:快速按下硬件上的“批准/拒绝”按钮可提升情绪。如果让AI等待30秒,宠物的情绪将降至“烦躁”。
4. 无硬件双缓冲的无闪烁用户界面
M5Stack 的屏幕效果很好,但快速更新文本会导致严重的画面撕裂和闪烁现象,因为 ESP3222 没有为显示屏提供真正的硬件双缓冲。如果你在每次循环中都调用 tft.fillScreen(BLACK),就会看起来像频闪灯一样。
修复方案采用双层脏标志系统:infoDirty 和 infoFullClear。
我们不会清空整个屏幕,而是只重新绘制发生变化的特定文本行。通过用黑色边框覆盖旧文本,再在上方绘制新文本,这种方式虽然需要手动跟踪光标位置,但最终实现的界面却非常平滑流畅。5.. 动画同步与伺服抖动防止
MyPalletizer的伺服电机精度很高,但如果以60Hz频率对它们施加矛盾的坐标指令,就会导致其剧烈抖动。
为了让手臂“跳舞”或庆祝,伺服动作必须严格与屏幕上ASCII宠物的动画节拍同步。我们采用模运算逻辑来实现这一功能。
以下是一段代码示例,展示了我们如何将手臂的庆祝数组与UI的点击事件进行关联:
测试与校准:切勿忽视
在启动克洛德并让它读取你整个文件系统之前,必须先校准机械臂。
MyPalletizerizer 内部的步进电机没有绝对编码器,开机时它们不知道自己处于什么位置。您必须运行内置的校准程序(通常由 Elephant Robotics 提供的独立代码),以设定机械零点位置。
如果跳过此设置,机械臂将默认处于当前已通电的位置 [0, 0, 0, 0]。当你的代码命令其归零时,它可能会以最高速度猛烈撞向桌面。请校准你的关节!
下一步与社区升级
该项目奠定了基础——为AI代理提供一个安全的物理接口。但仍有大量发展空间。以下是一些可供你探索的想法:
•• 近距离唤醒:添加一个I2C时序飞行(ToF)传感器。当您离开时,宠物会进入睡眠状态;当您坐在办公桌前时,它会通过摆动机械臂被唤醒。
• 语音转文字集成:M5Stack Core22 内置麦克风,您可以完全跳过键盘,直接用语音向 M5Stack 发送指令,再由它转发给 Claude。
•• 物理停止按钮:将一个大型红色紧急停止按钮连接到ESP32的GPIO引脚,以便在AI决定执行rm -rf /时使用。
从仅依赖软件的人工智能向具有实体形态的具身智能体过渡才刚刚开始。打造MyPalletizer Buddy不仅仅是一次有趣的周末项目,更让我们得以窥见未来与自主系统互动的方式——不再通过终端提示,而是借助物理、触觉化的交互界面。
本文编译自hackster.io





