02 ตุลาคม 2561

สร้าง Packages ของ ROS และ Hello World



REP (ROS Enhancement Proposals) ของ ROS

REP คือ กฏเกณฑ์ให้คนที่ใช้ ROS ทำตาม  เพื่อจะได้มีมาตรฐานตรงกัน

http://www.ros.org/reps/rep-0000.html


โดยการสร้าง Package ใน ROS จะมี Layout องค์ประกอบดังนี้

อ้างอิง  http://www.ros.org/reps/rep-0128.html#recommended-layout




1. Package Manifest — ตัวที่จะบอกว่า Package นี้ มีรายละเอียดยังไง ต้องการไปพึ่งพิง Code คนอื่นไหม (ตัวช่วย Compile Code , Reference Code ใน Level สูงๆ จะอ่านตรงนี้ และไปอ้างอิงให้)

2. CMakeLists.txt — เป็น พิมพ์เขียวของงานนี้ โดยจะเก็บรายละเอียดว่า เรา Run Code ไฟล์ไหนเป็นจุดเริ่มต้นของงาน, Code ต่างๆอ้างอิงกันยังไง , เราอ้างอิง Code Library ของชาวบ้านคนอื่นๆมาใช้กับ Code เราไฟล์ไหนบ้าง

* CMakeLists (MakeFile) ตัวนี้สำคัญ *

3. Codeของเรา จะเก็บใน Folder ดังนี้

(อ้างอิงตาม REP ซึ่งก็คือข้อควรปฏิบัติตามของ ROS ซึ่งควรจะทำตาม )

3.1. “src” — จะเก็บ Source Code ภาษา C++ ที่เป็นส่วน Implementation

(ส่วนของ Header จะเก็บแยก)

3.2. “include/ชื่อPackage/”- จะเก็บ Header ภาษา C++

3.3. “scripts” — จะเก็บ Code Python

3.4. Config ต่างๆ อันนี้ไม่ซีเรียสมาก แต่ปกติจะสร้าง Folder ชื่อ param , config บ้าง แล้วแต่คนชอบ


4. ตัวสร้าง Message , Service , Action ของ ROS หรือที่เรียก Message Structure , Message Prototype

4.1. ROS Message — จะอยู่ใน Folder “msg”

4.2. ROS Service — จะอยู่ใน Folder “srv”

4.3. ROS Action — จะอยู่ใน Folder “action”

โดย Catkin build system จะให้ ROS มาคว้า “หน้าตา” ของ Message Service และ Action จากบริเวณนี้ Auto เลย



สังเกตว่า ภาษา C++ จะมี ส่วน Implementation file , Header File เลยต้องมีที่เก็บแยกกัน


Python ไม่มี header เหมือนกับ C++ เลยใช้แค่ Folder ในการเก็บ Code แค่บริเวณเดียว


ถึงตอนนี้ พอสรุปได้ดังนี้

1 Workspace สามารถจะมีหลายๆ Packages
ใน 1 Package สามารถจะมีได้หลายๆ Node
และ ใน 1 Node มักจะทำหน้าที่ขั้นต่ำ 1 หน้าที่ ขึ้นไป

โครงสร้าง ก็หน้าตาประมาณนี้



การสร้าง Package ด้วยภาษา C++

เริ่มแรก เรามาออกแบบกันก่อน เรากำลังจะทำ Node 2 อัน ที่ทำหน้าที่พูดคนนึง และ รับฟังคนนึง

Package ชื่อ : chatter_subscriber_tutorial

โดยใช้ Dependencies ดังนี้:

1. catkin (build system)
2. roscpp (ROS ภาษา C++)
3. std_msgs (Package ข้อความพื้นฐานใน ROS)

อยากจะสร้าง Node ที่ทำหน้าที่เป็น
Publisher 1 Node ทำหน้าที่ส่งข้อความ
Subscriber 1 Node ทำหน้าที่รับข้อความแล้วหาคำตอบ — แล้วแสดง



สร้าง Workspace จากบทความก่อนหน้านี้ สร้าง Workspace ของ ROS


จะมี catkin_ws เป็น Workspace ของเราที่สร้างมาแล้วนั่นเอง


1. ใช้ CLI ของ Catkin ในการสร้าง Package ของ ROS ขึ้นมา ซึ่ง Package จะต้องอยู่ใน src ของ Workspace

catkin_create_pkg ชื่อ วรรค ตามด้วย dependency หลายๆตัว ที่เว้นวรรค 1 ที

cd catkin_ws/src
catkin_create_pkg chatter_subscriber_tutorial catkin roscpp std_msgs



เราจะได้โฟลเดอร์ที่มีชื่อว่า chatter_subscriber_tutorial ขึ้นมา พอเป็นเข้าไปด้านใน จะพบว่ามี โฟลเดอร์ และไฟล์ มารอดังนี้



วางโครงสร้างของ Code C++

เราจะต้องอธิบาย Package เราก่อนว่า ประกอบด้วยอะไร และไปใช้งานใครบ้าง


สองไฟล์นี้คือตัวเริ่มก่อโครงสร้างของ Package เรา ว่าจะมี Node อะไรบ้าง และ Node นั้นๆ ใช้ Library อะไรบ้าง มี header ไฟล์อะไรบ้าง

ติดตั้งโปรแกรม Geany IDE ไว้สำหรับแก้ไขไฟล์ต่างๆของเรา

sudo add-apt-repository ppa:geany-dev/ppa
sudo apt-get update
sudo apt-get install geany geany-plugins

เปิดโปรแกรม Geany IDE ด้วยคำสั่ง geany

geany


เปิดไฟล์ CMakeLists.txt ขึ้นมาแก้ไข 




1. add_executable จะเป็นการบอกว่า มีอะไรที่ run ได้บ้าง และ ตั้งชื่อมันว่าอะไร ด้วยการใช้คำสั่งดังนี้

add_executable(ตั้งชื่อexecutable วรรค ตามด้วย ไฟล์ Node ที่มี Main)
add_executable(ตั้งชื่อexecutable วรรค ตามด้วย ไฟล์ Node ที่ไม่มี Main)

add_executable(my_publisher src/my_publisher.cpp)
add_executable(my_subscriber src/my_subscriber.cpp)


2. add_dependencies จะเป็นการบอกว่า Executable นั้นๆ ไปใช้ Dependency ที่วางรอตรงไหนบ้าง

(ซึ่งปกติมักจะไปถามเอาจาก catkin ด้วย การพิมพ์ catkin_EXPORTED_TARGETS)

add_dependencies(ชื่อexecutable export-targetต่างๆ)

add_dependencies(my_publisher ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
add_dependencies(my_subscriber ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})


3. target_link_libraries

target_link_libraries(ชื่อexecutable วรรคตามด้วย Libraries ต่างๆ)

target_link_libraries(my_publisher ${catkin_LIBRARIES})
target_link_libraries(my_subscriber ${catkin_LIBRARIES})


แล้วคลิก Save
...................

ไฟล์เปล่าๆ แบบไม่มี Comment จาก Catkin

cmake_minimum_required(VERSION 2.8.3)
project(chatter_subscriber_tutorial)

find_package(catkin REQUIRED COMPONENTS
  roscpp
  std_msgs
)

catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES chatter_subscriber_tutorial
#  CATKIN_DEPENDS roscpp std_msgs
#  DEPENDS system_lib
)

include_directories(
# include
  ${catkin_INCLUDE_DIRS}
)

add_executable(my_publisher src/my_publisher.cpp)
add_executable(my_subscriber src/my_subscriber.cpp)

add_dependencies(my_publisher ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
add_dependencies(my_subscriber ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

target_link_libraries(my_publisher
  ${catkin_LIBRARIES}
)
target_link_libraries(my_subscriber
  ${catkin_LIBRARIES}
)


เขียนโค้ดภาษา C++

เปิดโปรแกรม Geany IDE คลิกสร้างไฟล์ใหม่

Save เก็บไฟล์ไว้ที่ โฟลเดอร์ chatter_subscriber_tutorial/src



1. สร้าง Node ตัวส่งข้อความ “Publisher”

ไฟล์ : my_publisher.cpp

#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>

int main(int argc, char **argv)
{
  ros::init(argc, argv, "talker");
  ros::NodeHandle n;
  ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);

  ros::Rate loop_rate(10);
  int count = 0;
  while (ros::ok())
  {
    std_msgs::String msg;
    std::stringstream ss;
    ss << "hello world " << count;
    msg.data = ss.str();
    ROS_INFO("%s", msg.data.c_str());
    chatter_pub.publish(msg);
    ros::spinOnce();
    loop_rate.sleep();
    ++count;
  }
  return 0;
}


2. สร้าง Node ตัวรับข้อความ “Subscriber”

ไฟล์ : my_subscriber.cpp

#include "ros/ros.h"
#include "std_msgs/String.h"

void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
  ROS_INFO("I heard: [%s]", msg->data.c_str());
}

int main(int argc, char **argv)
{
  ros::init(argc, argv, "listener");
  ros::NodeHandle n;
  ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
  ros::spin();
  return 0;
}



หลังจากเขียนโค้ดเสร็จแล้ว ก็ Build

cd catkin_ws
catkin_make



(หน้าต่างที่ 1) เราจะ RUN ระบบ ROS จะต้องมี Core ที่ทำหน้าที่เป็นหัวใจของระบบ

roscore



หลังจากนั้นจึง Run Node ของเราได้

rosrun ชื่อpackage ชื่อexecutableที่กำหนดในCMakeLists.txt

(หน้าต่างที่ 2) เปิด Publisher (ตัวส่งข้อมูล)

rosrun chatter_subscriber_tutorial my_publisher



(หน้าต่างที่ 3)  ตามมาด้วยการเปิด Subscriber (ตัวฟัง/รับข้อมูล)

rosrun chatter_subscriber_tutorial my_subscriber



กด Ctrl + C ถ้าต้องการหยุดการทำงาน


หมายเหตุ : เรียบเรียงและแก้ไขดัดแปลงจากบทความต้นฉบับด้านล่าง