ROS機器人操作系統在機器人應用領域很流行,依託代碼開源和模塊間協作等特性,給機器人開發者帶來了很大的方便。我們的機器人「miiboo」中的大部分程序也採用ROS進行開發,所以本文就重點對ROS基礎知識進行詳細的講解,給不熟悉ROS的朋友起到一個拋磚引玉的作用。本章節主要內容:

1.ROS是什麼

2.ROS系統整體架構

3.在ubuntu16.04中安裝ROS kinetic

4.如何編寫ROS的第一個程序hello_world

5.編寫簡單的消息發布器和訂閱器

6.編寫簡單的service和client

7.理解tf的原理

8.理解roslaunch在大型項目中的作用

9.熟練使用rviz

10.在實際機器人上運行ROS高級功能預覽


5.編寫簡單的消息發布器和訂閱器

通過上一節編寫ROS的第一個程序hello_world,我們對ROS的整個編程開發過程有了基本的瞭解,現在我們就來編寫真正意義上的使用ROS進行節點間通信的程序。由於之前已經建好了catkin_ws這樣一個工作空間,以後開發的功能包都將放在這裡面,這裡給新建的功能包取名為topic_example,在這個功能包中分別編寫兩個節點程序publish_node.cpp和subscribe_node.cpp,發布節點(publish_node)向話題(chatter)發布std_msgs::String類型的消息,訂閱節點(subscribe_node)從話題(chatter)訂閱std_msgs::String類型的消息,這裡消息傳遞的具體內容是一句問候語「how are you ...」,通信網路結構如圖16。

(圖16)消息發布與訂閱ROS通信網路結構圖

(1)功能包的創建

在catkin_ws/src/目錄下新建功能包topic_example,並在創建時顯式的指明依賴roscpp和std_msgs。打開命令行終端,輸入命令:

cd ~/catkin_ws/src/

#創建功能包topic_example時,顯式的指明依賴roscpp和std_msgs,
#依賴會被默認寫到功能包的CMakeLists.txt和package.xml中
catkin_create_pkg topic_example roscpp std_msgs

(2)功能包的源代碼編寫

功能包中需要編寫兩個獨立可執行的節點,一個節點用來發布消息,另一個節點用來訂閱消息,所以需要在新建的功能包topic_example/src/目錄下新建兩個文件publish_node.cpp和subscribe_node.cpp,並將下面的代碼分別填入。

首先,介紹發布節點publish_node.cpp,代碼內容如下:

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

#include <sstream>

int main(int argc, char **argv)
{
ros::init(argc, argv, "publish_node");
ros::NodeHandle nh;

ros::Publisher chatter_pub = nh.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 << "how are you " << count;
msg.data = ss.str();
ROS_INFO("%s", msg.data.c_str());

chatter_pub.publish(msg);

ros::spinOnce();
loop_rate.sleep();
++count;
}

return 0;
}

對發布節點代碼進行解析。

#include "ros/ros.h"

這一句是包含頭文件ros/ros.h,這是ROS提供的C++客戶端庫,是必須包含的頭文件。

#include "std_msgs/String.h"

由於代碼中需要使用ROS提供的標準消息類型std_msgs::String,所以需要包含此頭文件。

ros::init(argc, argv, "publish_node");

這一句是初始化ros節點並指明節點的名稱,這裡給節點取名為publish_node,一旦程序運行後就可以在ros的計算圖中被註冊為publish_node名稱標識的節點。

ros::NodeHandle nh;

這一句是聲明一個ros節點的句柄,初始化ros節點必須的。

ros::Publisher chatter_pub = nh.advertise<std_msgs::String>("chatter", 1000);

這句話告訴ros節點管理器我們將會在chatter這個話題上發布std_msgs::String類型的消息。這裡的參數1000是表示發布序列的大小,如果消息發布的太快,緩衝區中的消息大於1000個的話就會開始丟棄先前發布的消息。

ros::Rate loop_rate(10);

這句話是用來指定自循環的頻率,這裡的參數10 表示10Hz頻率,需要配合該對象的sleep()方法來使用。

while (ros::ok()) {...}

roscpp會默認安裝以SIGINT句柄,這句話就是用來處理由ctrl+c鍵盤操作、該節點被另一同名節點踢出ROS網路、ros::shutdown()被程序在某個地方調用、所有ros::NodeHandle句柄都被銷毀等觸發而使ros::ok()返回false值的情況。

std_msgs::String msg;

定義了一個std_msgs::String消息類型的對象,該對象有一個數據成員data用於存放我們即將發布的數據。要發布出去的數據將被填充到這個對象的data成員中。

chatter_pub.publish(msg);

利用定義好的發布器對象將消息數據發布出去,這一句執行後,ROS網路中的其他節點便可以收到此消息中的數據。

ros::spinOnce();

這一句是讓回調函數有機會被執行的聲明,這個程序裡面並沒有回調函數,所以這一句可以不要,這裡只是為了程序的完整規範性才放上來的。

loop_rate.sleep();

前面講過,這一句是通過休眠來控制自循環的頻率的。

接著,介紹訂閱節點subscribe_node.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, "subscribe_node");
ros::NodeHandle nh;

ros::Subscriber chatter_sub = nh.subscribe("chatter", 1000,chatterCallback);

ros::spin();

return 0;
}

對訂閱節點代碼進行解析。

之前解釋過的類似的代碼就不做過多的解釋了,這裡重點解釋一下前面沒遇到過的代碼。

void chatterCallback(const std_msgs::String::ConstPtr& msg)

{

ROS_INFO("I heard: [%s]",msg->data.c_str());

}

這是一個回調函數,當有消息到達chatter話題時會自動被調用一次,這個回調函數裡面就是一句話,用來列印從話題中訂閱的消息數據。

ros::Subscriber chatter_sub = nh.subscribe("chatter", 1000,chatterCallback);

這句話告訴ros節點管理器我們將會從chatter這個話題中訂閱消息,當有消息到達時會自動調用這裡指定的chatterCallback回調函數。這裡的參數1000是表示訂閱序列的大小,如果消息處理的速度不夠快,緩衝區中的消息大於1000個的話就會開始丟棄先前接收的消息。

ros::spin();

這一句話讓程序進入自循環的掛起狀態,從而讓程序以最好的效率接收消息並調用回調函數。如果沒有消息到達,這句話不會佔用很多CPU資源,所以這句話可以放心使用。一旦ros::ok()被觸發而返回false,ros::spin()的掛起狀態將停止並自動跳出。簡單點說,程序執行到這一句,就在這裡不斷自循環,與此同時檢查是否有消息到達並決定是否調用回調函數。

(3)功能包的編譯配置及編譯

創建功能包topic_example時,顯式的指明依賴roscpp和std_msgs,依賴會被默認寫到功能包的CMakeLists.txt和package.xml中,所以只需要在CMakeLists.txt文件的末尾行加入以下幾句用於聲明可執行文件就可以了:

add_executable(publish_node src/publish_node.cpp)
target_link_libraries(publish_node ${catkin_LIBRARIES})

add_executable(subscribe_node src/subscribe_node.cpp)
target_link_libraries(subscribe_node ${catkin_LIBRARIES})

接下來,就可以用下面的命令對功能包進行編譯了:

cd ~/catkin_ws/
catkin_make -DCATKIN_WHITELIST_PACKAGES="topic_example"

(4)功能包的啟動運行

首先,需要用roscore命令來啟動ROS節點管理器,ROS節點管理器是所有節點運行的基礎。

打開命令行終端,輸入命令:

roscore

然後,用source devel/setup.bash激活catkin_ws工作空間,用rosrun <package_name> <node_name>啟動功能包中的發布節點。

再打開一個命令行終端,分別輸入命令:

cd ~/catkin_ws/
source devel/setup.bash
rosrun topic_example publish_node

看到有輸出「how are you ...」,就說明發布節點已經正常啟動並開始不斷向chatter話題發布消息數據,如圖17。

(圖17)發布節點已經正常啟動

最後,用source devel/setup.bash激活catkin_ws工作空間,用rosrun <package_name> <node_name>啟動功能包中的訂閱節點。

再打開一個命令行終端,分別輸入命令:

cd ~/catkin_ws/
source devel/setup.bash
rosrun topic_example subscribe_node

看到有輸出「I heard:[how are you ...]」,就說明訂閱節點已經正常啟動並開始不斷從chatter話題接收消息數據,如圖18。

(圖18)訂閱節點已經正常啟動

到這裡,我們編寫的消息發布器和訂閱器就大功告成了,為了加深對整個程序工作流程的理解,我再把消息發布與訂閱的ROS通信網路結構圖拿出來,加深一下理解。

(圖19)消息發布與訂閱ROS通信網路結構圖

推薦閱讀:

相關文章