跳转到内容

调试与拉日志

板子和 iPhone App 都有日志,用电脑就能拿到。 板子把日志打印到 USB 串口,从固件 0.2.3 起还会保留一份最近的日志,App 和 CLI 都能读取。iPhone App 会写一份滚动的 debug.log,可以用数据线拷出来、在 App 里导出或发送,也可以通过蓝牙实时转发到 Mac。

不是开发者?在 App 里打开设置 → 关于 → 日志,使用把诊断信息发给开发者或导出全部日志(见在 App 里)。使用 CLI 时运行 esp32agent logs --save。网页安装器出错时点下载日志。把得到的文件发到 [email protected]。

板子和 App 部分来自开源仓库 esp32-iphone-agent 的代码,CLI 命令与 CLI 与 AI Agent 一致。页尾有一段标准提示词,可以直接贴给 Claude Code、Codex、Cursor 等编程 Agent。

方式 需要
板子接电脑(USB) 一根能传数据的 USB-C 线,任意串口监视器。仓库里用的是 arduino-cli monitor。
iPhone 接电脑(USB) 一台 Mac(用访达)。开发版 App 还可以用 Xcode 的 xcrun devicectl。
App 内 不用额外准备:设置 → 关于 → 日志和设置 → 诊断。
网页安装器 不用额外准备:handsy.tech/install 上的下载日志。
蓝牙 带蓝牙的 Mac、uv,以及克隆下来的仓库(用 hardware/tools/board_test.py)。
Handsy CLI 已安装 esp32agent 并与板子配对,见 CLI 与 AI Agent。

日志里可能有截图、任务内容和板子的 Wi-Fi 信息,请当作隐私数据保管。

板子只有一个 USB-C 口,Handsy 运行时被 iPhone 占用。要看串口日志,先把板子从手机上拔下来,接到电脑上。电脑上会出现一个 USB 串口:macOS 是 /dev/cu.usbmodem*,Linux 是 /dev/ttyACM*,Windows 是某个 COM 口。

终端窗口
arduino-cli monitor -p /dev/cu.usbmodem* -c baudrate=115200

其他串口工具也可以,波特率设为 115200。

运行 Handsy 固件的板子可能不提供串口。如果没有出现,先换一根数据线,再按住 BOOT 键插上板子。这会进入安装模式,网页安装器可以使用;但安装模式下固件不运行,要读日志请正常重新插拔。

handsy.tech/install 安装的固件会打印:一行启动信息(剩余堆内存、PSRAM、上次重启原因),一行 ble: 开头的蓝牙协议栈缓冲区信息,以及少数致命错误,例如 airplay: start failed、video: no PSRAM for the GOP buffer。启动信息在上电后立刻打印,所以要尽快打开串口,或者重新插拔后再打开。

需要详细日志时,用 --debug 编译烧录(需要 arduino-cli、esp32 core 3.3.x 和 NimBLE-Arduino,见仓库里的 hardware/tools/README.md):

终端窗口
hardware/flash.sh --debug # 打印每条命令和回复及耗时、AirPlay 握手、蓝牙连接事件
hardware/flash.sh --sta --debug # 同上,并让板子连入 hid_bridge/secrets.h 里的 Wi-Fi,而不是开热点

调试固件启动时会最多等 3 秒串口监视器,更容易看到启动日志。调完回到手机前,用普通的 hardware/flash.sh(或 handsy.tech/install)重新烧录。

串口也能接收板子的命令行 <id> <command>,和 App 通过蓝牙发送的一样(见 docs/ble-protocol.md)。发送 1 ping,应回复 1 ok。发送 0 status 会打印完整的状态 JSON:和 App 通过蓝牙读取的心跳相同,fw 是固件版本,rst 是重启原因,usb、ap、kbps、fps 和 ble 等字段的含义见 docs/ble-protocol.md 和下面的日志里常见的问题。其中不含密码,但分享前仍请遮住任何像密钥的内容。接在电脑上时,板子就是这台电脑的鼠标和键盘,move、click、type 会作用在电脑上。除非确实要改板子设置,不要发送 unpair、res、fps 和 0 net …。

App 把日志存在自己的“文稿”(Documents)目录里:

文件 内容
debug.log、debug.1.log 每个事件一行,带时间戳:发给板子的命令、回复和耗时,板子状态,蓝牙断开,模型请求耗时。满 2 MB 滚动。API 密钥会被替换成 ‹redacted›。
diagnostics/*.json iOS 交给 App 的崩溃、卡顿、CPU 和磁盘报告,通常在下次启动时生成。
traces/<yyyyMMdd-HHmmss>/ 每次运行一个文件夹:steps.jsonl、截图、模型调用记录。

访达:iPhone 接上 Mac,打开访达 → iPhone → 文件 → ESP32 Agent,把文件拖到 Mac 上。

开发版 App:可以用 Xcode 命令行工具拷出整个目录。设备标识和 bundle ID 换成你自己的(bundle ID 来自你的编译配置)。

终端窗口
xcrun devicectl device copy from \
--device '<device identifier>' \
--domain-type appDataContainer \
--domain-identifier '<installed app bundle ID>' \
--source Documents \
--destination ./handsy-logs

不用接线。打开设置 → 关于 → 日志:

  • 页面上显示 debug.log 的最近几行。
  • 把诊断信息发给开发者(显示时可用):一键发送调试日志、板子状态和崩溃报告,方便排查问题。不含运行记录、截图或 API 密钥。固件 0.2.3 及以后还会附带板子最近的日志。
  • 导出全部日志:生成 esp32-agent-logs.zip,包含调试日志、iOS 崩溃报告和每次运行(步骤、模型回复、截图、视频)。固件 0.2.3 及以后还包含 board.log,即板子最近的日志。不会包含你的 API 密钥。
  • 分享调试日志:只发送 debug.log。

用隔空投送把压缩包发到电脑,或作为附件发到 [email protected]。设置 → 诊断可以看板子、蓝牙和视频的实时数据,导出运行记录会导出所有运行。只导出一次运行时,在 Runs(记录)里打开它,选择 ⋯ → Export trace。

固件没有通过网络输出日志的功能:没有 HTTP、Telnet 或 UDP 日志端口。板子的 Wi-Fi 用于 iPhone 的屏幕镜像、板子把画面传给 App(TCP),以及 App 内的固件更新;命令和状态走蓝牙。如果 App 没有本地网络权限,画面会退回走蓝牙,速度较慢,见热点与家里 Wi-Fi。

手机连不上板子热点,或屏幕镜像里找不到板子时,先查看板子的网络设置:

  • 通过 USB 串口:把板子接到电脑,打开板子正常运行时的串口,波特率设为 115200,发送一行 0 netinfo,以换行结束。mode 是 ap 时,ssid 和 pass 就是板子自己热点的名称和密码;mode 是 sta 时,显示的是板子所加入网络的凭据。回复里还包含访问令牌,不要分享完整回复。
  • 在 App 里:ESP32 Agent App 会显示热点名和密码。

通过 Wi-Fi 还可以:

  • 不接线拷贝手机里的日志。如果在访达里开启了在 Wi-Fi 网络中显示此 iPhone(或在 Xcode 里开启 Connect via network),上面的访达和 devicectl 步骤也能无线完成,前提是 iPhone 和 Mac 在同一个网络。运行任务时手机连的是板子热点,Mac 不在这个网络里,所以请在任务结束后再拷。
  • 用 Mac 调试屏幕镜像。hardware/flash.sh --sta --debug 会让板子连入你的 Wi-Fi。让 Mac 屏幕镜像到 ESP32 Agent,在串口日志里看 AirPlay 握手:GET /info、pair-setup、pair-verify 1/2、fp-setup 1/2、setup: mirror stream、mirror: data connection accepted、mirror: SPS ... PPS。

板子可以同时保持两个蓝牙连接:iPhone App 和一台 Mac。App 连着板子时,会把每一行 debug.log 发给板子,板子再转给 Mac。这样板子不用从手机上拔下来,也能实时看到 App 日志。

在克隆下来的仓库目录里,保持 App 打开并已连接板子:

终端窗口
uv run hardware/tools/board_test.py info # 第一次运行会配对(macOS 会弹窗),并打印板子的状态 JSON
uv run hardware/tools/board_test.py app "log 200" --seconds 120 # 先输出最近 200 行 debug.log,再实时输出 120 秒
uv run hardware/tools/board_test.py app status # 一行概况:板子、镜像、校准、模型、运行

转发的日志显示为 HH:MM:SS [log] …,历史日志以 hist 开头。转发每秒最多 10 行,链路忙时会丢行;需要完整日志时,请用 USB 拷贝整个文件。脚本输出里的 Wi-Fi 密码会被打码。

状态 JSON 就是 App 每秒读取的心跳,字段说明见 docs/ble-protocol.md。

如果你用 Handsy CLI 让电脑上的 Agent 操作手机,CLI 就是你查看板子的入口。它通过蓝牙连接板子,板子不用从手机上拔下来。

终端窗口
esp32agent devices # 电脑能看到的板子
esp32agent device info # 已配对的板子
esp32agent screenshot # 保存 PNG 并返回 filePath,打开确认是手机当前画面
esp32agent history show latest # 最近一次 CLI 运行记录,保存在这台电脑上
esp32agent logs # CLI 自己的日志(daemon.log)
esp32agent logs --board # 板子最近的日志,不用拔下板子(固件 0.2.3 及以后)
esp32agent logs --save # CLI 日志、板子日志和板子状态,合成一个文件给支持团队

截图画面没有更新时,重新开启屏幕镜像,再截一张。App 优先于 CLI:CLI 提示板子忙时,先停止 App 任务再试。logs --save 会打印一个文本文件的路径,把它发到 [email protected]。设置 ESP32AGENT_DEBUG=1 后,daemon.log 会记录每条命令及结果(不记录输入的文字、截图或按键)。logs 命令会在 0.1.0 之后的 CLI 版本中提供,见 CLI 与 AI Agent。

从固件 0.2.3 起,板子除了把日志打印到 USB 串口,还会在内存里保留最近的日志。不用拔下板子就能读取:

  • 在 App 里:导出全部日志和把诊断信息发给开发者都会附带它。
  • 用 Handsy CLI:esp32agent logs --board,或用 esp32agent logs --save 合成一个文件。

这份日志不含 Wi-Fi 密码或密钥。板子重启后会清空,只保留上次重启的原因。

日志里看到 含义 可以试试
使用中出现 "rst":"power-on" 或 "brownout",up 回到接近 0 板子断电重启了,常见原因是手机短暂切断了 USB 供电。 换短的数据线;Lightning 手机检查转换器。
"rst":"watchdog" 或 "crash" 固件卡住 8 秒或崩溃后自动重启。 用 --debug 固件复现,保留串口日志。
"usb":"asleep",或回复 err phone usb asleep (unlock the phone) 手机锁屏或休眠,鼠标键盘指令发不出去。 解锁手机。
"usb":"off" 手机没有把板子识别成鼠标和键盘。 换一根能传数据的线,重新插好转换器。
"ap":"idle"、"kbps":0 屏幕镜像没有在运行。 连上板子 Wi-Fi,在屏幕镜像里选 ESP32 Agent。
"ap":"connected" 但不是 streaming,或画面卡住 镜像已建立,但没有视频进来。 停止任务,重新连接镜像,保持手机解锁。
snap 回复 err no video … 板子还没有缓存到视频帧。 先开始屏幕镜像。
"ble":{"drop":…,"rdrop":…} 持续增加 蓝牙链路上丢了视频或回复,rdrop 应保持为 0。 接好天线,让板子靠近手机。
启动时出现 video: no PSRAM for the GOP buffer 板子没有可用的 PSRAM。 使用 8 MB PSRAM 的 XIAO ESP32S3 标准版。
debug.log 里的 cmd #N … → err … 某条板子命令失败,这一行带回复内容和耗时。 对照这次运行的 steps.jsonl 找到对应步骤。
ble: disconnect conn N, reason 0x…(调试固件) 蓝牙连接断开。 检查天线和距离,对照同一时间的 App 日志。

把下面这段贴给你的编程 Agent,填好前两项,再附上或指向你拉到的日志。

请帮我调试 Handsy AI(ESP32 Agent):一块 Seeed XIAO ESP32S3 板子插在 iPhone 上,作为 USB 鼠标和键盘;
通过 AirPlay 屏幕镜像接收画面;通过自己的 Wi-Fi(TCP)把画面传给 iOS App;通过低功耗蓝牙接收命令。
源码:https://github.com/onehumanbeing/esp32-iphone-agent
日志指南:https://handsy.tech/zh/docs/debugging
我的连接方式:<USB 板子接电脑 | USB iPhone 接电脑 | Mac 蓝牙 | Handsy CLI | Wi-Fi | 只有日志文件>
问题描述:<我做了什么、预期是什么、实际发生了什么、大概什么时间>
1. 按我的连接方式拉日志。每条命令运行前先给我看。
- 板子 USB:先找到串口(macOS 是 /dev/cu.usbmodem*,Linux 是 /dev/ttyACM*,Windows 是 COM 口),再运行
`arduino-cli monitor -p <port> -c baudrate=115200`。需要更详细的日志时,可以建议我用
`hardware/flash.sh --debug` 重新烧录,并提醒我调完再烧回普通固件。
发送 `0 status` 获取完整状态 JSON(字段见 docs/ble-protocol.md,`fw` 是固件版本)。
要查看板子的 Wi-Fi 设置,发送一行 `0 netinfo`;回复里的 `pass` 和令牌要打码。
- iPhone USB:通过访达(iPhone → 文件 → ESP32 Agent)拷出 Documents 里的 debug.log、debug.1.log、
diagnostics/ 和 traces/;开发版 App 可以用 `xcrun devicectl device copy from --domain-type appDataContainer`。
- 蓝牙:在克隆的仓库目录里,保持 App 打开并已连接板子,先运行
`uv run hardware/tools/board_test.py info`,再运行
`uv run hardware/tools/board_test.py app "log 200" --seconds 120`。
- Handsy CLI:运行 `esp32agent device info`、`esp32agent screenshot`(打开返回的 filePath 对应的 PNG)、
`esp32agent history show latest`、`esp32agent logs --save`(板子日志需要固件 0.2.3+)。
CLI 提示板子忙时,等 App 任务结束再试。
- 只有 App:用「设置 › 关于 › 日志 › 导出全部日志」生成的压缩包。
- Wi-Fi:固件没有网络日志端口。可以通过 Wi-Fi 同步拷贝 iPhone 文件,或用 `hardware/flash.sh --sta --debug`
固件加串口日志,从 Mac 调试 AirPlay。
2. 结合源码阅读日志:板子看 hardware/hid_bridge/ 下的 .ino 和 .cpp,状态 JSON 字段和命令看
docs/ble-protocol.md,App 看 ios/IPhoneAgent。
3. 按顺序检查:状态字段 rst / up / usb / ap / kbps / ble.drop / ble.rdrop / ble.n;失败的 `cmd #N … → err`
行及耗时;蓝牙断开;AirPlay 握手步骤;diagnostics/ 里的 iOS 报告。
4. 输出:相关日志的时间线(带时间戳)、最可能的原因、能解释它的源码文件和函数、下一步用来确认的测试。
规则:默认只读。未经我同意,不要烧录、擦除、发送 `unpair`、写入 `0 net …`,也不要修改 `res`/`fps`。
板子接在电脑上时就是这台电脑的鼠标和键盘,不要通过串口发送 move/click/type。
不要打印或粘贴 Wi-Fi 密码、API 密钥或令牌,一律打码。截图和运行记录属于隐私数据。