ROS Topic 通信简单示例

需求

正如前面所提到的,我们有时需要可视化激光扫描数据。需要将数据从激光测距仪传输到 rviz ,为了控制 Hokuyo 激光测距仪,我们启动 hokuyo_node 节点,该节点与激光对话并在扫描主题上发布 sensor_msgs/Laser Scan 消息。为了可视化激光扫描数据,我们启动 rviz 节点并订阅扫描主题。订阅后,rviz 节点开始接收激光扫描消息,并将其呈现到屏幕上。

对该示例简化后,让发布方以 10 hz的频率发布文本消息,订阅方接收消息并打印。

发布节点 Publisher Node

思路

  1. 初始化 ROS 节点和句柄;

  2. 告诉 master 节点,将创建一个使用 std_msgs/String 消息的话题 chatter;

  3. 以 10 hz 的频率循环发布消息;

cpp实现

功能包src目录下创建talker.cpp文件,编写以下内容:

#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());
        
        chatter_pub.publish(msg);

        ros::spinOnce();

	    loop_rate.sleep();
  }
  return 0;
}

代码解释

ros/ros.h中包含了 ROS 最常用的头文件。

包含 std_msgsarrow-up-right 包中的 std_msgs/Stringarrow-up-right 消息。实际上,std_msgs/String只包含一个类型为std::string的变量data。关于消息的更多定义,可以参见 msgarrow-up-right

初始化 ROS 节点。ros::init包含多个重载,对于当前使用的这个实现,第三个参数为节点的名字。因为传入了argcargv,所以可以通过命令行或者roslaunch,对节点名称重映射(remapping)。此外:

创建进程节点句柄。创建第一个节点句柄的时候,会执行节点初始化。析构最后一个节点句柄时,会清楚节点占用的资源(内存之类的)。

第一个参数是话题名称。这是告诉 master节点,我们将在主题 chatter 上发布 std_msgs/Stringarrow-up-right 类型的消息。这样做的目的是,让 master节点告诉任何订阅chatter话题的节点,我们将在这个话题上发布数据。

第二个参数是发布队列长度。他的意思是,在丢弃旧的消息之前最多存储 1000(发布队列长度)条消息。通常是可以自己设置,具体根据实际情况,因为在嵌入式编程中,任何一点小小的内存都是很珍贵的。

ros::NodeHandle::advertise() 返回一个 ros::Publisher 对象,它的作用:

  • 含有一个发布消息到刚才创建的话题chatter的方法publish()

  • 当程序运行到该对象作用范围之外,

ros::Rate用于设定我们需要的频率。他会根据上次Rate::sleep()的时间,自动推算休要休眠多长时间。在这里我们设定了 10 Hz 的频率。

默认情况下,roscpp 将安装一个能够接受Ctrl-C的 SIGINT 处理程序,ctrl+c将导致 ros::ok() 返回 false。

ros::ok() 将在以下情况下返回 false:

  • 收到 SIGINT (Ctrl-C);

  • 我们被另一个同名节点踢出网络;

  • ros::shutdown() 已被应用程序的另一部分调用;

  • 所有 ros::Node 句柄已被销毁;

一旦 ros::ok() 返回 false,所有 ROS 调用都会失败。

在ROS中广播消息,使用的是 msg filearrow-up-right 生成的消息类。ROS 内置了许多可使用的数据类型,目前我们在这里使用的是String消息类型,它仅含一个成员变量data

现在,我们可以准确的将消息广播给订阅该消息的节点。

ROS_INFO 是 ROS 中替代 printf/cout 的命令行输出方法。具体可以参见rosconsole documentationarrow-up-right

在当前这个简单的程序中,调用 ros::spinOnce() 可以说是多余的,因为我们不用处理任何回调。当然,如果在这个程序中,有订阅相关的内容,并且没有使用ros::spinOnce() ,那么回调函数就不会调用。所以,添加这一行语句没有问题的。

使用ros::Rate调用sleep()的目的是,休眠一段时间以保证 10hz 的发布频率。

完整注释版

订阅节点 Subscriber Node

思路

  1. 初始化 ROS 节点和句柄;

  2. 订阅 chatter 话题;

  3. 等待消息到达;

  4. 消息到达会调用回调函数,在回调函数编写消息处理代码。

cpp实现

代码解释

订阅程序代码中一些代码和发布程序是一样的,在下面就不再赘述。

chatter话题的数据到达时,回调函数chatterCallBack()将会被调用。消息使用 boost shared_ptrarrow-up-right 传递,意味着可以根据需要存储,不用担心在下文被删除,也不用复制基础数据。

master订阅chatter 话题。当新消息到达时,ROS 将会调用chatterCallback()

第一个参数仍然是话题名称。

第二个参数是接收队列长度,当数据发布的很快,而订阅方处理过慢时,最多存储该队列长度的数据。在这里长度为1000,也就是缓存超过1000,前面的旧数据将会丢弃。

NodeHandle::subscribe()返回ros::Subscriber对象,在取消订阅之前,都必须保持该对象存在。当该对象析构时,将自动取消订阅chatter话题。

ROS 提供了多个NodeHandle::subscribe() 版本,可以自定义回调函数、指定类成员函数或者任何 Boost 调用。roscpp overviewarrow-up-right 给出了更详细的解释。

ros::spin()进入一个循环,当消息到来时,调用回调函数。当 ros::ok()falseros::spin()也会退出。即,ctrl+cros::shutdown()等情况也适用 ros::spin()

ROS 还提供了其他处理调用的的方式。在 roscpp_tutorialsarrow-up-right 中有相关演示程序。更多信息可以参见 roscpp overviewarrow-up-right

完整注释版

编译节点

CMakeLists.txt

修改 CMakeLists.txtarrow-up-right 文件,找到下面语句修改,或者直接复制到文件末尾:

这将创建两个可执行文件,talkerlistener,默认情况下它们会进入你的开发空间arrow-up-right的包目录,默认位于 ~/<workspace_name>/devel/lib/<package_name>

这一步时确保在使用之前生成功能包的消息头。如果您在 catkin 工作空间中使用来自其他包的消息,您还需要将依赖项添加到它们各自的生成目标中,因为 catkin 并行构建所有项目。

我们可以直接调用可执行文件或者使用rosrun

它们没有放在 '/bin' 中,因为在将软件包安装到系统时会污染 PATH。 如果您希望您的可执行文件在安装时位于 PATH 上,您可以设置安装目标,请参阅:catkin/CMakeLists.txtarrow-up-right

更多描述信息可以参见 CMakeLists.txtarrow-up-right 文件:catkin/CMakeLists.txtarrow-up-right

catkin_make

修改完后,在命令行输入:

运行

首先,确保roscore的启动

在调用rosrun运行节点之前,确保设置了环境变量source 工作空间下的setup.sh

Publisher

打开第一个终端,运行talker

Subscriber

rosrun运行listener

Reference

Last updated