ROS主題發布訂閱


     節點是一個可執行程序,它連接到了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系統最基本的頭文件。

 
#include "std_msgs/String.h"

這條代碼包括了std_msgs/String消息,它存在於std_msgs包中。這是有std_msgs中的String.msg文件自動產生的。

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

 


初始化ROS,它允許ROS通過命令行重新命名,現在還不太重要。這里也是我們確切說明節點名字的地方,在運行的系統中,節點的名字必須唯一

 
ros::NodeHandle n;  


為處理的節點創建了一個句柄,第一個創建的節點句柄將會初始化這個節點,最后一個銷毀的節點將會釋放節點所使用的所有資源。

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

 


告訴主機,我們將會在一個名字為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數據成員,當然更復雜的消息也是可以的。

 
  1. 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話題時,這個回調函數將會被調用。

    ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);  

 

訂閱chatter話題,當一個新的消息到達時,ROS將會調用chatterCallback()函數。第二個參數是對列的長度,如果我們處理消息的速度不夠快,會將收到的消息緩沖下來,一共可以緩沖1000條消息,滿1000之后,后面的到達的消息將會覆蓋前面的消息。

 

NodeHandle::subscribe()將會返回一個ros::Subscriber類型的對象,當訂閱對象被銷毀以后,它將會自動從chatter話題上撤銷。

 

    ros::spin();  

 


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文件看起來樣該是下面這個樣子:

[html] view plain copy
  1. cmake_minimum_required(VERSION 2.8.3)  
  2. project(beginner_tutorials)  
  3.   
  4. ## Find catkin and any catkin packages  
  5. find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs genmsg)  
  6.   
  7. ## Declare ROS messages and services  
  8. add_message_files(FILES Num.msg)  
  9. add_service_files(FILES AddTwoInts.srv)  
  10.   
  11. ## Generate added messages and services  
  12. generate_messages(DEPENDENCIES std_msgs)  
  13.   
  14. ## Declare a catkin package  
  15. catkin_package()  
  16.   
  17. ## Build talker and listener  
  18. include_directories(include ${catkin_INCLUDE_DIRS})  
  19.   
  20. add_executable(talker src/talker.cpp)  
  21. target_link_libraries(talker ${catkin_LIBRARIES})  
  22. add_dependencies(talker beginner_tutorials_generate_messages_cpp)  
  23.   
  24. add_executable(listener src/listener.cpp)  
  25. target_link_libraries(listener ${catkin_LIBRARIES})  
  26. add_dependencies(listener beginner_tutorials_generate_messages_cpp)  

這將會創建兩個可執行文件,talker和listener。它們將會產生在~/catkin_ws/devel/lib/share/<package name>目錄下,下面開始構建,在你的工作空間根目錄下輸入:

[html] view plain copy
    1. catkin_make 

       

  在前面的兩篇博客中我們用C++在ROS中創建了一個發布者和接收者,並使用catkin_make構建了新的節點,下面就需要驗證一下,我們寫的是否正確。
     首先運行roscore

[html] view plain copy
  1. roscore  


     打開一個新的終端在里面運行talker:

cd ~/catkin_ws/devel/lib/beginner_tutorials
./talker

 


     打開一個新的終端在里面運行listener:

cd ~/catkin_ws/devel/lib/beginner_tutorials
./listener

 


     說明了我們的程序是正確的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM