# هاتف

استخدم هاتفك (iOS أو Android) للتحكم في روبوتك.

**في هذا الدليل ستتعلم:**

- كيفية توصيل هاتف يعمل بنظام iOS/Android
- كيف تُخزَّن وضعية الهاتف إلى أهداف الطرف النهائي (EE) للروبوت
- كيفية ضبط حدود السلامة، وتحكم الماسك، وإعدادات الحركيات العكسية

للاستخدام هاتفك للتحكم في روبوتك، قم بتثبيت التبعيات ذات الصلة باستخدام:

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

يوفر `kinematics` حركية عكسية يستخدمها الأمثلة، و`feetech` محركات SO-100/SO-101، و`core_scripts` أدوات التسجيل والتصور.

## ابدأ

### المنصات المدعومة

- iOS: يستخدم تطبيق HEBI Mobile I/O (وضعية ARKit + أزرار). حمّل التطبيق أولاً، ثم افتحه، وستكتشف الأمثلة التطبيق على شبكتك وتبث وضعية الهاتف والمدخلات.
- أندرويد: يستخدم حزمة `teleop` (WebXR). عند بدء عملية بايثون، تُطبع رابطًا محليًا. افتح الرابط على هاتفك، ثم انقر على Start، واستخدم Move لبث وضعية الجسم.

روابط:

- مكتبة Android WebXR: [`teleop` على PyPI](https://pypi.org/project/teleop/)
- تطبيق iOS: [HEBI Mobile I/O](https://docs.hebi.us/tools.html#mobile-io)

### اتجاه الهاتف والتحكم

- الاتجاه: امسك الهاتف بحيث تكون الشاشة موجهة للأعلى والحافة العلوية موجهة في نفس اتجاه ملقط الروبوت. يضمن هذا أن تُحاذاة المعايرة إطار الهاتف مع إطار الروبوت بحيث تبدو الحركة طبيعية، راجع الصورة أدناه للاسترشاد.
- تفعيل/تعطيل — هذا هو **مفتاح إيقاف الطوارئ، يجب عليك إبقاء الضغط عليه طوال الحركة**:
  - iOS: اضغط باستمرار على `B1` لتحريك الروبوت، وأفلت الزر لتجميد الذراع في موضعها الحالي. كل ضغطة تعيد تثبيت الهاتف على الوضعية الحالية للذراع، مما يتيح لك الإفلات، وإرجاع يدك إلى موضع مريح، والضغط مرة أخرى، والمواصلة — دون أن يقفز الروبوت.
  - أندرويد: الأمر نفسه مع زر `Move` في صفحة WebXR.
- التحكم في الماسك (يعمل سواء كان زر التفعيل مضغوطاً أم لا):
  - iOS: يتحكم منزلق `A3` في الماسك كمدخل سرعة — ادفعه للأعلى لفتحه، واسحبه للأسفل لإغلاقه، وأفلته في المنتصف لتثبيته.
  - أندرويد: تعمل الأزرار `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: اختيار المنصة

عدّل الأمثلة لتستخدم `PhoneOS.IOS` أو `PhoneOS.ANDROID` في `PhoneConfig`. الواجهة البرمجية متطابقة عبر جميع المنصات، والفرق الوحيد هو مصدر الإدخال. جميع الأمثلة موجودة في `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` لالتقاط وضعية المرجع.
- أندرويد: اضغط على زر `Move` في صفحة WebXR لالتقاط وضعية المرجع.

لماذا نحتاج إلى المعايرة؟ نلتقط الوضعية الحالية حتى تُعبَّر الوضعية اللاحقة في إطار محاذاة مع الروبوت. عند الضغط على الزر مرة أخرى لتفعيل التحكم، يتم التقاط الموقع مرة أخرى لتجنب الانحراف في حال إعادة تموضع هاتفك أثناء تعطيله.

### الخطوة 3: تشغيل مثال

شغّل أحد السكربتات (البرامج النصية) الموضّحة كمثال لأداء التشغيل عن بُعد، أو تسجيل مجموعة بيانات، أو إعادة تشغيل مجموعة بيانات، أو تقييم سياسة.

تفترض جميع البرامج النصية أنك قمت بإعداد روبوتك ومعايرته (مثل متتبع SO-100).

**يجب عليك تقديم ملف URDF الخاص بالروبوت.** تحل كل الأمثلة مشكلة الحركية العكسية، والتي تتطلب نموذجًا حركيًا للذراع، ولا يتم توزيع ملف URDF مع LeRobot.

بالنسبة إلى SO-100/SO-101 المستخدمة في هذا الشرح، انسخ مجلد `SO101` من [مستودع SO-ARM100](https://github.com/TheRobotStudio/SO-ARM100/tree/main/Simulation/SO101) إلى `examples/phone_to_so100/`، بحيث يقع الملف في `examples/phone_to_so100/SO101/so101_new_calib.urdf`، وذلك عند التواجد في مستودع LeRobot:

```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 أعلاه.

بعد تشغيل المثال:

- أندرويد: بعد بدء تشغيل السكربت، افتح الرابط المحلي المطبوع على هاتفك، ثم انقر على 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
  )
  ```

### شرح خطوات خط الأنابيب

- MapPhoneActionToRobotAction: يحوّل وضعية الهاتف المُعايَرة والمدخلات إلى فروقات مستهدفة وأمر للقبضة. يتم التحكم في الحركة عبر إشارة تفعيل (B1 على iOS، وMove على Android).
- EEReferenceAndDelta: يثبّت وضعية مرجعية للذراع عند التفعيل ويجمعها مع فروقات الهدف لإنتاج وضعية مطلوبة مطلقة للذراع في كل إطار. عند التعطيل، يستمر في إرسال آخر وضعية مُرسلة.
- EEBoundsAndSafety: يقيّد وضعية الطرف النهائي (EE) ضمن مساحة العمل ويحدّ من معدل القفزات من أجل السلامة. كما يُعلن عن ميزات `action.ee.*`.
- InverseKinematicsEEToJoints: يحوّل وضعية الطرف النهائي إلى مواضع المفاصل باستخدام IK. يُوصى باستخدام `initial_guess_current_joints=True` للتحكم في الحلقة المغلقة؛ اضبط `False` لإعادة التشغيل في الحلقة المفتوحة من أجل الاستقرار.
- GripperVelocityToJoint: يدمج إدخال مشابك يشبه السرعة في موضع مطلق للمشابك باستخدام الحالة المقاسة الحالية.
- ForwardKinematicsJointsToEE: يحسب `observation.state.ee.*` من المفاصل المرصودة من أجل التسجيل والتدريب على حالة الطرف النهائي.

### استكشاف الأخطاء وإصلاحها

- لم يتم اكتشاف iOS: تأكد من أن تطبيق HEBI Mobile I/O مفتوح وأن جهاز الحاسوب المحمول/الهاتف متصلان بنفس الشبكة.
- عنوان URL الخاص بنظام Android غير قابل للوصول: تحقق من أن العنوان المحلي الذي استخدمته يحتوي على `https` بدلاً من `http`، واستخدم عنوان IP الدقيق الذي طبعه السكربت، واسمح للمتصفح بالدخول وتجاهل مشكلة الشهادة.
- يبدو أن الحركة معكوسة: تحقق من محور واحد في كل مرة، ثم اضبط علامات القلب في `MapPhoneActionToRobotAction` أو بدّل المحاور لتتوافق مع إعدادك.
- تتحرك الذراع بالكاد أو تقاوم حدودها: أعد التشغيل من وضعية بداية متوسطة المدى (انظر أعلاه) — يُحلّ IK من التكوين الحالي للذراع.

