AirPlay mirroring
The phone mirrors its screen to UxPlay running on your computer. A GStreamer pipeline turns the stream into JPEG frames for the agent and for the web page.
The agent sees the phone through AirPlay screen mirroring and touches it through a tiny ESP32-S3 that plugs into the USB port as a mouse and keyboard. Any OpenAI-compatible vision model can be the brain.
Sign in with Google to start a run. · Fork it, flash it, self-host it.
plan Open Whiteboard and draw a smiley face
see screenshot 1179x2556 → actor model
tap (589, 1421) precise d=0.4px 1.3s
draw smile path, 24 points 2.6s
check screen changed → continue 0.8s
act home → Settings → Wi-Fi 1.1s
step handling next screen▍

Most iPhone automation drives the phone through XCTest / WebDriverAgent. That needs a Mac, Xcode, a signed helper app and an Apple developer account - a free account's signature expires every 7 days. This project uses the same paths a person uses: the screen and a pointer. The phone needs nothing installed.
Four small pieces, all of them understandable in an afternoon.
The phone mirrors its screen to UxPlay running on your computer. A GStreamer pipeline turns the stream into JPEG frames for the agent and for the web page.
An ESP32-S3 plugged into the phone shows up as a standard USB mouse and keyboard. With iOS AssistiveTouch on, the mouse moves an on-screen pointer - a click is a tap and a drag is a swipe. The board connects to the computer over Wi-Fi and executes simple line commands; all logic stays on the computer.
A planner model writes a short plan from the goal and the current screen. An actor model then looks at each new screenshot and picks the next action: tap, swipe, draw a path, type, press a key, go home, or finish. Both roles can be the same model - a cloud API, or a model you run yourself.
The pointer is relative, so the server tracks where it is. Before each tap it wiggles the pointer, finds it on the mirrored screen and corrects any miss, refining the calibration as it goes. After each action it checks whether the screen actually changed - a missed tap is noticed and retried instead of built upon. Repeated actions trigger a re-plan; a dropped link or a bad reply is recovered from.
The hosted Studio wraps the open-source web UI with queueing, model routing and history - the same panels, no local server required.
plan Open Clock, set an alarm for 7:00 AM that repeats every day, then save it
step 1 home → Clock tap (87, 512)
step 2 tap Alarm tab tap (295, 142)
step 3 type 0700 into the time wheel key x4
step 4 tick the Repeat days tap x4
step 5 Save tap (354, 812)
result alarm saved · screen changed · finished 11 steps · 46s
The phone powers the board. No capture cards, no docks, no soldering.
Tested with the Seeed Studio XIAO ESP32S3 (plain version without pre-soldered headers - not the Sense camera version, not the Plus). About $7 from Seeed, or ¥40-50 on Taobao ("XIAO ESP32S3"). Any ESP32-S3 board whose USB-C port is wired to the S3 native USB works.
USB-C models plug in directly. Lightning models need a Lightning to USB Camera Adapter. The phone powers the board over the cable.
macOS or Linux, on the same network as the phone; it runs UxPlay and the server. Use a USB-C hub with power pass-through if the phone should keep charging while the board is plugged in.
Any OpenAI-compatible chat API that accepts images. Running a model locally is optional - the project was developed against a local Qwen3.8-Flash setup at about 1.3 s per action and 2 s per plan.
Four steps: flash the board, run the server, set up the phone once, then give it a task.
Install arduino-cli and the ESP32 core (3.x), copy the secrets template and edit it with your 2.4 GHz Wi-Fi name/password and the IP of the computer running the server.
cp hardware/hid_bridge/secrets.example.h hardware/hid_bridge/secrets.h
# edit secrets.h: 2.4 GHz Wi-Fi + the server's IP address
hardware/flash.sh # optional: [serial port] [--debug]UxPlay mirrors the phone; the Python server plans, acts and serves the web UI on port 8890.
# 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 # set LLM_BASE_URL / LLM_MODEL / LLM_API_KEY
set -a && . ./.env && set +a && .venv/bin/python -m hidagentOpen http://<computer-ip>:8890 in a browser.
Turn on AssistiveTouch, plug the board into the phone, mirror the screen, then calibrate the pointer from the web page.
1. Settings → Accessibility → Touch → AssistiveTouch: on
2. Plug the board into the phone; a round pointer appears
3. Control Center → Screen Mirroring → iPhone Agent
4. Web page → Manual controls → CalibrateCalibration moves the pointer, finds it on the live screen and corrects itself in a few seconds.
Type a goal - for example "Open Settings and go to Wi-Fi" - and press Start, or click one of the examples. Every step lands on the step wall with its screenshot and timings.
"Set an alarm for 7:00 AM that repeats every day, then save it"Press Esc at any time for the emergency stop.
Clone https://github.com/onehumanbeing/esp32-iphone-agent and follow its README to set it up on this machine: flash the ESP32-S3 plugged in over USB, install UxPlay and the server, write server/.env for my model API, start the server and give me the web UI address. Ask me for the Wi-Fi name/password and the model API key.Two recorded runs from the project and the desk setup itself. Every step keeps its screenshot, its tap or stroke and its timing.

"Open Whiteboard and draw a smiley face": the agent searches for the app, opens it and draws the face with continuous strokes. Every step's screenshot, with its tap or stroke drawn on top, stays on the step wall.

"Set an alarm for 7:00 AM that repeats every day, then save it": the agent opens Clock, types 0700 into the time wheel, ticks the days under Repeat several taps at a time, and saves. Left: the real iPhone with the ESP32 plugged in; right: the web UI.

A XIAO ESP32S3 plugged into an iPhone, next to the web UI. The phone powers the board - no capture hardware, no docking station, no Mac in the loop.
Firmware, server and web UI are all in one repository. Read it, fork it, or self-host the whole thing.
All of it: the ESP32-S3 firmware, the Python server and the web UI. Fork it, ship it, or build your own hosted version.
hardware/ holds the ESP32-S3 firmware (Arduino) and the flash script. server/ holds the agent, web UI, screen capture and board link - Python and FastAPI.
Issues and pull requests are welcome. Run the checks before sending a change: cd server && pip install -e ".[dev]" && ruff check . && pytest.
Built on UxPlay (AirPlay receiver) · Arduino ESP32 core and TinyUSB (USB HID) · FastAPI.
No. The phone needs nothing installed - no helper app, no signing, no developer account. Your computer just runs UxPlay and the server, and it can be Linux.
No. The agent uses exactly the paths a person uses: the mirrored screen for eyes and a USB mouse/keyboard for hands, with iOS AssistiveTouch moving an on-screen pointer.
USB-C models plug straight into the board. Lightning models need a Lightning to USB Camera Adapter. The phone powers the board, and a USB-C hub with power pass-through keeps the phone charging.
Any OpenAI-compatible chat API that accepts images - a cloud model, or one you run yourself with vLLM, SGLang, llama.cpp or Ollama. Planner and actor roles can use the same model or different ones.
The Studio is the same web UI with queueing, cloud vision-model routing and session history, so you can run the agent without installing anything locally. The open-source server stays MIT-licensed and fully self-hostable.
Yes - it only sees pixels and acts with a pointer, so anything you can touch, it can touch. The known exceptions are screens iOS refuses to mirror, like passcode entry, Apple Pay and DRM video.
Plans are billed by Stripe, monthly or yearly. You can cancel at any time from the billing portal and keep access until the end of the period.
A planner writes a short plan from your goal and the current screen; the actor looks at each new screenshot and picks the next action. After every action the agent verifies that the screen actually changed, so a missed tap is retried instead of built upon.
Flash a $7 board, plug it in, and let a vision model take it from there. Or start with the hosted Studio and skip the setup.