澳门新萄京:写一个简单的发布和订阅,ROS主题
分类:服务器

1 函数意义

本文转载自: 

博主提示:本文基于ROS Kinetic Kame,如有更(gèng)新版本,可能存在细微差别,请大兄弟以官方资料为准。

博主向来愚钝,若大兄弟发现该文章有不妥之处,还请速速告知。

澳门新萄京:写一个简单的发布和订阅,ROS主题发布订阅。1 写一个发布(Publiser)功能的Node

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

 

     节点是一个可执行程序,它连接到了ROS的网络系统中。我们将会创建一个发布者,也就是说话者节点,它将会持续的广播一个信息。

首先要知道,这俩兄弟学名叫ROS消息回调处理函数。它俩通常会出现在ROS的主循环中,程序需要不断调用ros::spin,两者区别在于前者调用后不会再返回,也就是你的主程序到这儿就不往下执行了,而后者在调用后还可以继续执行之后的程序。

1 函数意义

首先要知道,这俩兄弟学名叫ROS消息回调处理函数。它俩通常会出现在ROS的主循环中,程序需要不断调用ros::spin() 或 ros::spinOnce(),两者区别在于前者调用后不会再返回,也就是你的主程序到这儿就不往下执行了,而后者在调用后还可以继续执行之后的程序。

其实消息回调处理函数的原理非常简单。我们都知道,ROS存在消息发布订阅机制,什么?不知道?不知道还不快去: (ROS官方基础教程) 瞅瞅。

好,我们继续,如果你的程序写了相关的消息订阅函数,那么程序在执行过程中,除了主程序以外,ROS还会自动在后台按照你规定的格式,接受订阅的消息,但是所接到的消息并不是立刻就被处理,而是必须要等到ros::spin()或ros::spinOnce()执行的时候才被调用,这就是消息回到函数的原理,怎么样,简单吧,至于为什么这么设计?咳咳,嗯,肯定有他的道理。。。

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;
}

参考文献: 老王说ROS

     改变目录到之前所建立的那个包下:

其实消息回调处理函数的原理非常简单。我们都知道,ROS存在消息发布订阅机制,什么?不知道?不知道还不快去: 瞅瞅。

2 区别

就像上面说的,ros::spin() 在调用后不会再返回,也就是你的主程序到这儿就不往下执行了,而 ros::spinOnce() 后者在调用后还可以继续执行之后的程序。

其实看函数名也能理解个差不多,一个是一直调用;另一个是只调用一次,如果还想再调用,就需要加上循环了。

这里一定要记住,ros::spin()函数一般不会出现在循环中,因为程序执行到spin()后就不调用其他语句了,也就是说该循环没有任何意义,还有就是spin()函数后面一定不能有其他语句(return 0 除外),有也是白搭,不会执行的。ros::spinOnce()的用法相对来说很灵活,但往往需要考虑调用消息的时机,调用频率,以及消息池的大小,这些都要根据现实情况协调好,不然会造成数据丢包或者延迟的错误。

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)它包含了一个澳门新萄京:写一个简单的发布和订阅,ROS主题发布订阅。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主题。

rosspin、rosspinOnce及多线程订阅

 

好,我们继续,如果你的程序写了相关的消息订阅函数,那么程序在执行过程中,除了主程序以外,ROS还会自动在后台按照你规定的格式,接受订阅的消息,但是所接到的消息并不是立刻就被处理,而是必须要等到ros::spin执行的时候才被调用,这就是消息回到函数的原理,怎么样,简单吧,至于为什么这么设计?咳咳,嗯,肯定有他的道理。。。

3 常见使用方法

这里需要特别强调一下,如果大兄弟你的程序写了相关的消息订阅函数,那千万千万千万不要忘了在相应位置加上ros::spin()或者ros::spinOnce()函数,不然你是永远都得不到另一边发出的数据或消息的,博主血的教训,万望紧记。。。

2 写一个订阅node

1、 Ros:spin() VS ros::spinOnce() 区别

cd ~/catkin_ws/src/beginner_tutorials  

2 区别

3.1 ros::spin()

ros::spin()函数用起来比较简单,一般都在主程序的最后,加入该语句就可。例子如下:

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;
}

对于前者来说,spin(),运行一次之后,会一直停在那不动,所以spin()后面可以不用加任何东西。ROS 会自动的调用一些回调函数。

 

就像上面说的,ros::spin() 在调用后不会再返回,也就是你的主程序到这儿就不往下执行了,而 ros::spinOnce() 后者在调用后还可以继续执行之后的程序。

发送端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>
 
int main(int argc, char **argv)
{
    ros::init(argc, argv, "talker");
    ros::NodeHandle n;
    ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
    ros::Rate loop_rate(10);
 
    int count = 0;
    while (ros::ok())
    {
        std_msgs::String msg;
        std::stringstream ss;
        ss << "hello world " << count;
        msg.data = ss.str();
        ROS_INFO("%s", msg.data.c_str());
 
        /**
         * 向 Topic: chatter 发送消息, 发送频率为10Hz(1秒发10次);消息池最大容量1000。
         */
        chatter_pub.publish(msg);
 
        loop_rate.sleep();
         count;
    }
    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() 回调函数

Spinonce 就灵活的多了,不过需要控制好它的调用周期,它比spin定时要准很多。

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

其实看函数名也能理解个差不多,一个是一直调用;另一个是只调用一次,如果还想再调用,就需要加上循环了。

接收端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "ros/ros.h"
#include "std_msgs/String.h"
 
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
    ROS_INFO("I heard: [%s]", msg->data.c_str());
}
 
int main(int argc, char **argv)
{
    ros::init(argc, argv, "listener");
    ros::NodeHandle n;
    ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
 
    /**
     * ros::spin() 将会进入循环, 一直调用回调函数chatterCallback(),每次调用1000个数据。
     * 当用户输入Ctrl C或者ROS主进程关闭时退出,
     */
    ros::spin();
    return 0;
}

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     

当我们用nh.createTimer创建定时函数的时候,发现其定时周期不可控,用rqt查看的。所以,我们改成spinOnce 的模式来定时调用。此能实现精准调用。

mkdir -p ~/catkin_ws/src/beginner_tutorials/src  

这里一定要记住,ros::spin()函数一般不会出现在循环中,因为程序执行到spin()后就不调用其他语句了,也就是说该循环没有任何意义,还有就是spin()函数后面一定不能有其他语句,有也是白搭,不会执行的。ros::spinOnce()的用法相对来说很灵活,但往往需要考虑调用消息的时机,调用频率,以及消息池的大小,这些都要根据现实情况协调好,不然会造成数据丢包或者延迟的错误。

3.2 ros::spinOnce()

对于ros::spinOnce()的使用,虽说比ros::spin()更自由,可以出现在程序的各个部位,但是需要注意的因素也更多。比如:

 

     创建文件src/talker.cpp:

3 常见使用方法

1 对于有些传输特别快的消息,尤其需要注意合理控制消息池大小和ros::spinOnce()执行频率; 比如消息送达频率为10Hz, ros::spinOnce()的调用频率为5Hz,那么消息池的大小就一定要大于2,才能保证数据不丢失,无延迟。

参考文档:老王说ROS(7)用ros做系统设计

gedit src/talker.cpp   

这里需要特别强调一下,如果大兄弟你的程序写了相关的消息订阅函数,那千万千万千万不要忘了在相应位置加上ros::spin函数,不然你是永远都得不到另一边发出的数据或消息的,博主血的教训,万望紧记。。。

接收端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "ros/ros.h"
#include "std_msgs/String.h"
  
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
    /*...TODO...*/ 
}
  
int main(int argc, char **argv)
{
    ros::init(argc, argv, "listener");
    ros::NodeHandle n;
    ros::Subscriber sub = n.subscribe("chatter", 2, chatterCallback);
  
    ros::Rate loop_rate(5);
    while (ros::ok())
    {
        /*...TODO...*/ 
 
        ros::spinOnce();
        loop_rate.sleep();
    }
    return 0;
}

 

 

 

3.1 ros::spin()

2 ros::spinOnce()用法很灵活,也很广泛,具体情况需要具体分析。但是对于用户自定义的周期性的函数,最好和ros::spinOnce并列执行,不太建议放在回调函数中;

 

1
2
3
4
5
6
7
8
9
10
11
/*...TODO...*/
ros::Rate loop_rate(100);
  
while (ros::ok())
{
    /*...TODO...*/
    user_handle_events_timeout(...);
 
    ros::spinOnce();                 
    loop_rate.sleep();
}

以上都是同步的,在一个线程当中。

     将下面的内容复制进去:

ros::spin()函数用起来比较简单,一般都在主程序的最后,加入该语句就可。例子如下:

2、多线程

    #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"#include "std_msgs/String.h"#include int main(int argc, char **argv){ ros::init; ros::NodeHandle n; ros::Publisher chatter_pub = n.advertise<:string>; ros::Rate loop_rate; int count = 0; while  { std_msgs::String msg; std::stringstream ss; ss << "hello world " << count; msg.data = ss.str(); ROS_INFO("%s", msg.data.c_str; /** * 向 Topic: chatter 发送消息, 发送频率为10Hz;消息池最大容量1000。 */ chatter_pub.publish; loop_rate.sleep();   count; } return 0;}

#include "ros/ros.h"#include "std_msgs/String.h"void chatterCallback(const std_msgs::String::ConstPtr& msg){ ROS_INFO("I heard: [%s]", msg->data.c_str;}int main(int argc, char **argv){ ros::init(argc, argv, "listener"); ros::NodeHandle n; ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback); /** * ros::spin() 将会进入循环, 一直调用回调函数chatterCallback(),每次调用1000个数据。 * 当用户输入Ctrl C或者ROS主进程关闭时退出, */ ros::spin(); return 0;}

在ROS当中,原作者是不推荐用多线程的,他建议用多进程,变成一个个节点的形式进行通信。

 

3.2 ros::spinOnce()

分为两种模式:同步和异步。

 

对于ros::spinOnce更自由,可以出现在程序的各个部位,但是需要注意的因素也更多。比如:

同步:MultiThreadSpinner s(4),一共5个线程。包括了主线程。

     解释一下代码:

1 对于有些传输特别快的消息,尤其需要注意合理控制消息池大小和ros::spinOnce()执行频率; 比如消息送达频率为10Hz, ros::spinOnce()的调用频率为5Hz,那么消息池的大小就一定要大于2,才能保证数据不丢失,无延迟。

异步:AsyncSpinner s(4), 一共5个线程。包括了主线程。

#include "ros/ros.h"  
/**接收端**/
#include "ros/ros.h"#include "std_msgs/String.h"void chatterCallback(const std_msgs::String::ConstPtr& msg){ /*...TODO...*/ }int main(int argc, char **argv){ ros::init(argc, argv, "listener"); ros::NodeHandle n; ros::Subscriber sub = n.subscribe("chatter", 2, chatterCallback); ros::Rate loop_rate { /*...TODO...*/ ros::spinOnce; } return 0;}

 

 

2 ros::spinOnce()用法很灵活,也很广泛,具体情况需要具体分析。但是对于用户自定义的周期性的函数,最好和ros::spinOnce并列执行,不太建议放在回调函数中;

利用Timer 可能会出现一些问题。

ros/ros.h包括了使用ROS系统最基本的头文件。

/*...TODO...*/ros::Rate loop_rate){ /*...TODO...*/ user_handle_events_timeout; loop_rate.sleep();}

3、当程序当中有数据处理线程的时候,建议开辟 异步多线程 订阅,算法写在订阅函数里面。

 

当然,目前的处理当中,我更倾向于重新开辟一个线程,然后通过循环数组来进行数据交互。

#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

 

澳门新萄京 1

     说明了我们的程序是正确的。

 

本文由澳门新萄京发布于服务器,转载请注明出处:澳门新萄京:写一个简单的发布和订阅,ROS主题

上一篇:澳门新萄京:支出宝当面付之扫码支付,java支付 下一篇:没有了
猜你喜欢
热门排行
精彩图文