Skip to content

Debugging & logs

Both halves of Handsy keep logs you can reach from a computer. The board prints to its USB serial port, and from firmware 0.2.3 also keeps a recent log the app and the CLI can read. The iPhone app writes a rolling debug.log you can copy over USB, export or send from the app, or stream live to a Mac over Bluetooth.

Not a developer? Open Settings → About → Logs in the app and use Send diagnostics to the developer or Export all logs (see From the app). For the CLI, run esp32agent logs --save. For the web installer, click Download log. Send what you get to [email protected].

The board and app paths come from the open-source code in esp32-iphone-agent; the CLI commands are the ones in CLI & AI agents. At the end there is a standard prompt to paste into Claude Code, Codex, Cursor, or another coding agent.

Path You need
Board over USB A data USB-C cable and any serial monitor. The repo uses arduino-cli monitor.
iPhone over USB A Mac with Finder. For development builds, Xcode’s xcrun devicectl.
In the app Nothing extra: Settings → About → Logs and Settings → Diagnostics.
Web installer Nothing extra: Download log on handsy.tech/install.
Bluetooth A Mac with Bluetooth, uv, and a clone of the repo for hardware/tools/board_test.py.
Handsy CLI esp32agent installed and paired with the board; see CLI & AI agents.

Logs can hold screenshots, task text, and the board’s Wi-Fi details. Treat them as private.

The board has one USB-C port, and the iPhone uses it while Handsy runs. To read the serial log, unplug the board from the phone and plug it into your computer. It shows up as a USB serial port: /dev/cu.usbmodem* on macOS, /dev/ttyACM* on Linux, a COM port on Windows.

Terminal window
arduino-cli monitor -p /dev/cu.usbmodem* -c baudrate=115200

Any serial terminal at 115200 baud works too.

A board running Handsy firmware may not offer a serial port. If none shows up, try another data cable, then hold BOOT while plugging the board in. That starts install mode, which the web installer can use; the firmware doesn’t run in install mode, so replug normally to read its log.

What the firmware from handsy.tech/install prints: a short boot line with available heap, PSRAM, and the last reset reason, a ble: line with the Bluetooth stack’s buffers, and a few fatal messages such as airplay: start failed or video: no PSRAM for the GOP buffer. The boot line is printed right after power-up, so open the monitor quickly, or replug and reopen.

For a detailed log, build with --debug (needs arduino-cli with the esp32 core 3.3.x and NimBLE-Arduino; see hardware/tools/README.md):

Terminal window
hardware/flash.sh --debug # every command and reply with timing, the AirPlay handshake, BLE link events
hardware/flash.sh --sta --debug # also joins the Wi-Fi in hid_bridge/secrets.h instead of opening a hotspot

A debug build waits up to 3 seconds at boot for a serial monitor, so the boot lines are easier to catch. Reflash with plain hardware/flash.sh (or from handsy.tech/install) before going back to the phone.

The serial port also accepts the board’s command lines, <id> <command>, the same ones the app sends over Bluetooth (see docs/ble-protocol.md). 1 ping should answer 1 ok. Send 0 status to print the full status JSON: the same heartbeat the app reads over Bluetooth, with the firmware version in fw, the reset reason in rst, and the usb, ap, kbps, fps and ble fields described in docs/ble-protocol.md and under Common issues. It holds no passwords, but mask anything that looks like a secret before sharing. On a computer the board is that computer’s mouse and keyboard, so move, click, and type act on the computer. Avoid unpair, res, fps, and 0 net … unless you mean to change the board.

The app keeps its logs in its Documents folder:

File Contents
debug.log, debug.1.log One timestamped line per event: board commands and replies with timing, board status, BLE disconnects, model request timing. Rolls over at 2 MB. API keys are replaced by ‹redacted›.
diagnostics/*.json Crash, hang, CPU, and disk reports that iOS hands to the app, usually at the next launch.
traces/<yyyyMMdd-HHmmss>/ One folder per run: steps.jsonl, screenshots, model calls.

Finder: connect the iPhone, open Finder → iPhone → Files → ESP32 Agent, and drag the files to your Mac.

Development builds: copy the container with Xcode’s command-line tool. Use your device identifier and the bundle ID from your build configuration.

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

No cable needed. Open Settings → About → Logs:

  • Recent lines of debug.log are shown on the page.
  • Send diagnostics to the developer (where shown) sends the debug log, the board’s state, and crash reports in one tap, so a problem can be looked into. It never includes runs, screenshots, or API keys. With firmware 0.2.3 and later it also includes the board’s recent log.
  • Export all logs makes esp32-agent-logs.zip: debug logs, iOS crash reports, and every run (steps, model replies, screenshots, video). With firmware 0.2.3 and later it also holds board.log, the board’s recent log. Your API key is never in it.
  • Share debug log sends only debug.log.

AirDrop the zip to your computer, or attach it to a mail to [email protected]. Settings → Diagnostics shows the live board, Bluetooth, and video numbers, and Export traces exports all runs. To export a single run, open it in Runs and choose ⋯ → Export trace.

The firmware has no network log stream: there is no HTTP, Telnet, or UDP log endpoint. The board’s Wi-Fi carries Screen Mirroring from the iPhone, the picture from the board to the app (TCP), and in-app firmware updates. Commands and status go over Bluetooth. If the app has no Local Network permission, the picture falls back to Bluetooth and is slower: see Hotspot vs home Wi-Fi.

Check the board’s network settings when the phone cannot join the hotspot or the board is missing from Screen Mirroring:

  • Over USB serial: with the board on your computer, open its normal running serial port at 115200 baud and send one line, 0 netinfo. For the board’s own hotspot, mode is ap and ssid and pass are the hotspot name and password. If mode is sta, they describe the network the board joined instead. The reply also contains an access token, so keep it private.
  • In the app: the ESP32 Agent app shows the hotspot name and password.

Over Wi-Fi you can also:

  • Copy the phone’s files without a cable. If you turned on Show this iPhone when on Wi-Fi in Finder (or Connect via network in Xcode), the Finder and devicectl steps above also work wirelessly, as long as the iPhone and Mac share a network. While a task runs, the phone is on the board’s hotspot, which your Mac is not on, so copy logs afterwards.
  • Debug mirroring from a Mac. hardware/flash.sh --sta --debug makes the board join your Wi-Fi. Mirror the Mac to ESP32 Agent and watch the AirPlay handshake on the serial log: GET /info, pair-setup, pair-verify 1/2, fp-setup 1/2, setup: mirror stream, mirror: data connection accepted, mirror: SPS ... PPS.

The board accepts two Bluetooth connections at once: the iPhone app and a Mac. While the app is connected, it copies every debug.log line to the board, and the board passes them to the Mac. That gives you the app’s log live, with the board still plugged into the phone.

From a clone of the repo, with the app open and connected to the board:

Terminal window
uv run hardware/tools/board_test.py info # first run: pairs (macOS asks), prints the board's status JSON
uv run hardware/tools/board_test.py app "log 200" --seconds 120 # last 200 debug.log lines, then live lines for 120 s
uv run hardware/tools/board_test.py app status # one line: board, mirroring, calibration, model, run

Relayed lines print as HH:MM:SS [log] …; lines from the history start with hist. The relay sends at most 10 lines a second and drops lines while the link is busy, so take the full file over USB when you need every line. Wi-Fi passwords are masked in the script’s output.

The status JSON is the same heartbeat the app reads every second. Its fields are listed in docs/ble-protocol.md.

If you drive the phone from a computer agent with Handsy CLI, the CLI is your view of the board. It talks to the board over Bluetooth while the board stays in the phone.

Terminal window
esp32agent devices # boards the computer can see
esp32agent device info # the paired board
esp32agent screenshot # saves a PNG and reports its filePath; check it shows the current screen
esp32agent history show latest # the latest CLI run, stored on this computer
esp32agent logs # the CLI's own log (daemon.log)
esp32agent logs --board # the board's recent log, without unplugging it (firmware 0.2.3 and later)
esp32agent logs --save # CLI log, board log and board state in one file for support

If the screenshot is stale, restart Screen Mirroring and take a fresh one. The app has priority over the CLI: if the CLI reports that the board is busy, stop the app task and retry. logs --save prints the path of a text file; send it to [email protected]. Set ESP32AGENT_DEBUG=1 to record each command and its result in daemon.log (no typed text, screenshots, or keys). The logs command comes with a later CLI release than 0.1.0; see CLI & AI agents.

From firmware 0.2.3, the board keeps its recent log lines in memory, in addition to printing them on USB serial. You can read it without unplugging the board:

  • In the app: Export all logs and Send diagnostics to the developer include it.
  • With Handsy CLI: esp32agent logs --board, or esp32agent logs --save for one file.

The log holds no Wi-Fi passwords or keys. It is cleared when the board restarts, apart from the reason for the last reset.

In the log What it means Try
"rst":"power-on" or "brownout" mid-session, up back near 0 The board lost power and restarted. Often the phone briefly dropped USB power. Use a short data cable, check the adapter on Lightning phones.
"rst":"watchdog" or "crash" The firmware hung for 8 s or crashed and rebooted. Reproduce with a --debug build and keep the serial log.
"usb":"asleep", or a reply err phone usb asleep (unlock the phone) The phone is locked or asleep, so mouse and keyboard reports cannot go through. Unlock the phone.
"usb":"off" The phone has not set the board up as a mouse and keyboard. Swap to a cable that carries data, reseat the adapter.
"ap":"idle", "kbps":0 Screen Mirroring is not running. Join the board’s Wi-Fi and pick ESP32 Agent in Screen Mirroring.
"ap":"connected" but not streaming, or a frozen picture Mirroring started but no video is arriving. Stop the task, reconnect mirroring, keep the phone unlocked.
snap reply err no video … The board has no video frames buffered yet. Start mirroring first.
"ble":{"drop":…,"rdrop":…} rising Video or replies are being lost on the Bluetooth link. rdrop should stay 0. Attach the antenna, move the board closer.
video: no PSRAM for the GOP buffer at boot The board has no usable PSRAM. Use the standard XIAO ESP32S3 with 8 MB PSRAM.
cmd #N … → err … in debug.log A board command failed; the line shows the reply and the time it took. Match it to the step in the run’s steps.jsonl.
ble: disconnect conn N, reason 0x… (debug build) The Bluetooth link dropped. Check the antenna and distance; compare with the app’s log at the same time.

Paste this into your coding agent, fill in the first two lines, and attach or point it at the logs you collected.

You are helping me debug Handsy AI (ESP32 Agent): a Seeed XIAO ESP32S3 board plugged into an iPhone as a
USB mouse and keyboard, receiving the screen over AirPlay Screen Mirroring, sending the picture to the iOS app
over its Wi-Fi (TCP), and taking commands over Bluetooth LE. Source: https://github.com/onehumanbeing/esp32-iphone-agent
Log guide: https://handsy.tech/docs/debugging
My connection: <USB board-to-computer | USB iPhone-to-computer | Bluetooth from a Mac | Handsy CLI | Wi-Fi | log files only>
The problem: <what I did, what I expected, what happened, roughly when>
1. Collect logs for my connection. Show me each command before running it.
- Board over USB: find the port (/dev/cu.usbmodem* on macOS, /dev/ttyACM* on Linux, COM on Windows), then
`arduino-cli monitor -p <port> -c baudrate=115200`. For more detail, offer to reflash with
`hardware/flash.sh --debug` and remind me to reflash a normal build afterwards.
Send `0 status` for the full status JSON (fields in docs/ble-protocol.md; `fw` is the firmware version).
To check the board's Wi-Fi settings, send the line `0 netinfo`; mask `pass` and the token in the reply.
- iPhone over USB: copy Documents/debug.log, debug.1.log, diagnostics/ and traces/ via Finder
(iPhone → Files → ESP32 Agent), or `xcrun devicectl device copy from --domain-type appDataContainer`
for a development build.
- Bluetooth: from a clone of the repo, with the app open and connected:
`uv run hardware/tools/board_test.py info`, then
`uv run hardware/tools/board_test.py app "log 200" --seconds 120`.
- Handsy CLI: `esp32agent device info`, `esp32agent screenshot` (open the PNG at the reported filePath),
`esp32agent history show latest`, `esp32agent logs --save` (board log needs firmware 0.2.3+).
If the CLI says the board is busy, wait for the app task to finish.
- App only: the zip from Settings › About › Logs › Export all logs.
- Wi-Fi: the firmware has no network log endpoint. Use the iPhone file copy over Wi-Fi sync, or a
`hardware/flash.sh --sta --debug` build with the serial log to debug AirPlay from a Mac.
2. Read the logs together with the source: hardware/hid_bridge/*.ino/*.cpp for the board,
docs/ble-protocol.md for status JSON fields and commands, ios/IPhoneAgent for the app.
3. Check in order: the status fields rst / up / usb / ap / kbps / ble.drop / ble.rdrop / ble.n;
failed `cmd #N … → err` lines and their timing; Bluetooth disconnects; AirPlay handshake steps; iOS reports
in diagnostics/.
4. Report: a timeline of the relevant lines (with timestamps), the most likely cause, the file and function in
the source that explains it, and the next test to confirm it.
Rules: read-only by default. Do not flash, erase, `unpair`, write `0 net …`, or change `res`/`fps` without
asking me. On a computer the board acts as its mouse and keyboard, so do not send move/click/type over serial.
Never print or paste Wi-Fi passwords, API keys, or tokens; mask them. Screenshots and run traces are private.