# Phone

スマートフォン（iOSまたはAndroid）を使用してロボットを操作します。

**このガイドでは、以下の内容を学習します：**

- iOS/Androidスマートフォンを接続する方法
- How phone pose is mapped to robot end‑effector (EE) targets
- セーフティリミット、グリップ制御、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`ボタンでも同様です。
- グリップ制御（enable ボタンを押しているかどうかに関係なく動作します）：
  - 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 フォローア）が設定・キャリブレーション済みであることを前提としています。

**ロボットの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でグリップを制御します。

また、例に示されているプロセッサのステップを編集することで、マッピングや安全制限をカスタマイズできます。入力と運動学（kinematics）のステップを変更することで、入力の再マッピング（例：別のアナログ入力を使用する）や、他のロボット（例：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
  ```

### 重要なパイプラインのステップとオプション

- 運動学は複数のステップで使用されます。運動学の処理には、Pinocchio のラッパーである [Placo](https://github.com/Rhoban/placo) を使用します。ロボットの 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` ステップは、ターゲットのデルタを絶対的な目標EE姿勢に変換し、有効化時に参照を保存します。`end_effector_step_sizes` はEE姿勢のステップサイズであり、変更することでモーションの速度を変更できます。

  ```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` ステップでは、EE の動きをワークスペース内に制限し、EE の大きなステップジャンプを検出することで安全性を確保します。`end_effector_bounds` は EE ポーズの範囲であり、ワークスペースを変更するために修正できます。`max_ee_step_m` は EE ポーズのステップ制限であり、安全性の制限を変更するために修正できます。

  ```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解のいずれかが使用されます。

- クローズドループ（record/evalで使用）: `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
  )
  ```

### パイプラインのステップの説明

- MapPhoneActionToRobotAction: 校正済みのスマートフォンの姿勢と入力を、ターゲットのデルタとグリップコマンドに変換します。モーションは、有効化シグナル（iOSではB1、AndroidではMove）によって制御されます。
- EEReferenceAndDelta: 有効化時に参照EE姿勢をラッチし、ターゲットのデルタと組み合わせることで、各フレームで絶対的な所望のEE姿勢を生成します。無効化された場合、最後にコマンドされた姿勢を継続して送信します。
- EEBoundsAndSafety: EEの姿勢をワークスペース内に制限し、安全性のためにジャンプをレート制限します。また、`action.ee.*`の特徴を宣言します。
- InverseKinematicsEEToJoints: IK を用いて EE の姿勢を関節位置に変換します。クローズドループ制御には`initial_guess_current_joints=True`が推奨されます。オープンループの再生では安定性のために`False`に設定してください。
- GripperVelocityToJoint: 現在の測定状態を用いて、速度のようなグリッパー入力を絶対的なグリッパー位置に積分します。
- ForwardKinematicsJointsToEE: 観測された関節から`observation.state.ee.*`を計算し、EE状態のロギングと学習に使用します。

### トラブルシューティング

- iOS が検出されません: HEBI Mobile I/O が開かれていること、およびラップトップとスマートフォンが同じネットワークに接続されていることを確認してください。
- Android の URL にアクセスできません: ローカルで `https` ではなく `http` を使用していることを確認し、スクリプトが表示した正確な IP を使用して、ブラウザで入力し、証明書の問題を無視するようにしてください。
- モーションが逆方向に感じられる場合は、一度に1軸ずつ確認し、`MapPhoneActionToRobotAction`内の符号反転を調整するか、軸を入れ替えてセットアップに合わせるようにしてください。
- アームがほとんど動かないか、限界に達して抵抗している場合は、中間範囲の開始姿勢から再起動してください（上記を参照）— IKはアームの現在の構成から解算されます。

