شرح URDF في ROS 2: عرض ذراع روبوتية ومحاكاتها في RViz وGazebo

OmArm Zero robot arm in RViz and Gazebo side by side

سلسلة OmArm Zero
الجزء 1: البناء · الجزء 2: URDF + RViz + Gazebo (هذا المقال) · الجزء 3: MoveIt 2 · الجزء 4: الالتقاط والوضع بالرؤية الحاسوبية

يُحمَّل الفيديو من YouTube (Google)، وقد يضع ملفات تعريف الارتباط (Cookies). سياسة الخصوصية

نظرة سريعة

ما ستبنيه: توأم رقمي لذراع OmArm Zero، وهي ذراع روبوتية بـ 6 درجات حرية (DOF: 5 مفاصل للذراع إضافة إلى القابض). تكتبه بصيغة URDF/Xacro (ملفات نصية تصف أجزاء الذراع ومفاصلها لـ ROS 2، القسم 6)، وتعرضه في RViz، وتحاكيه فيزيائيًا في Gazebo Harmonic. في النهاية تتبع الذراع المحاكاة أوامر مسارات المفاصل (joint trajectory: زوايا مستهدفة للمفاصل مع زمن لكل منها).

المتطلبات المسبقة: ذراع OmArm Zero من الجزء 1، أو ذراعك الخاصة مع نموذج Fusion 360. مهارات أساسية في استخدام الطرفية (Terminal). لا تحتاج إلى خبرة سابقة في ROS.

البرمجيات: Ubuntu 24.04، ROS 2 Jazzy، Gazebo Harmonic، RViz2

الوقت: من 2 إلى 4 ساعات، بما في ذلك تصحيح الأخطاء

مستوى الصعوبة: متوسط. ستعدّل ملفات XML، وتشغّل ملفات الإطلاق (launch files)، وتصحح أخطاء إعدادات المفاصل.

الملفات: حزمة مساحة عمل ROS 2 الخاصة بذراع OmArm Zero (29 €) من متجر OmArTronics؛ نظرة عامة وروابط على GitHub

1. لماذا التوأم الرقمي؟

في الجزء 1 بنيتُ ذراع OmArm Zero، ووصّلتها، وكتبت برنامجها الثابت (Firmware)، وتحكمت بها من متصفح الويب. نجح ذلك. لكن المشكلة ظهرت مع كل تسلسل حركة جديد: كنت أشغّله على الذراع الحقيقية فأراها تصطدم بالمكتب أو بنفسها. أتلفتُ بهذه الطريقة ثلاثة أذرع سيرفو (Horn، وهي الأذرع البلاستيكية المثبتة على أعمدة محركات السيرفو).

التوأم الرقمي يحل هذه المشكلة: نسخة افتراضية من الذراع بالأجزاء والمفاصل والحدود نفسها. أستطيع أن أجعلها تصطدم كلما شئت دون أن ينكسر شيء.

تُظهر المحاكاة أيضًا ما تخفيه الذراع الحقيقية: الإطار الإحداثي لكل مفصل وكيفية ارتباط هذه الأطر ببعضها (القسم 4). أستطيع اختبار الحركيات العكسية (Inverse Kinematics)، أي حساب زوايا المفاصل لموضع مستهدف للقابض، دون تشغيل أي محرك سيرفو. RViz هو العارض ثلاثي الأبعاد (3D) في ROS 2، أما Gazebo فهو محاكٍ فيزيائي يضيف الجاذبية والاحتكاك والتصادمات. في الجزء 3، يخطط MoveIt (إطار تخطيط الحركة في ROS 2) مساراته لهذه الذراع المحاكاة.

ROS 2 (Robot Operating System 2) هو إطار العمل البرمجي الذي يربط المحاكاة والعارض ثلاثي الأبعاد (3D)، ولاحقًا الذراع الحقيقية. تشرح الأقسام من 2 إلى 4 مفاهيمه الأساسية. إذا كنت تعرف العُقد والمواضيع وملفات الإطلاق مسبقًا، فانتقل مباشرة إلى القسم 5.

2. المكونات الأساسية في ROS 2: تشبيه بالمطبخ

يقوم ROS 2 على سبعة مفاهيم أساسية: العُقد (nodes)، والمواضيع (topics)، والرسائل (messages)، والخدمات (services)، والإجراءات (actions)، والمعاملات (parameters)، وملفات الإطلاق (launch files). نشبّه كل مفهوم منها فيما يلي بمطبخ مطعم.

العُقد (Nodes): محطات المطبخ

Illustration comparing kitchen stations with ROS 2 nodes

في المطبخ محطات: الشواية، والسلطات، وفرن البيتزا. كل محطة تؤدي مهمة واحدة، وتعمل باستقلالية، ولها طاهيها الخاص. إذا أخذ طاهي الشواية استراحة، تواصل محطة السلطات عملها.

العقدة (Node) في ROS 2 محطة من هذا النوع: برنامج واحد بمهمة واحدة. camera_driver تلتقط الصور، object_detector تعثر على الأجسام، motor_controller تشغّل المحركات. إذا تعطلت إحدى العقد، تستمر البقية في العمل، ويمكنك استبدال أي عقدة دون المساس بالعقد الأخرى.

في محاكاة Gazebo، يشغّل ملف الإطلاق robot_state_publisher، وجسر ROS-Gazebo، إضافة إلى gripper_mimic_bridge، وكذلك عقدة spawn تُنهي عملها بمجرد أن يصبح الروبوت داخل Gazebo. ويضيف ملحق التحكم (Plugin) داخل Gazebo (القسم 10) controller_manager وعقدة واحدة لكل وحدة تحكم (Controller). أما الإطلاق الخاص بـ RViz وحده فيشغّل ثلاث عقد (القسم 8).

Animated diagram showing ROS 2 nodes communicating via topics and services
رسم متحرك من وثائق ROS 2 الرسمية (CC-BY-4.0، © Open Robotics).

المواضيع (Topics) والنشر/الاشتراك: سبورة المطبخ

Illustration comparing a kitchen whiteboard with ROS 2 topics and publish/subscribe

يكتب الطاهي “الطاولة 5: شريحة اللحم جاهزة” على سبورة المطبخ، ثم يمسحها من أجل الطلب التالي. كل من ينظر إلى السبورة يراها في اللحظة نفسها، ومن ينظر لاحقًا تكون قد فاتته.

هذا هو الموضوع (Topic) في ROS 2. تنشر العقدة البيانات على قناة لها اسم، ويمكن لأي عدد من العقد الأخرى الاشتراك فيها. لا يعرف الناشر من يستمع إليه. ويتلقى كل مشترك نسخته الخاصة من كل رسالة: وجود ثلاثة مشتركين في /camera/image يعني ثلاث نسخ من كل إطار صورة. وافتراضيًا، لا تتلقى العقدة التي تشترك لاحقًا الرسائل السابقة.

المواضيع الرئيسية في OmArm Zero: /joint_states (الزوايا الحالية لجميع المفاصل وعددها 7)، /tf (الأطر الإحداثية، القسم 4) و /joint_trajectory_controller/joint_trajectory (أوامر الحركة).

Animated diagram showing one ROS 2 publisher node sending messages through a topic to one subscriber node
رسم متحرك من وثائق ROS 2 الرسمية (CC-BY-4.0، © Open Robotics).
Animated diagram showing multiple ROS 2 publisher and subscriber nodes communicating through a single topic
رسم متحرك من وثائق ROS 2 الرسمية (CC-BY-4.0، © Open Robotics).

الرسائل (Messages): قسائم طلب بتنسيق ثابت

Illustration comparing structured order slips with ROS 2 message types

النادل الذي يكتب الطلب على منديل دون تنسيق ثابت يتسبب في الأخطاء. أما قسيمة الطلب الصحيحة فلها حقول ثابتة: رقم الطاولة، والطبق، ودرجة النضج، والأطباق الجانبية.

تعمل رسائل ROS 2 بالطريقة نفسها: لكل نوع رسالة حقول محددة الأنواع. نوع الرسالة sensor_msgs/msg/JointState يحتوي على ترويسة (header، وهي طابع زمني) وأربع قوائم: name (أسماء المفاصل)، ثم position وvelocity وeffort (الموضع والسرعة والجهد، بعدد عشري واحد لكل مفصل؛ وeffort هو عزم الدوران في المفصل الدوّار). كل عقدة تقرأ الرسالة تعرف ما تتوقعه.

اعرض تعريف أي نوع رسالة باستخدام ros2 interface show sensor_msgs/msg/JointState. في التعريف، يعني string[] قائمة من القيم النصية، ويعني float64[] قائمة من الأعداد العشرية بحجم 64 بت. راقب الرسائل مباشرة باستخدام ros2 topic echo /joint_states.

الخدمات (Services): طرح سؤال على المطبخ

Illustration comparing a question to the kitchen with a ROS 2 service call

“هل السمك طازج اليوم؟” تسأل، وتنتظر، وتحصل على إجابة واحدة. لا يوجد تدفق مستمر. ينتظر الزبون حتى يعود النادل.

تعمل الخدمة (Service) في ROS 2 بالطريقة نفسها: يرسل العميل (Client) طلبًا واحدًا ويتلقى ردًا واحدًا. استخدم المواضيع للبيانات المستمرة (تدفقات المستشعرات، وحالات المفاصل)، والخدمات للعمليات التي تُنفَّذ مرة واحدة (مثل إدخال نموذج، أي وضعه في المحاكاة، أو تبديل وحدة تحكم).

تستخدم ذراع OmArm Zero خدمة لتبديل وحدات التحكم (/controller_manager/switch_controller). ويجري إدخال الروبوت إلى المحاكاة بطريقة مشابهة: يشغّل ملف الإطلاق ros_gz_sim create، الذي يستدعي خدمة Gazebo /world/empty/create.

Animated diagram showing a ROS 2 service client sending a request to a service server and receiving a response
رسم متحرك من وثائق ROS 2 الرسمية (CC-BY-4.0، © Open Robotics).
Animated diagram showing multiple ROS 2 service clients sending requests to a single service server node
رسم متحرك من وثائق ROS 2 الرسمية (CC-BY-4.0، © Open Robotics).

الإجراءات (Actions): طلب شريحة لحم

Illustration comparing a steak order with progress updates to a ROS 2 action

بعض المهام تستغرق وقتًا طويلًا. ومع الخدمة لا يحصل العميل على أي معلومات حتى تنتهي المهمة. يشبه ذلك أن تطلب شريحة لحم مطهوة جيدًا فيختفي النادل 25 دقيقة دون أي خبر.

أما الإجراء (Action) فيُبلغ عن التقدم. ترسل هدفًا، وتتلقى تحديثات عن التقدم أثناء طهي الطبق، ثم النتيجة في النهاية. ويمكنك أيضًا إلغاء المهمة في منتصفها.

للإجراء ثلاثة أجزاء: الهدف (goal: ما تريده)، والتغذية الراجعة (feedback: التحديثات أثناء التنفيذ)، والنتيجة (result: نجح، أو أُجهض، أو أُلغي). تستخدم ذراع OmArm Zero الإجراء FollowJointTrajectory : يرسل MoveIt مسارًا (Trajectory) بوصفه الهدف، فتنفذه وحدة التحكم، وتُبلغ عن مواضع المفاصل كتغذية راجعة، ثم تُبلغ عن النجاح عند الانتهاء.

Animated diagram showing a ROS 2 action client sending a goal to an action server and receiving feedback and results
رسم متحرك من وثائق ROS 2 الرسمية (CC-BY-4.0، © Open Robotics).

المعاملات (Parameters): ضبط حرارة الفرن

Illustration comparing the oven temperature dial with a ROS 2 parameter changed at runtime

فكّر في قرص ضبط حرارة الفرن: أدِره من 180 °C إلى 220 °C ويستمر الفرن في العمل. هذا هو المعامل (Parameter): قيمة إعداد يمكنك تغييرها أثناء التشغيل دون إعادة تشغيل أي شيء.

كل عقدة في ROS 2 تملك معاملاتها الخاصة؛ ولا يوجد خادم مركزي. اقرأها باستخدام ros2 param get وغيّرها باستخدام ros2 param set. لماذا لا نكتب هذه القيم مباشرة في الكود؟ لأن ضبط الروبوت يعني تغيير الأرقام باستمرار: معدلات التحديث، وحدود السرعة، وقيم الكسب في PID (PID gains). ترمز PID إلى التناسبي والتكاملي والتفاضلي (proportional, integral, derivative): يستجيب P لخطأ الموضع الحالي، وI للخطأ المتراكم مع الوقت، وD لسرعة تغيّر الخطأ. ومن دون المعاملات، سيتطلب كل تغيير في قيم الكسب تعديل الكود وإعادة بناء الحزمة.

تخزّن ذراع OmArm Zero معاملات وحدات التحكم في gazebo_controllers.yaml، مثل معدل حلقة التحكم (update_rate: 100) وقائمة المفاصل الخاصة بوحدة التحكم في المسارات (القسم 10). وYAML تنسيق نصي بسيط لقيم الإعدادات.

ملفات الإطلاق (Launch Files): قائمة مهام الافتتاح الصباحية

Illustration comparing the morning opening checklist with a ROS 2 launch file

كل صباح يتبع المطبخ قائمة المهام نفسها: تشغيل الفرن، وإشعال الشواية، وتجهيز المحطات، وتوزيع الموظفين. ومن دونها يقوم المدير بكل شيء يدويًا، خطوة بخطوة.

ملف الإطلاق هو قائمة المهام هذه: ملف واحد (هنا سكربت Python) يشغّل العقد ويضبط معاملاتها. ومن دونه ستشغّل كل عقدة في طرفية مستقلة.

يستخدم هذا الشرح ملفي إطلاق:

# RViz visualization (interactive joint sliders)
ros2 launch omarm_zero_description display.launch.py

# Full Gazebo simulation (physics, controllers, bridge)
ros2 launch omarm_zero_description gazebo.launch.py

3. الحزم ومساحات العمل: تنظيم المطبخ

Directory structure of a ROS 2 workspace with its packages

مبنى المطعم هو مساحة العمل (Workspace). أما المطبخ والمخبز وصالة الطعام فهي أقسام (حزم: Packages). تتشارك المبنى لكنها تعمل باستقلالية، ولكل منها وصفاتها وموظفوها.

في ROS 2، مساحة العمل مجلد. وداخل المجلد src/ الخاص بها، تكون كل حزمة وحدة مستقلة بذاتها لها الكود والإعدادات وملفات الإطلاق والاعتماديات (Dependencies) الخاصة بها. مساحة عمل OmArm Zero:

omarm_zero_ws/
+-- src/
|   +-- omarm_zero_description/      -- URDF, meshes, launch, configs
|   +-- omarm_zero_moveit_config/    -- MoveIt planning configuration
|   +-- omarm_zero_gamepad_control/  -- Gamepad teleoperation
|   +-- omarm_zero_real_control/     -- Real hardware interface
+-- build/    -- generated by colcon
+-- install/  -- generated by colcon
+-- log/

الحزم مستقلة. من يحتاج إلى ملف URDF الخاص بـ OmArm Zero فقط ينسخ omarm_zero_description إلى مساحة عمله ويستغني عن حزمتي يد الألعاب (Gamepad) وMoveIt. تُدرج كل حزمة اعتمادياتها في package.xml، و colcon build (أداة البناء في ROS 2) يتولى الباقي.

ابنِ مساحة العمل:

cd ~/omarm_zero_ws
colcon build
source install/setup.bash

‏ source هو الأمر الذي يحمّل إعدادات مساحة العمل في الطرفية الحالية. بعد ذلك يعثر ROS 2 على جميع الحزم في مساحة العمل، ويمكنك تشغيل عقدها وملفات الإطلاق الخاصة بها. كرّر هذا الأمر في كل طرفية جديدة.

4. TF2: الأطر الإحداثية

Kinematic chain of the OmArm Zero and its TF2 frame tree

TF2 هي مكتبة ROS 2 التي تتتبع الأطر الإحداثية. وهي تجيب عن سؤال واحد: “أين القابض الآن بالنسبة إلى القاعدة؟”

الإطار الإحداثي نقطة مرجعية لها محاورها X وY وZ الخاصة، ولكل وصلة (Link) في الذراع إطار. عندما يدور الكتف يدور إطاره معه، وكذلك جميع الأطر التالية على امتداد الذراع. تتتبع TF2 هذه الأطر جميعًا في الوقت الفعلي، كخريطة لمواضع جميع المفاصل بعضها بالنسبة إلى بعض. ويمكن لأي عقدة أن تسأل: “أين gripper_base_1 في الإطار العالمي (world)؟” فتتتبع TF2 السلسلة عبر كل مفصل لتعطي الإجابة.

شجرة الأطر في OmArm Zero:

world -> base_link -> base_link_upper_1 -> arm_link_1_1 -> arm_link_2_1 -> arm_link_3_1
    -> gripper_base_1 +-- gripper_left_1
                      +-- gripper_right_1

كل سهم يمثل علاقة أب-ابن (إزاحة في الموضع والدوران) تنشرها العقدة robot_state_publisher وتحدّثها في كل مرة يتحرك فيها مفصل. اعرض الشجرة كاملة باستخدام:

ros2 run tf2_tools view_frames

ينشئ هذا الأمر ملف PDF يعرض كل إطار والعقدة التي تنشره. وهو أول ما تفحصه عندما يظهر جزء في المكان الخطأ.

عمليًا: يحسب MoveIt موضع القابض من زوايا المفاصل الحالية (الحركيات الأمامية)، ويحسب زوايا المفاصل التي تنقله إلى موضع جديد (الحركيات العكسية). يعتمد الحسابان على أطوال الوصلات ومواضع أصول المفاصل (joint origins) من ملف URDF، وهي البيانات نفسها التي تستخدمها TF2. إذا كانت هذه البيانات خاطئة أو قديمة، فإن المخطِّط يخطط لروبوت لا يطابق الواقع.

5. تجهيز بيئة العمل

تحتاج إلى Linux أو WSL2 (Windows Subsystem for Linux، الذي يشغّل Ubuntu داخل Windows) وذاكرة RAM لا تقل عن 8 GB. أستخدم Ubuntu 24.04 مع ROS 2 Jazzy وGazebo Harmonic. تتطلب الإصدارات الأخرى من Ubuntu أو ROS 2 أسماء حزم مختلفة (على سبيل المثال ros-humble-* بدلًا من ros-jazzy-*).

تثبيت ROS 2 Jazzy

اتبع دليل التثبيت الرسمي على docs.ros.org. النسخة المختصرة:

sudo apt install software-properties-common
sudo add-apt-repository universe
sudo apt update && sudo apt install curl -y
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null
sudo apt update && sudo apt install ros-jazzy-desktop -y

حمّل ملف إعداد ROS 2 في كل طرفية جديدة:

source /opt/ros/jazzy/setup.bash

أضفه إلى ملف .bashrc حتى يُحمَّل تلقائيًا:

echo "source /opt/ros/jazzy/setup.bash" >> ~/.bashrc

تثبيت Gazebo Harmonic

يتولى Gazebo الفيزياء والتصيير (Rendering) والمستشعرات. ثبّته مع حزم الجسر الخاصة بـ ROS 2:

sudo apt install ros-jazzy-ros-gz ros-jazzy-gz-ros2-control ros-jazzy-gz-ros2-control-demos -y

تثبيت الاعتماديات الإضافية

يثبّت الأمر التالي حزم التحكم، وناشر حالة المفاصل (joint state publisher) الخاص بـ RViz، وXacro.

sudo apt install ros-jazzy-ros2-control ros-jazzy-ros2-controllers ros-jazzy-joint-state-publisher-gui ros-jazzy-xacro -y

التحقق من التثبيت

printenv ROS_DISTRO
gz sim --version

يجب أن يطبع الأمر الأول jazzy، والثاني رقم إصدار Gazebo. إذا فشل gz sim ، فتحقق من أن ros-jazzy-ros-gz قد ثُبّت دون أخطاء، وأن /opt/ros/jazzy/setup.bash مُحمَّل (sourced) في هذه الطرفية.

احصل على مساحة عمل OmArm Zero

إذا كنت تستخدم OmArm Zero، فنزّل حزمة مساحة عمل ROS 2 (29 €، ملف ZIP واحد للأجزاء من 2 إلى 4) وفك ضغطها في مساحة عملك. أما إذا كنت تبني ذراعك الخاصة، فتخطَّ هذه الخطوة وأنشئ الحزمة عبر التصدير من Fusion 360 في القسم 7.

mkdir -p ~/omarm_zero_ws/src
unzip ~/Downloads/OmArm-Zero-ROS2-Workspace-*.zip -d ~/omarm_zero_ws/src
cd ~/omarm_zero_ws
colcon build
source install/setup.bash

6. URDF وXacro: وصف الروبوت

Links and joints, the two building blocks of a URDF robot description

لا يعرف ROS 2 بعدُ مفاصل الذراع ولا أطوال وصلاتها ولا كتلتها. يحتاجها RViz لرسم الذراع، وGazebo لمحاكاتها، وMoveIt للتخطيط لها.

يوفر URDF (Unified Robot Description Format) هذا الوصف. وهو ملف XML (تنسيق نصي بوسوم متداخلة) يصف الأجسام الصلبة للروبوت ومفاصله ومحاور دورانها وخصائص الكتلة. ويقرأ RViz وGazebo وMoveIt جميعها الوصف نفسه.

لا يكفي تصدير CAD وحده (أي نموذج 3D من برنامج تصميم مثل Fusion 360). فملف STL (سطح 3D مكوَّن من مثلثات) يحتوي على الشكل، لكنه لا يحدد الأجزاء التي تتحرك ولا مواضع محاور الدوران. يضيف URDF هذه المعلومات: الحركيات (Kinematics) الخاصة بالذراع، أي كيفية حركة أجزائها بعضها بالنسبة إلى بعض.

يتكون URDF من عنصرين أساسيين: الوصلات (links، وهي الأجسام الصلبة) والمفاصل (joints، أي نقاط الربط بينها).

الوصلات (Links): الأجسام الصلبة

الوصلة جزء صلب واحد. في OmArm Zero: القاعدة، والقاعدة الدوارة (base_link_upper_1)، وجزءان للذراع، والمعصم، وقاعدة القابض، وإصبعان. وتحدد كل وصلة ثلاث خصائص.

المظهر المرئي (Visual): الشكل ثلاثي الأبعاد (3D) الذي يُعرض في RViz وGazebo، وهو هنا مجسم STL (Mesh) من CAD:

<link name="base_link">
  <visual>
    <geometry>
      <mesh filename="file://$(find omarm_zero_description)/meshes/base_link.stl"
            scale="0.001 0.001 0.001"/>
    </geometry>
    <material name="silver"/>
  </visual>
</link>

التصادم (Collision): شكل مبسّط لحساب الفيزياء. يمكن أن يكون المجسم نفسه (أبطأ، ودقيق) أو شكلًا أوليًا مثل الأسطوانة (أسرع، وتقريبي). تستخدم OmArm Zero المجسمات الكاملة؛ فالذراع صغيرة بما يكفي كي لا يمثل الأداء مشكلة.

القصور الذاتي (Inertial): الكتلة، ومركز الكتلة، وموتّر القصور الذاتي (كيفية توزّع الكتلة، وهو ما يحدد مدى صعوبة تدوير الوصلة). يحتاج Gazebo إلى هذه القيم لكل وصلة متحركة؛ ومن دونها تكون الفيزياء خاطئة. صدّرتها من CAD:

<inertial>
  <origin xyz="-0.000886 0.019817 0.028036" rpy="0 0 0"/>
  <mass value="0.2095"/>
  <inertia ixx="0.000388" iyy="0.000273" izz="0.000577"
           ixy="1.2e-05" iyz="-1.5e-05" ixz="2e-06"/>
</inertial>

المفاصل (Joints): نقاط الربط

يربط المفصل بين وصلتين ويحدد كيفية حركة إحداهما بالنسبة إلى الأخرى. في OmArm Zero توجد 7 مفاصل دوّارة (revolute: 5 مفاصل للذراع و2 لإصبعَي القابض). ويحدد كل مفصل نوعه: revolute (يدور ضمن حدود)، continuous (يدور دون حدود)، prismatic (ينزلق) أو fixed (لا يتحرك). ويحدد المفصل أيضًا وصلته الأم ووصلته الابنة (parent وchild)، ومحور دورانه، وحدوده (نطاق الزاوية، والسرعة القصوى، وأقصى عزم دوران).

تأتي الأسماء من التصدير من Fusion 360. تحتفظ المفاصل بالأسماء الافتراضية في Fusion، وهي تحتوي على مسافة (Revolute 1). ويجب أن تستخدم ملفات الإعداد والأوامر هذا الإملاء حرفيًا. أما الوصلات فتحصل على رقم نسخة المكوّن (Instance) لاحقةً لاسمها: يسمّي Fusion النسخة الأولى arm_link_1:1، ويستبدل السكربت النقطتين بشرطة سفلية (arm_link_1_1). مفصل دوران القاعدة (Revolute 1):

<joint name="Revolute 1" type="revolute">
  <origin xyz="0.0 0.0 0.05087" rpy="0 0 0"/>
  <parent link="base_link"/>
  <child link="base_link_upper_1"/>
  <axis xyz="0.0 0.0 1.0"/>
  <dynamics damping="2.0" friction="0.02"/>
  <limit lower="0.0" upper="3.14" effort="40.0" velocity="0.2"/>
</joint>

يربط Revolute 1 الوصلة base_link إلى base_link_upper_1، ويدور حول المحور Z ويتحرك من 0 إلى 3.14 rad (من 0 إلى 180°). تقلل قيمة التخميد (damping) الاهتزاز في المحاكاة. في كل وسم <origin> ، تمثل xyz إزاحة الموضع بالأمتار، وتمثل rpy الدوران بالراديان (roll وpitch وyaw).

Xacro: ملف URDF مع المتغيرات والتضمين

يصبح ملف URDF العادي طويلًا: وصف OmArm Zero يزيد على 250 سطرًا، رغم أنها ذراع صغيرة. يضيف Xacro (XML Macros) إلى URDF المتغيرات والعمليات الحسابية وتضمين الملفات (includes). وتوجد إعدادات Gazebo في ملف مستقل يجري تضمينه:

<xacro:include filename="$(find omarm_zero_description)/urdf/omarm_zero.gazebo" />

عند تشغيل ملف الإطلاق، يقوم xacro.process_file() بتوسيع كل شيء إلى سلسلة نصية URDF واحدة.

مقطع ros2_control

في أسفل ملف URDF، يوجد قسم <ros2_control> يخبر إطار التحكم بالمفاصل الموجودة وبالواجهات (Interfaces) التي يوفرها كل منها:

<ros2_control name="GazeboSystem" type="system">
  <hardware>
    <plugin>gz_ros2_control/GazeboSimSystem</plugin>
  </hardware>
  <joint name="Revolute 1">
    <command_interface name="position"/>
    <state_interface name="position"/>
    <state_interface name="velocity"/>
  </joint>
  <!-- ... same for Revolute 2-7 -->
</ros2_control>

يربط هذا المقطع ملف URDF بـ ros2_control. يحل Gazebo محل العتاد الحقيقي. لكل مفصل، تكتب وحدة التحكم موضعًا مستهدفًا (واجهة الأوامر: command interface) وتقرأ الموضع والسرعة الحاليين (واجهات الحالة: state interfaces). أما على الذراع الحقيقية، فتحل عقدتا جسر بلغة Python من الجزء 3 محل منظومة ros2_control بالكامل، وتتواصلان مع ESP32، وهي لوحة المتحكم الدقيق في الذراع. ويبقى ملف URDF وأسماء المفاصل دون تغيير.

7. إنشاء ملف URDF: الطريقة اليدوية والتصدير من Fusion 360

توجد طريقتان لإنشاء ملف URDF: كتابته يدويًا (مناسبة للتعلم، لكنها بطيئة لذراع كاملة) أو تصديره من CAD. جرّبت الطريقتين: استخدمت التصدير من CAD للهندسة والمجسمات (Meshes) والقصور الذاتي، ثم عدّلت حدود المفاصل وإعدادات وحدات التحكم يدويًا.

الكتابة اليدوية

يبدأ كل ملف URDF بوسم <robot> وإطار “world” ثابت:

<?xml version="1.0" ?>
<robot name="omarm_zero" xmlns:xacro="http://www.ros.org/wiki/xacro">
  <link name="world"/>
  <joint name="world_to_base" type="fixed">
    <parent link="world"/>
    <child link="base_link"/>
  </joint>
  <!-- ... all links and joints go here ... -->
</robot>

من هناك تعمل على امتداد السلسلة الحركية (Kinematic Chain): عرّف وصلة، ثم المفصل الذي يربطها بالوصلة التالية، ثم تلك الوصلة، وهكذا. في OmArm Zero تكون السلسلة كالتالي: world → base_link → base_link_upper_1 → arm_link_1_1 → arm_link_2_1 → arm_link_3_1 → gripper_base_1 ← أصابع القابض.

من أين تأتي الأرقام:

  • الأصول والإزاحات (Origins and offsets): من تعريفات المفاصل في CAD، أو بالقياس بالقدمة (Caliper) إذا لم يكن لديك نموذج CAD. توقّع عدة جولات من الفحص في RViz.
  • محاور الدوران: انظر إلى المفصل. القاعدة التي تدور يمينًا ويسارًا محورها 0 0 1 (Z). والكوع الذي ينثني إلى الأمام محوره 0 1 0 (Y). محور Revolute 2 في OmArm Zero هو 0.016327 0.999867 0.0 بدلًا من القيمة الدقيقة 0 1 0، لأن محور هذا المفصل مائل بنحو 0.9° في نموذج CAD.
  • خصائص القصور الذاتي: الكتلة ومركز الكتلة وموتر القصور الذاتي، كما شُرح في قسم الوصلات (Links).
  • حدود المفاصل: الزوايا بين المصدّات الميكانيكية بوحدة الراديان (توضّح الخطوة 5 كيف قستُها).

نصيحتي: أضف زوجًا واحدًا من وصلة ومفصل في كل مرة، وتحقّق في RViz بعد كل إضافة. تصحيح ملف URDF كامل من 8 وصلات دفعة واحدة عملية بطيئة؛ لقد أضعتُ فترة ما بعد الظهر كاملة بهذه الطريقة قبل أن أنتقل إلى الاختبار التدريجي.

من CAD إلى URDF: سير العمل في Fusion 360

قياس الإزاحات وحساب موترات القصور الذاتي يدويًا عمل مرهق. صُمّمت الذراع في Autodesk Inventor، ثم استوردتُ النموذج إلى Fusion 360 لتصدير URDF، واستخدمتُ fusion360-urdf-ros2 من تطوير Lentin Joseph (استنادًا إلى عمل syuntoku14 و dheena2k2). يقرأ سكربت Fusion 360 هذا (برنامج Python صغير يعمل داخل Fusion) التجميعَ، ويصدّر حزمة ROS 2 تضم ملفات Xacro وشبكات STL (Meshes) وملفات الإطلاق (Launch). وهذا يوفّر ساعات من القياس. لكنك ما زلت بحاجة إلى فهم URDF، لأن الحزمة المُصدَّرة تحتاج إلى إصلاحات يدوية (الخطوة 5).

الخطوة 1: ثبّت السكربت

استنسخ أو نزّل مستودع fusion360-urdf-ros2 من GitHub. في Fusion 360، اضغط Shift + S، ثم انقر على علامة الزائد الخضراء بجوار “My Scripts” واختر مجلد المستودع. لا يحتاج السكربت إلى أي حزم Python إضافية.

Adding the fusion360-urdf-ros2 script under My Scripts in Fusion 360

الخطوة 2: جهّز نموذج CAD

تعتمد جودة التصدير على طريقة تجهيز النموذج. إليك أربع قواعد:

استخدم نظام إحداثيات يمينيًا (اليد اليمنى: الإبهام = X، السبابة = Y، الإصبع الوسطى = Z)، بحيث تكون القاعدة عند نقطة الأصل ويشير المحور Z إلى الأعلى. يتوقع ROS أن يشير X إلى الأمام وY إلى اليسار وZ إلى الأعلى. في Fusion 360، تحقّق من الإعداد Preferences > General > Design > Default modeling orientation واختر “Z up”. يجب أن يتطابق محور دوران المفصل الأول مع المحور Z.

OmArm Zero coordinate system in Fusion 360 with the Z axis pointing up through the base joint

سمِّ المكوّن الجذري base_link. يستخدم السكربت أسماء المكوّنات أسماءً للوصلات في URDF، وتسمّي أعراف ROS 2 الوصلة السفلية base_link. أعدتُ تسمية المكوّن الجذري عندي؛ أما بقية أسماء الوصلات فتأتي من أسماء المكوّنات في Fusion.

The OmArm Zero base (component base_link:1) in Fusion 360

حوّل جميع الأجسام (Bodies) إلى مكوّنات (Components). يتعامل السكربت مع كل مكوّن على أنه وصلة URDF واحدة، وتُدمج الأجسام الموجودة داخل المكوّن الواحد. انقر بزر الفأرة الأيمن على كل جسم واختر “Create Components from Bodies”.

Fusion 360 browser tree with every OmArm Zero component listed on its own

أوقف خيار “Capture Design History” قبل التصدير. يفشل التصدير إذا كان سجل التصميم (Design History) مفعّلًا. انقر بزر الفأرة الأيمن على المكوّن الأعلى مستوى واختر “Do not capture Design History”. أعد تفعيله بعد التصدير.

الخطوة 3: عرّف المفاصل في Fusion 360

لكل مفصل فعلي، أنشئ مفصلًا مطابقًا في Fusion 360 (Assemble → Joint). هناك تفصيل واحد مهم: يجب أن يكون المكوّن الأب هو Component2، أي الاختيار الثاني في مربع الحوار، أما الاختيار الأول فهو المكوّن الابن. إذا بدّلت بين المكوّنين، تنعكس السلسلة الحركية وتظهر الذراع في RViz مقلوبة من الداخل إلى الخارج.

كل مفصل من النوع الدوّار (Revolute). ضع نقطة الأصل حيث يقع ذراع السيرفو (Horn). اختبر الحركة في Fusion قبل التصدير: انقر على المفصل، واسحب المقبض، وتحقّق من اتجاه الدوران. تصحيح اتجاه خاطئ هنا يستغرق 10 ثوانٍ، أما التصحيح نفسه في ملف URDF بصيغة XML فيستغرق نحو 30 دقيقة من التجربة والخطأ.

Creating a joint and testing its motion in Fusion 360

لا يدعم السكربت المكوّنات المتداخلة. سطّح جميع التجميعات الفرعية (Sub-assemblies) بحيث تقع كل وصلة URDF مباشرة تحت المكوّن الجذري.

الخطوة 4: شغّل التصدير

اضغط Shift + S، واختر fusion360-urdf-ros2 ثم انقر على Run. يطلب منك السكربت مجلدًا للإخراج، ثم يسألك هل تستخدم Gazebo Harmonic (أجبتُ بـ Yes). يقرأ السكربت المكوّنات والمفاصل والمواد، ويصدّر شبكات STL، ويحسب القصور الذاتي.

Export dialog of fusion360-urdf-ros2 in Fusion 360

ينشئ السكربت urdf/, meshes/, launch/ وملفات الحزمة. وأضفتُ config/, bin/ و gripper_mimic_bridge.py يدويًا. بنية الحزمة بعد إضافاتي:

omarm_zero_description/
+-- urdf/          (omarm_zero.xacro, .gazebo, .ros2control, materials.xacro)
+-- meshes/        (base_link.stl, base_link_upper_1.stl, arm_link_{1,2,3}_1.stl,
|                   gripper_base_1.stl, gripper_{left,right}_1.stl)
+-- launch/        (display.launch.py, gazebo.launch.py)
+-- config/        (gazebo_controllers.yaml, gz_ros2_control.yaml, ros_gz_bridge_gazebo.yaml)
+-- bin/           (mock_gazebo_sim)
+-- omarm_zero_description/  (gripper_mimic_bridge.py)
+-- package.xml
+-- setup.py
+-- setup.cfg

إذا كنت تبني ذراعك الخاصة، فانسخ الحزمة المُصدَّرة إلى مساحة العمل (Workspace) وابنِها. أما مالكو OmArm Zero فلديهم الحزمة مسبقًا من القسم 5.

# Create the workspace if you haven't already
mkdir -p ~/omarm_zero_ws/src

# Copy the exported package into the workspace
cp -r omarm_zero_description ~/omarm_zero_ws/src/

# Build
cd ~/omarm_zero_ws
colcon build
source install/setup.bash

تحتوي الحزمة المُصدَّرة على ملفات Xacro والشبكات (Meshes) وقيم القصور الذاتي وملفات الإطلاق (Launch). أما ملحقات Gazebo (Plugins) وإعدادات وحدات التحكم (Controllers) وحدود المفاصل الصحيحة فغير موجودة، وتضيفها الخطوة 5. اختبر التصدير في RViz وفي Gazebo:

ros2 launch omarm_zero_description display.launch.py
ros2 launch omarm_zero_description gazebo.launch.py

في Gazebo، لا تستجيب الذراع المُصدَّرة لأي أوامر بعد، لأن التصدير لا يتضمن وحدات تحكم.

الخطوة 5: إصلاحات ما بعد التصدير

ما اضطررتُ إلى إصلاحه بعد التصدير:

اتجاهات المفاصل. لا يتطابق الدوران الموجب في Fusion دائمًا مع الاتجاه الموجب للسيرفو. شغّلتُ RViz، وحرّكتُ كل منزلق، وقارنتُ النتيجة بالذراع الحقيقية. إذا كان الاتجاه خاطئًا، فاعكس المحور (0 0 1 → 0 0 -1).

حدود المفاصل. غالبًا لا يحتوي الملف المُصدَّر على وسوم <limit> أو يحتوي على قيم افتراضية خاطئة. حرّكتُ كل مفصل حقيقي إلى مصدّاته الميكانيكية، ودوّنتُ الزوايا، ثم حوّلتها إلى الراديان:

<limit lower="0" upper="3.14159" velocity="1.0" effort="1.0"/>

تتحرك القاعدة (Revolute 1) من 0 إلى 180° (من 0 إلى π rad). وتتحرك المفاصل من Revolute 2 إلى Revolute 5 من -180° إلى 0° (من -π إلى 0 rad). الحدود الخاطئة تسمح للذراع في المحاكاة ببلوغ أوضاع لا تستطيع الذراع الحقيقية بلوغها، فتفشل على الذراع الحقيقية حركاتٌ تنجح في المحاكاة.

خصائص القصور الذاتي. يحسبها السكربت من المواد المعرّفة في Fusion. إذا كان النموذج لا يزال يستخدم المادة “Steel” بينما الذراع مطبوعة من PLA (بلاستيك شائع في الطباعة ثلاثية الأبعاد 3D)، فستكون الكتلة أكبر من الحقيقية بنحو ست مرات. تبلغ كثافة الفولاذ نحو 7.85 g/cm³، وكثافة PLA نحو 1.24 g/cm³. وزنتُ الذراع الحقيقية (535 g)، وقارنتُ الوزن بالمجموع في URDF، ثم صحّحتُ المواد وأعدتُ التصدير. أصبح مجموع كتل الوصلات الثماني في URDF الآن 535 g. إذا كانت الذراع في المحاكاة أثقل بكثير من الحقيقية، فستتحرك بطريقة مختلفة عنها.

مقياس الشبكات (Mesh Scale). يصدّر Fusion بوحدة المليمتر، بينما يتوقع ROS وحدة المتر. يضيف السكربت scale="0.001 0.001 0.001"؛ تأكّد من وجوده. بدونه يظهر الروبوت في RViz أكبر بـ 1000 مرة.

Robot arm in RViz after the export, with the TF frames of every link visible

إعدادات وحدات التحكم. لا يتضمن الملف المُصدَّر أي وحدات تحكم. أضفتُ gazebo_controllers.yaml، وأعددتُ joint_trajectory_controller و joint_state_broadcaster، وأضفتُ الملحق gz_ros2_control إلى ملف Xacro. يشرح القسم 10 هذه الإعدادات.

8. العرض في RViz

يقرأ RViz ملف URDF، ويشترك في الموضوع (Topic) /tf ويعرض النموذج ثلاثي الأبعاد (3D). لا توجد فيه فيزياء ولا جاذبية؛ إذ يعرض RViz فقط شكل الروبوت عند زوايا المفاصل الحالية. إذا كان الوصف خاطئًا، فستلاحظ ذلك فورًا.

شغّله مع منزلقات تفاعلية (Sliders):

ros2 launch omarm_zero_description display.launch.py

تُشغَّل ثلاث عُقد (Nodes): robot_state_publisher, joint_state_publisher_gui (المنزلقات) و rviz2. اسحب منزلقًا فيدور المفصل في العرض ثلاثي الأبعاد (3D).

استخدم المنزلقات لاختبار كل مفصل. حرّك “Revolute 1” ويجب أن تدور القاعدة. إذا قفزت وصلة إلى موضع خاطئ، فنقطة أصلها (Origin) أو محورها خاطئ.

الصحيح: تشكّل الوصلات سلسلة من القاعدة إلى القابض، ولا يحرّك كل مفصل إلا الأجزاء التي تليه، ولا يتداخل أي جزء مع آخر.

الخطأ: تقع وصلة عند نقطة أصل العالم (قيمة <origin>خاطئة)، أو ينثني مفصل جانبيًا بدلًا من أن ينثني إلى الأمام (محور خاطئ)، أو يظهر القابض أكبر بـ 1000 مرة (غياب scale="0.001").

قضيتُ نحو ساعة في هذه المرحلة: أغيّر رقمًا واحدًا، وأحفظ، وأوقف ملف الإطلاق بـ Ctrl+C، ثم أشغّله من جديد وأتحقق. إذا بدا كل شيء صحيحًا في RViz، فنقاط الأصل والمحاور صحيحة؛ أما الكتلة والقصور الذاتي والتصادمات فلا تُختبر إلا في Gazebo. وإذا بدا خاطئًا، فلا تشغّل Gazebo بعد.

9. المحاكاة في Gazebo

يضيف Gazebo الفيزياء: الجاذبية والاحتكاك والتصادمات وقوى التلامس. بدون وحدة تحكم، تسقط الذراع. وبين أوامرك والمفاصل في المحاكاة تقع منظومة تحكم (القسم 10).

شغّل المحاكاة الكاملة:

ros2 launch omarm_zero_description gazebo.launch.py
OmArm Zero simulated in Gazebo Harmonic, moving under joint control

تسلسل التشغيل:

  1. شغّل Gazebo بعالم فارغ.
  2. Start robot_state_publisher مع ملف URDF بعد معالجته.
  3. بعد 5 s: أنزِل الروبوت في العالم (Spawn)، ليتمكّن Gazebo من التهيئة أولًا.
  4. بعد 8 s و10 s: حمّل joint_state_broadcaster، ثم joint_trajectory_controller.
  5. عند التشغيل أيضًا: شغّل جسر ROS-Gazebo (ينقل ساعة المحاكاة إلى ROS 2) وجسر Mimic الخاص بالقابض (Revolute 6 → Revolute 7).

تضمن هذه التأخيرات أن يعمل Gazebo قبل إنزال الروبوت، وأن يكون الروبوت موجودًا قبل تحميل وحدات التحكم (التفاصيل في القسم 12).

أرسل الذراع إلى هدف:

ros2 topic pub --once /joint_trajectory_controller/joint_trajectory \
  trajectory_msgs/msg/JointTrajectory \
  "{joint_names: ['Revolute 1', 'Revolute 2', 'Revolute 3', 'Revolute 4', 'Revolute 5', 'Revolute 6'],
    points: [{positions: [1.57, -1.57, -1.57, -1.57, -1.57, 0.0],
              time_from_start: {sec: 3, nanosec: 0}}]}"

تتحرك الذراع إلى الهدف خلال 3 ثوانٍ. تحسب وحدة التحكم بالمسار (Trajectory Controller) الزوايا الوسيطة بين الموضع الحالي والموضع الهدف، وتلتزم بحدود السرعة. لا يذكر الأمر إلا ستة مفاصل، وتقبله وحدة التحكم لأن gazebo_controllers.yaml يضبط allow_partial_joints_goal: true، ولأن جسر Mimic الخاص بالقابض يضيف المفصل Revolute 7 (القسم 11). في المرة الأولى التي نجح فيها ذلك، بعد ساعات من تصحيح الأخطاء، هتفتُ فرحًا وأنا على مكتبي.

10. ros2_control: بنية التحكم

ros2_control architecture as stacked layers: controller manager, controllers, hardware interfaces, gz_ros2_control plugin and Gazebo

ros2_control يفصل وحدات التحكم عن العتاد (Hardware)، بحيث تستطيع وحدات التحكم نفسها قيادة محاكاة أو روبوت حقيقي. وهو منظومة متعددة الطبقات: يرسل التطبيق الأهداف في الأعلى، ويتحرك العتاد في الأسفل، ويمكن استبدال كل طبقة بينهما على حدة.

الطبقات:

  • طبقة التطبيق: MoveIt أو الكود الخاص بك أو ros2 topic pub. ترسل أهداف المسار (Trajectory Goals).
  • مدير وحدات التحكم (Controller Manager): يحمّل وحدات التحكم، وينقلها بين حالات مثل غير النشطة والنشطة (دورة حياتها)، ويشغّل حلقة التحكم بتردد 100 Hz.
  • وحدات التحكم: joint_trajectory_controller يحوّل المسار إلى أمر موضع واحد في كل خطوة زمنية. joint_state_broadcaster ينشر الحالات الحالية للمفاصل. وكلاهما ملحق (Plugin) من ros2_controllers.
  • واجهة العتاد (Hardware Interface): الملحق (Plugin) الذي يرسل الأوامر إلى العتاد ويقرأ حالته. في المحاكاة: gz_ros2_control/GazeboSimSystem. أما ذراع OmArm Zero الحقيقية فلا تستخدم ملحق ros2_control إطلاقًا: في الجزء 3، تحلّ عقدتا جسر مكتوبتان بلغة Python (omarm_zero_real_control) محل المنظومة كلها. وتتواصلان مع ESP32 عبر اتصال USB التسلسلي افتراضيًا، أو عبر واي فاي (Wi-Fi).
  • العتاد: محرك الفيزياء في Gazebo، أو لوحة ESP32 الحقيقية مع لوحة قيادة السيرفو PCA9685 ومحركات السيرفو.

الإعدادات في gazebo_controllers.yaml:

controller_manager:
  ros__parameters:
    update_rate: 100
    joint_state_broadcaster:
      type: joint_state_broadcaster/JointStateBroadcaster
    joint_trajectory_controller:
      type: joint_trajectory_controller/JointTrajectoryController

joint_trajectory_controller:
  ros__parameters:
    joints:
      - Revolute 1
      - Revolute 2
      - Revolute 3
      - Revolute 4
      - Revolute 5
      - Revolute 6
      - Revolute 7
    command_interfaces:
      - position
    state_interfaces:
      - position
      - velocity
    allow_partial_joints_goal: true
    gains:
      "Revolute 1": {p: 200.0, i: 1.0, d: 20.0, i_clamp: 10.0}
      "Revolute 2": {p: 300.0, i: 1.0, d: 30.0, i_clamp: 10.0}
      "Revolute 3": {p: 200.0, i: 1.0, d: 20.0, i_clamp: 10.0}
      "Revolute 4": {p: 150.0, i: 0.5, d: 15.0, i_clamp: 10.0}
      "Revolute 5": {p: 100.0, i: 0.5, d: 10.0, i_clamp: 10.0}
      "Revolute 6": {p: 10.0, i: 0.05, d: 1.0, i_clamp: 1.0}
      "Revolute 7": {p: 10.0, i: 0.05, d: 1.0, i_clamp: 1.0}

القيم p, i و d هي معاملات الكسب الثلاثة لوحدة PID؛ أما i_clamp فيحدّ من مقدار نمو الحد التكاملي. ومع هذه الإعدادات لا يكون لمعاملات الكسب أي تأثير. joint_trajectory_controller لا يستخدمها إلا عندما تكون واجهة الأوامر (Command Interface) من نوع السرعة (Velocity) أو القوة/العزم (Effort). أما مع أوامر الموضع، فيرسل المواضع الهدف مباشرة إلى واجهة العتاد. وفي Gazebo، يحوّل gz_ros2_control كل أمر موضع إلى سرعة للمفصل تتناسب مع خطأ الموضع، مضروبةً في position_proportional_gain. تضبط OmArm Zero هذه القيمة على 2.5 في urdf/omarm_zero.gazebo (القيمة الافتراضية 0.1). القيمة الأعلى تحرّك المفاصل نحو أهدافها بسرعة أكبر.

11. جسر mimic الخاص بالقابض

للقابض إصبعان متناظران: عندما ينغلق الإصبع الأيسر، ينغلق الإصبع الأيمن بالمقدار نفسه في الاتجاه المعاكس. في الذراع الحقيقية يتولى ذلك ترسان متعشّقان (الجزء 1)، وفي المحاكاة يتولاه البرنامج.

‏ gripper_mimic_bridge عقدةٌ (Node) تستمع إلى موضوع الأوامر (Topic) /joint_trajectory_controller/joint_trajectory. إذا احتوى أمرٌ على Revolute 6 دون Revolute 7، تنسخه العقدة، وتضيف إليه Revolute 7 بالهدف نفسه، ثم تنشر الأمر الموسَّع على الموضوع نفسه. يشير محور Revolute 7 في URDF إلى الاتجاه المعاكس لمحور Revolute 6، لذا تحرّك القيمة نفسها الإصبع الأيمن في الاتجاه الآخر. وتتجاهل العقدة الأوامر التي تحتوي على Revolute 7 أصلًا، حتى لا تستجيب لرسائلها هي. الملف كله 76 سطرًا من Python.

يحتوي URDF على وسم <mimic> مخصّص لهذا الغرض تحديدًا، لكنه لم يعمل مع إصدار gz_ros2_control الذي أستخدمه. الجسر هو الحل البديل؛ ولا يغيّر شيئًا في إعدادات وحدات التحكم.

12. ملفات الإطلاق التي تربط كل شيء معًا

هكذا يبدو تسلسل بدء التشغيل من القسم 9 بلغة Python. يعرض المقطع البنية فقط؛ أما الملف الكامل فموجود في حزمة مساحة العمل.

def generate_launch_description():
    # Process URDF from Xacro into a string
    robot_description_config = xacro.process_file(robot_description_file)
    robot_description_xml = robot_description_config.toxml()

    # robot_state_publisher needs the URDF string as a parameter
    robot_state_publisher = Node(
        package='robot_state_publisher',
        executable='robot_state_publisher',
        parameters=[{'robot_description': robot_description_xml,
                     'use_sim_time': True}],
    )

    # Gazebo launch: an empty world
    gazebo = IncludeLaunchDescription(...)

    # Spawn AFTER Gazebo is ready (5s delay)
    spawn_robot = TimerAction(period=5.0, actions=[...])

    # Controllers AFTER the robot exists (8s, 10s delays)
    ensure_joint_state_broadcaster = TimerAction(period=8.0, ...)
    ensure_joint_trajectory_controller = TimerAction(period=10.0, ...)

    # Bridge and gripper mimic run alongside everything
    ros_gz_bridge = Node(package='ros_gz_bridge', ...)
    gripper_mimic_bridge = Node(package='omarm_zero_description', ...)

التفصيل المهم هو TimerAction، وهو إجراء إطلاق (Launch Action) يبدأ إجراءات أخرى بعد تأخير محدد. يستغرق Gazebo نحو 4 ثوانٍ ليبدأ على جهازي. إذا أُنزل الروبوت قبل أن يصبح Gazebo جاهزًا، فلن تكون خدمة الإنزال (Spawn Service) موجودة بعد، وسينتظرها أمر الإنزال (يعرض القسم 13 سطر السجل). تحتاج وحدات التحكم إلى وجود الروبوت أولًا، وتحتاج وحدة التحكم بالمسار إلى أن تكون وحدة بث الحالات (State Broadcaster) نشطة. التأخيرات البالغة 5 و8 و10 ثوانٍ تمنح كل طبقة وقتها. على الحواسيب الأبطأ، زِد هذه التأخيرات. وإذا لم يحدث شيء بعد التشغيل، فتحقّق من التأخيرات أولًا.

13. الاختبار والتحقق

أثناء تشغيل المحاكاة، أجرِ هذه الفحوصات.

العُقد: ros2 node list يجب أن يعرض /robot_state_publisher, /controller_manager, /gripper_mimic_bridge وعُقد Gazebo. إذا كانت إحدى العُقد مفقودة، فابحث عن الأخطاء في الطرفية (Terminal).

حالات المفاصل:

ros2 topic hz /joint_states

‏ joint_state_broadcaster ينشر مرة واحدة في كل دورة تحكم، لذا يجب أن يكون المعدل قريبًا من 100 Hz، أي قيمة update_rate لمدير وحدات التحكم. إذا عمل Gazebo أبطأ من الزمن الحقيقي، ينخفض المعدل. وإذا لم يصل شيء، فهذا يعني أن joint_state_broadcaster لم يُحمَّل.

شجرة TF:

ros2 run tf2_tools view_frames

يعرض ملف PDF كل إطار (Frame) من world حتى أصابع القابض. الإطارات المفقودة تعني أن سلسلة المفاصل في URDF مقطوعة.

وحدات التحكم:

ros2 control list_controllers

يجب أن تُظهر وحدتا التحكم كلتاهما الحالة [active]. إذا كانت joint_trajectory_controller تُظهر الحالة [inactive] أو كانت مفقودة، فشغّل ros2 control load_controller --set-state active joint_trajectory_controller. يستخدم ملف الإطلاق الأمر نفسه. إذا كانت وحدة التحكم محمّلة مسبقًا، يتخطى الأمر خطوة التحميل ويكتفي بتفعيلها.

حرّك الذراع:

ros2 topic pub --once /joint_trajectory_controller/joint_trajectory \
  trajectory_msgs/msg/JointTrajectory \
  "{joint_names: ['Revolute 1', 'Revolute 2', 'Revolute 3', 'Revolute 4', 'Revolute 5', 'Revolute 6'],
    points: [{positions: [1.57, -1.0, -2.0, -1.57, -1.57, -0.3],
              time_from_start: {sec: 4, nanosec: 0}}]}"

يجب أن تصل الذراع إلى الموضع الجديد خلال 4 ثوانٍ وأن تتحرك بسلاسة. إذا اهتز مفصل أو تحرك ببطء شديد، فراجع قسم المشكلات الشائعة أدناه.

المشكلات الشائعة

كلّفتني كل واحدة من هذه المشكلات ساعة على الأقل.

الروبوت لا يظهر في Gazebo. بدأ الإنزال (Spawn) قبل أن يصبح Gazebo جاهزًا. ros_gz_sim create ينتظر عندها، وتكرر الطرفية السطر [ros_gz_sim]: Requesting list of world names. حتى يستجيب Gazebo. زِد تأخير الإنزال (مثلًا من 5 إلى 8 ثوانٍ) وأزِح تأخيرات وحدات التحكم بالمقدار نفسه، حتى لا تبدأ وحدات التحكم قبل وجود الروبوت.

الذراع تنهار تحت تأثير الجاذبية. لم تُحمَّل وحدات التحكم، والأمر ros2 control list_controllers لا يعرض شيئًا. من الأسباب المعتادة خطأ إملائي في مسار ملف gz_ros2_control بصيغة YAML، أو تأخير إنزال قصير جدًا.

المفصل يتحرك ببطء شديد، أو يتجاوز هدفه، أو يرتجف. مع أوامر الموضع، لا يكون لمعاملات كسب PID في gazebo_controllers.yaml أي تأثير (القسم 10). تأتي السرعة من position_proportional_gain في urdf/omarm_zero.gazebo: ارفعها إذا كانت المفاصل بطيئة جدًا، واخفضها إذا تجاوز مفصل هدفه أو ارتجف حوله. كما أن قيم التخميد (Damping) في URDF تبطئ المفاصل.

الوصلات في موضع خاطئ (RViz). قيمة xyz في الوسم <origin> الخاص بالمفصل نسبيةٌ إلى الإطار الأب (Parent Frame)، لا إلى العالم. فخ شائع: الشبكات بالمقياس الصحيح، لكن نقاط الأصل لم تُحوَّل من المليمتر إلى المتر.

رسالة “Package not found” بعد بناء ناجح. نسيت تنفيذ source install/setup.bash. يلزم ذلك في كل طرفية جديدة؛ ضعه في .bashrc.

14. ما الخطوة التالية

في الجزء 3 أضيف MoveIt 2: تسحب علامة (Marker) في RViz فيخطط MoveIt مسارًا خاليًا من التصادمات للذراع. وفي الذراع الحقيقية، تحلّ عقدتا جسر مكتوبتان بلغة Python محل منظومة ros2_control وتتواصلان مع ESP32 عبر اتصال USB التسلسلي (واي فاي اختياري). الجزء 4 يضيف كاميرا: معايرة الكاميرا، واكتشاف علامات ArUco (مربعات مطبوعة بالأبيض والأسود تستطيع الكاميرا تحديد موقعها)، ومسار عمل ذاتي التشغيل للالتقاط والوضع (Pick and Place).

الأسئلة الشائعة

هل يمكنني محاكاة روبوت في Gazebo بدون ROS 2؟ نعم. يعمل Gazebo بمفرده ويحاكي الفيزياء، لكن بدون ROS 2 لا يمكنك استخدام ros2_control، ولا MoveIt، ولا وحدات تحكم ROS 2 المستخدمة في هذا الدرس.

لماذا ينهار الروبوت الخاص بي في Gazebo؟ لم تُحمَّل وحدات التحكم؛ تحقّق من ذلك بالأمر ros2 control list_controllers. الأسباب المعتادة هي تأخير إنزال قصير جدًا، أو خطأ إملائي في مسار ملف YAML الخاص بـ gz_ros2_control، أو قسم ros2_control في URDF لا يتطابق مع إعدادات وحدات التحكم.

ما الفرق بين RViz وGazebo؟ RViz أداة عرض: يُظهر الروبوت عند زوايا مفاصل معطاة ولا يحاكي أي فيزياء. أما Gazebo فمحاكٍ يحسب الجاذبية والاحتكاك والتصادمات وقوى التلامس؛ لذا استخدم RViz للتحقق من ملف URDF، وGazebo لاختبار وحدات التحكم.

أضف تعليق