معمل 04: التحكم عن بعد بلوحة المفاتيح لروبوت المستودع
المقرر: معمل الروبوتات القسم: علوم الحاسب، كلية الحاسبات والمعلومات، جامعة المنصورة الفصل الدراسي: خريف 2025 إصدار ROS: ROS 2 Jazzy Jalisco إصدار Gazebo: Gazebo Jetty (gz-sim)
نظرة عامة
في هذا المعمل، ستُنشئ node مخصصة للتحكم عن بعد (teleop) بلوحة المفاتيح للتحكم يدويًا في روبوت المستودع الخاص بك. هذا يبني على محاكاة المستودع في المعمل 03 بإضافة تحكم بديهي عبر لوحة المفاتيح.
أهداف التعلّم:
- إنشاء node مخصصة بلغة Python في ROS 2 لاستقبال إدخال لوحة المفاتيح
- فهم التحكم في السرعة والنشر إلى topic باسم cmd_vel
- تنفيذ ميزات أمان وإيقاف طارئ (emergency stop)
- اختبار والتحقق من تحكم الروبوت في المحاكاة
المتطلبات الأساسية:
- إتمام المعمل 03 (محاكاة المستودع)
- حزمة warehouse_simulation تعمل بشكل صحيح
- الروبوت يستجيب بشكل صحيح لرسائل /cmd_vel
الجزء 1: فهم التحكم عن بعد بلوحة المفاتيح (Keyboard Teleop)
ما هو الـTeleop؟
التشغيل عن بعد (Teleoperation أو Teleop) = تشغيل الروبوت عن بعد بواسطة مُشغِّل بشري.
بالنسبة لروبوت المستودع الخاص بنا:
- المدخَل (Input): ضغطات لوحة المفاتيح (w، a، s، d، إلخ)
- المعالجة: node في ROS تحوّل ضغطات المفاتيح إلى أوامر سرعة
- المُخرَج (Output): رسائل Twist تُنشر إلى topic باسم
/cmd_vel - النتيجة: الروبوت يتحرك في Gazebo
لماذا نُنشئ teleop مخصصًا؟
توجد حزم teleop قياسية في ROS، لكن إنشاء حزمتنا الخاصة يتيح:
- التخصيص: تكييف عناصر التحكم مع احتياجاتنا المحددة
- التعلّم: فهم كيفية عمل nodes في ROS
- الميزات: إضافة وظائف مخصصة (حدود للسرعة، إيقاف طارئ، إلخ)
- الدمج: تكامل أفضل مع حزمة warehouse_simulation الخاصة بنا
الجزء 2: تصميم node الـTeleop
مخطط التحكم
سنستخدم تحكمًا تراكميًا في السرعة (incremental velocity control):
w : زيادة السرعة الأمامية (+0.1 م/ث لكل ضغطة)
x : زيادة السرعة الخلفية (-0.1 م/ث لكل ضغطة)
a : زيادة معدل الدوران لليسار (+0.2 راد/ث لكل ضغطة)
d : زيادة معدل الدوران لليمين (-0.2 راد/ث لكل ضغطة)
s : توقف (ضبط جميع السرعات على صفر)
SPACE : إيقاف طارئ (نفس تأثير 's')
q : الخروج من البرنامجلماذا تراكمي (incremental)؟
- تسارع/تباطؤ سلس
- تحكم دقيق في سرعة الروبوت
- الروبوت يحافظ على سرعته حتى تُغيّرها أنت
حدود السرعة
من أجل السلامة، سننفّذ حدودًا قصوى:
- أقصى سرعة خطية (linear velocity): 1.0 م/ث
- أقصى سرعة زاوية (angular velocity): 2.0 راد/ث
هذا يمنع الروبوت من التحرك بسرعة خطرة.
الجزء 3: التنفيذ
الخطوة 3.1: إنشاء node الـTeleop
أنشئ الملف:
cd ~/Desktop/ROS-2-Practical-Course-Roadmap-2025/ros_ws/warehouse_simulation/warehouse_simulation
nano teleop_key.pyأضف كود node الـteleop الكامل:
#!/usr/bin/env python3
"""
Teleop Key Node for Warehouse Robot
Control the robot using keyboard keys:
w/x : increase/decrease linear velocity
a/d : increase/decrease angular velocity
s : stop
SPACE : emergency stop
q : quit
"""
import sys
import tty
import termios
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist
class TeleopKeyNode(Node):
def __init__(self):
super().__init__('teleop_key')
# Publisher for cmd_vel
self.publisher = self.create_publisher(Twist, '/cmd_vel', 10)
# Velocity settings
self.linear_velocity = 0.0
self.angular_velocity = 0.0
self.linear_step = 0.1 # m/s
self.angular_step = 0.2 # rad/s
self.max_linear = 1.0
self.max_angular = 2.0
self.get_logger().info('Teleop Key Node Started!')
self.get_logger().info('Use w/x for linear, a/d for angular, s to stop, SPACE for emergency stop, q to quit')
def get_key(self):
"""Get a single keypress from the terminal"""
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(fd)
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
def update_velocity(self, linear_change, angular_change):
"""Update velocity with limits"""
self.linear_velocity += linear_change
self.angular_velocity += angular_change
# Apply limits
self.linear_velocity = max(-self.max_linear, min(self.max_linear, self.linear_velocity))
self.angular_velocity = max(-self.max_angular, min(self.max_angular, self.angular_velocity))
def publish_velocity(self):
"""Publish the current velocity"""
msg = Twist()
msg.linear.x = self.linear_velocity
msg.angular.z = self.angular_velocity
self.publisher.publish(msg)
self.get_logger().info(f'Linear: {self.linear_velocity:.2f} m/s, Angular: {self.angular_velocity:.2f} rad/s')
def run(self):
"""Main control loop"""
print("\n" + "="*60)
print("Warehouse Robot Teleop Control")
print("="*60)
print("Controls:")
print(" w : Increase forward speed")
print(" x : Increase backward speed")
print(" a : Turn left (increase)")
print(" d : Turn right (increase)")
print(" s : Stop all movement")
print(" SPACE : Emergency stop")
print(" q : Quit")
print("="*60 + "\n")
try:
while rclpy.ok():
key = self.get_key()
if key == 'w':
self.update_velocity(self.linear_step, 0)
self.publish_velocity()
elif key == 'x':
self.update_velocity(-self.linear_step, 0)
self.publish_velocity()
elif key == 'a':
self.update_velocity(0, self.angular_step)
self.publish_velocity()
elif key == 'd':
self.update_velocity(0, -self.angular_step)
self.publish_velocity()
elif key == 's' or key == ' ':
self.linear_velocity = 0.0
self.angular_velocity = 0.0
self.publish_velocity()
if key == ' ':
self.get_logger().warn('EMERGENCY STOP!')
elif key == 'q':
self.get_logger().info('Quitting...')
break
elif key == '\x03': # Ctrl+C
break
except Exception as e:
self.get_logger().error(f'Error: {e}')
finally:
# Stop the robot before exiting
self.linear_velocity = 0.0
self.angular_velocity = 0.0
self.publish_velocity()
self.get_logger().info('Teleop Key Node Stopped')
def main(args=None):
rclpy.init(args=args)
teleop_node = TeleopKeyNode()
try:
teleop_node.run()
except KeyboardInterrupt:
pass
finally:
teleop_node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()احفظ واخرج (Ctrl+O، Enter، Ctrl+X).
الخطوة 3.2: اجعل الملف قابلًا للتنفيذ
chmod +x ~/Desktop/ROS-2-Practical-Course-Roadmap-2025/ros_ws/warehouse_simulation/warehouse_simulation/teleop_key.pyالخطوة 3.3: تحديث setup.py
عدّل ملف setup.py:
cd ~/Desktop/ROS-2-Practical-Course-Roadmap-2025/ros_ws/warehouse_simulation
nano setup.pyحدّث قسم entry_points:
entry_points={
'console_scripts': [
'teleop_key = warehouse_simulation.teleop_key:main',
],
},احفظ واخرج.
الخطوة 3.4: بناء الحزمة
cd ~/Desktop/ROS-2-Practical-Course-Roadmap-2025/ros_ws
colcon build --packages-select warehouse_simulation
source install/setup.bashالمُخرَج المتوقَّع:
Starting >>> warehouse_simulation
Finished <<< warehouse_simulation [X.XXs]
Summary: 1 package finished [X.XXs]الجزء 4: اختبار node الـTeleop
الطرفية 1: تشغيل المحاكاة
cd ~/Desktop/ROS-2-Practical-Course-Roadmap-2025/ros_ws
source install/setup.bash
ros2 launch warehouse_simulation warehouse_simulation.launch.pyانتظر حتى:
- تُفتح نافذة Gazebo
- يظهر الروبوت في المستودع
- تظهر رسائل الـbridge الدالة على اتصال الـtopics
الطرفية 2: تشغيل node الـTeleop
cd ~/Desktop/ROS-2-Practical-Course-Roadmap-2025/ros_ws
source install/setup.bash
ros2 run warehouse_simulation teleop_keyيجب أن ترى:
============================================================
Warehouse Robot Teleop Control
============================================================
Controls:
w : Increase forward speed
x : Increase backward speed
a : Turn left (increase)
d : Turn right (increase)
s : Stop all movement
SPACE : Emergency stop
q : Quit
============================================================
[INFO] [teleop_key]: Teleop Key Node Started!
[INFO] [teleop_key]: Use w/x for linear, a/d for angular, s to stop, SPACE for emergency stop, q to quitتسلسل الاختبار
جرّب هذا التسلسل لاختبار جميع عناصر التحكم:
-
اضغط
w3 مرات:- يجب أن تزيد السرعة: 0.1، 0.2، 0.3 م/ث
- يجب أن يتحرك الروبوت للأمام في Gazebo
-
اضغط
aمرتين:- يجب أن تزيد السرعة الزاوية: 0.2، 0.4 راد/ث
- يجب أن يبدأ الروبوت بالانعطاف لليسار أثناء تحركه للأمام
-
اضغط
s:- يجب أن تنخفض كلتا السرعتين إلى صفر
- يجب أن يتوقف الروبوت
-
اضغط
xمرتين:- يجب أن تصبح السرعة الخطية: -0.1، -0.2 م/ث
- يجب أن يتحرك الروبوت للخلف
-
اضغط
d3 مرات:- يجب أن تصبح السرعة الزاوية: -0.2، -0.4، -0.6 راد/ث
- يجب أن ينعطف الروبوت لليمين أثناء تحركه للخلف
-
اضغط
SPACE:- يجب أن تظهر رسالة الإيقاف الطارئ
- يجب أن يتوقف الروبوت فورًا
-
اضغط
q:- يجب أن يخرج البرنامج بشكل نظيف
الجزء 5: فهم الكود
المفاهيم الأساسية
1. بنية node في ROS
class TeleopKeyNode(Node):
def __init__(self):
super().__init__('teleop_key')
self.publisher = self.create_publisher(Twist, '/cmd_vel', 10)- ترث من الصنف (class)
Node - تُنشئ publisher لرسائل Twist على topic باسم
/cmd_vel
2. التعامل مع إدخال الطرفية (Terminal Input)
def get_key(self):
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(fd)
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch- تضبط الطرفية على الوضع الخام (raw mode) لقراءة ضغطات مفاتيح فردية
- تُعيد إعدادات الطرفية بعد القراءة
3. التحكم التراكمي في السرعة (Incremental Velocity Control)
def update_velocity(self, linear_change, angular_change):
self.linear_velocity += linear_change
self.angular_velocity += angular_change
# Apply limits
self.linear_velocity = max(-self.max_linear, min(self.max_linear, self.linear_velocity))- تُضيف التغيير إلى السرعة الحالية (تراكمي)
- تُقيّد القيمة ضمن الحد الأقصى/الأدنى
4. نشر رسائل Twist
def publish_velocity(self):
msg = Twist()
msg.linear.x = self.linear_velocity
msg.angular.z = self.angular_velocity
self.publisher.publish(msg)- تُنشئ رسالة Twist
- تضبط linear.x (الأمام/الخلف) وangular.z (الدوران)
- تُنشر إلى
/cmd_vel
الجزء 6: المراقبة واستكشاف الأخطاء
مراقبة الرسائل المنشورة
افتح طرفية جديدة:
source ~/Desktop/ROS-2-Practical-Course-Roadmap-2025/ros_ws/install/setup.bash
ros2 topic echo /cmd_velأثناء ضغطك على المفاتيح، سترى:
linear:
x: 0.3
y: 0.0
z: 0.0
angular:
x: 0.0
y: 0.0
z: 0.2
---التحقق من معلومات الـNode
ros2 node list
# Should show: /teleop_key
ros2 node info /teleop_key
# Shows publishers, subscribers, etc.تصور (Visualize) النظام
ros2 run rqt_graph rqt_graphيجب أن ترى:
/teleop_key --> /cmd_vel --> /parameter_bridge --> Gazeboالجزء 7: تحسينات وتمارين
تمرين 1: إضافة إعدادات سرعة مسبقة (Speed Presets)
عدّل الكود لإضافة مفاتيح أرقام (1-5) لسرعات مسبقة الضبط:
# In the run() method, add:
elif key == '1':
self.linear_step = 0.05
self.get_logger().info('Speed: SLOW')
elif key == '2':
self.linear_step = 0.1
self.get_logger().info('Speed: NORMAL')
elif key == '3':
self.linear_step = 0.2
self.get_logger().info('Speed: FAST')تمرين 2: إضافة عرض للموضع (Position Display)
اطبع الموضع الحالي من الـodometry:
# Add subscriber in __init__:
self.odom_sub = self.create_subscription(
Odometry, '/odom', self.odom_callback, 10
)
# Add callback:
def odom_callback(self, msg):
self.position = msg.pose.pose.positionتمرين 3: تحذير من التصادم (Collision Warning)
أضف تحذيرًا عند اكتشاف عوائق:
# Add subscriber for laser scan:
self.scan_sub = self.create_subscription(
LaserScan, '/scan', self.scan_callback, 10
)
def scan_callback(self, msg):
min_dist = min(msg.ranges)
if min_dist < 0.5: # 50cm
self.get_logger().warn(f'Obstacle detected at {min_dist:.2f}m!')الجزء 8: خيارات بديلة للـTeleop
الخيار 1: Teleop القياسي في ROS (اضغط باستمرار للتحرك)
sudo apt install ros-jazzy-teleop-twist-keyboard
ros2 run teleop_twist_keyboard teleop_twist_keyboardالفروقات:
- يجب الضغط باستمرار على المفتاح للحفاظ على السرعة
- تخطيط مفاتيح مختلف (i/j/k/l للحركة)
- بدون تحكم تراكمي
الخيار 2: التحكم عبر يد تحكم (Gamepad)
sudo apt install ros-jazzy-joy ros-jazzy-teleop-twist-joy
# Terminal 1
ros2 run joy joy_node
# Terminal 2
ros2 run teleop_twist_joy teleop_nodeيتطلب: يد تحكم/عصا تحكم USB
الجزء 9: استكشاف الأخطاء وإصلاحها
المشكلة 1: خطأ "Inappropriate ioctl for device"
السبب: التشغيل في طرفية غير تفاعلية (IDE/سكريبت)
الحل: شغّل teleop_key في نافذة طرفية حقيقية
المشكلة 2: الروبوت لا يتحرك
تحقق من:
# Is simulation running?
ros2 topic list | grep cmd_vel
# Is teleop publishing?
ros2 topic echo /cmd_vel
# Are there multiple publishers?
ros2 topic info /cmd_velالحل: تأكد من وجود publisher واحد فقط لـ/cmd_vel
المشكلة 3: الروبوت يتحرك من تلقاء نفسه
تحقق من:
ros2 topic echo /cmd_velالحل: هناك عنصر آخر ينشر إلى /cmd_vel. أوقف الـnodes الأخرى.
المشكلة 4: المفاتيح لا تستجيب
تحقق من: أن نافذة الطرفية لديها التركيز (نشطة)
الحل: انقر على الطرفية التي تُشغّل teleop_key قبل الضغط على المفاتيح
متطلبات التسليم
ما الذي يجب تسليمه
-
حزمة الكود:
cd ~/Desktop/ROS-2-Practical-Course-Roadmap-2025/ros_ws tar -czf lab04_teleop.tar.gz warehouse_simulation/ -
عرض توضيحي بالفيديو (2-3 دقائق):
- أظهِر واجهة التحكم بالـteleop
- وضّح جميع أوامر الحركة (w، x، a، d، s)
- أظهِر الروبوت يتحرك في Gazebo استجابةً للمفاتيح
- أظهِر التغذية الراجعة للسرعة في الطرفية
- وضّح الإيقاف الطارئ (SPACE)
-
تقرير المعمل (بصيغة PDF):
- مقدمة
- اشرح كيفية عمل node الـteleop
- لقطات شاشة تُظهر:
- تشغيل الـteleop مع رسائل التحكم
- الروبوت يتحرك في Gazebo
- مُخرَج topic echo
- مخطط الـnode (rqt_graph)
- أجِب عن أسئلة النقاش
- صف تحسينًا واحدًا نفّذته (اختياري)
- الخاتمة
أسئلة للنقاش
أجِب عن هذه الأسئلة في تقريرك:
-
اشرح الفرق بين التحكم التراكمي في السرعة (teleop الخاص بنا) والتحكم المباشر في السرعة (teleop القياسي). ما مزايا كلٍّ منهما؟
-
لماذا نحتاج إلى تقييد (clamp) السرعات ضمن حد أقصى/أدنى؟ ماذا قد يحدث لو لم نفعل ذلك؟
-
دالة
get_key()تستخدمtty.setraw()ثم تُعيد الإعدادات. لماذا هذا ضروري؟ -
في رسالة Twist، نضبط
linear.xوangular.z. لماذا لا نستخدم linear.y أو angular.x/y لروبوت أرضي؟ -
كيف يمكنك تعديل node الـteleop ليجعل الروبوت يتبع مسارًا محددًا مسبقًا (مثل مربع) تلقائيًا؟
-
ماذا يحدث إذا تعطّل node الـteleop أثناء تحرك الروبوت؟ كيف يمكنك تنفيذ ميزة أمان للتعامل مع ذلك؟
معايير التقييم
| العنصر | الدرجة | المعايير |
|---|---|---|
| تنفيذ الـTeleop | 30 | node تعمل بشكل صحيح، جميع المفاتيح فعّالة |
| جودة الكود | 20 | نظيف، موثَّق بتعليقات جيدة، يتبع أفضل الممارسات |
| الاختبار | 20 | اختبار شامل موثَّق |
| العرض بالفيديو | 15 | واضح، يُظهر جميع الميزات |
| تقرير المعمل | 15 | مكتوب بعناية، يجيب عن جميع الأسئلة |
| الإجمالي | 100 |
تحديات إضافية (+10 درجات لكل تحدٍّ، بحد أقصى +30)
-
تحكم متعدد السرعات: نفّذ 5 مستويات سرعة (المفاتيح 1-5) مع تغذية راجعة بصرية
-
تسارع سلس (Smooth Ramping): أضِف تسارعًا/تباطؤًا تدريجيًا بدلًا من تغييرات السرعة الفورية
-
تسجيل المسار: سجّل مسار الروبوت واحفظه في ملف لإعادة تشغيله لاحقًا
-
منطقة أمان: إيقاف تلقائي عند اكتشاف عائق ضمن مسافة 30 سم
-
لوحة حالة (Status Dashboard): اطبع لوحة حالة لحظية تُظهر السرعة والموضع والعوائق القريبة
مرجع سريع
بنية الملفات
warehouse_simulation/
├── warehouse_simulation/
│ ├── __init__.py
│ └── teleop_key.py ← Created in this lab
├── config/
│ └── bridge.yaml
├── launch/
│ └── warehouse_simulation.launch.py
├── package.xml
└── setup.py ← Updated in this labالأوامر الأساسية
# Build
colcon build --packages-select warehouse_simulation
# Launch simulation (Terminal 1)
ros2 launch warehouse_simulation warehouse_simulation.launch.py
# Run teleop (Terminal 2)
ros2 run warehouse_simulation teleop_key
# Monitor velocity (Terminal 3)
ros2 topic echo /cmd_vel
# Check nodes
ros2 node list
ros2 node info /teleop_key
# Visualize
ros2 run rqt_graph rqt_graphملخّص عناصر التحكم
w - Forward speed +0.1 m/s
x - Backward speed -0.1 m/s
a - Turn left +0.2 rad/s
d - Turn right -0.2 rad/s
s - Stop
SPACE - Emergency stop
q - Quitتحكّم عن بعد سعيد! استمتع بالتحكم في روبوت المستودع الخاص بك! 🎮🤖
تم إعداد محتوى هذا المعمل لكلية الحاسبات والمعلومات، جامعة المنصورة - خريف 2025 تم تحديثه ليتوافق مع Gazebo Jetty وROS 2 Jazzy