1. 程式人生 > >ROS系統學習3---ROS最小話題系統的製作

ROS系統學習3---ROS最小話題系統的製作

上一篇部落格中,我們介紹了怎麼建立一個ROS的最小系統,該系統包括工作空間、包和一個存放在包中的節點,然後我們讓該節點列印“Hello ROS”。

在這一篇部落格中,我們將進一步來搭建一個最小的話題系統。

在開始之前,我們來簡單的介紹下ROS的"話題"。

回想我們以前程式設計,一般是用什麼進行通訊的?

是網路協議,比如我們要在兩個程式中傳輸資料,一般的做法是建立一個TCP或者UDP連線,然後從A->B扔資料。

而ROS系統本質上是一個管理多程序工作的系統,它裡面的一個節點就是一個程序,節點之間又不可避免的要進行資料互動,因此通訊是免不了的。我們可以非常容易的想到直接用TCP或者UDP來通訊就可以了,但是ROS給我們一個更加上層的方法,那就是"話題"(雖然我們只需要花0.1s就能想到它的本質也是TCP或UDP,但人家幫我們封裝好了,當然是更加方便的)。

那麼"話題"是怎麼實現通訊的呢?

它的操作非常簡單,節點A如果有資料要往外扔,那麼它就直接通過"話題"告訴ROS系統它要把資料釋出出來。節點B如果想要接收節點A的資料,則告訴ROS系統它要訂閱這個話題。

這樣,當節點A釋出了話題而節點B訂閱了該話題,則ROS系統會幫他們之間建立通訊。

說白了,ROS相當於一個媒婆,將節點A和節點B連到一塊去。

另外需要注意的是釋出話題的時候需要先宣告話題所具有的資料型別,這就相當於媒婆起碼要知道你是男是女。

下面我們就做出兩個節點,一個用話題釋出資料,另外一個訂閱話題接收資料。它們對應的是兩個cpp,分別叫做"topicSend.cpp"和"topicReceive.cpp"。

如果你想新建一個工作空間或者包的話,請移步上一篇文章。本篇文章將直接沿用上一篇文章建好的工作空間和包。也就是在“/home/weixin/HelloRos/src/printHelloRosPK/src”這個路徑下再加入兩個cpp檔案,內容和註釋如下:

topicSend.cpp

#include "ros/ros.h"
#include "std_msgs/String.h"  //訊息傳送相關標頭檔案  
#include <sstream>

int main(int argc, char **argv)
{
  ros::init(argc, argv, "topicSend");
  //前面兩個引數跟系統的重對映有關,在這裡不考慮。第三個引數為節點名稱,我們定義為topicSend
  ros::NodeHandle n;
  //節點控制代碼,第一個建立的節點控制代碼會為節點初始化。最後一個銷燬的節點控制代碼則會釋放該節點所佔用的資源
  ros::Publisher hello_pub = n.advertise<std_msgs::String>("hello", 1000);
  //advertise()會告知ROS我們要釋出一個話題,話題名是第一個引數的“hello”。這樣ROS的管理程序就會告訴所
  //有訂閱了"hello"話題的節點,將要有資料釋出。第二個引數是釋出序列的大小。如果我們釋出的訊息的頻率太高,
  //緩衝區中的訊息在大於1000個的時候就會開始丟棄先前釋出的訊息。
  //advertise()返回一個ros::Publisher物件,它有兩個作用:
  //1)它有一個publish()成員函式可以讓你在topic上釋出訊息;
  //2)如果訊息型別不對,它會拒絕釋出。
  ros::Rate loop_rate(10);
  //ros::Rate物件可以允許你指定自迴圈的頻率。它會追蹤記錄自上一次呼叫Rate::sleep()後時間的流逝,並休眠直到過了一個頻率週期的時間。
  //上面程式的寫法就是讓程式以10Hz的頻率釋出訊息
  int count = 0;//訊息傳送計數器
  while (ros::ok())
  {
	//如果下列條件之一發生,ros::ok() 返回false:
	/*
	1.SIGINT 被觸發 (Ctrl-C)
	2.被另一同名節點踢出 ROS 網路
	3.ros::shutdown() 被程式的另一部分呼叫
	4.節點中的所有 ros::NodeHandles 都已經被銷燬
	*/
    std_msgs::String msg;
    //標準的String訊息,它只有一個數據成員 "data"。當然,你也可以釋出更復雜的訊息型別。
    std::stringstream ss;
    ss << "hello world " << count;
    msg.data = ss.str();
    ROS_INFO("%s", msg.data.c_str());//將要釋出的訊息打印出來看看
    hello_pub.publish(msg);//向所有訂閱 chatter 話題的節點發送訊息
    ros::spinOnce();
	//在這個例子中並不是一定要呼叫 ros::spinOnce(),因為我們不接受回撥。然而,如果你的程式裡包含其他回撥函式,最好在這裡加上
	//ros::spinOnce()這一語句,否則你的回撥函式就永遠也不會被呼叫了。
    loop_rate.sleep();
    //呼叫ros::Rate物件來休眠一段時間以使得釋出頻率為10Hz
    ++count;
  }
  return 0;
}

topicReceive.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("hello", 1000, chatterCallback);
  //告訴ROS我們要訂閱話題“hello”上的訊息。當有訊息釋出到這個話題時,ROS就會呼叫chatterCallback()函式。
  //第二個引數是佇列大小,以防我們處理訊息的速度不夠快,當快取達到1000條訊息後,再有新的訊息到來就將開始丟棄先前接收的訊息。
  ros::spin();
  //ros::spin()進入自迴圈,可以儘可能快的呼叫訊息回撥函式。如果沒有訊息到達,它不會佔用很多CPU,所以不用擔心。
  //一旦ros::ok()返回false,ros::spin()就會立刻跳出自迴圈。這有可能是ros::shutdown()被呼叫,或者是使用者按下了Ctrl-C,
  //使得 master 告訴節點要終止執行。也有可能是節點被人為關閉的。
  return 0;
}

最後我們把下面幾句話新增到CMakeLists.txt末尾,告訴編譯器我們要編譯什麼,和它們需要什麼依賴關係:

add_executable(topicSend /home/weixin/HelloRos/src/printHelloRosPK/src/topicSend.cpp)#定義了這個工程會生成一個檔名為"topicSend"的可執行檔案
target_link_libraries(topicSend ${catkin_LIBRARIES})#指定在連結目標檔案的時候需要連結的外部庫
add_dependencies(topicSend beginner_tutorials_generate_messages_cpp)#為可執行檔案新增對生成的訊息檔案的依賴

add_executable(topicReceive /home/weixin/HelloRos/src/printHelloRosPK/src/topicReceive.cpp)
target_link_libraries(topicReceive ${catkin_LIBRARIES})
add_dependencies(topicReceive beginner_tutorials_generate_messages_cpp)

接著編譯下就可以了,回到工作空間中:

catkin_make

開啟兩個節點,就能看到兩個節點直接一個在傳送資料,另外一個在接收資料,因為比較簡單,就省略了。