进阶 Agent 工作流
一旦你不再“一块板子,一份固件“地想问题,espctl 就能解锁两个会改变 ESP32 工作台用法的模式。两个模式都完全由 agent 驱动 —— 用户用日常 语言说意图,AI 助手把 MCP 工具调用编排好。用户负责的事只剩两件: 插好板子、看仪表盘。
| 模式 | 维度 | 它给你什么 |
|---|---|---|
| 一块板子,多种角色 | 时间 | 一块 ESP32-S3 随当天的任务在多份固件之间轮换。 |
| 多块板子,一个操作员 | 空间 | 一台 mini-PC 或 Pi 同时驱动四块板,每块跑不同角色。 |
时间维度:一块板子,多种角色
带 USB 的 ESP32-S3,flash 和 RAM 都够装下面任意一个:
- USB 逻辑分析仪(sigrok 兼容固件 —— 见 PulseView 与 Sigrok 固件)
- Wi-Fi 网关 + MQTT/HTTP 遥测
- USB 转串口调试桥 —— 用来探另一颗目标芯片的 UART
- 你当下正在迭代的项目
espctl 把“换角色“压成一句对话。Agent 取项目、build、烧到对应串口、 确认新固件起来 —— 一个已经在构建服务器有缓存的小项目,端到端通常 60–120 秒。
对话过程
用户: 把 /dev/cu.usbmodem1101 上的板子换成逻辑分析仪。
Agent: → build { target: "esp32s3",
repo_url: "https://github.com/.../sigrok-fx2lafw-esp",
git_ref: "v0.2",
build_type: "release" }
← { task_id: "8a2f...", status: "running" }
→ build.status { task_id: "8a2f..." } (每 2 秒轮询一次)
← { status: "succeeded", firmware_path: ".../firmware.bin" }
→ flash.run { firmware_path: ".../firmware.bin",
port: "/dev/cu.usbmodem1101",
baud: 460800 }
← { success: true, chip: "ESP32-S3", flashed_bytes: 487424 }
→ monitor.run { port: "/dev/cu.usbmodem1101",
duration_sec: 10,
filter: "fx2lafw" }
← { lines_captured: 4, output: "I (321) fx2lafw: ready, 24 MHz max\n..." }
Agent: ✓ /dev/cu.usbmodem1101 上的逻辑分析仪已就绪,最高 24 MHz 采样率。
打开 PulseView,把它指向这个串口就能开始抓信号。
Agent 用到的工具
| 步骤 | MCP 工具 | 为什么 |
|---|---|---|
| 取项目 + 编译 | build | 不需要本地工具链;构建服务器缓存了所有 IDF 版本。 |
| 跟踪进度 | build.status | 返回 pending → running → succeeded/failed。 |
| 写新固件 | flash.run | 纯 Rust 的 espflash,不依赖 Python。 |
| 验证启动 | monitor.run | 可选但便宜 —— 10 秒抓取就能证实角色切换生效。 |
同一块 ESP32-S3 一个工作日里可以连续轮换好几个角色,完全不用动接线。
如果你给 agent 一份固定的项目仓库列表(sigrok-fx2lafw-esp、
your-esphome-gateway、usb-uart-bridge、my-current-project),
它在第一次提示后就能学会这套轮换;后续可以简写:
用户: 现在换成串口桥。
Agent: [回顾上下文,挑出 bridge 项目,build,flash]
什么时候适合这种模式
- 硬件吃紧的环境(工坊、教室、单板实验室)。
- 现场演示,只带一块板子,按需切换。
- 调试上电序列 —— 先用逻辑分析仪固件抓一段行为,然后再把 同一块板子切回生产角色。
什么时候不适合
- 任何需要两个角色同时存在的场景 —— 用下面的空间维度模式。
- 生产现场固件 —— 烧录会破坏前一个角色的状态。时间维度模式是 开发用,不是部署用。
空间维度:多块板子,一个操作员
一台 mini-PC 或 Raspberry Pi 4/5 的 USB 口数量足够挂四块 ESP 板子。
每块板子在系统里枚举成独立的串口(Linux 上是 /dev/ttyUSB0..3、
Windows 上是 COM3..6、macOS 上是 /dev/cu.usbmodem*)。因为每个
碰板子的 espctl 工具都接受显式 port 参数,agent 把每个串口当作
一个独立目标对待。
一个实用的四角色工作台:
| 槽位 | 角色 | 固件项目 |
|---|---|---|
/dev/ttyUSB0 | USB 逻辑分析仪 | sigrok 兼容(见 PulseView 与 Sigrok 固件) |
/dev/ttyUSB1 | 待测目标(最新构建) | 你的项目,main 分支 |
/dev/ttyUSB2 | 待测目标(上一发版,A/B 对比) | 你的项目,release-1.4 分支 |
/dev/ttyUSB3 | 网关 + 载波采样 | ESPHome Wi-Fi sink + ADC 采样 |
对话过程
用户: 把四板工作台搭起来。最新 main 在 USB1,上一版本在 USB2,
网关在 USB3,逻辑分析仪在 USB0。
Agent: → espctl ports (通过 shell 调命令行)
← /dev/ttyUSB0..3 全部枚举到,均为 ESP32-S3
(并行 —— 同时下两个构建)
→ build { target: "esp32s3", git_ref: "main", ... }
→ build { target: "esp32s3", git_ref: "release-1.4", ... }
← { task_id: "main-9c1a...", status: "running" }
← { task_id: "rel--7b22...", status: "running" }
→ build.status { task_id: "main-9c1a..." }
→ build.status { task_id: "rel--7b22..." }
← 两个都 succeeded
(并行 —— 四个 flash,每个绑定到自己的串口)
→ flash.run { firmware_path: <sigrok>, port: "/dev/ttyUSB0" }
→ flash.run { firmware_path: <main.bin>, port: "/dev/ttyUSB1" }
→ flash.run { firmware_path: <rel.bin>, port: "/dev/ttyUSB2" }
→ flash.run { firmware_path: <gateway>, port: "/dev/ttyUSB3" }
← 全部成功
(并行 —— 四个监视器,各抓 30 秒启动日志)
→ monitor.run { port: "/dev/ttyUSB0", duration_sec: 30, filter: "fx2lafw" }
→ monitor.run { port: "/dev/ttyUSB1", duration_sec: 30, filter: "heartbeat" }
→ monitor.run { port: "/dev/ttyUSB2", duration_sec: 30, filter: "heartbeat" }
→ monitor.run { port: "/dev/ttyUSB3", duration_sec: 30, filter: "wifi" }
Agent: ✓ 工作台就绪。
USB0: 逻辑分析仪,24 MHz 就位
USB1: main @ a4f2c1e,1 Hz 心跳
USB2: release-1.4 @ 9d20b5a,1 Hz 心跳
USB3: 网关上线,Wi-Fi 入网名为 "lab-bench-gw"
PulseView 已配好读 USB0;监听 USB3 的 Wireshark 能看到 USB1
和 USB2 发出的 MQTT 上报。
Agent 用到的工具
| 步骤 | MCP 工具 / CLI | 为什么 |
|---|---|---|
| 枚举板子 | espctl ports | 确认哪些口真的接了 ESP,以及它们的 VID:PID。 |
| 并行编译两份固件 | 两个并发的 build | 各自返回自己的 task_id;agent 同时轮询两个。构建服务器在独立沙箱里跑。 |
| 并行烧四块板 | 四个 flash.run,每个 port: 独立绑定 | 本地 USB 串口按口分流;只要口不同就互不干扰。 |
| 验证每个角色 | 四个 monitor.run,各自带 filter: | 同样的并行规则 —— 不同口,不打架。 |
操作员的工作
监控,不是驱动:
- 把四块板插好。
- 告诉 agent 每个口的角色。
- 看 agent 流式回报的进度。
- 打开消费工作台输出的应用界面 —— PulseView、Wireshark/MQTT 浏览器、 ADC 波形可视化。
用户从头到尾没有自己敲过 espctl build、flash 或 monitor。
什么时候适合这种模式
- 固件 A/B 回归测试(USB1 vs USB2)。
- 上电调试时同时跑分析仪和 DUT。
- 单人维护的实验台,一个操作员协调多个测试夹具。
- CI 跑批 —— 一台 Pi 把这个模式做成定时任务,可以无人值守地 把每个 PR 都在多块板上跑一遍。
什么时候不适合
- 量产烧录 —— 那个场景应该把 espctl 横向扩展到多台主机加队列, 而不是一台 Pi 同时驱四口。
- 需要板间亚毫秒同步的场景。USB 串口不是实时通信。
为什么 espctl 能这么用
两个模式都依赖 espctl MCP 接口的三个性质:
- 工具是无状态的。
flash.run和monitor.run把port作为显式参数,agent 历史里没有任何东西把某次工具调用绑死到 某块板上。同一个工具用两个不同的port调两次就是干净的并发。 - 构建任务彼此独立。
build立即返回task_id,在后台运行。 两次build调用产生两个task_id,agent 各自轮询自己的build.status。 - 碰硬件的部分跑在本地。
flash.run和monitor.run跑在 用户的机器上(或操作员的 mini-PC/Pi 上),直接走 USB。构建走 远程,硬件走本地。Agent 在两端之间无感切换。
另见
- 典型 8 步工作流 —— 单板单构建的流程。如果还没看过, 先看这个。
- PulseView 与 Sigrok 固件 —— 逻辑分析仪 角色用的项目。
- 固件与烧录 —— 上面对话里出现的每个工具。
- 构建生命周期 ——
build和build.status的细节。 - 系统总览 —— 为什么 build 跑远程、 flash 跑本地。