節點是一個可執行程序,它連接到了ROS的網絡系統中。我們將會創建一個發布者,也就是說話者節點,它將會持續的廣播一個信息。
改變目錄到之前所建立的那個包下:
cd ~/catkin_ws/src/beginner_tutorials
在beginner_tutorials包下面建立一個src文件夾:
mkdir -p ~/catkin_ws/src/beginner_tutorials/src
創建文件src/talker.cpp:
gedit src/talker.cpp
將下面的內容復制進去:
#include "ros/ros.h" #include "std_msgs/String.h" #include <sstream> /** * This tutorial demonstrates simple sending of messages over the ROS system. */ int main(int argc, char **argv) { /** * The ros::init() function needs to see argc and argv so that it can perform * any ROS arguments and name remapping that were provided at the command line. For programmatic * remappings you can use a different version of init() which takes remappings * directly, but for most command-line programs, passing argc and argv is the easiest * way to do it. The third argument to init() is the name of the node. * * You must call one of the versions of ros::init() before using any other * part of the ROS system. */ ros::init(argc, argv, "talker"); /** * NodeHandle is the main access point to communications with the ROS system. * The first NodeHandle constructed will fully initialize this node, and the last * NodeHandle destructed will close down the node. */ ros::NodeHandle n; /** * The advertise() function is how you tell ROS that you want to * publish on a given topic name. This invokes a call to the ROS * master node, which keeps a registry of who is publishing and who * is subscribing. After this advertise() call is made, the master * node will notify anyone who is trying to subscribe to this topic name, * and they will in turn negotiate a peer-to-peer connection with this * node. advertise() returns a Publisher object which allows you to * publish messages on that topic through a call to publish(). Once * all copies of the returned Publisher object are destroyed, the topic * will be automatically unadvertised. * * The second parameter to advertise() is the size of the message queue * used for publishing messages. If messages are published more quickly * than we can send them, the number here specifies how many messages to * buffer up before throwing some away. */ ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000); ros::Rate loop_rate(10); /** * A count of how many messages we have sent. This is used to create * a unique string for each message. */ int count = 0; while (ros::ok()) { /** * This is a message object. You stuff it with data, and then publish it. */ std_msgs::String msg; std::stringstream ss; ss << "hello world " << count; msg.data = ss.str(); ROS_INFO("%s", msg.data.c_str()); /** * The publish() function is how you send messages. The parameter * is the message object. The type of this object must agree with the type * given as a template parameter to the advertise<>() call, as was done * in the constructor above. */ chatter_pub.publish(msg); ros::spinOnce(); loop_rate.sleep(); ++count; } return 0; }
解釋一下代碼:
#include "ros/ros.h"
ros/ros.h包括了使用ROS系統最基本的頭文件。
這條代碼包括了std_msgs/String消息,它存在於std_msgs包中。這是有std_msgs中的String.msg文件自動產生的。
ros::init(argc, argv, "talker");
初始化ROS,它允許ROS通過命令行重新命名,現在還不太重要。這里也是我們確切說明節點名字的地方,在運行的系統中,節點的名字必須唯一
為處理的節點創建了一個句柄,第一個創建的節點句柄將會初始化這個節點,最后一個銷毀的節點將會釋放節點所使用的所有資源。
告訴主機,我們將會在一個名字為chatter的話題上發布一個std_msgs/String類型的消息,這就使得主機告訴了所有訂閱了chatter 話題的節點,我們將在這個話題上發布數據。第二個參數是發布隊列的大小,它的作用是緩沖。當我們發布消息很快的時候,它將能緩沖1000條信息。如果慢了 的話就會覆蓋前面的信息。
NodeHandle::advertise()將會返回ros::Publisher對象,該對象有兩個作用,首先是它包括一個publish()方法可以在制定的話題上發布消息,其次,當超出范圍之外的時候就會自動的處理。
ros::Rate loop_rate(10);
一個ros::Rate對象允許你制定循環的頻率。它將會記錄從上次調用Rate::sleep()到現在為止的時間,並且休眠正確的時間。在這個例子中,設置的頻率為10hz。
int count = 0; while (ros::ok()) {
默認情況下,roscpp將會安裝一個SIGINT監聽,它使當Ctrl-C按下時,ros::ok()將會返回false。
ros::ok()在以下幾種情況下也會返回false:(1)按下Ctrl-C時(2)我們被一個同名同姓的節點從網絡中踢出(3)ros::shutdown()被應用程序的另一部分調用(4)所有的ros::NodeHandles都被銷毀了。一旦ros::ok()返回false,所有的ROS調用都會失敗。
std_msgs::String msg; std::stringstream ss; ss << "hello world " << count; msg.data = ss.str();
我們使用message-adapted類在ROS中廣播信息,這個類一般是從msg文件中產生的。我們現在使用的是標准的字符串消息,它只有一個data數據成員,當然更復雜的消息也是可以的。
-
chatter_pub.publish(msg);
現在我們向話題chatter發布消息。
ROS_INFO("%s", msg.data.c_str());
ROS_INFO是cout和printf的替代品。
ros::spinOnce();
在這個簡單的程序中調用ros::spinOnce();是不必要的,因為我們沒有收到任何的回調信息。然而如果你為這個應用程序添加一個訂閱者,並且在這里沒有調用ros::spinOnce(),你的回調函數將不會被調用。所以這是一個良好的風格。
loop_rate.sleep();
休眠一下,使程序滿足前面所設置的10hz的要求。 下面總結一下創建一個發布者節點的步驟:(1)初始化ROS系統(2)告訴主機我們將要在chatter話題上發布std_msgs/String類型的消息(3)循環每秒發送10次消息。
打開一個終端,進入到beginner_tutorials包下面:
cd ~/catkin_ws/src/beginner_tutorials
編輯文件src/listener.cpp:
gedit src/listener.cpp
將下面的代碼復制到文件中:
#include "ros/ros.h" #include "std_msgs/String.h" /** * This tutorial demonstrates simple receipt of messages over the ROS system. */ void chatterCallback(const std_msgs::String::ConstPtr& msg) { ROS_INFO("I heard: [%s]", msg->data.c_str()); } int main(int argc, char **argv) { /** * The ros::init() function needs to see argc and argv so that it can perform * any ROS arguments and name remapping that were provided at the command line. For programmatic * remappings you can use a different version of init() which takes remappings * directly, but for most command-line programs, passing argc and argv is the easiest * way to do it. The third argument to init() is the name of the node. * * You must call one of the versions of ros::init() before using any other * part of the ROS system. */ ros::init(argc, argv, "listener"); /** * NodeHandle is the main access point to communications with the ROS system. * The first NodeHandle constructed will fully initialize this node, and the last * NodeHandle destructed will close down the node. */ ros::NodeHandle n; /** * The subscribe() call is how you tell ROS that you want to receive messages * on a given topic. This invokes a call to the ROS * master node, which keeps a registry of who is publishing and who * is subscribing. Messages are passed to a callback function, here * called chatterCallback. subscribe() returns a Subscriber object that you * must hold on to until you want to unsubscribe. When all copies of the Subscriber * object go out of scope, this callback will automatically be unsubscribed from * this topic. * * The second parameter to the subscribe() function is the size of the message * queue. If messages are arriving faster than they are being processed, this * is the number of messages that will be buffered up before beginning to throw * away the oldest ones. */ ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback); /** * ros::spin() will enter a loop, pumping callbacks. With this version, all * callbacks will be called from within this thread (the main one). ros::spin() * will exit when Ctrl-C is pressed, or the node is shutdown by the master. */ ros::spin(); return 0; }
void chatterCallback(const std_msgs::String::ConstPtr& msg) { ROS_INFO("I heard: [%s]", msg->data.c_str()); }
當一個消息到達chatter話題時,這個回調函數將會被調用。
訂閱chatter話題,當一個新的消息到達時,ROS將會調用chatterCallback()函數。第二個參數是對列的長度,如果我們處理消息的速度不夠快,會將收到的消息緩沖下來,一共可以緩沖1000條消息,滿1000之后,后面的到達的消息將會覆蓋前面的消息。
NodeHandle::subscribe()將會返回一個ros::Subscriber類型的對象,當訂閱對象被銷毀以后,它將會自動從chatter話題上撤銷。
ros::spin()進入了一個循環,可以盡快的調用消息的回調函數。不要擔心,如果它沒有什么事情可做時,它也不會浪費太多的CPU。當ros::ok()返回false時,ros::spin()將會退出。這就意味着,當ros::shutdown()被調用,或按下CTRL+C等情況,都可以退出。下面總結一下寫一個訂閱者的步驟:(1)初始化ROS系統(2)訂閱chatter話題(3)Spin,等待消息的到來(4)當一個消息到達時,chatterCallback()函數被調用。
下面看一下如何構建節點。這時候你的CMakeLists.txt看起來應該是下面這個樣子,包括前面所做的修改,注釋部分可以除去:
cmake_minimum_required(VERSION 2.8.3) project(beginner_tutorials) ## Find catkin and any catkin packages find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs genmsg) ## Declare ROS messages and services add_message_files(DIRECTORY msg FILES Num.msg) add_service_files(DIRECTORY srv FILES AddTwoInts.srv) ## Generate added messages and services generate_messages(DEPENDENCIES std_msgs) ## Declare a catkin package catkin_package()
將下面幾行代碼添加到CMakeLists.txt的最后。最終你的CMakeLists.txt文件看起來樣該是下面這個樣子:
- cmake_minimum_required(VERSION 2.8.3)
- project(beginner_tutorials)
- ## Find catkin and any catkin packages
- find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs genmsg)
- ## Declare ROS messages and services
- add_message_files(FILES Num.msg)
- add_service_files(FILES AddTwoInts.srv)
- ## Generate added messages and services
- generate_messages(DEPENDENCIES std_msgs)
- ## Declare a catkin package
- catkin_package()
- ## Build talker and listener
- include_directories(include ${catkin_INCLUDE_DIRS})
- add_executable(talker src/talker.cpp)
- target_link_libraries(talker ${catkin_LIBRARIES})
- add_dependencies(talker beginner_tutorials_generate_messages_cpp)
- add_executable(listener src/listener.cpp)
- target_link_libraries(listener ${catkin_LIBRARIES})
- add_dependencies(listener beginner_tutorials_generate_messages_cpp)
這將會創建兩個可執行文件,talker和listener。它們將會產生在~/catkin_ws/devel/lib/share/<package name>目錄下,下面開始構建,在你的工作空間根目錄下輸入:
- catkin_make
在前面的兩篇博客中我們用C++在ROS中創建了一個發布者和接收者,並使用catkin_make構建了新的節點,下面就需要驗證一下,我們寫的是否正確。
首先運行roscore
- roscore
打開一個新的終端在里面運行talker:
cd ~/catkin_ws/devel/lib/beginner_tutorials
./talker
打開一個新的終端在里面運行listener:
cd ~/catkin_ws/devel/lib/beginner_tutorials ./listener
說明了我們的程序是正確的。