四步看懂 esp32-iphone-agent:Clone、烧录、配对、接管,爆款项目就该这样上手
【免费下载链接】esp32-iphone-agentLet an AI model use a real iPhone with a $7 ESP32: AirPlay eyes, USB HID hands, any OpenAI-compatible vision model. No Mac, no developer account, no jailbreak.项目地址: https://gitcode.com/gh_mirrors/es/esp32-iphone-agent
把一部真实 iPhone 交给大模型去"动手",听起来需要 Mac、Xcode、开发者账号和越狱——这正是绝大多数 iOS 自动化方案(XCTest / WebDriverAgent)劝退普通开发者的门槛。而 esp32-iphone-agent 给出的答案朴素到近乎不讲道理:一块约 40 元的 ESP32-S3 开发板,插进手机的 USB-C 口,伪装成鼠标和键盘;手机把屏幕通过 AirPlay 镜像到电脑;任何一个兼容 OpenAI 接口的视觉模型,就能看着屏幕、握着指针,替你完成"打开白板画个笑脸""设置每天 7 点的闹钟"这类真实任务。手机端零安装、零签名、零越狱。
本文按"Clone → 烧录 → 配对 → 接管"四步拆解这个项目,不只是复述 README,而是把仓库里固件、服务端、Agent 循环的关键源码逐一对照,帮你搞清每一步背后的原理,以及遇到报错时该往哪儿查。
先看大局:为什么一块 ESP32 能"接管" iPhone
项目的核心思路是"走人用手机的路径":人操作手机无非两件事——看屏幕、动手指。于是项目把这两件事拆成硬件与软件的分工:
| 部件 | 角色 | 实现 |
|---|---|---|
| 眼 | 看屏幕 | iPhone 通过 AirPlay 镜像到电脑上的 UxPlay,GStreamer 流水线把视频流转成 8 fps、quality=75 的 JPEG 帧,供 Agent 与网页使用(screen.py) |
| 手 | 动屏幕 | ESP32-S3 通过 TinyUSB 对 iPhone 呈现为标准 USB 鼠标 + 键盘;打开 iOS 辅助触控(AssistiveTouch)后,鼠标指针即屏幕指针,点击就是点按、拖动就是滑动(hid_bridge.ino) |
| 脑 | 做决策 | 规划模型(planner)先写计划,执行模型(actor)每步看最新截图选动作;两角色可用同一模型(agent.py) |
与 XCTest/WebDriverAgent 类方案对比,差别一目了然:
| esp32-iphone-agent | XCTest / WebDriverAgent 类工具 | |
|---|---|---|
| Apple 开发者账号 | 不需要 | 需要(给测试程序签名) |
| Xcode | 不需要 | 需要 |
| 电脑 | Mac 或 Linux | Mac |
| 手机上安装 App | 不需要 | 需要测试程序 |
| 越狱 | 不需要 | 不需要 |
| 额外硬件 | ESP32-S3(约 40 元) | USB 线 |
| 任意 App(含游戏) | 可以(只认像素) | 大多可以(依赖无障碍控件树) |
值得一提的是,"ESP32 × iPhone"在中文社区是一条被反复探索的路线:从早年的 HomeKit 配件配对教程,到近两年的 ESP32 + Claude 边缘自然语言编程工作流,热潮从未断过。本项目与前者的关键分野在于:它不把 ESP32 做成被手机控制的"配件",而是反过来让 ESP32 成为手机的"手脚",由 AI 全权接管——这也正是它在同类项目中显得与众不同的原因。
第一步 Clone:仓库结构与必读文件
克隆下来后,仓库的物理结构本身就是最好的架构说明书:
hardware/ ESP32-S3 固件(Arduino)和刷写脚本 server/ Agent、网页界面、屏幕采集和板子连接(Python,FastAPI) serve/:可选的极简服务,用来自己部署 Hugging Face 视觉模型动手前值得先读五个文件:
- README.md(另有 README.zh-CN.md):架构、快速开始、板子协议、限制与路线图,信息密度极高;
- hardware/hid_bridge/secrets.example.h:固件唯一的"配置项"——Wi-Fi 与服务器地址;
- hardware/flash.sh:一键编译烧录脚本,内置 1200 波特率进入 ROM 引导的细节;
- server/.env.example:服务端全部环境变量及注释;
- server/hidagent/agent.py:规划与执行双模型的核心循环,建议第二步装好环境后精读。
硬件清单同样简洁:一块 USB-C 直连 S3 原生 USB 的 ESP32-S3 板(官方实测 Seeed Studio XIAO ESP32S3 普通版,未焊排针;国内淘宝搜"XIAO ESP32S3"约 40 多元)、一台与手机同局域网的 macOS/Linux 电脑、任意支持图片输入的 OpenAI 兼容视觉模型。USB-C 机型直接插,Lightning 机型需 Lightning 转 USB 相机转换器,板子由手机供电。若希望插着板子时手机还能充电,用一个支持 PD 充电直通的 USB-C 扩展坞即可。
第二步 烧录:固件与服务端环境
烧固件:改一个头文件,跑一个脚本
固件侧没有复杂配置,唯一要填的是网络信息。先复制模板再编辑:
cp hardware/hid_bridge/secrets.example.h hardware/hid_bridge/secrets.h # 编辑 secrets.h:填 2.4 GHz Wi-Fi 名称/密码,以及运行服务端的电脑 IP模板内容即全部配置(secrets.example.h):
#define WIFI_SSID "your-wifi" #define WIFI_PASS "your-password" #define SERVER_HOST "192.168.1.10" // machine running `python -m hidagent` #define SERVER_PORT 7300然后安装 arduino-cli 与 ESP32 core(3.x),执行:
hardware/flash.sh # 可选参数:[串口] [--debug]脚本内部有两个容易忽略的工程细节(flash.sh):
- 目标板配置写死为
esp32:esp32:esp32s3:USBMode=default,CDCOnBoot=cdc,FlashSize=8M,其中CDCOnBoot=cdc是调试串口回显的前提; - 已运行的桥接固件本身是个 TinyUSB 设备,直接烧会失败,所以脚本先用
stty 1200触摸把板子踢进 ROM 引导程序,再等待串口变化后上传。
固件本身不含任何业务逻辑(hid_bridge.ino):上电后连 Wi-Fi,与SERVER_PORT(7300)建立 TCP,之后每行收一条命令、回一句ok或err <原因>。几个值得玩味的实现点:
- 移动采用
STEP=10单位/8ms 的匀速分片(movePaced),注释明说"匀速让 iOS 指针加速度可预测"; - 手机锁屏时 USB 会休眠,板子先尝试
tud_remote_wakeup()唤醒,失败则立即回err phone usb asleep而非干等; - 8 秒看门狗兜底:一旦 USB 阻塞导致循环卡死,自动重启而不是失联;
WiFi.setSleep(false)关掉 modem sleep,因为"每省 100ms 就够一条命令的延迟"。
起服务端:UxPlay + 虚拟环境 + .env
# UxPlay,macOS: brew install uxplay # UxPlay,Debian / Ubuntu: sudo apt install uxplay gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-libav cd server python3 -m venv .venv && .venv/bin/pip install -e . cp .env.example .env # 填 LLM_BASE_URL / LLM_MODEL / LLM_API_KEY set -a && . ./.env && set +a && .venv/bin/python -m hidagent浏览器打开http://<电脑 IP>:8890即可看到控制页。服务端环境变量见 server/.env.example,核心几个:
| 变量 | 默认值 | 说明 |
|---|---|---|
LLM_BASE_URL | http://localhost:8000/v1 | OpenAI 兼容接口地址 |
LLM_MODEL/LLM_API_KEY | — | 模型名与 API Key |
LLM_EXTRA_BODY | {} | 合并进每次请求的 JSON(例如关闭模型的思考模式) |
LLM_TOKEN_PARAM | auto | 输出长度参数名:max_tokens或新版模型的max_completion_tokens,auto自动识别 |
ACTOR_*/PLANNER_* | — | 单独覆盖执行/规划模型的任意LLM_*配置 |
AIRPLAY_NAME | iPhone Agent | 手机"屏幕镜像"列表里显示的名字 |
PORT/DEVICE_PORT | 8890/7300 | 网页端口 / 板子连接端口 |
HIDAGENT_DEBUG | 0 | 调试模式 |
值得指出的是LLM_TOKEN_PARAM=auto背后的实现(llm.py):客户端默认发max_tokens,一旦收到 400 且响应体里出现max_completion_tokens字样,就永久切换到新参数——这让同一套配置能兼容新旧两代 OpenAI 模型。图像则以data:image/jpeg;base64,...的 data URL 随消息发送。
常见报错对照表
结合源码逐行核对,把最容易踩的坑整理成表:
| 现象 / 报错 | 出处 | 原因与处置 |
|---|---|---|
board not connected | device.py | 板子掉线或未上电。板子会自动重连,服务端等待 15 秒;仍失败则核对secrets.h的SERVER_HOST/SERVER_PORT与 Wi-Fi |
err phone usb asleep (unlock the phone) | hid_bridge.ino | 手机锁屏/休眠导致 USB 挂起。解锁手机;Agent 运行期间关闭自动锁定 |
err unknown command/err unknown key | hid_bridge.ino | 协议行格式错误或按键组合不在支持表内(enter/esc/cmd+h/cmd+space…),对照板子协议表 |
HTTP 400 提示max_completion_tokens | llm.py | 新版 OpenAI 模型参数名变化,auto会自动切换;也可显式指定 |
/api/frame.jpg返回 404 "no live screen" | app.py | 手机未开屏幕镜像。控制中心 → 屏幕镜像 → iPhone Agent |
| 启动任务被拒:"no phone screen" | agent.py | 同上,Agent 拒绝"盲操作" |
pointer not calibrated | phone.py | 未校准。在网页执行 手动操控 → 校准 |
| 手机镜像列表里看不到接收端 | screen.py | 多网卡的 Linux 上把 Avahi(mDNS)限制到手机所在网卡 |
| 日志反复出现 "AirPlay receiver exited, restarting" | screen.py | UxPlay 断连即退出属正常,守护线程会自动拉起;频繁退出则检查 gstreamer 插件是否装全 |
想全本地化?一个可选的部署配方
云端 API 开箱即用;若想全部跑在自己硬件上,README 给出了一套实测配置:nvidia/Qwen3.8-Flash-Next-NVFP4,双 DGX Spark 张量并行(TP=2)提供 OpenAI 兼容接口,关闭思考模式后每个动作约 1.3 秒、每次规划约 2 秒:
LLM_BASE_URL=http://<dgx-spark-ip>:8888/v1 LLM_MODEL=Qwen3.8-Flash-Next LLM_EXTRA_BODY='{"chat_template_kwargs": {"enable_thinking": false}}'vLLM、SGLang、llama.cpp、Ollama 等任何 OpenAI 兼容服务配上视觉模型,用法完全一致。排查性能问题时,HIDAGENT_DEBUG=1会为每次运行保存独立 traces 目录(每次模型调用的提示词、原始回复、耗时与截图),固件侧hardware/flash.sh --debug则通过 USB 串口回显每条命令耗时——双向对账,问题定位会快很多。
第三步 配对:iPhone 侧需要做的准备
手机侧全程零安装,一次性准备四步:
- 设置 → 辅助功能 → 触控 → 辅助触控:打开。这是"鼠标 = 指针"的前提;
- 把板子插到手机上:屏幕出现圆形指针,网页显示"控制器已连接";
- 控制中心 → 屏幕镜像 → iPhone Agent:网页出现实时画面;
- 网页里打开手动操控 → 校准:服务端自动移动指针、在镜像画面里找到它,几秒内完成;若找不到,会提示你在画面上精确点一下指针。
配对环节背后是 phone.py 里一套很有意思的指针数学。鼠标是相对移动的,服务端必须自己维护"指针现在在哪"的状态机:
- 校准取距左上角 150 与 350 个单位两个点(
CALIBRATION_STOPS),两点拟合出"鼠标单位 → 屏幕比例"的缩放,因为 iOS 指针运动并非严格线性,单点校准不够; - 每 10 次手势(
REHOME_EVERY)把指针"撞"回左上角(home命令),抵消漂移; - 校准数据连同历次"实际落点"写入
~/.hidagent/calibration.json,重启不丢;后续每次点按都用历史落点做直线拟合,边用边纠偏,AIM_TOLERANCE=0.008(屏幕比例的 0.8%)以内视为命中; - 点按与滑动还有专门的时序控制:松手前停顿 0.25s 防止 iOS 惯性甩动,点击后静止 0.15s 让 iOS 识别为"点按"而非"拖拽起点"。
板子协议是理解全部行为的钥匙,共 8 条命令(README.md 与 hid_bridge.ino 一致):
| 命令 | 作用 |
|---|---|
ping | 健康检查 |
home | 把指针推到左上角(已知位置) |
move <dx> <dy> | 相对移动指针,匀速 |
press/release/click | 左键按下 / 松开 / 点击 |
scroll <v> <h> | 滚轮 |
type <text> | 输入 ASCII 文字 |
key <combo> | 按键,如enter、cmd+h、cmd+space |
服务端侧 device.py 给每条命令配了与工作量挂钩的超时:移动按距离算(每 10 单位 8ms),打字按字符数算,ping则宽容到 8 秒——空闲保活时会用ping探测,只在屏幕仍在镜像时才用move 1 0小幅挪指针防手机自动锁屏。断线时板子会松开所有按键并自动重连,防止"手势做到一半按键卡住变成长按"。
第四步 接管:第一个自动化任务与它背后的闭环
一切就绪后,网页 UI 的核心玩法是:在输入框键入一个目标(例如"打开设置,进入 Wi-Fi"),点开始;也可以直接点示例任务。执行过程全部可视化——实时手机画面、实时日志、以及"步骤截图墙":模型每一步做决定时看到的截图都会留下来,上面叠画出点按位置(红色圆圈与波纹)或滑动路径(黄色轨迹),并附模型理由与思考/执行耗时。紧急停止(或按 Esc)会立即取消任务并松开指针。
"第一个自动化脚本"其实不用写脚本
严格说,这个项目的"编程接口"有两层。第一层就是自然语言目标本身——规划模型会把它拆成不超过 6 步的计划(agent.py 的PLANNER_PROMPT),执行模型再按计划一步步选动作。如果你想把自动化沉淀成脚本,第二层是网页背后完整的 FastAPI 接口(app.py),可以直接用 curl 或任何 HTTP 客户端驱动:
# 下达任务 curl -X POST http://<computer-ip>:8890/api/agent \ -H 'Content-Type: application/json' -d '{"text":"打开设置,进入 Wi-Fi"}' # 手动点按 / 滑动 / 输入(网页"手动操控"背后的同一套接口) curl -X POST http://<computer-ip>:8890/api/tap \ -H 'Content-Type: application/json' -d '{"x":0.5,"y":0.3}' # 紧急停止并复位指针 curl -X POST http://<computer-ip>:8890/api/agent/stop执行模型的动作原语
执行模型每轮看到的是最新一帧截图(坐标 0–1000,相对截图),输出严格的THINK:/DO:格式,解析器在 agent.py 的parse_actions/parse_command里,动作集合为:
tap X Y [precise] 点按;precise 表示点前先定位并修正指针(约慢 1s), 用于列表行、勾选框、小图标这类紧凑目标 swipe X1 Y1 X2 Y2 滑动("向下滑"指 Y2 > Y1) path X1 Y1 X2 Y2 … 一笔连续笔画(2–20 个点),用于绘图 circle X Y R 画圆 type TEXT 输入文字(仅 ASCII,先点输入框) key COMBO 按键(enter、backspace、esc、cmd+a…) home | back | wait replan 计划不再适配当前屏幕 done ANSWER 目标达成,向用户汇报结果 fail REASON 确认失败并说明原因模型的提示词里埋了不少"防呆"约束,值得体会:只在键盘弹出时才输入;时间/数字滚轮优先"点击中行数字直接键入"(如type 0700),键入失败才改滑;列表查找每次滚动不超过一屏、必须读全可见项、滑到两端仍找不到就fail并报出最近的可选项,禁止盲猜按钮位置——这些规则直接决定了任务完成的稳健度。
闭环:让模型"知道自己点没点中"
这是本项目区别于"一条指令点一次"的同类方案的核心竞争力,代码里三层闭环环环相扣:
- 指针闭环:点按前先小幅"抖动"指针(
NUDGE=12个单位),用前后两帧的像素差定位指针真实位置(vision.py 的pointer()),偏了立即纠正,并把误差回写进校准拟合; - 屏幕变化校验:每个动作后调用
vision.changed()比对前后帧——指针移动会被掩码排除,状态栏时钟被忽略,连一个勾选框的微小变化都能检出;屏幕"没变"会被如实告诉模型,而不是将错就错地继续; - 重复动作侦测:同一动作连续出现且上次"没变化",判定为卡住,触发重新规划(agent.py 的
_repeating与_replan),断线或模型回复异常同样走恢复路径,连续 3 次(max_failures)才宣告失败。
也就是说,从"眼睛看"到"手操作"再到"确认结果",每一步都有证据链。这正是 README.zh-CN.md 里"点空了会被发现并重试,而不是将错就错"那句话的代码出处。
写在最后:边界与路线图
任何爆款项目都该把边界说清楚,本项目亦然(README.md 的 Limitations 一节):
- iOS 不镜像部分界面(密码输入、Apple Pay、有版权保护的视频),Agent 看不到这些画面;手机锁屏镜像即断,Agent 工作时需关闭自动锁定;
- 键盘输入仅支持 ASCII(中文输入在路线图中,计划走 iOS 拼音键盘);
- 坐标与校准按竖屏设计,横屏 App 暂不支持;
- 修改辅助触控跟踪速度后需重新校准。
路线图上的下一步包括:基于 DiffusionGemma 的纯本地方案(server/serve/已有实验性的 OpenAI 兼容服务)、横屏支持、中文输入与更多 App 示例任务。仓库还自带一套覆盖解析器、指针识别、板子协议的 pytest 测试(server/tests/),提交前cd server && pip install -e ".[dev]" && ruff check . && pytest即可。
最后一条使用准则也写在 README 里:Agent 是在替你动手,只自动化你拥有或被允许使用的设备与账号,并让紧急停止按钮随时在手边。硬件自动化与 AI 结合的玩法很多,但"看得见、停得下"永远应该是底线。
【免费下载链接】esp32-iphone-agentLet an AI model use a real iPhone with a $7 ESP32: AirPlay eyes, USB HID hands, any OpenAI-compatible vision model. No Mac, no developer account, no jailbreak.项目地址: https://gitcode.com/gh_mirrors/es/esp32-iphone-agent
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考