Logo

معمل عملي: محاكاة روبوت باستخدام Gazebo Jetty وROS 2 Jazzy

12 دقيقة قراءة
شرائح الدرس
1 / 15

معمل: محاكاة روبوت باستخدام Gazebo Jetty وROS 2 Jazzy

بناء وربط روبوت مستودع في بيئة المحاكاة

المقرر: معمل الروبوتات القسم: علوم الحاسب، كلية الحاسبات والمعلومات، جامعة المنصورة الفصل الدراسي: خريف 2025 إصدار ROS: ROS 2 Jazzy Jalisco إصدار Gazebo: Gazebo Jetty (gz-sim)


جدول المحتويات

  1. أهداف التعلّم
  2. المتطلبات الأساسية
  3. مهم: Gazebo Jetty مقابل الإصدار الكلاسيكي
  4. تطبيقات من واقع الحياة
  5. الجزء 1: التثبيت والإعداد
  6. الجزء 2: فهم Gazebo Jetty
  7. الجزء 3: بناء روبوت المستودع
  8. الجزء 4: بناء عالَم مستودع (Warehouse World)
  9. الجزء 5: دمج ROS 2 مع Gazebo
  10. الجزء 6: اتباع الجدار ذاتيًا (Autonomous Wall Following)
  11. استكشاف الأخطاء وإصلاحها
  12. متطلبات التسليم

أهداف التعلّم

بنهاية هذا المعمل، ستكون قادرًا على:

  • فهم الفروقات بين Gazebo Classic وGazebo Jetty (gz-sim)
  • إنشاء نماذج روبوتات باستخدام صيغة SDF لأجل Gazebo Jetty
  • استخدام إضافات (plugins) gz-sim للتحكم في الروبوت والحساسات
  • دمج ROS 2 Jazzy مع Gazebo Jetty باستخدام حزم ros_gz
  • تنفيذ ملاحة ذاتية (autonomous navigation) بالاعتماد على تغذية راجعة من الحساسات
  • ربط (bridge) topics الخاصة بـGazebo مع topics الخاصة بـROS 2
  • إنشاء ملفات launch لـROS 2 خاصة ببيئات المحاكاة

المتطلبات الأساسية

  • Ubuntu 24.04 LTS (المعروف باسم Noble Numbat)
  • ROS 2 Jazzy Jalisco مثبَّت
  • Gazebo Jetty (وليس النسخة الكلاسيكية Classic Gazebo!)
  • فهم أساسي لما يلي:
    • أوامر الطرفية (Terminal) في Linux
    • البرمجة بلغة Python
    • مفاهيم ROS 2 (الـnodes، الـtopics، الـpublishers، الـsubscribers)
    • صيغ ملفات SDF/XML

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

# تحقق من تثبيت ROS 2
ros2 doctor
# المتوقع: ros2 doctor version [رقم الإصدار]
 
# تحقق من تثبيت Gazebo Jetty (وليس النسخة الكلاسيكية!)
gz sim --version
# يجب أن يظهر إصدار Gazebo Sim (وليس Gazebo Classic)
 

ملاحظة: إذا لم يعمل الأمر gz sim --version، فقد تحتاج إلى تثبيت Gazebo Jetty (راجع الجزء 1).


مهم: Gazebo Jetty مقابل الإصدار الكلاسيكي

فروقات حاسمة:

الميزةGazebo ClassicGazebo Jetty (الجديد)
الأمرgazebogz sim
الحزمةgazebo_ros_pkgsros_gz
أسماء الإضافات (Plugins)libgazebo_ros_*.sogz-sim-*-system
مسار النماذجGAZEBO_MODEL_PATHGZ_SIM_RESOURCE_PATH
إضافة الدفع التفاضلي (DiffDrive)libgazebo_ros_diff_drive.sogz-sim-diff-drive-system
التشغيل (Launch)gazebo world.sdfgz sim world.sdf

هذا المعمل يستخدم Gazebo Jetty (الإصدار الجديد)! إذا كنت تتّبع دروسًا قديمة خاصة بـGazebo Classic، فلن تعمل معك!


تطبيقات من واقع الحياة

روبوتات المستودعات المتنقلة (Mobile warehouse robots) تُستخدم على نطاق واسع في اللوجستيات الحديثة:

  • مراكز تنفيذ الطلبات لدى Amazon: روبوتات ذاتية تنقل الرفوف
  • التصنيع: مناولة المواد بين محطات العمل
  • الرعاية الصحية: توصيل الأدوية والإمدادات
  • التجزئة (Retail): إدارة المخزون وتتبع الأصناف

لماذا المحاكاة؟

  • فعّالة من حيث التكلفة: الاختبار دون المخاطرة بالعتاد الفعلي
  • آمنة: تجربة سيناريوهات خطرة دون مخاطر حقيقية
  • تكرار سريع: اختبار تصميمات متعددة بسرعة
  • قابلة للتوسّع (Scalable): اختبار عدد كبير من الروبوتات في آنٍ واحد

الجزء 1: التثبيت والإعداد

الخطوة 1.1: تثبيت Gazebo Jetty

إن لم يكن مثبَّتًا بعد:

sudo apt-get update
sudo apt-get install lsb-release gnupg
 
sudo curl https://packages.osrfoundation.org/gazebo.gpg --output /usr/share/keyrings/pkgs-osrf-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/pkgs-osrf-archive-keyring.gpg] https://packages.osrfoundation.org/gazebo/ubuntu-stable $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/gazebo-stable.list > /dev/null
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/pkgs-osrf-archive-keyring.gpg] https://packages.osrfoundation.org/gazebo/ubuntu-prerelease $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/gazebo-prerelease.list > /dev/null
sudo apt-get update
sudo apt-get install gz-jetty

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

gz sim --version

يجب أن يظهر معلومات إصدار Gazebo Sim.


الخطوة 1.2: تثبيت حزم دمج ROS 2 مع Gazebo

# ثبّت حزم ros_gz (وليس gazebo_ros_pkgs!)
sudo apt install ros-jazzy-ros-gz

هذا يُثبّت:

  • ros-jazzy-ros-gz-bridge: يربط بين topics الخاصة بـGazebo وROS
  • ros-jazzy-ros-gz-sim: أدوات مساعدة (utilities) لتشغيل Gazebo عبر launch
  • ros-jazzy-ros-gz-image: ربط تدفقات الصور (Image bridging)

الخطوة 1.3: إعداد بيئة العمل

أضف إلى ملف ~/.bashrc الخاص بك:

# استيراد بيئة ROS 2
source /opt/ros/jazzy/setup.bash
 
# ضبط مسارات موارد Gazebo
export GZ_SIM_RESOURCE_PATH=$HOME/.gz/models:$GZ_SIM_RESOURCE_PATH

ثم أعد تحميل الملف:

source ~/.bashrc

الجزء 2: فهم Gazebo Jetty

الخطوة 2.1: تشغيل Gazebo Jetty

gz sim shapes.sdf

ما يجب أن تراه:

  • نافذة ثلاثية الأبعاد (3D) تحتوي على أشكال متنوعة
  • أزرار تشغيل/إيقاف مؤقت (Play/pause) في الأسفل
  • قائمة الإضافات (Plugin menu) (ثلاث نقاط عمودية) في أعلى اليمين

الأوامر الأساسية:

  • gz sim: التشغيل مع واجهة المستخدم الرسومية (GUI)
  • gz sim -s: تشغيل الخادم فقط (بدون واجهة - headless)
  • gz sim -g: تشغيل الواجهة الرسومية فقط
  • gz sim -v 4: مخرجات تفصيلية (Verbose)

الخطوة 2.2: استكشاف الواجهة

الإضافات (Plugins) المهمة (متاحة عبر القائمة المنسدلة):

  • Entity Tree: عرض جميع الكائنات (objects) في العالَم (world)
  • 3D View: العرض الرئيسي ثلاثي الأبعاد
  • World Control: تشغيل/إيقاف المحاكاة مؤقتًا
  • Transform Control: تحريك وتدوير الكائنات

لإضافة plugins:

  1. انقر على النقاط العمودية الثلاث (⋮) في أعلى اليمين
  2. اختر إضافة (plugin) من القائمة
  3. ستظهر الإضافة في الواجهة

الخطوة 2.3: اختبار بعالَم بسيط

أنشئ ملف اختبار باسم ~/test_world.sdf:

<?xml version="1.0" ?>
<sdf version="1.8">
  <world name="test_world">
    
    <!-- Physics -->
    <physics name="1ms" type="ignored">
      <max_step_size>0.001</max_step_size>
      <real_time_factor>1.0</real_time_factor>
    </physics>
    
    <!-- Physics System Plugin -->
    <plugin
      filename="gz-sim-physics-system"
      name="gz::sim::systems::Physics">
    </plugin>
    
    <!-- Scene Broadcaster Plugin -->
    <plugin
      filename="gz-sim-scene-broadcaster-system"
      name="gz::sim::systems::SceneBroadcaster">
    </plugin>
    
    <!-- Ground Plane -->
    <include>
      <uri>https://fuel.gazebosim.org/1.0/OpenRobotics/models/Ground Plane</uri>
    </include>
    
    <!-- Sun -->
    <include>
      <uri>https://fuel.gazebosim.org/1.0/OpenRobotics/models/Sun</uri>
    </include>
    
  </world>
</sdf>

شغّله:

gz sim ~/test_world.sdf

الجزء 3: بناء روبوت المستودع

الخطوة 3.1: إنشاء دليل النموذج (Model Directory)

mkdir -p ~/.gz/models/mobile_warehouse_robot
cd ~/.gz/models/mobile_warehouse_robot

ملاحظة: نستخدم ~/.gz/models (وليس ~/.gazebo/models)!


الخطوة 3.2: إنشاء model.config

nano model.config

أضف:

<?xml version="1.0"?>
<model>
  <name>Mobile Warehouse Robot</name>
  <version>1.0</version>
  <sdf version="1.8">model.sdf</sdf>
  
  <author>
    <name>Your Name</name>
    <email>[email protected]</email>
  </author>
  
  <description>
    A differential drive mobile warehouse robot for Gazebo Jetty.
    Features two drive wheels, one caster, and a laser range finder.
  </description>
</model>

احفظ واخرج (Ctrl+O، Enter، Ctrl+X).


الخطوة 3.3: إنشاء model.sdf

هذا هو الوصف الرئيسي للروبوت:

nano model.sdf

أضف نموذج الروبوت الكامل:

<?xml version="1.0" ?>
<sdf version="1.8">
  <model name="mobile_warehouse_robot">
    
    <!-- Base Link (Main Body) -->
    <link name="base_link">
      <inertial>
        <pose>0 0 0.12 0 0 0</pose>
        <mass>15.0</mass>
        <inertia>
          <ixx>0.5</ixx>
          <ixy>0</ixy>
          <ixz>0</ixz>
          <iyy>0.5</iyy>
          <iyz>0</iyz>
          <izz>0.3</izz>
        </inertia>
      </inertial>
      
      <!-- Visual -->
      <visual name="base_visual">
        <pose>0 0 0.12 0 0 0</pose>
        <geometry>
          <box>
            <size>0.5 0.4 0.2</size>
          </box>
        </geometry>
        <material>
          <ambient>0.2 0.2 0.8 1</ambient>
          <diffuse>0.2 0.2 0.8 1</diffuse>
          <specular>0.1 0.1 0.1 1</specular>
        </material>
      </visual>
      
      <!-- Collision -->
      <collision name="base_collision">
        <pose>0 0 0.12 0 0 0</pose>
        <geometry>
          <box>
            <size>0.5 0.4 0.2</size>
          </box>
        </geometry>
      </collision>
    </link>
    
    <!-- Left Wheel -->
    <link name="left_wheel">
      <pose>0.15 0.25 0.08 -1.5707 0 0</pose>
      <inertial>
        <mass>1.0</mass>
        <inertia>
          <ixx>0.01</ixx>
          <ixy>0</ixy>
          <ixz>0</ixz>
          <iyy>0.01</iyy>
          <iyz>0</iyz>
          <izz>0.01</izz>
        </inertia>
      </inertial>
      
      <visual name="left_wheel_visual">
        <geometry>
          <cylinder>
            <radius>0.08</radius>
            <length>0.05</length>
          </cylinder>
        </geometry>
        <material>
          <ambient>0.1 0.1 0.1 1</ambient>
          <diffuse>0.1 0.1 0.1 1</diffuse>
        </material>
      </visual>
      
      <collision name="left_wheel_collision">
        <geometry>
          <cylinder>
            <radius>0.08</radius>
            <length>0.05</length>
          </cylinder>
        </geometry>
        <surface>
          <friction>
            <ode>
              <mu>1.0</mu>
              <mu2>1.0</mu2>
            </ode>
          </friction>
        </surface>
      </collision>
    </link>
    
    <!-- Right Wheel -->
    <link name="right_wheel">
      <pose>0.15 -0.25 0.08 -1.5707 0 0</pose>
      <inertial>
        <mass>1.0</mass>
        <inertia>
          <ixx>0.01</ixx>
          <ixy>0</ixy>
          <ixz>0</ixz>
          <iyy>0.01</iyy>
          <iyz>0</iyz>
          <izz>0.01</izz>
        </inertia>
      </inertial>
      
      <visual name="right_wheel_visual">
        <geometry>
          <cylinder>
            <radius>0.08</radius>
            <length>0.05</length>
          </cylinder>
        </geometry>
        <material>
          <ambient>0.1 0.1 0.1 1</ambient>
          <diffuse>0.1 0.1 0.1 1</diffuse>
        </material>
      </visual>
      
      <collision name="right_wheel_collision">
        <geometry>
          <cylinder>
            <radius>0.08</radius>
            <length>0.05</length>
          </cylinder>
        </geometry>
        <surface>
          <friction>
            <ode>
              <mu>1.0</mu>
              <mu2>1.0</mu2>
            </ode>
          </friction>
        </surface>
      </collision>
    </link>
    
    <!-- Caster Wheel (Back Support) -->
    <link name="caster">
      <pose>-0.2 0 0.04 0 0 0</pose>
      <inertial>
        <mass>0.5</mass>
        <inertia>
          <ixx>0.001</ixx>
          <ixy>0</ixy>
          <ixz>0</ixz>
          <iyy>0.001</iyy>
          <iyz>0</iyz>
          <izz>0.001</izz>
        </inertia>
      </inertial>
      
      <visual name="caster_visual">
        <geometry>
          <sphere>
            <radius>0.04</radius>
          </sphere>
        </geometry>
        <material>
          <ambient>0.3 0.3 0.3 1</ambient>
          <diffuse>0.3 0.3 0.3 1</diffuse>
        </material>
      </visual>
      
      <collision name="caster_collision">
        <geometry>
          <sphere>
            <radius>0.04</radius>
          </sphere>
        </geometry>
        <surface>
          <friction>
            <ode>
              <mu>0.01</mu>
              <mu2>0.01</mu2>
            </ode>
          </friction>
        </surface>
      </collision>
    </link>
    
    <!-- Laser Sensor Link -->
    <link name="lidar_link">
      <pose>0.25 0 0.25 0 0 0</pose>
      <inertial>
        <mass>0.1</mass>
        <inertia>
          <ixx>0.0001</ixx>
          <ixy>0</ixy>
          <ixz>0</ixz>
          <iyy>0.0001</iyy>
          <iyz>0</iyz>
          <izz>0.0001</izz>
        </inertia>
      </inertial>
      
      <visual name="lidar_visual">
        <geometry>
          <cylinder>
            <radius>0.05</radius>
            <length>0.07</length>
          </cylinder>
        </geometry>
        <material>
          <ambient>0.1 0.1 0.1 1</ambient>
          <diffuse>0.1 0.1 0.1 1</diffuse>
        </material>
      </visual>
      
      <collision name="lidar_collision">
        <geometry>
          <cylinder>
            <radius>0.05</radius>
            <length>0.07</length>
          </cylinder>
        </geometry>
      </collision>
      
      <!-- GPU Lidar Sensor -->
      <sensor name="gpu_lidar" type="gpu_lidar">
        <pose>0 0 0 0 0 0</pose>
        <topic>lidar</topic>
        <update_rate>10</update_rate>
        <lidar>
          <scan>
            <horizontal>
              <samples>640</samples>
              <resolution>1</resolution>
              <min_angle>-1.57</min_angle>
              <max_angle>1.57</max_angle>
            </horizontal>
          </scan>
          <range>
            <min>0.08</min>
            <max>10.0</max>
            <resolution>0.01</resolution>
          </range>
        </lidar>
        <always_on>1</always_on>
        <visualize>true</visualize>
      </sensor>
    </link>
    
    <!-- JOINTS -->
    
    <!-- Left Wheel Joint -->
    <joint name="left_wheel_joint" type="revolute">
      <parent>base_link</parent>
      <child>left_wheel</child>
      <axis>
        <xyz>0 0 1</xyz>
        <limit>
          <lower>-1e+12</lower>
          <upper>1e+12</upper>
        </limit>
      </axis>
    </joint>
    
    <!-- Right Wheel Joint -->
    <joint name="right_wheel_joint" type="revolute">
      <parent>base_link</parent>
      <child>right_wheel</child>
      <axis>
        <xyz>0 0 1</xyz>
        <limit>
          <lower>-1e+12</lower>
          <upper>1e+12</upper>
        </limit>
      </axis>
    </joint>
    
    <!-- Caster Joint -->
    <joint name="caster_joint" type="ball">
      <parent>base_link</parent>
      <child>caster</child>
    </joint>
    
    <!-- Lidar Joint -->
    <joint name="lidar_joint" type="fixed">
      <parent>base_link</parent>
      <child>lidar_link</child>
    </joint>
    
    <!-- PLUGINS -->
    
    <!-- Differential Drive Plugin -->
    <plugin
      filename="gz-sim-diff-drive-system"
      name="gz::sim::systems::DiffDrive">
      <left_joint>left_wheel_joint</left_joint>
      <right_joint>right_wheel_joint</right_joint>
      <wheel_separation>0.5</wheel_separation>
      <wheel_radius>0.08</wheel_radius>
      <odom_publish_frequency>50</odom_publish_frequency>
      <topic>/model/warehouse_robot/cmd_vel</topic>
      <odom_topic>/model/warehouse_robot/odometry</odom_topic>
      <frame_id>odom</frame_id>
      <child_frame_id>base_link</child_frame_id>
    </plugin>
    
  </model>
</sdf>

نقاط أساسية:

  • يستخدم gz-sim-diff-drive-system (وليس libgazebo_ros_diff_drive.so!)
  • إصدار SDF رقم 1.8 (الإصدار الحديث)
  • حساس GPU lidar (أداء أفضل)
  • جميع الوحدات بالأمتار والراديان (radians)
  • مهم: يستخدم مسارات topics مطلقة (absolute topic paths) (/model/warehouse_robot/cmd_vel) لضمان تواصل سليم عبر الـbridge

احفظ واخرج.

ملاحظة: مسارات الـtopics المطلقة أمر بالغ الأهمية! إذا استخدمت مسارات نسبية مثل <topic>cmd_vel</topic>، فقد لا يستجيب الروبوت لأوامر ROS بسبب عدم تطابق أسماء الـtopics مع الـbridge. راجع المعمل 04 لشرح تفصيلي.


الخطوة 3.4: اختبار الروبوت

gz sim model.sdf

إذا لم يظهر الروبوت:

  • تحقق من أنك أنشأت الملفات في ~/.gz/models/mobile_warehouse_robot/
  • تحقق من وجود كلٍّ من model.config وmodel.sdf
  • جرّب إعادة تشغيل Gazebo

مشاكل شائعة:

  • الروبوت يسقط عبر الأرضية: أضِف نموذج أرضية (ground plane)
  • الروبوت غير مرئي: تحقق من وجود أخطاء في بنية SDF

أغلق Gazebo (Ctrl+C في الطرفية).


الجزء 4: بناء عالَم مستودع (Warehouse World)

الخطوة 4.1: إنشاء دليل العالَم (World Directory)

mkdir -p ~/.gz/models/small_warehouse
cd ~/.gz/models/small_warehouse

الخطوة 4.2: إنشاء model.config

nano model.config
<?xml version="1.0"?>
<model>
  <name>Small Warehouse</name>
  <version>1.0</version>
  <sdf version="1.8">model.sdf</sdf>
  
  <author>
    <name>Your Name</name>
    <email>[email protected]</email>
  </author>
  
  <description>
    A small warehouse environment with walls for testing robot navigation.
  </description>
</model>

الخطوة 4.3: إنشاء model.sdf

nano model.sdf
<?xml version="1.0" ?>
<sdf version="1.8">
  <world name="warehouse_world">
    
    <physics name="1ms" type="ignored">
      <max_step_size>0.001</max_step_size>
      <real_time_factor>1.0</real_time_factor>
    </physics>
    
    <plugin filename="gz-sim-physics-system" name="gz::sim::systems::Physics"></plugin>
    <plugin filename="gz-sim-scene-broadcaster-system" name="gz::sim::systems::SceneBroadcaster"></plugin>
    
    <light type="directional" name="sun">
      <cast_shadows>true</cast_shadows>
      <pose>0 0 10 0 0 0</pose>
      <diffuse>0.8 0.8 0.8 1</diffuse>
      <specular>0.2 0.2 0.2 1</specular>
      <direction>-0.5 0.1 -0.9</direction>
    </light>
    
    <!-- Ground -->
    <model name="ground_plane">
      <static>true</static>
      <link name="link">
        <collision name="collision">
          <geometry>
            <plane>
              <normal>0 0 1</normal>
              <size>100 100</size>
            </plane>
          </geometry>
        </collision>
        <visual name="visual">
          <geometry>
            <plane>
              <normal>0 0 1</normal>
              <size>100 100</size>
            </plane>
          </geometry>
          <material>
            <ambient>0.8 0.8 0.8 1</ambient>
            <diffuse>0.8 0.8 0.8 1</diffuse>
          </material>
        </visual>
      </link>
    </model>
    
    <!-- Warehouse using model:// -->
    <include>
      <name>warehouse</name>
      <pose>0 0 0 0 0 0</pose>
      <uri>model://small_warehouse</uri>
    </include>
    
    <!-- Robot using model:// -->
    <include>
      <name>warehouse_robot</name>
      <pose>-3 -3 0.1 0 0 0</pose>
      <uri>model://mobile_warehouse_robot</uri>
    </include>
    
  </world>
</sdf>
 

الخطوة 4.4: إنشاء ملف العالَم الكامل

الآن أنشئ ملف عالَم (world file) يحتوي كل شيء:

mkdir -p ./ros_ws/worlds
nano ./ros_ws/worlds/warehouse.sdf
<?xml version="1.0" ?>
<sdf version="1.8">
  <world name="warehouse_world">
    
    <!-- Physics -->
    <physics name="1ms" type="ignored">
      <max_step_size>0.001</max_step_size>
      <real_time_factor>1.0</real_time_factor>
    </physics>
    
    <!-- Required Plugins -->
    <plugin
      filename="gz-sim-physics-system"
      name="gz::sim::systems::Physics">
    </plugin>
    
    <plugin
      filename="gz-sim-user-commands-system"
      name="gz::sim::systems::UserCommands">
    </plugin>
    
    <plugin
      filename="gz-sim-scene-broadcaster-system"
      name="gz::sim::systems::SceneBroadcaster">
    </plugin>
    
    <plugin
      filename="gz-sim-sensors-system"
      name="gz::sim::systems::Sensors">
      <render_engine>ogre2</render_engine>
    </plugin>
    
    <!-- GUI -->
    <gui fullscreen="0">
      <!-- 3D View -->
      <plugin filename="MinimalScene" name="3D View">
        <gz-gui>
          <title>3D View</title>
          <property type="bool" key="showTitleBar">false</property>
          <property type="string" key="state">docked</property>
        </gz-gui>
        <engine>ogre2</engine>
        <scene>scene</scene>
        <ambient_light>0.4 0.4 0.4</ambient_light>
        <background_color>0.8 0.8 0.8</background_color>
        <camera_pose>-8 -8 8 0 0.5 0.78</camera_pose>
      </plugin>
      
      <!-- World Control -->
      <plugin filename="WorldControl" name="World control">
        <gz-gui>
          <title>World control</title>
          <property type="bool" key="showTitleBar">false</property>
          <property type="bool" key="resizable">false</property>
          <property type="double" key="height">72</property>
          <property type="double" key="width">121</property>
          <property type="double" key="z">1</property>
          <property type="string" key="state">floating</property>
          <anchors target="3D View">
            <line own="left" target="left"/>
            <line own="bottom" target="bottom"/>
          </anchors>
        </gz-gui>
        <play_pause>true</play_pause>
        <step>true</step>
        <start_paused>false</start_paused>
      </plugin>
      
      <!-- World Stats -->
      <plugin filename="WorldStats" name="World stats">
        <gz-gui>
          <title>World stats</title>
          <property type="bool" key="showTitleBar">false</property>
          <property type="bool" key="resizable">false</property>
          <property type="double" key="height">110</property>
          <property type="double" key="width">290</property>
          <property type="double" key="z">1</property>
          <property type="string" key="state">floating</property>
          <anchors target="3D View">
            <line own="right" target="right"/>
            <line own="bottom" target="bottom"/>
          </anchors>
        </gz-gui>
        <sim_time>true</sim_time>
        <real_time>true</real_time>
        <real_time_factor>true</real_time_factor>
        <iterations>true</iterations>
      </plugin>
      
      <!-- Entity Tree -->
      <plugin filename="EntityTree" name="Entity tree">
      </plugin>
    </gui>
    
    <!-- Lighting -->
    <light type="directional" name="sun">
      <cast_shadows>true</cast_shadows>
      <pose>0 0 10 0 0 0</pose>
      <diffuse>0.8 0.8 0.8 1</diffuse>
      <specular>0.2 0.2 0.2 1</specular>
      <attenuation>
        <range>1000</range>
        <constant>0.9</constant>
        <linear>0.01</linear>
        <quadratic>0.001</quadratic>
      </attenuation>
      <direction>-0.5 0.1 -0.9</direction>
    </light>
    
    <!-- Ground Plane -->
    <include>
      <uri>https://fuel.gazebosim.org/1.0/OpenRobotics/models/Ground Plane</uri>
    </include>
    
    <!-- Warehouse -->
    <include>
      <name>warehouse</name>
      <pose>0 0 0 0 0 0</pose>
      <uri>model://small_warehouse</uri>
    </include>
    
    <!-- Robot -->
    <include>
      <name>warehouse_robot</name>
      <pose>-4 -4 0.1 0 0 0</pose>
      <uri>model://mobile_warehouse_robot</uri>
    </include>
    
  </world>
</sdf>

نقاط أساسية:

  • يستخدم إضافات (plugins) من نوع gz-sim (وليس إضافات gazebo_ros!)
  • يتضمن جميع إضافات النظام اللازمة
  • إعدادات واجهة المستخدم الرسومية (GUI) لتجربة استخدام أفضل
  • النماذج يُشار إليها عبر مسارات model://

الخطوة 4.5: اختبار العالَم

gz sim ./ros_ws/worlds/warehouse.sdf

ما يجب أن تراه:

  • مستودع بجدران
  • الروبوت في موضع البداية
  • عرض ثلاثي الأبعاد، أدوات تحكم، وشجرة الكائنات (entity tree)

إذا لم تُحمَّل النماذج:

  • تحقق من أن GZ_SIM_RESOURCE_PATH يتضمن ~/.gz/models
  • تحقق من وجود أدلة النماذج
  • تحقق من وجود أخطاء في بنية SDF

أغلق Gazebo.


الجزء 5: دمج ROS 2 مع Gazebo

الآن سنربط (bridge) topics الخاصة بـGazebo مع topics الخاصة بـROS 2 حتى نتمكن من التحكم في الروبوت من خلال ROS.

الخطوة 5.1: فهم ros_gz_bridge

يقوم ros_gz_bridge بترجمة الرسائل بين:

  • Gazebo Transport (gz.msgs.*)
  • ROS 2 (geometry_msgs، sensor_msgs، إلخ)

إعداد الـBridge:

  • يمكن تعريفه في ملفات YAML
  • أو إنشاؤه برمجيًا

الخطوة 5.2: إنشاء مساحة عمل ROS 2

  • لدينا بالفعل ros_ws

الخطوة 5.3: إنشاء حزمة المُشغِّل (Launcher Package)

cd ~/Desktop/ROS-2-Practical-Course-Roadmap-2025/ros_ws
ros2 pkg create --build-type ament_python warehouse_simulation

الخطوة 5.4: إنشاء إعدادات الـBridge

ملاحظة: في حزم ament_python، توضع مجلدات launch/ وconfig/ في جذر الحزمة، وليس داخل مجلد فرعي باسم src.

cd ~/Desktop/ROS-2-Practical-Course-Roadmap-2025/ros_ws/src/warehouse_simulation
mkdir -p config launch
nano config/bridge.yaml

أضف:

# Bridge configuration for warehouse robot
 
# Command velocity (ROS -> Gazebo)
- ros_topic_name: "/cmd_vel"
  gz_topic_name: "/model/warehouse_robot/cmd_vel"
  ros_type_name: "geometry_msgs/msg/Twist"
  gz_type_name: "gz.msgs.Twist"
  direction: ROS_TO_GZ
 
# Odometry (Gazebo -> ROS)
- ros_topic_name: "/odom"
  gz_topic_name: "/model/warehouse_robot/odometry"
  ros_type_name: "nav_msgs/msg/Odometry"
  gz_type_name: "gz.msgs.Odometry"
  direction: GZ_TO_ROS
 
# Lidar (Gazebo -> ROS)
- ros_topic_name: "/scan"
  gz_topic_name: "/world/warehouse_world/model/warehouse_robot/link/lidar_link/sensor/gpu_lidar/scan"
  ros_type_name: "sensor_msgs/msg/LaserScan"
  gz_type_name: "gz.msgs.LaserScan"
  direction: GZ_TO_ROS
 
# TF (Gazebo -> ROS)
- ros_topic_name: "/tf"
  gz_topic_name: "/model/warehouse_robot/pose"
  ros_type_name: "tf2_msgs/msg/TFMessage"
  gz_type_name: "gz.msgs.Pose_V"
  direction: GZ_TO_ROS

هذا يربط:

  • /cmd_vel في ROS ← سرعة روبوت Gazebo
  • odometry الخاصة بـGazebo → /odom في ROS
  • lidar الخاص بـGazebo → /scan في ROS
  • pose الخاص بـGazebo → /tf في ROS

الخطوة 5.5: إنشاء ملف Launch

nano launch/warehouse_simulation.launch.py

أضف:

#!/usr/bin/env python3
 
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import IncludeLaunchDescription, SetEnvironmentVariable
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch_ros.actions import Node
 
 
def generate_launch_description():
    
    # Get package directories
    pkg_ros_gz_sim = get_package_share_directory('ros_gz_sim')
    pkg_warehouse_sim = get_package_share_directory('warehouse_simulation')
    
    # Paths - use os.path.join() instead of PathJoinSubstitution for node parameters
    world_file = os.path.join(os.path.expanduser('~'), 'warehouse_ws', 'worlds', 'warehouse.sdf')
    bridge_config = os.path.join(pkg_warehouse_sim, 'config', 'bridge.yaml')
    
    # Set Gazebo resource path
    gz_resource_path = SetEnvironmentVariable(
        name='GZ_SIM_RESOURCE_PATH',
        value=os.path.join(os.path.expanduser('~'), '.gz', 'models')
    )
    
    # Launch Gazebo
    gz_sim = IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            os.path.join(pkg_ros_gz_sim, 'launch', 'gz_sim.launch.py')
        ),
        launch_arguments={
            'gz_args': f'-r {world_file}'
        }.items()
    )
    
    # ROS-Gazebo Bridge
    bridge = Node(
        package='ros_gz_bridge',
        executable='parameter_bridge',
        arguments=[
            '--ros-args',
            '-p', f'config_file:={bridge_config}'
        ],
        output='screen'
    )
    
    return LaunchDescription([
        gz_resource_path,
        gz_sim,
        bridge,
    ])

ماذا يفعل هذا الملف:

  1. يضبط GZ_SIM_RESOURCE_PATH لإيجاد النماذج
  2. يشغّل Gazebo بعالَم المستودع (warehouse world)
  3. يبدأ تشغيل الـbridge باستخدام إعدادات YAML الخاصة بنا

الخطوة 5.6: تحقق من بنية الحزمة

يجب أن تبدو حزمتك الآن كالتالي:

warehouse_simulation/
├── package.xml
├── setup.py
├── setup.cfg
├── resource/
│   └── warehouse_simulation
├── warehouse_simulation/          # Python module directory
│   └── __init__.py
├── launch/                        # At package root!
│   └── warehouse_simulation.launch.py
└── config/                        # At package root!
    └── bridge.yaml

مهم: مجلَّدا launch/ وconfig/ في نفس مستوى setup.py، وليسا داخل أي مجلد src!


الخطوة 5.7: تحديث ملفات الحزمة

عدّل package.xml:

nano package.xml

أضف هذه الاعتماديات (dependencies) داخل وسم <package>:

<depend>ros_gz_sim</depend>
<depend>ros_gz_bridge</depend>
<depend>geometry_msgs</depend>
<depend>nav_msgs</depend>
<depend>sensor_msgs</depend>
<depend>tf2_msgs</depend>

عدّل setup.py:

nano setup.py

تأكد من أنه يبدو تمامًا كالتالي:

import os
from glob import glob
from setuptools import setup
 
package_name = 'warehouse_simulation'
 
setup(
    name=package_name,
    version='0.0.1',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
        (os.path.join('share', package_name, 'launch'), glob('launch/*.launch.py')),
        (os.path.join('share', package_name, 'config'), glob('config/*.yaml')),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='Your Name',
    maintainer_email='[email protected]',
    description='Warehouse robot simulation with Gazebo Jetty',
    license='Apache-2.0',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
        ],
    },
)

نقطة أساسية: دوال glob() تبحث عن launch/*.launch.py وconfig/*.yaml نسبةً إلى موقع setup.py (جذر الحزمة).


الخطوة 5.8: بناء الحزمة

cd ~/Desktop/ROS-2-Practical-Course-Roadmap-2025/ros_ws
colcon build --packages-select warehouse_simulation
source install/setup.bash

الخطوة 5.9: تحقق من التثبيت

تحقق من أن الملفات تم تثبيتها بشكل صحيح:

ls -la install/warehouse_simulation/share/warehouse_simulation/launch/
ls -la install/warehouse_simulation/share/warehouse_simulation/config/

يجب أن ترى:

  • warehouse_simulation.launch.py في مجلد launch
  • bridge.yaml في مجلد config

الخطوة 5.10: تشغيل المحاكاة

ros2 launch warehouse_simulation warehouse_simulation.launch.py

ما يجب أن يحدث:

  • يُفتح Gazebo بالمستودع والروبوت
  • يبدأ الـbridge بين ROS وGazebo
  • يتم ربط الـtopics (/cmd_vel، /odom، /scan، /tf)

الخطوة 5.9: تحقق من topics الخاصة بـROS

افتح طرفية (terminal) جديدة:

source ./ros_ws/install/setup.bash
ros2 topic list

يجب أن ترى:

/cmd_vel
/odom
/scan
/tf
...