# 手机

使用您的手机（iOS 或 Android）来控制您的机器人。

**在本指南中，您将了解：**

- 如何连接 iOS/Android 手机
- 手机姿态如何映射到机器人末端执行器（EE）目标
- 如何调整安全限制、夹爪控制和 IK 设置

要通过手机控制您的机器人，请使用以下命令安装相关依赖项：

```bash
pip install 'lerobot[phone,kinematics,feetech,core_scripts]'
```

`kinematics` 提供了示例中使用的逆运动学，`feetech` 对应 SO-100/SO-101 电机，`core_scripts` 则是录制和可视化工具。

## 快速入门

### 支持的平台

- iOS：使用 HEBI Mobile I/O 应用（ARKit 位姿 + 按钮）。请先下载该应用，打开后示例将在您的网络上发现它，并流式传输手机位姿和输入。
- Android：使用 `teleop` 包（WebXR）。启动 Python 进程时，会打印一个本地 URL。在手机上打开该链接，点击 Start，然后使用 Move 来流式传输姿态。

链接：

- Android WebXR 库：[PyPI 上的 `teleop`](https://pypi.org/project/teleop/)
- iOS 应用：[HEBI Mobile I/O](https://docs.hebi.us/tools.html#mobile-io)

### 手机方向与操作

- 方向：手持手机，使屏幕朝上，且手机顶部边缘与机器人夹爪指向同一方向。这可以确保校准将手机坐标系与机器人坐标系对齐，从而使运动感觉自然，请参考下图。
- 启用/禁用 — 这是一个**死人开关，在整个运动过程中必须持续按住**：
  - iOS：按住 `B1` 可移动机器人，松开后机械臂将保持在当前位置。每次按下都会将手机重新锚定到机械臂当前的姿态，因此您可以松开、将手移回舒适的位置、再次按下并继续操作——机器人不会发生跳变。
  - Android：与 WebXR 页面上的 `Move` 按钮相同。
- 夹爪控制（无论是否按住启用按钮均可使用）：
  - iOS：模拟滑块 `A3` 作为速度输入控制夹爪——向上推以打开，向下拉以关闭，松开至中心位置以保持。
  - Android：按钮 `A` 和 `B` 分别执行递增/递减操作（A 打开，B 关闭）。您可以在 `GripperVelocityToJoint` 步骤中调整速度。
- 握持方式：用**双手**握住手机，一只手的拇指放在启用按钮（`B1`）上，另一只手的拇指放在夹爪滑块（`A3`）上，这样可以在不改变握持姿势的同时同时控制机械臂和夹爪。操作时请保持手机平放——手机的姿态即为指令，因此在运动过程中重新握持会导致机器人移动。
- 机器人初始位姿：`teleoperate.py` 在启动时（手机校准之前）会将机械臂移动到其[归位位姿](./so101#calibrate)，并在按下 Ctrl+C 时将其移回脚本启动时所在的初始位置。归位操作会命令每个本体关节移动到 `set_half_turn_homings()` 在校准期间居中对齐的半转值——即校准示意图中保持的位姿——同时夹爪保持原位，以避免归位时夹爪闭合夹住物体。这一点很重要，因为目标位姿是通过逆运动学（IK）从你按下启用按钮时机械臂所处的位置求解的——如果机械臂处于折叠或完全伸展状态，其剩余活动空间很小，容易触及关节限位。其他示例脚本（`record.py`、`evaluate.py`、`replay.py`）不会执行机械臂归位，因此请在启动这些脚本前手动将其放置到该位姿。

### 步骤 1：选择平台

修改示例，在 `PhoneConfig` 中使用 `PhoneOS.IOS` 或 `PhoneOS.ANDROID`。各平台的 API 完全相同，仅输入源不同。所有示例均位于 `examples/phone_to_so100/` 中：`teleoperate.py`、`record.py`、`replay.py`、`evaluate.py` 和 `rollout.py`。

遥操作示例：

```python
from lerobot.teleoperators.phone import Phone, PhoneConfig
from lerobot.teleoperators.phone.config_phone import PhoneOS

teleop_config = PhoneConfig(phone_os=PhoneOS.IOS)  # or PhoneOS.ANDROID
teleop_device = Phone(teleop_config)
```

### 步骤 2：连接并校准

创建 `Phone(teleop_config)` 并调用 `connect()` 后，系统会自动提示进行校准。请将手机保持在上文所述的朝向，然后：

- iOS：长按 `B1` 以捕获参考姿势。
- Android：在 WebXR 页面上按`Move`按钮以捕获参考位姿。

为什么要进行校准？我们会捕获当前的位姿，以便后续的位姿都在与机器人对齐的坐标系中表示。当您再次按下按钮以启用控制时，系统会重新捕获位置，以避免手机在禁用期间被移动后产生漂移。

### 步骤 3：运行示例

运行其中一个示例脚本，以进行遥操作、录制数据集、回放数据集或评估策略。

所有脚本都假定您已配置并校准了您的机器人（例如 SO-100 follower）。

**您必须提供机器人的 URDF。** 每个示例都会求解逆运动学，这需要机械臂的运动学模型，而 LeRobot 并不附带 URDF。

对于本教程中使用的 SO-100/SO-101，请在 LeRobot 仓库中，将 [SO-ARM100 仓库](https://github.com/TheRobotStudio/SO-ARM100/tree/main/Simulation/SO101) 中的 `SO101` 文件夹复制到 `examples/phone_to_so100/`，使文件位于 `examples/phone_to_so100/SO101/so101_new_calib.urdf`：

```bash
git clone --depth 1 https://github.com/TheRobotStudio/SO-ARM100.git /tmp/SO-ARM100
cp -r /tmp/SO-ARM100/Simulation/SO101 examples/phone_to_so100/SO101
```

如果将文件存储在其他位置，或者要控制另一条机械臂，请使用 `--urdf` 将脚本指向不同的文件。

- 运行此示例以进行遥操作，并传入从动臂的串口（使用 `lerobot-find-port` 查找）：

  ```bash
  cd examples/phone_to_so100
  python teleoperate.py --port /dev/tty.usbmodem5A460814411
  ```

  `--id` 用于选择要使用的已存储校准，`--urdf` 指向上述 URDF。

运行示例后：

- Android：启动脚本后，在手机上打开打印出的本地 URL，点击 Start，然后长按 Move。
- iOS：首先打开 HEBI Mobile I/O；按住 B1 以移动机器人。A3 控制夹爪。

此外，您可以通过编辑示例中显示的处理器步骤来自定义映射或安全限制。您还可以通过修改输入和运动学步骤来重新映射输入（例如，使用不同的模拟输入）或将流水线适配到其他机器人（例如，LeKiwi）。有关此内容的更多信息，请参阅[机器人和遥操作器的处理器](./processors_robots_teleop)指南。

- 运行此示例以记录数据集，该数据集会保存绝对末端执行器观测值和动作：

  ```bash
  cd examples/phone_to_so100
  python record.py
  ```

- 运行此示例以重放记录的回合：

  ```bash
  cd examples/phone_to_so100
  python replay.py
  ```

- 运行此示例以评估预训练策略：

  ```bash
  cd examples/phone_to_so100
  python evaluate.py
  ```

### 重要的流水线步骤和选项

- 运动学在多个步骤中都会用到。我们使用 [Placo](https://github.com/Rhoban/placo)，它是 Pinocchio 的一个封装，用于处理我们的运动学。我们通过传入机器人的 URDF 和目标坐标系来构建运动学对象。我们将 `target_frame_name` 设置为夹爪坐标系。

  ```python
  kinematics_solver = RobotKinematics(
    urdf_path="./SO101/so101_new_calib.urdf",
    target_frame_name="gripper_frame_link",
    joint_names=list(robot.bus.motors.keys()),
  )

  ```

- `MapPhoneActionToRobotAction` 步骤会将校准后的手机位姿和输入转换为目标增量和夹爪指令，下方展示了该步骤的输出。

  ```python
  action["enabled"] = enabled
  action["target_x"] = pos[1] if enabled else 0.0
  action["target_y"] = -pos[0] if enabled else 0.0
  action["target_z"] = pos[2] if enabled else 0.0
  action["target_wx"] = rotvec[1] if enabled else 0.0
  action["target_wy"] = -rotvec[0] if enabled else 0.0
  action["target_wz"] = rotvec[2] if enabled else 0.0
  action["gripper_vel"] = gripper_vel  # Still send gripper action when disabled
  ```

- `EEReferenceAndDelta` 步骤将目标增量转换为期望的绝对末端执行器位姿，并在启用时存储参考值。`end_effector_step_sizes` 是末端执行器位姿的步长，可以修改以改变运动速度。

  ```python
  EEReferenceAndDelta(
      kinematics=kinematics_solver,
      end_effector_step_sizes={"x": 0.5, "y": 0.5, "z": 0.5},
      motor_names=list(robot.bus.motors.keys()),
      use_latched_reference=True,
  ),
  ```

- `EEBoundsAndSafety` 步骤会将末端执行器的运动限制在工作空间内，并检查末端执行器是否存在较大的步长跳跃，以确保安全性。`end_effector_bounds` 是末端执行器位姿的边界，可以修改以更改工作空间。`max_ee_step_m` 是末端执行器位姿的步长限制，可以修改以更改安全限制。

  ```python
  EEBoundsAndSafety(
      end_effector_bounds={"min": [-1.0, -1.0, -1.0], "max": [1.0, 1.0, 1.0]},
      max_ee_step_m=0.10,
  )
  ```

- `GripperVelocityToJoint` 步骤利用当前测量状态，将类似速度的夹爪输入转换为夹爪的绝对位置。`speed_factor` 是用于乘以速度的系数。

  ```python
  GripperVelocityToJoint(speed_factor=20.0)
  ```

#### 不同的 IK 初始猜测

我们在运动学步骤中使用不同的 IK 初始猜测值。初始猜测值可以是当前测量的关节值，也可以是上一次的 IK 解。

- 闭环（用于记录/评估）：设置 `initial_guess_current_joints=True`，使 IK 从每帧测量的关节位置开始。

  ```python
  InverseKinematicsEEToJoints(
      kinematics=kinematics_solver,
      motor_names=list(robot.bus.motors.keys()),
      initial_guess_current_joints=True,  # closed loop
  )
  ```

- 开环（用于重放）：将 `initial_guess_current_joints=False`，使 IK 从上一个 IK 解继续，而不是从测量状态开始。这确保了在无反馈重放时动作的稳定性。

  ```python
  InverseKinematicsEEToJoints(
      kinematics=kinematics_solver,
      motor_names=list(robot.bus.motors.keys()),
      initial_guess_current_joints=False,  # open loop
  )
  ```

### Pipeline 步骤详解

- MapPhoneActionToRobotAction：将校准后的手机位姿和输入转换为目标增量和夹爪指令。运动由使能信号（iOS 上的 B1，Android 上的 Move）控制。
- EEReferenceAndDelta：在启用时锁定参考末端执行器位姿，并将其与目标增量结合，以在每一帧生成绝对的目标末端执行器位姿。禁用时，它会持续发送最后一条指令位姿。
- EEBoundsAndSafety：将 EE 位姿限制在工作空间内，并对跳跃进行速率限制以确保安全。同时声明了 `action.ee.*` 特征。
- InverseKinematicsEEToJoints：通过 IK 将末端执行器位姿转换为关节位置。对于闭环控制，建议使用 `initial_guess_current_joints=True`；为确保稳定性，在开环重放时请设置为 `False`。
- GripperVelocityToJoint：使用当前测量状态，将类似速度的夹爪输入积分到绝对夹爪位置。
- ForwardKinematicsJointsToEE：根据观测到的关节计算 `observation.state.ee.*`，用于末端执行器状态的日志记录和训练。

### 故障排除

- 未检测到 iOS：请确保 HEBI Mobile I/O 已打开，并且您的笔记本电脑/手机处于同一网络中。
- 无法访问 Android URL：请检查您是否误用了 `https` 而非 `http`，请使用脚本打印出的确切 IP 地址，并允许浏览器进入且忽略证书问题。
- 运动感觉是反的：请逐个轴进行检查，然后在 `MapPhoneActionToRobotAction` 中调整符号翻转，或交换轴以匹配您的设置。
- 手臂几乎不动或无法突破其极限：请从中段起始姿态重新开始（参见上文）——IK 是从手臂的当前构型求解的。

