ROS学习笔记(十三)- 写一个简单的发布和订阅(C++篇)

1 写一个发布(Publiser)功能的Node

Node是连接到ROS网络的可执行程序,是ROS的一个术语。现在我们要创建一个node,它可以不断地广播消息。
首先打开我们的package,具体方法已经很熟练了,不再赘余。

1.1 源码

创建一个src文件夹,这个文件夹包含所有源文件。
好像本来就有了,不重复创建了。
在src文件夹里创建talker.cpp
vim 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. (advertise()函数是告诉ROS想要在哪个主题上发布的方式)
   * 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 是一个简单的头文件包含方法,这样能把ROS里大部分常用的头文件都包含进来。
#include "std_msgs/String.h"
这个包含了std_msgs/String message,存在于std_msgs package内。这个头文件从package包内的String.msg文件自动生成的。
ros::init(argc, argv, "talker");
初始化ROS,这个现在还不重要,它允许ROS通过命令行进行名称重绘。这里也是我们可以指定node名称的地方,需要注意的是node名称必须唯一。
命名规则差不多就是变量的命名规则。

ros::NodeHandle n;
为正在运行的node创建handle。第一个handle就是用来初始化node的,最后一个会清除掉node所使用的所有资源。
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
告诉主线master我们将要推送一个类型为std_msgs::String的message到topic chatter。这个使master告诉所有node接听chatter,因为我们将会推送数据到这个topic。第二个参数是我们推送队列的大小。防止我们推送的太快了,所以建立一个1000的缓冲区。
NodeHandle::advertise() 返回一个ros::Publisher对象,有两个目的:1)它包含了一个publish()方法,可以用来推送消息到它建立的topic。2)当所有的这个对象都被销毁时,主题会自动的被回收。
ros::Rate loop_rate(10);
一个ros::Rate 对象允许你去制定一个循环运行的频率。它会根据
Rate::sleep()出现的位置,去消耗时间来使程序运行总时间满足设定的频率。

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

下列情况ros::ok()将会返回 false:
-接收到一个SIGNT(比如Ctrl+C时)
-我们被另一个同名node踢出了网络
-应用的另一部分调用了ros::shutdown()
-所有ros::NodeHandles都已经被销毁了
只要ros::ok()返回了FALSE,所有ROS调用都会失败。

std_msgs::String msg;

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

我们使用一个消息适应类在ROS上广播了一条消息,通常从msg file 生成。更加复杂的类型也是可以的,不过现在我们就用标准的String类型message,只有一个成员:“data”
chatter_pub.publish(msg);
现在我们相当于把消息发给了连接过来的每一个人。
ROS_INFO("%s", msg.data.c_str());
ROS_INFO 和扩展的函数是替代 printf/cout函数的。
ros::spinOnce();
我们这个简单的程序其实没必要调用这个函数,因为我们没有接受任何回调。当我们需要在程序里增加一个订阅功能时,就必须用这个了,如果没有,你的回调就永远不会被调用。所以,保险起见还是加上这个。
loop_rate.sleep();
使用ros::Rate 对象使剩余时间睡眠来让我们进行10Hz的推送。
下面是运行流程的概要:
-初始化ROS系统
-声明我们要向主题发布消息
-循环使消息以10Hz推送给chatter主题。

2 写一个订阅node

2.1 源码

在 beginner_tutorials package里创建 src/listener.cpp 文件,然后输入以下内容。
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 源码解析

上面讲过的片段就不再重复叙述了。

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

这是一个回调函数,当有新的message发布到chatter topic上面,这个message会经过boost shared_ptr,也就是说如果有需要的话,你可以把它储存起来。
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
订阅chatter topic。当有新message到达的时候,ROS会调用chatterCallback。第二个参数就是队列大小,就是用来防止消息来的太快,作为缓冲区使用。
NodeHandle::subscribe() 返回一个ros::Subscriber对象,必须持续接收,除非退订topic。Subscriber 对象被销毁时,他会自动退订topic。
ros::spin();
ros::spin();进入一个循环,尽快调用message回调。不用担心的是,如果没什么工作要做,它不会占用太多CPU。当 ros::ok() 返回false的时候,它就会退出。或者手动跳出?;褂刑嫠椒ㄌ龌氐?。

  • 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系统
    • 订阅chatter topic
    • Spin,等待message到达
    • 当message到达时,调用 chatterCallback() 回调函数

3 编译自己的node

我们之前使用catkin_creat_pkg创建了一个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底部加入以下内容:

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,默认将会放在devel文件夹里,位置在~/catkin_ws/devel/lib/<package name>。
需要注意的是,你必须为message生成可执行程序添加依赖项目标:
add_dependencies(talker beginner_tutorials_generate_messages_cpp)
这确保了这个package 的message header在使用前被生成。如果你想在这个工作区里的其他package内使用这个message,你就需要把依赖放入他们各自的文件里,因为catkin是平行编译所有项目的。
你可以直接调用可执行程序或者通过rosrun调用。他们不会被放在 '<prefix>/bin',因为这样当安装我们的package到系统的时候会污染PATH。如果你想要让你的可执行文件在安装时装入PATH,你可以设定一个安装目标(catkin/CMakeLists.txt )。
现在运行catkin_make:

# In your catkin workspace
$ catkin_make

如果你建立了新的pkg,你需要告诉catkin强制make,使用(--force-cmake)选项。
官网链接
下面我们来运行他们,记得开新窗口:

#新窗口
cd catkin_ws
source ./devel/setup.bash
rosrun beginner_tutorials talker
#新窗口
cd catkin_ws
source ./devel/setup.bash
rosrun beginner_tutorials listener     
最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容