ROS學(xué)習(xí)筆記(十三)- 寫一個(gè)簡單的發(fā)布和訂閱(C++篇)

1 寫一個(gè)發(fā)布(Publiser)功能的Node

Node是連接到ROS網(wǎng)絡(luò)的可執(zhí)行程序,是ROS的一個(gè)術(shù)語?,F(xiàn)在我們要創(chuàng)建一個(gè)node,它可以不斷地廣播消息。
首先打開我們的package,具體方法已經(jīng)很熟練了,不再贅余。

1.1 源碼

創(chuàng)建一個(gè)src文件夾,這個(gè)文件夾包含所有源文件。
好像本來就有了,不重復(fù)創(chuàng)建了。
在src文件夾里創(chuàng)建talker.cpp
vim talker.cpp
然后粘貼以下內(nèi)容:

#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. (advertise()函數(shù)是告訴ROS想要在哪個(gè)主題上發(fā)布的方式)
   * 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;
}

1.2 源碼解析

#include "ros/ros.h"

ros/ros.h 是一個(gè)簡單的頭文件包含方法,這樣能把ROS里大部分常用的頭文件都包含進(jìn)來。
#include "std_msgs/String.h"
這個(gè)包含了std_msgs/String message,存在于std_msgs package內(nèi)。這個(gè)頭文件從package包內(nèi)的String.msg文件自動生成的。
ros::init(argc, argv, "talker");
初始化ROS,這個(gè)現(xiàn)在還不重要,它允許ROS通過命令行進(jìn)行名稱重繪。這里也是我們可以指定node名稱的地方,需要注意的是node名稱必須唯一。
命名規(guī)則差不多就是變量的命名規(guī)則。

ros::NodeHandle n;
為正在運(yùn)行的node創(chuàng)建handle。第一個(gè)handle就是用來初始化node的,最后一個(gè)會清除掉node所使用的所有資源。
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
告訴主線master我們將要推送一個(gè)類型為std_msgs::String的message到topic chatter。這個(gè)使master告訴所有node接聽chatter,因?yàn)槲覀儗扑蛿?shù)據(jù)到這個(gè)topic。第二個(gè)參數(shù)是我們推送隊(duì)列的大小。防止我們推送的太快了,所以建立一個(gè)1000的緩沖區(qū)。
NodeHandle::advertise() 返回一個(gè)ros::Publisher對象,有兩個(gè)目的:1)它包含了一個(gè)publish()方法,可以用來推送消息到它建立的topic。2)當(dāng)所有的這個(gè)對象都被銷毀時(shí),主題會自動的被回收。
ros::Rate loop_rate(10);
一個(gè)ros::Rate 對象允許你去制定一個(gè)循環(huán)運(yùn)行的頻率。它會根據(jù)
Rate::sleep()出現(xiàn)的位置,去消耗時(shí)間來使程序運(yùn)行總時(shí)間滿足設(shè)定的頻率。

int count = 0;
  while (ros::ok())
  {

下列情況ros::ok()將會返回 false:
-接收到一個(gè)SIGNT(比如Ctrl+C時(shí))
-我們被另一個(gè)同名node踢出了網(wǎng)絡(luò)
-應(yīng)用的另一部分調(diào)用了ros::shutdown()
-所有ros::NodeHandles都已經(jīng)被銷毀了
只要ros::ok()返回了FALSE,所有ROS調(diào)用都會失敗。

std_msgs::String msg;

    std::stringstream ss;
    ss << "hello world " << count;
    msg.data = ss.str();

我們使用一個(gè)消息適應(yīng)類在ROS上廣播了一條消息,通常從msg file 生成。更加復(fù)雜的類型也是可以的,不過現(xiàn)在我們就用標(biāo)準(zhǔn)的String類型message,只有一個(gè)成員:“data”
chatter_pub.publish(msg);
現(xiàn)在我們相當(dāng)于把消息發(fā)給了連接過來的每一個(gè)人。
ROS_INFO("%s", msg.data.c_str());
ROS_INFO 和擴(kuò)展的函數(shù)是替代 printf/cout函數(shù)的。
ros::spinOnce();
我們這個(gè)簡單的程序其實(shí)沒必要調(diào)用這個(gè)函數(shù),因?yàn)槲覀儧]有接受任何回調(diào)。當(dāng)我們需要在程序里增加一個(gè)訂閱功能時(shí),就必須用這個(gè)了,如果沒有,你的回調(diào)就永遠(yuǎn)不會被調(diào)用。所以,保險(xiǎn)起見還是加上這個(gè)。
loop_rate.sleep();
使用ros::Rate 對象使剩余時(shí)間睡眠來讓我們進(jìn)行10Hz的推送。
下面是運(yùn)行流程的概要:
-初始化ROS系統(tǒng)
-聲明我們要向主題發(fā)布消息
-循環(huán)使消息以10Hz推送給chatter主題。

2 寫一個(gè)訂閱node

2.1 源碼

在 beginner_tutorials package里創(chuàng)建 src/listener.cpp 文件,然后輸入以下內(nèi)容。
vim 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;
}

2.2 源碼解析

上面講過的片段就不再重復(fù)敘述了。

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

這是一個(gè)回調(diào)函數(shù),當(dāng)有新的message發(fā)布到chatter topic上面,這個(gè)message會經(jīng)過boost shared_ptr,也就是說如果有需要的話,你可以把它儲存起來。
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
訂閱chatter topic。當(dāng)有新message到達(dá)的時(shí)候,ROS會調(diào)用chatterCallback。第二個(gè)參數(shù)就是隊(duì)列大小,就是用來防止消息來的太快,作為緩沖區(qū)使用。
NodeHandle::subscribe() 返回一個(gè)ros::Subscriber對象,必須持續(xù)接收,除非退訂topic。Subscriber 對象被銷毀時(shí),他會自動退訂topic。
ros::spin();
ros::spin();進(jìn)入一個(gè)循環(huán),盡快調(diào)用message回調(diào)。不用擔(dān)心的是,如果沒什么工作要做,它不會占用太多CPU。當(dāng) ros::ok() 返回false的時(shí)候,它就會退出?;蛘呤謩犹?。還有替他方法跳出回調(diào)。

  • There are other ways of pumping callbacks, but we won't worry about those here. The roscpp_tutorials package has some demo applications which demonstrate this. The roscpp overview also contains more information.*
    概要:
    • 初始化ROS系統(tǒng)
    • 訂閱chatter topic
    • Spin,等待message到達(dá)
    • 當(dāng)message到達(dá)時(shí),調(diào)用 chatterCallback() 回調(diào)函數(shù)

3 編譯自己的node

我們之前使用catkin_creat_pkg創(chuàng)建了一個(gè)package.xml和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(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底部加入以下內(nèi)容:

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)

這個(gè)將會創(chuàng)建兩個(gè)可執(zhí)行文件,talker和listener,默認(rèn)將會放在devel文件夾里,位置在~/catkin_ws/devel/lib/<package name>。
需要注意的是,你必須為message生成可執(zhí)行程序添加依賴項(xiàng)目標(biāo):
add_dependencies(talker beginner_tutorials_generate_messages_cpp)
這確保了這個(gè)package 的message header在使用前被生成。如果你想在這個(gè)工作區(qū)里的其他package內(nèi)使用這個(gè)message,你就需要把依賴放入他們各自的文件里,因?yàn)閏atkin是平行編譯所有項(xiàng)目的。
你可以直接調(diào)用可執(zhí)行程序或者通過rosrun調(diào)用。他們不會被放在 '<prefix>/bin',因?yàn)檫@樣當(dāng)安裝我們的package到系統(tǒng)的時(shí)候會污染PATH。如果你想要讓你的可執(zhí)行文件在安裝時(shí)裝入PATH,你可以設(shè)定一個(gè)安裝目標(biāo)(catkin/CMakeLists.txt )。
現(xiàn)在運(yùn)行catkin_make:

# In your catkin workspace
$ catkin_make

如果你建立了新的pkg,你需要告訴catkin強(qiáng)制make,使用(--force-cmake)選項(xiàng)。
官網(wǎng)鏈接
下面我們來運(yùn)行他們,記得開新窗口:

#新窗口
cd catkin_ws
source ./devel/setup.bash
rosrun beginner_tutorials talker
#新窗口
cd catkin_ws
source ./devel/setup.bash
rosrun beginner_tutorials listener     
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容