- الجزء الأول: مقدمة
- الجزء الثالث:المواضيع Topics
في الجزء الأول من سلسلة “برمجة الروبوتات باستخدام نظام تشغيل الروبوت ROS” تعرفنا على نظام التشغيل الخاص بالروبوتات ROS وفوائد استخدامه والبنية العامة للنظام وختاماً الخطوات الأولى للتنصيب والبدء باستخدام النظام. سنتعرّف في الجزء الثاني على أحد أهم المبادئ الأساسيّة وهي الـحزم packages في ROS: كيفية إنشائها وما هي أهم مكوناتها، وسنتطرّق أيضاً إلى مبدأ ثاني مهم وهو ملف الـlaunch: ما هي أهميته وبنيته. وفي الجزء الثاني حان الوقت لكي نبني معاً أول كود خاص بنا في نظام الـROS.
ماهي الحزم packages؟
توضع كل ملفات الـROS التابعة لبرنامج/تطبيق محدد في مكان واحد يسمى حزمة package . حيث أن الحزم تُساعِد على تنظيم الملفات وتسهيل المشاركة لاحقاً بين المطورين. تتضمن الحزم الملفات التنفيذية، وملفات الدعم ،وملفات الإعدادات. أهم المكونات للحزمة:
- مجلد الـlaunch والذي بدوره يحوي على ملف الـlaunch.
- مجلد الـ src والذي يحوي الأكواد المصدريّة source code المُراد تنفيذها والمكتوبة بلغة الـC++ أو بلغة الـpython أو حتى بلغات أخرى مدعومة بشكل تجريبي.
- ملف CMakelist.txt الذي يتضمّن قواعد عن كيفية بناء الكود وأين سيتم تنزيله.
- ملف Package.xml ويحوي معلومات عن الحزمة package والملفات التي تعتمد عليها.
عندما نريد كتابة أي برنامج خاص بنا علينا في البداية إنشاء catkin workspace ثم إنشاء الـpackage داخلها. catkin workspace هو عبارة عن مجلد يتم وضع الحزم الخاصة به لبنائها لاحقاً كما سنتعرف في هذا المقال.
كيف نُنشِئ package و workspace خاص بنا؟
دائماً عند إنشاء package يجب أن نكون ضمن مكان محدد وهو الـcatkin workspace ومن الممكن وضعه في أي مسار تحت أي اسم.
لإنشاء workspace: أولاً ننشئ مسار للمجلد الذي أطلقنا عليه catkin_ws وداخله الـ src:
mkdir –p ~/catkin_ws/src
بعد كتابة التعليمة نلاحظ إنشاء مجلد باسم catkin_ws في الـ home.
ومن ثم ننتقل إلى داخل مجلد الـ catkin_ws
cd ~/catkin_ws
لنُنفّذ التعليمة التالية التي تقوم بإنشاء ملف CMakeLists.txt الخاص بملف الـsrc.
catkin_make
لإنشاء الـpackage نتوجّه إلى الـcatkin workspace الذي قمنا بإنشائه في الخطوة السابقة وبالذات إلى المجلدsrc بواسطة التعليمة:
roscd /catkin_ws
حيث تقوم التعليمة roscd بتغيير المسار(التنقّل بين ملفات نظام الـROS) وهي تماثل تعليمة cd (change directory) في نظام الـLinux .
نكتب التعليمة التالية لعرض الملفات التي يحويها هذا المجلد عبر تعليمة ls ويمكننا أيضاً رؤية المسار الذي انتقلنا عليه بواسطة التعليمة pwd لنرى أننا نتواجد في المسار /home/user_name/catkin_ws. الآن لننتقل للمجلد src:
roscd /src
ولننشئ حزمة package عبر التعليمة التالية:
catkin_create_pkg <package_name> [depend1] [depend2] [depend3]
حيث نستبدل [ package-name] بالاسم الذي نريده لتسمية الـ package الخاص بنا، على سبيل المثال نسميه my_package .
حيث depend1 وdepend2 وdepend3 هي تبعيات النظام ولكن فعلياً هذه الرسالة لاتفعل الكثير فقط تقوم بإنشاء مجلد (لنضع فيه الملفات الخاصة بمشروعنا) .
للتأكّد من أنّ الـ package قد تمّ إنشاؤه و أنه قد أُدرِج في لائحة الـ package الخاصة بالنظام، نُنفِّذ التعليمة التالية:
rospack list
ولكن نلاحظ الكثير من الـ package لذلك يمكننا فلترة النتائج لرؤية الـ package الذي قمنا بإنشائه :
rospack list |grep my_package
مثال: دعونا نُنشِئ package باسم simply_ros يقوم هذا البرنامج بطباعة رسالة “welcome to ros community” مع العلم أننا سنكتب هذا الكود بلغة الـC++ وهذا الكود تحت اسم my_first_code.cpp.
حتى نُنشِئ package نستخدم ما يلي:
cd catkin_ws/src catkin_create_pkg simply_ros roscpp
نتوجه الآن إلى مجلد الـsrc للـpackage الذي قمنا بإنشائه لنكتب الكود داخله:
الشكل(1):التعليمات الأولى للبدء بإنشاء package
ننشئ ملف باسم my_code داخل مجلد الـsrc في الpackage الذي قمنا بإنشائه ، نكتب بداخله الكود التالي:
#include <ros/ros.h> int main(int argc,char **argv) { ros::init(argc,argv,"welcome_msg"); ros::NodeHandle nh; ROS_INFO("welcome to ros community"); }
دعونا نفصل أجزاء الكود السابق:
#include <ros/ros.h>
لتضمين الـclasses الرئيسية الخاصة بـROS. يتوجب علينا إدراجها في أي برنامج ROS سيتم كتابته بلغة الـ C++.
int main(int argc,char **argv)
للبدء في البرنامج الرئيسي في لغة الـC++.
ros:: init
لتهيئة عقدة اسم هذه العقدة هو البارامتر الثالث وهنا اسم العقدة welcome_msg .
ros:: NodeHandle nh
هي الآلية التي يتواصل بها البرنامج مع النظام. وتقوم بإنشاء هذا البرنامج كعقدة في ROS. يمكن الرجوع إلى التوثيق الرسمي لآلية عمل الـNodeHandles في الموقع الرسمي.
ROS_INFO(“welcome to ros community”)
لإرسال رسالة المضمنة ما بين الهلالين.
بعد حفظ الكود السابق، يتوجب علينا إنشاء ملف launch داخل مجلد launch
ما هو ملف الـlaunch؟
هو عبارة عن الملف التنفيذي للأكواد التي نقوم بكتابتها ويتكوّن ملف الـ launch من اقتباسين(tags) مهمين؛ الأول هو المتعلق بالـlaunch والاقتباس الثاني هو متعلق بالعقدة وعلينا أن ننهي الاقتباس بنهاية الملف. لإنشاء package الـlaunch:
New Folder >>F2 >>launch
لكتابة ملف الـlaunch نتوجه إلى terminal (نافذة سطر الأوامر) ونكتب:
nano my_file.launch
بنية ملف الـ launch:
<launch> <node pkg="simply_ros" type="my_first_code " name="welcome_msg" output="screen"> </node> </launch>
Pkg: هو اسم الـpackage الذي يحوي على الملف الذي نقوم بتنفيذه.
Type: اسم ملف الكود الكتوب بلغة الـ C++.
Name: اسم العقدة التي قمنا بتعريفها داخل الكود والتي هي المسؤولة عن تنفيذ الكود التي كتبناه لتنفيذ مهمة معينة.
Output: المكان الذي نرغب بعرض النتائج عليه.
عندما نريد الكتابة بلغة الـC++ علينا تعديل ملف الـCMakeLists.txt للإشارة بأننا نرغب بإنشاء ملف تنفيذي للكود الذي قمنا بكتابته. ولفعل ذلك علينا إضافة بعض اﻷسطر إلى هذا الملف. فعلياً تكون هذه اﻷسطر موجودة ولكن معلّقة. فما علينا سوى إزالة التعليق عنهم، أو يمكننا إضافتهم بآخر الكود مباشرة.
add_executable( my_first_code src/my_first_code.cpp ) add_dependencies( my_first_code ${my_first_code_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) target_link_libraries(my_first_code ${catkin_LIBRARIES} )
ولكن ماذا تفعل هذه السطور التي قمنا بإضافتها؟
add_executable( my_first_code src/my_first_code.cpp )
يقوم هذا السطر بإضافة الملف التنفيذي للكود my_first_code.cpp المتواجد في ملف الـsrc. وسيذهب هذا الملف التنفيذي إلى مسار الـcatkin_ws/devel/lib.
add_dependencies(my_first_code ${my_first_code_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
يعطي هذا السطر للملف التنفيذي كل تبعيات النظام اللازمة.
target_link_libraries(my_first_code ${catkin_LIBRARIES} )
لتعيين المكاتب المستخدمة في الكود.
بعد ذلك يتوجب علينا إجراء compile للكود لإمكانية تنفيذه وذلك عن طريق التعليمة:
catkin_make
ما يظهر عند تنفيذ تعليمة catkin_make
لتنفيذ البرنامج السابق نكتب:
roslaunch simply_ros my_file.launch
خرج الكود الذي قمنا بكتابته لطباعة جملة
ولكن نلاحظ أن أن هذا الكود قام بطباعة الرسالة لمرة واحدة وتوقف في حال كنا نريد استمرار طباعة الجملة في هذه نضع التعليمة المسؤولة عن الكتابة ضمن حلقة while. فيؤول الكود للشكل:
#include <ros/ros.h> int main(int argc, char** argv) { ros::init(argc, argv, "welcome_msg"); ros::NodeHandle nh; ros::Rate loop_rate(2); while (ros::ok()) { ROS_INFO("welcome to ros community"); ros::spinOnce(); loop_rate.sleep(); } return 0; }
لتعرف على الأجزاء الجديدة في الكود:
ros::Rate loop_rate(2)
معدل إرسال الرسائل وهنا يساوي إلى 2HZ أي نصف ثانية، بمعنى أوضح أن الرسائل ستطبع كل نصف ثانية.
ros::ok()
بشرط أنه ما دام ليس هناك أي مقاطعة للحلقة while سيستمر البرنامج بطباعة الجملة.
loop_rate.sleep()
لدخول البرنامج في حالة سبات بعد إرسال الرسالة إلى أن يحين موعد الإرسال القادم.
ros::spinOnce
يقوم بالتحقق فيما إذا كان هناك استدعاء للخدمة callbacks/service calls وتكمن فائدته في قدرته على التحكم في معدل التحديثات في الـROS. تتبع الـspinOnce لمفهوم أكثر تعقيداً يستخدمه الـROS وللاستزادة يمكنك مراجعة السؤال المطروح في منتديات ROS.
عند تنفيذ التعليمة نرى الخرج:
أخيراً، نلاحظ أننا قمنا في هذه الجزء بالتعرف بشكل جيد على الـpackages وقمنا بكتابة كود يقوم بطباعة رسالة مروراً بأفكار أخرى مثل كيفيّة كتابة ملفات الـlaunch وكيفية كتابة ملف بلغة الـC++ داخل نظام ROS. يمكنك أيضاً الاطلاع على كيفية بناء يد روبوتية من الصفر باستخدام ROS.
المراجع
- http://wiki.ros.org/catkin/Tutorials/create_a_workspace
- http://www.theconstructsim.com/
الجزء الثالث:المواضيع Topics