ROS 编写一个简单的发布者和订阅者(C++)

0 编写发布者节点

节点是在ROS里面的一个专业术语,它可以被ROS的网络所链接。在这里我们将创建一个名叫“talker”的发布者节点,它将连续的广播一个消息。

改变你现在的位置到你之前在catkin 工作区域里建立的beginner_tutorials 包。

0.1 代码

在beginner_tutorials包里建立一个src文件夹:

这个目录将包含我们所建立的beginner_tutorials的源文件。

在beginner_tutorials里面创建一个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;
}

0.2 代码的解释

现在我们来分割代码.

#include "ros/ros.h"
ros/ros.h是一个十分方便的头文件,它包含了打多数ROS系统里使用公共共有部分的所有头文件。
<pre name="code" class="cpp">#include "std_msgs/String.h"
这个头文件存在于std_msgs包中,它是在这个包里由String.msg文件自动生成的。关于更多的对于消息的定义,可以参考msg页。
<pre name="code" class="cpp">ros::inti(argc  ,argv,  "talker");
这是安装ROS,它允许ROS可以通过命令行对名称进行重映射,但是现在不是特别重要。
它也允许我们对我们的节点进行专有名称的指定。不过,节点的名称必须是唯一的。这个名字必须是一个基本名称,不能有/在里面。
ros::NodeHandle n;
创建处理这一过程的节点。在第一个NodeHandle创建的时候将会初始化节点,在最后一个NodeHandle消失的时候将会清除所有节点
使用的资源。
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter",1000);
告诉master我们将要在chatter的话题中发布一个std_msgs/String类型的消息。它允许master告诉所有正在监听chatter的节点,我们将
要在这话题里面发布数据了。第二个参数是我们发布队列的大小。通过这种方式,我们发布的够快的话,它将可以在开始清楚旧的数
据之前,缓存最大1000个消息。
NodeHandle::advertise() 返回一个 ros::Publisher对象,它为两个目的服务:1)它包含了一个publish()方法,会让你在你创建的话题里
面发布消息;2)当它越界的话,它将不被通知。
<pre name="code" class="cpp">ros::Rate loop_rate(10);
一个 ros::Rate对象允许你制定一个回送的频率。它将会记录自从上一次Rate::sleep()到现在多长时间,并且在正确时间数的时候沉睡。
在这个例子里我们希望在10hz的运行。
<pre name="code" class="cpp">int count  =  0;
while (ros::ok())
{
通过默认roscpp将会案子一个SIGINT handler,这个handler可以使用Ctrl-C来处理,如果触发了话,它将会导致ros::ok()返回false。
ros::ok()将会返回false如果:

 
 
 
 

  • 一个SIGINT 被接受了(Ctrl-C)
  • 我们因为另外一个相同名字的节点被网络踢出去了
  • 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文件里形成。更加复杂的数据结构也是可能的,但我们这里使用了一个标准的String消息,它有一个成员:“data”。

chatter_pub.publish(msg);
我们实际上对所有链接它的都广播了消息。

ROS_INFO("%s",  msg.data.c_str());
ROS_INFO 和友类是我们对printf/count 的代替品。
ros::spinOnce();
对于这个简单的程序,调用ros::spinOnce()不是必须的,因为我们没有收到任何的回调。然而,如果你在这个应用里添加一个订阅者
并且不使用ros::spinOnce(),你的回调将不会被调用。所以最好的办法是加上它。
loop_rate.sleep();
现在我们使用ros::Rate对象来设置沉睡,然后在10hz订阅速度的时候提醒我们。
下面有一个精简版的原理:

  • 初始化ROS系统;
  • 建议我们将在chatter话题里对master发布一个std_msgs/String 的消息
  • 当在发布消息时一秒十次将会回环(loop)

现在我们需要写一个接受消息的节点。

1 编写接受消息的节点

1.1 代码

在beginner_tutorials里面创建一个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;
}

1.1代码解释

现在我们分开来进行,忽略一些之前解释过的。

void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
  ROS_INFO("I heard: [%s]", msg->data.c_str());
}
这是一个回调函数,它将在一个新的消息到达chatter话题的时候进行回调。这个消息在一个boost shared_ptr中被传递,这意味这你如
果想,可以将它储存为off,不用担心他在你的下层被删除,不用负责潜在的数据。
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
用master来对chatter话题进行订阅。ROS每当一个新的消息到达时会调用一个chatterCallback()函数。第二个参数是队列的大小,以便
我们不能快速的处理消息,通过这种方式,如果队列到达了1000个消息,我们将在下一个新的消息到来的时候扔掉旧的。
NodeHandle::subscribe()返回一个ros::Subscriber对象,这个对象你必须一直存在除非你不希望订阅了。当这个订阅对象被销毁时,它
将自动不被chatter话题订阅。
这里有一些版本的NodeHandle::subscribe()函数,它允许你知名一个类的成员函数,甚至是被一个Boost.Function object调用的任何东西。
这个roscpp overview 包含了更多的信息。
ros::spin();
ros::spin()进入了一个回环,这个回环尽可能快的调用了消息回调。不用担心,它不会使用过多的CPU。一旦ros::ok()返回false,ros::spin()
将会退出。这意味着ros::shutdown()已经被调用了,既不是通过默认的Ctrl-C handler让master告诉我们要关闭,也不是手动调用了它。
和上面一样,这里有一个精简的版本:

  • 初始化ROS系统
  • 订阅chatter话题
  • 运行,等待消息到达
  • 当一个消息到达后,chatterCallback()函数被调用

3. 建立你自己的节点

你可以使用之前Catkin_create_pkg里面创建的package.xml和CMakeList.txt文件。

形成的CMakeList.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()

不用担心修改了注释的例子,简单的添加这写行到你的CMakeList.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)

你最后的CMakeList.txt应该像这样:

它将会创建两个可执行文件——talker和listener,它们默认会到你的devel space里面的包的文件夹下,文件夹默认在 ~/catkin_ws/devel/lib<package_name>。

记住你为可执行文件目标必须添加依赖到消息生成目标:

add_dependencies(talker beginner_tutorials_generate_messages_cpp)
这个确保这个包的消息头文件在被使用之前已经形成了。如果你从其他包里使用的消息在你catkin工作区里,你也需要为他们各自形成的目标添加依赖,因为catkin建立是平行的建立所有的工程的。对于*Groovy*你可以使用下面的来决定所有的目标:

target_link_libraries(talker ${catkin_LIBRARIES})
你能直接调用或者你能使用rosrun来调用他们。它们不在'<prefix>/bin',因为当安装你的包到系统里去时将会污染PATH。
如果你希望你的可执行文件在安装的时候在PATH里面,你能设置一个安装目标,可以参考:catkin/CMakeList.txt
现在运行 catkin_make:

如果出现了
The specified base path "/home/exbot/catkin_ws/src/beginner_tutorials" contains a package but "catkin_make"
 must be invoked in the root of workspace
的错误,是因为catkin_make这个命令只能在工作区顶层运行,它只会编译~/catkin_ws/src下的源码。如果想要在编译
其他文件夹下的源码可以这样:

source后面的是你扔源码的路径

或者






<pre name="code" class="cpp"><pre name="code" class="cpp"> 




 


 




### 回答1: 关于ROS的发布和订阅话题,它是ROS中常用的一种通信方式。发布者发布消息到话题中,而订阅者则可以从话题中获取消息。通过话题的发布和订阅机制,节点之间可以进行高效的数据传输和信息共享。具体实现可以使用ROS提供的rospy库来完成。 ### 回答2: ROS是机器人操作系统(Robot Operating System)的简称,是一个用于编写机器人软件的开源框架。在ROS中,发布和订阅话题是非常常见的通信方式。 发布者(Publisher)和订阅者(Subscriber)是ROS中的两种角色。发布者负责向话题中发布消息,而订阅者则负责从话题中接收消息。 在ROS中,发布和订阅话题C可以通过以下步骤进行: 1. 创建一个话题C。 在ROS中,可以通过编写一个发布器节点来创建一个话题C。发布器节点负责向话题C中发布消息。 2. 编写一个发布器节点。 发布器节点可以使用ROS提供的编程语言(如C++或Python)来编写。在节点中,需要进行ROS初始化,并创建一个发布者对象来发布消息到话题C。 3. 编写一个订阅器节点。 订阅器节点也需要使用ROS提供的编程语言来编写。在节点中,也需要进行ROS初始化,并创建一个订阅者对象来接收话题C中的消息。 4. 运行发布器节点和订阅器节点。 在终端中,可以使用rosrun命令来分别运行发布器节点和订阅器节点。 5. 订阅器节点接收发布器节点发布的消息。 当发布器节点向话题C中发布消息时,订阅器节点将能够接收到该消息,并对消息进行处理。 通过发布和订阅话题C,可以实现不同节点之间的信息交流。发布者节点可以发布各种类型的消息到话题C中,而订阅者节点可以根据自身的需要从话题C中接收并处理消息。这种基于话题的通信方式使得ROS中不同节点之间能够有效地协作和共享信息,从而更好地完成机器人的各种任务。 ### 回答3: ROS(Robot Operating System)是一个用于构建机器人应用程序的开源框架。在ROS中,发布者订阅者是两个常用的概念,用于实现不同节点的通信。 在ROS中,发布者用于发布一个话题(Topic),而订阅者则用于订阅该话题。话题是ROS中的一种消息机制,通过话题可以在不同节点之间传递数据。 具体到题目中的情况,如果要使用ROS发布和订阅话题c,首先需要创建一个发布者节点和一个订阅者节点,并且定义话题c的消息类型。 对于发布者节点,它会发送某个类型的消息到话题c。代码中需要指定发布者节点要发布的消息类型,以及话题的名称c。然后,在发布者节点中通过调用ROS提供的API函数,将消息发送到话题c。 对于订阅者节点,它会订阅话题c,接收发布者节点发送的消息。同样,代码中需要指定订阅者节点要订阅的消息类型,以及话题的名称c。然后,在订阅者节点中通过调用ROS提供的API函数,接收话题c中的消息。 总的来说,ROS发布和订阅话题c的过程就是发布者节点将消息发送到话题c,而订阅者节点通过订阅话题c接收消息。通过这种方式,不同节点之间可以灵活地进行消息传递和通信,从而实现更复杂的机器人应用程序。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值