Use an iPhone as a full sensor suite (LiDAR RGBD, IMU, confidence) for low-cost robot manipulation and autonomous navigation. Stream to Python or ROS2 over WiFi or USB.
📖 Blog: iPhone as a Robot Sensor Suite (work in progress)
The iOS streaming app is required to stream sensor data from the iPhone. We are deciding whether to release the iOS source code or provide a free iOS app. Stay tuned!
The iPhone LiDAR (dToF flash sensor) + RGB camera + IMU replaces multiple traditional robot sensors:
| iPhone Sensor | Replaces | Output |
|---|---|---|
| LiDAR + RGB + ML | Depth camera (RealSense) | PointCloud2, depth image |
| LiDAR (middle row) | 2D LiDAR (RPLIDAR, Hokuyo) | LaserScan |
| RGB camera | USB camera | Color image |
| IMU (accelerometer + gyroscope) | External IMU | IMU data |
iPhone (iOS App) PC / Robot (ROS2)
┌────────────────────┐ ┌──────────────────────────┐
│ ARKit captures: │ WiFi / USB │ Python SDK │
│ - RGB image │ ──────────────→ │ - Decode stream │
│ - LiDAR depth │ TCP stream │ │
│ - IMU data │ │ ROS2 Driver │
│ - Camera params │ │ - PointCloud2 │
│ - Camera pose │ │ - LaserScan │
│ - Confidence map │ │ - RGB + Depth images │
└────────────────────┘ │ - CameraInfo │
│ - IMU │
│ - TF tree │
│ │
│ Calibration │
│ - ArUco marker pose │
│ - base → camera_link TF │
└──────────────────────────┘
├── sdk/ # Python client library
├── ros2-driver/ # ROS2 Jazzy package
├── calibration/ # ArUco-based camera-to-robot calibration
└── images/ # Demo images
- iPhone: iPhone 12 Pro or newer (with LiDAR) running the iOS streaming app
- Host machine: Ubuntu with ROS2 Jazzy (for ROS2 usage) or any OS with Python 3.10+ (for Python-only usage)
- Network: iPhone and host machine on the same WiFi network (for WiFi mode)
- USB mode (optional):
sudo apt install libimobiledevice-utils libusbmuxd-tools(Linux) orbrew install libimobiledevice(macOS)
- Install and launch the iOS streaming app on your iPhone Pro
- The app displays the server IP address on screen — you'll need this for the next steps
cd sdk
python3 -m venv venv
source venv/bin/activate
pip install -e ".[visualization]"
# View RGB + depth side-by-side
python examples/simple_viewer.py <IPHONE_IP>
# Test protocol v2 data (confidence, IMU)
python examples/test_v2.py <IPHONE_IP># 1. Create virtual environment (--system-site-packages inherits ROS2 Python packages like cv_bridge, rclpy)
cd ros2-driver
python3 -m venv --system-site-packages venv
source venv/bin/activate
# 2. Install Python dependencies
# numpy<2 is required for compatibility with ROS2 Jazzy's cv_bridge
pip install "numpy<2" -e ../sdk -e .
# 3. Build with colcon
source /opt/ros/jazzy/setup.bash
cd .. # project root (parent of ros2-driver/)
colcon build --packages-select ros2_driver --symlink-installYou need two terminals — one for the ROS2 node and one for RViz2.
Terminal 1: Start the ROS2 node
export ROS_DOMAIN_ID=50
source /opt/ros/jazzy/setup.bash
source ros2-driver/venv/bin/activate
# WiFi mode: replace with your iPhone's IP from the iOS app
python3 -m ros2_driver.iphone_sensor_node --ros-args -p host:=<IPHONE_IP>
# USB mode: connect iPhone via USB cable (no WiFi needed)
python3 -m ros2_driver.iphone_sensor_node --ros-args -p usb:=trueYou should see output like:
[INFO] Connecting to <IPHONE_IP>:8888...
[INFO] Connected! Starting frame loop.
[INFO] First frame: depth=(192, 256), color=(1440, 1920, 3), conf=yes, imu=yes
[INFO] Published 30 frames
Terminal 2: Visualize in RViz2
export ROS_DOMAIN_ID=50
source /opt/ros/jazzy/setup.bash
rviz2 -d ros2-driver/rviz/iphone_sensor.rvizA pre-configured RViz layout is included that displays the RGB image, point cloud, laser scan, TF tree, and more.
Terminal 2 (alternative): Verify topics
export ROS_DOMAIN_ID=50
source /opt/ros/jazzy/setup.bash
ros2 topic list
ros2 topic hz /color/image_rawPrint ArUco marker (DICT_6X6_250, ID 3, 3.8cm) and place it with axes aligned to robot base frame.
python3 -m calibration.camera_calibrationSee calibration/README.md for detailed instructions.
| Topic | Type | Rate | Description |
|---|---|---|---|
color/image_raw |
sensor_msgs/Image |
30fps | RGB image (bgr8, 1920x1440) |
color/camera_info |
sensor_msgs/CameraInfo |
30fps | RGB intrinsics |
depth/image_rect_raw |
sensor_msgs/Image |
30fps | Depth (32FC1 meters, 256x192) |
depth/camera_info |
sensor_msgs/CameraInfo |
30fps | Depth intrinsics (scaled from RGB) |
aligned_depth_to_color/image_raw |
sensor_msgs/Image |
~6fps | Depth upscaled to RGB resolution |
aligned_depth_to_color/camera_info |
sensor_msgs/CameraInfo |
~6fps | Same as RGB intrinsics |
depth/color/points |
sensor_msgs/PointCloud2 |
~6fps | Colored point cloud |
confidence/image_raw |
sensor_msgs/Image |
30fps | Confidence map (mono8, 0/1/2) |
imu |
sensor_msgs/Imu |
30fps | Accelerometer + gyroscope |
scan |
sensor_msgs/LaserScan |
30fps | 2D laser scan from middle row of depth |
QoS: All topics use BEST_EFFORT reliability, VOLATILE durability, KEEP_LAST(1). In RViz2, set the display's Reliability Policy to "Best Effort" or you won't see data.
TF tree: world -> camera_link -> camera_color_optical_frame / camera_depth_optical_frame
| Parameter | Default | Description |
|---|---|---|
host |
192.168.1.100 |
iPhone IP address (WiFi mode) |
port |
8888 |
TCP port |
usb |
false |
Use USB mode via iproxy (ignores host) |
camera_name |
camera |
Topic/TF prefix |
publish_pointcloud |
true |
Enable PointCloud2 |
publish_aligned_depth |
true |
Enable aligned depth to color |
publish_confidence |
true |
Enable confidence map |
publish_imu |
true |
Enable IMU topic |
publish_scan |
true |
Enable LaserScan topic |
depth_range_min |
0.1 |
Min depth in meters |
depth_range_max |
5.0 |
Max depth in meters |
min_confidence |
1 |
Min ARKit confidence for point cloud (0=low, 1=medium, 2=high) |
The iPhone LiDAR is a 3D dToF (direct Time-of-Flight) flash sensor. ARKit processes the raw data through three internal pipelines:
| Pipeline | Input | Output | Persistence | We Use It |
|---|---|---|---|---|
| Depth | LiDAR + RGB + ML | sceneDepth (256x192 depth image) |
Per-frame | Yes |
| Scene Mesh | Many LiDAR frames accumulated | ARMeshAnchor (triangle mesh + classification) |
Persistent | Not yet |
| Body Tracking | RGB + Neural Engine ML | ARBodyAnchor (91 skeleton joints) |
Per-frame | Not yet |
Currently we only use Pipeline 1 (depth). The depth image is unprojected to a point cloud (all pixels) and sliced into a LaserScan (middle row).

