固件与烧录
四个工具处理构建流水线的末端——列出可用固件、下载固件、烧录到 真实设备,以及烧录后从设备捕获串口输出。
| 工具 | 做什么 |
|---|---|
firmware.list | 列出已完成的、有固件可下载的构建。 |
firmware.download | 获取某次构建固件的下载元数据。 |
flash.run | 通过串口把固件烧录到本地连接的 ESP 设备。 |
monitor.run | 在限定时间内,从本地设备的串口捕获输出。 |
firmware.list
显示哪些构建已成功完成、有固件可以下载。
输入:
{}
可选:按特定构建过滤:
{ "job_id": "0abf...e2" }
| 字段 | 必需 | 说明 |
|---|---|---|
job_id | 否 | 只看一次构建。不填则列出所有成功的构建。 |
返回:
{
"builds": [
{
"task_id": "0abf...e2",
"target": "esp32s3",
"status": "succeeded",
"build_type": "release"
}
]
}
无副作用。 随时可以调用。
firmware.download
获取下载构建固件所需的元数据。实际的二进制传输走 firmware WebRTC DataChannel——这个工具给你制品信息。
输入:
{
"job_id": "0abf...e2"
}
| 字段 | 必需 | 说明 |
|---|---|---|
job_id | 是 | 一次成功构建的 task ID。 |
output_dir | 否 | 固件文件保存位置。 |
返回:
{
"job_id": "0abf...e2",
"status": "succeeded",
"artifact_lines": [
"build/flash_bundle.tar.gz",
"build/bootloader.bin",
"build/partition_table/partition-table.bin",
"build/<project>.bin"
],
"output_dir": "/tmp/firmware"
}
构建必须是 succeeded 状态。对失败或运行中的构建调用会报错。
主制品是
flash_bundle.tar.gz。 远程构建会打包出一个带签名的 自描述烧录包(manifest.json+files/,内含 bootloader、分区表、 应用三段),在同一会话里原子返回给客户端——没有单独的 fetch 步骤。flash.run和命令行espctl flash都消费这个文件。单独的.bin文件还列在artifact_lines里方便检查,但你几乎不会再直接 把它们传给烧录器。
flash.run
把固件烧录到连接在电脑 USB 口的 ESP 设备。底层直接用纯 Rust 的
espflash 库——不依赖 Python
esptool.py。你不需要 pip install esptool,不需要 venv,也不
需要 PATH 里有 Python。
输入:
{
"firmware_path": "/path/to/build/flash_bundle.tar.gz",
"port": "/dev/ttyUSB0",
"baud": 460800
}
| 字段 | 必需 | 说明 |
|---|---|---|
firmware_path | 是 | 一个 flash_bundle.tar.gz(由 build + firmware.download 产出)、一个已解压的烧录包目录,或者原始的 .bin / .elf 文件。推荐用烧录包形式,它在一个文件里带齐 bootloader、分区表、应用和签名清单。 |
port | 否 | 串口。只连了一个 ESP 设备时自动检测。 |
baud | 否 | 烧录波特率。默认 460800。 |
返回: 烧录操作的状态(成功或错误详情)。
传入烧录包时,flash.run 会读取 manifest.json,校验每一段的
sha256,然后在一次 espflash 会话里把所有段一次性写进 flash
(关键——如果逐段写,芯片会在第一段后重启、第二段就永远 hang)。
芯片只在最后重启一次。
政策:绝不安装
esptool.py。 如果flash.run或命令行espctl flash失败,按docs/infra-bugs-2026-04-11.md的格式在 aegis 仓库里建一份docs/espctl-flash-bugs-YYYY-MM-DD.md提 bug。 不要通过装 Python esptool 来绕开故障——那只会把真正的 build-to-flash 流水线 bug 悄悄藏起来。
仅限本地。 这个工具跑在你的电脑上,不是构建服务器上。只在 本地/stdio MCP 模式下可用——浏览器里不行。浏览器烧录用 MCP 控制台的 Flash 标签。
monitor.run
在限定时间内从连接好的 ESP 设备捕获串口输出。一般紧跟在
flash.run 之后用,验证板子启动、固件确实在跑——例如观察
向导 Phase-0 验证固件每秒发出的 heartbeat 日志行。
输入:
{
"port": "/dev/cu.usbmodem1101",
"baud": 115200,
"duration_sec": 30,
"filter": "heartbeat",
"reset_on_connect": true
}
| 字段 | 必需 | 说明 |
|---|---|---|
port | 否 | 串口(如 /dev/ttyUSB0、/dev/cu.usbmodem14101、COM3)。只连了一个 ESP 设备时自动检测。 |
baud | 否 | 波特率。默认 115200(ESP-IDF 控制台默认值——和 flash.run 的 460800 不同)。 |
duration_sec | 否 | 捕获时长。默认 30 秒,上限 600 秒。 |
filter | 否 | 子串。只有包含它的行会出现在 output 里。"heartbeat" 验证时很有用。 |
reset_on_connect | 否 | 默认 true——打开串口后拉一次 DTR/RTS 脉冲,让芯片在捕获窗口内重启进入应用。没有自动复位电路的板子,或者已经被别的工具复位过的情况,设为 false。比 espctl probe 更克制——不会进 bootloader。 |
返回:
{
"success": true,
"port": "/dev/cu.usbmodem1101",
"baud": 115200,
"duration_ms": 30024,
"bytes_read": 18432,
"lines_captured": 32,
"output": "I (123) heartbeat: tick 0\nI (1234) heartbeat: tick 1\n...",
"truncated": false,
"message": "Captured 18432 byte(s) over 30024 ms from /dev/cu.usbmodem1101 at 115200 baud."
}
捕获缓冲区上限约 512 KB;如果设备在窗口内输出更多,truncated
会变成 true,末尾会被截断。
仅限本地。 和
flash.run一样,只在本地/stdio MCP 模式下 可用——浏览器里不行。浏览器监视器用 MCP 控制台 — Monitor 标签 的 Web Serial。
没有 panic 解码器。 这是一份 UTF-8 lossy 的原始字节转储。 没有
idf.py monitor那种基于 ELF 的 backtrace 解码。需要长时间 交互式监视的话,用命令行espctl monitor。
CLI: espctl ports
列出操作系统看得到的所有串口。USB 串口还会带上 VID:PID。烧录或 开监视器之前,先用这个命令找到你的板子。
espctl ports
没有任何标志。如果列表为空会打印 No serial ports found.。
输出
Human 模式(表格):
PORT TYPE USB VID:PID
----------------------------------------------------------------------
/dev/cu.usbmodem1101 USB 303A:1001
/dev/cu.Bluetooth-Incoming-... Bluetooth -
JSON(--json):一个端口对象数组。USB 项还带 vid、pid、
vid_pid、manufacturer、product、serial_number。
# 过滤出 USB 串口适配器
espctl --json ports | jq '.[] | select(.vid_pid != null)'
CLI: espctl probe
对真实设备打开 bootloader 握手,报告芯片型号(含 revision)、MAC
地址和 flash 大小。底层用和 espctl flash 一样的纯 Rust
espflash 连接 ——
不依赖 Python esptool.py。
espctl probe --port <port>
输入
| 标志 | 说明 |
|---|---|
--port | 必填。不知道端口的话先跑 espctl ports。 |
输出
Human 模式:
Port: /dev/cu.usbmodem1101
Chip: ESP32-S3 (revision v0.2)
MAC: 7c:df:a1:00:11:22
Flash size: 8MB
JSON(--json):
{
"port": "/dev/cu.usbmodem1101",
"chip_type": "ESP32-S3 (revision v0.2)",
"mac_address": "7c:df:a1:00:11:22",
"flash_size": "8MB"
}
失败模式
- 端口不在操作系统端口列表里 → 退出 1(消息会提示先跑
espctl ports)。 - bootloader 握手失败 → 退出 1。
CLI: espctl flash
把烧录包烧到连着的设备。MCP 等价工具是 flash.run ——
同一个引擎,同一次会话内写完。
espctl flash <bundle_path> --port <port> [--baud <rate>]
标志矩阵
| 参数 | 默认 | 说明 |
|---|---|---|
bundle_path(位置参数) | 必填 | 一个已解压的烧录包目录,或 flash_bundle.tar.gz。 |
--port | 必填 | 串口。 |
--baud | 460800 | 烧录波特率。 |
示例
# 默认波特率(460800)
espctl flash ./build/flash_bundle.tar.gz --port /dev/cu.usbmodem1101
# 更快 —— 前提是 USB↔串口 转换板和数据线撑得住
espctl flash ./build/flash_bundle.tar.gz --port /dev/ttyUSB0 --baud 921600
烧录包形式(由 espctl build 产出)带一个 manifest.json(含
sha256 校验)和所有段(bootloader、分区表、应用)。烧录器在
一次 espflash 会话里把所有段写完 —— 逐段写会让芯片在第一段后
重启,第二段就 hang 死。
CLI: espctl monitor
打开串口监视器,把输出实时打到你的终端。默认情况下断开会自动 重连。
espctl monitor --port <port> [--baud <rate>] \
[--no-reconnect] [--no-reset-on-connect]
标志矩阵
| 标志 | 默认 | 说明 |
|---|---|---|
--port | 必填 | 串口。 |
--baud | 115200 | 监视器波特率(IDF 控制台默认值)。 |
--no-reconnect | false | 断开时直接退出,而不是等设备回来。 |
--no-reset-on-connect | false | 打开端口时跳过 DTR/RTS 复位脉冲。 |
关于 --no-reset-on-connect
默认行为下,monitor 会在打开端口时拉一次 RTS 复位脉冲,让芯片
在监视器下重新启动到应用。在没有自动复位电路的板子上,或者已经
有别的工具复位过、你不想再被打断的情况下,加 --no-reset-on-connect。
典型流程
远程构建 + 本地烧录 + 本地监视:
espctl build . --target esp32s3
espctl flash ./build/flash_bundle.tar.gz --port /dev/cu.usbmodem*
espctl monitor --port /dev/cu.usbmodem*
构建一步是默认远程的 —— 不需要 --remote 参数。服务器地址来自
espctl login 或默认 https://esphome.cloud。用 --remote <url>
可覆盖,用 --local 切换到仅计划模式。
另见
- 构建生命周期——怎么启动产生固件的构建。
- 日志与构建产物——读构建输出和 manifest 文件。
- MCP 控制台 — Flash 标签—— 浏览器烧录。