Skip to content

我一行代码没写,机械臂遥控器做出来了——Agent 开发实录 ​

这篇文章里的 App 和固件代码,我一行都没写。 编译是 AI 干的,装 App 是 AI 干的,点屏幕做测试的是 AI,连"机械臂到底动没动",都是它盯着手机摄像头画面自己确认的。 我只负责动嘴,外加干点它干不了的活:解锁手机、插一下数据线、把机械臂活动范围里的东西挪开。

手机支在支架上跑遥控器 App,屏幕中间就是摄像头回传的机械臂画面——AI 靠截图里的这块画面,确认机械臂真的动了

先交代 AI 是谁:这次的主开发工具是 WorkBuddy,全程在一个对话框里指挥。下面是整个过程的原样记录——怎么让它装 App、怎么让它直连 ESP32 调固件、怎么让它"看见"机械臂,还有中途翻的那些车。


目录 ​


一、任务:给机械臂配个手机遥控器 ​

手上这套硬件不复杂:

部件干什么用
机械臂总线舵机方案,各关节串在一条舵机总线上,靠文本指令驱动
控制板ESP32,跑 MicroPython 固件,负责解析指令、驱动舵机
蓝牙模块ZL-BT4.0,挂在 ESP32 串口上的透传模块,负责跟手机连 BLE
手机安卓真机,跑遥控器 App,顺便用自带摄像头"看着"机械臂

链路一句话说完:手机 App 通过蓝牙连到 ZL-BT4.0,模块透传给 ESP32,ESP32 去驱动舵机总线。

App 要做的东西也不复杂:左右两个虚拟摇杆,中间一块摄像头画面,再加一排按钮管夹爪开合、抬臂落臂。题图就是做完之后的样子。

真正磨人的不是写不出来,是改起来太慢。改一行 UI,就得重新编译、装包、解锁手机、点开 App、连蓝牙、试动作、看效果——一整轮十几分钟,每一环都得人守着。这次要做的,就是把这整条人肉流水线整个甩给 Agent。


二、总体思路:给 Agent 接上手、脚、眼 ​

我一开始以为最难的是代码。后来发现根本不是——代码它写得比我快多了。真正卡住的是:它看不见机械臂。

Agent 再聪明,也只是个会读代码的"大脑",天生摸不到你的手机,也摸不到控制板。所以第一件事不是让它写代码,是先给它接上三条物理通路:

通路物理载体它拿到的能力解决什么问题
手adb over USB装包、拉起 App、模拟点击、截图、读日志怎么"操作"手机
脚USB 串口直连 ESP32(Thonny / mpremote)REPL 当场调固件、上传模块、听启动日志怎么"调试"控制板
眼手机摄像头 → App 画面 → 截图回读看见机械臂在物理世界里的真实动作怎么"验证"动作真的发生了

三条通路接通之后,开发循环长这样:

我下指令 → 它改代码 → 真编译 → adb 装机 → 模拟点击
        → 截图(UI + 摄像头画面)→ 判断机械臂动没动
        → 达标:交报告 / 不达标:带着证据回去接着改

注意这个循环里没有"人"。人只出现在两头:开头说"做什么、边界在哪",结尾看证据拍板。


三、主开发工具:WorkBuddy 到底帮我干了啥 ​

这次的主工具是 WorkBuddy。它和"帮我生成一段代码"的那种 AI 补全,完全是两回事,区别主要在四点。

它能直接动手。 项目文件它自己读写,命令行它自己跑。编译、adb 装机、连串口,全是它在会话里自己执行的——不是丢给我一段命令让我去跑。

它会自己转完整个循环。 改代码 → 编译 → 装机 → 实测 → 取证 → 汇报,哪步报错它自己拿着报错回去修,修完再跑,直到达标,或者明确告诉我卡在哪。

踩过的坑它会记住。 每次调试里验证过的流程、命令、坑,可以沉淀成 skill 存进固定目录,下次同类任务开工前它自动先读一遍。同一个坑基本只踩一次,第八节细说。

汇报必须带证据。 它不说"应该好了",而是甩截图:操作前后的对比图、日志关键行、实测数值。这点在硬件项目上特别要紧——最终要验证的是物理世界,不是代码逻辑。

而我这边的工作界面,就是一个对话框。


四、手:USB 调试,让它自己装 App、自己点屏幕 ​

为什么必须是真机、不能拿模拟器凑合?两个原因:遥控器离不开真实的蓝牙链路,也离不开真实的摄像头。所以手机开 USB 调试、插上线,剩下的全归它。

它拿到一台插着 USB 的安卓机之后,干的活是这样:

powershell
$ADB = "D:\ide\androidSDK\platform-tools\adb.exe"

# ① 真编译(改完代码必须真编译,光"读着没问题"不算数)
& "D:\ide\gradle-8.9\bin\gradle.bat" -p "D:\proj\CheapCam" :app:assembleDebug

# ② 装到真机(-r 覆盖安装)
& $ADB install -r .\app\build\outputs\apk\debug\app-debug.apk

# ③ 拉起 App
& $ADB shell am start -n com.light.cheapcam/.remote.HomeActivity

# ④ 截图取证:截图 → 拉回电脑 → 脚本分析
& $ADB shell screencap -p /sdcard/s.png
& $ADB pull /sdcard/s.png D:\tmp\s1.png

# ⑤ 模拟点击:比如按下遥控页的某个按钮
& $ADB shell input tap 540 1400

# ⑥ 再截一张,跟 ④ 对比,验证点击生效
& $ADB shell screencap -p /sdcard/s2.png
& $ADB pull /sdcard/s2.png D:\tmp\s2.png

# ⑦ 读 App 日志(连接状态、收发数据全在这)
& $ADB logcat -d | grep CarBle

这套下来,"装机—启动—操作—取证"已经不用人碰手机了。但中间有三个坑,都是它自己撞过然后记下来的。

点击坐标不能目测,得量。 最直觉的办法是用 uiautomator dump 拿控件位置,但遥控器界面有一直刷新的东西(遥测、画面),界面永远不静止,dump 直接报 ERROR: could not get idle state,这条路堵死。它的替代方案:截图拉回电脑,用脚本按颜色把按钮中心坐标量出来,再模拟点击。靠肉眼按比例估坐标的做法,实测连点错五六次,早就弃用了。

点完必须确认前台是谁。 有一轮测试怎么点都没反应,查了半天,发现那一下点进了屏幕底部的系统手势区——App 早被切到后台,后面几十次点击全打在桌面上。现象看着像"指令没发出去",实际上是点了个寂寞。打那以后它养成一个习惯:每次点击完,先确认前台还是不是 App,再下结论。

装包可能被厂商拦下。 某些国产 ROM 的安装验证器,会让 adb install 永远卡住,或者装完了弹个"风险提示"页把前台抢走。对策:装机前关掉验证器,装完把应用市场强杀掉。这种坑第一次查要一个小时,写进 skill 之后,第二次只要十秒。


五、脚:Thonny 直连 ESP32,固件当场改当场试 ​

App 只是遥控器的上半身,真正干活的是 ESP32 里的固件。这条路的入口就是 Thonny:一个对 MicroPython 支持特别好的轻量 IDE。解释器选 MicroPython (ESP32)、选好串口,它的 Shell 就直接变成这块板子的 REPL——敲一行,板子跑一行;文件面板还能直接看到板上的文件,右键上传。

Thonny 对人来说很顺手。但 AI 点不了图形界面,所以它走的是同一根串口线的命令行形态(mpremote 或者 pyserial),能力完全等价。只有一条规矩:两边不能同时连。串口是独占的,Thonny 开着,那头就报 PermissionError 13。

这条通路最爽的地方,是可以把固件当草稿调。比如我想知道"某个关节的指令对不对",在 REPL 里直接发一条总线指令:

python
# Thonny 的 Shell(或者 Agent 用的 mpremote exec)里直接执行
from machine import UART
import time

u = UART(2, 115200)          # 跟舵机总线 / 蓝牙模块相连的那路串口
u.write('#005P1700T1000!')   # 5 号关节(夹爪)1 秒内转到 1700
time.sleep(1.5)
u.write('#005P1500T1000!')   # 回中位

几秒钟,机械臂当场给你答案——不用走"改代码、上传、重启、连蓝牙、点 App"那一整轮。固件协议的调试、关节行程的标定,全在这个模式下完成。

调好的代码要正式上板,流程固定四步:

bash
python -m serial.tools.list_ports                                          # ① 扫串口(COM 号会漂移,先扫再连)
python -m mpremote connect COM6 fs cp :/factory/arm.py D:/backup/arm.py    # ② 先把板上旧文件备份拉回
python -m mpremote connect COM6 fs cp D:/proj/factory/arm.py :/factory/arm.py  # ③ 上传新固件
python -m mpremote connect COM6 soft-reset                                 # ④ 软复位,新代码才生效

两个细节必须知道,都是踩出来的。

上传失败是静默的。 板子刚上电那几秒是个窗口期,这时候 fs cp 可能只打几行启动日志就失败了,不报任何错。所以上传完必须把文件拉回来逐字节比对,一致才算数——不然你会拿着旧固件"验证"半天新功能,还纳闷怎么没效果。

连串口这个动作本身会复位板子。 开发板上有 auto-reset 电路,Thonny 连上也一样。所以有条铁律:人正在真机测 App 的时候,那头必须停掉一切串口操作——一连接板子就复位、蓝牙就断,App 那边"连接莫名断开"的假故障就来了。这也是"人当指挥"的一部分:两边要错开,我说开始测,串口就得收手。


六、眼:手机摄像头,让它看见物理结果 ​

这是三条通路里最巧的一条,也最容易被忽略。

先说前面两条路各漏了什么。adb 截图只能证明"App 认为它把指令发出去了",串口日志只能证明"固件收到了"——但机械臂在物理世界里到底动没动、动得对不对?没人回答。

答案其实一直摆在眼前:手机就架在机械臂对面,摄像头对着它,App 中间那块画面就是实时取景。它每次截图,顺手就把这块画面截进来了——这就够了:

① 模拟点击"夹爪闭合"
② 截图 A:画面里爪子是张开的
③ 等 1.5 秒,留足舵机到位时间
④ 截图 B:画面里爪子闭合了
⑤ 两张一比——结论成立,物理世界确认执行

这一步等于把它的自测从"软件闭环"升级成了"物理闭环"。它验证的不再只是自己的代码,而是整条链路:蓝牙通没通、指令被执行没有、舵机到位没有——一张摄像头截图全兜住。

三个小经验:

  • 留时间余量。总线舵机走完一段行程要一两秒,截图间隔太短,会冤枉它"没动"。
  • 先挑动作大又安全的关节。夹爪是最好的首发对象:动作醒目、行程短,就算误触发也打不飞什么东西。链路确认没问题了,再去碰摆臂这种大动作。
  • 摄像头画面本身也是 App 的功能项。画面卡不卡、断流后恢复没有,这些同样靠截图取证来测。

七、人还需要干什么:指挥,加一点辅助 ​

三条通路打通之后,我数了数自己还在干的活,就两页纸:

角色具体干什么
指挥下目标("给遥控页加个夹爪按钮");划边界("行程别打满""先别夹东西");验收(看它交上来的截图和日志,拍板通过还是返工)
辅助解锁手机、在弹窗上点一次"允许 USB 调试";插拔数据线;给控制板断电重启;把手机支架摆到能拍全机械臂的位置;保证机械臂活动范围里没有易碰倒的东西

有两件事目前必须人来,而且我觉得这是"特性",不是"缺陷"。

一是安卓的 USB 调试授权和锁屏解锁,电脑侧绕不过去。手机一锁屏,adb 的调试进程就被系统回收,AI 那头就"失明"了。这是安卓的安全设计——不然任何一台插上 USB 的电脑都能解锁你的手机。AI 能做的极限是:检测到掉线,把诊断准备好,然后喊一句"请解锁手机"。整个流程里最典型的"人的辅助",就是这一下。

二是物理安全必须有人兜底。机械臂不会自己判断前面是窗帘还是手指。所有真动作的测试,都是我先清场、确认安全,再放行。

一句话:**人从操作员,退到了指挥员加安全员。**以前人肉流水线上的每一环——写码、装机、点击、观察——现在都归 AI;人留下的,是判断权和物理世界的钥匙。


八、踩坑踩出来的 4 个 skill ​

前面反复提到 skill,这里展开讲讲。

skill 是 WorkBuddy 的经验沉淀机制:把一次调试里实测验证过的流程、命令、坑,写成 SKILL.md(外加可复用的脚本),放进固定目录。下次接同类任务,Agent 开工前自动先读对应的手册——相当于项目自带一本"避坑指南",而且越写越厚。

这个项目前前后后沉淀出四个,目录长这样:

~/.workbuddy/skills/
├── android-hardware-ui-selftest/   # 安卓真机 UI 黑盒自测(Agent 的"手")
│   ├── SKILL.md                    #   流程、华为 ROM 的坑、adb 掉线分诊
│   └── scripts/
│       ├── shot.py                 #   截图 → 拉回 → 可选裁剪,一条命令
│       ├── find_button.py          #   按颜色定位实色按钮中心点
│       ├── find_field.py           #   定位输入框的可点区域
│       ├── find_seg.py             #   定位分段控件的选中项
│       └── find_chip.py            #   定位一排浅色 chip 按钮的各自中心
├── cheapcar-serial-ops/            # 串口直连 ESP32(Agent 的"脚")
│   ├── SKILL.md                    #   扫口、上传/拉回比对、软复位、看门狗逃生
│   └── scripts/
│       └── car.py                  #   自研 REPL 客户端(适配本固件的非标准协议)
├── cheapcam-build-verify/          # 改码后"真编译 + 真产物校验"
│   ├── SKILL.md                    #   工具链、版本坑、编辑器静默丢改动、语音/WebRTC 链路
│   └── assets/                     #   语音迁移检查、变异测试等配套脚本
└── cheapcam-junit-mutation-test/   # 安全关键逻辑的 JVM 单测 + 变异测试
    └── SKILL.md                    #   首次引入 JUnit 的步骤、变异测试脚本模板

四个各管一段:

skill管什么一句话
android-hardware-ui-selftest真机 UI 自测截图→像素定位→注入点击→截图复验,附全套定位脚本
cheapcar-serial-opsESP32 串口调测扫口、直连 REPL、上传固件四步法、"实测期间禁碰串口"铁律
cheapcam-build-verify编译与产物校验"改完必须真编译",外加编辑器静默丢改动这类"白干一轮"级坑
cheapcam-junit-mutation-test测试有效性测试全过 ≠ 测试有效,变异测试证明断言真的抓得住缺陷

沉淀本身也有讲究,三条原则是拿翻车换的:

  • 只写实测验证过的。 没跑通的方法也记,标成"试过但不成立"——比如"读总线回执判断舵机版本"这条路实测走不通,不记下来,下次还得有人去试一遍。
  • 坑和对策必须成对。 只写"华为会吞日志"没用,要写 getprop persist.log.tag 确诊、setprop 解封、测完还原的整套动作。
  • 能脚本化的写成脚本。 坐标定位、哈希比对这种活,一条命令的事,不靠现场重新发挥。

九、翻车速查表 ​

下面这张表,每一条都对应一次真实的翻车:

症状根因对策
uiautomator dump 报 could not get idle state遥控界面一直刷新,永不静止放弃 dump,截图 + 像素脚本定位坐标
adb install 永久卡住国产 ROM 安装验证器弹"风险确认"装机前关 verifier;装完强杀应用市场
adb 反复掉线:device → offline → 空表华为省电策略:一锁屏就回收 adbd开测先设屏幕常亮;掉线只能人工解锁手机(安全设计,绕不过)
点了"没反应"坐标点进系统手势区/别的页面,App 已切后台每次点击后确认前台是谁,再下结论
App 里一条自己的日志都没有华为 ROM 默认 persist.log.tag=M,把第三方日志吞了按标签解封,测完还原
固件上传"成功",行为却没变fs cp 失败是静默的 / 没软复位,板上跑的还是旧模块上传后拉回做字节级比对;软复位后再验证
测试期间 App 蓝牙"莫名断开"调试者连串口,触发了 ESP32 硬件复位人正在实测时,停止一切串口操作
测试全绿,现场却出 bug断言没真正抓住缺陷(假绿)变异测试:故意改坏代码,确认测试真的会转红

十、总结,以及下一步 ​

回头看,这套东西说穿了就三句话:

  1. 给 Agent 接上手、脚、眼三条通路,"改代码→装机→实测→取证"的循环就自动转起来了。
  2. 把人放到正确的位置:指挥加安全员。人不再是流水线上的一环,而是流程的所有者。
  3. 让经验滚起来:坑全沉淀成 skill,同一个坑只踩一次;安全相关的逻辑配变异测试,保证"测试通过"这四个字有含金量。

这套玩法不挑工具——本质就是"Agent + adb + 串口 + 摄像头",任何能执行命令行的 AI 编码工具,都能搭出同样的通路。

最后留个问题:**你手边有什么设备,是想给它配个遥控器的?**小车、云台、窗帘电机都行,评论区聊聊,说不定下一篇就做它。

至于这个项目,下一步是把"自测 + 摄像头取证"串成一条一键触发的巡检流水线——每次改完固件或 App,自动跑一轮全关节动作检查,我只看最后那份带截图的体检报告。