ROS通信机制——发布/订阅者模型

目录

        • 发布/订阅者模型
          • (一)发布者
          • (二)订阅者
          • (三)综合
          • 参考资料

ROS (Robot Operating System),即机器人操作系统,是为了加快机器人的开发效率,提高机器人软件代码的复用率,用于机器人算法快速仿真验证的一个软件平台。

ROS已经成为机器人领域的一个普遍标准。换句话说,未来的任何一个机器人工程师都必须会ROS,越来越多的机器人也将基于ROS来进行开发。

ROS主要可以由以下四个部分组成: ROS = 通信机制 + 开发工具 + 应用功能 + 生态系统。

  • 通信机制:ROS的通信机制属于分布式松耦合通信机制,即各通信对象(ROS中指节点)之间相对独立。
  • 开发工具:主要有命令行工具,Qt工具箱,Rviz三维可视化软件,Gazebo物理仿真平台等。
  • 应用功能:包括机器人的主要功能,如即时定位与地图构建(SLAM),导航(Navigation),运动规划(MoveIt)等。
  • 生态系统:主要是指ROS的社区资源。

ROS通信机制中有两大通信模型:

  • 异步通信模型:Publish and Subscribe model (发布/订阅者模型)
  • 同步通信模型:Server and Client model (服务/客户端模型)

发布/订阅者模型

发布/订阅者模型是基于话题(topic)和消息(message)来实现的。发布者负责发布某一话题topic,而话题的内容就是消息message,其有属于自己的消息类型。当某个话题被发布后,该话题的订阅者便会接收到该消息。

(一)发布者

举个例子。

我们打算编写这样一个节点:该节点名为“velocity_publisher”,负责发布turtlesim仿真器的海龟速度消息,而海龟仿真器负责订阅该话题消息。

ROS通信机制——发布/订阅者模型_第1张图片

再编写程序之前,我们先来看看ROS的海龟仿真器turtlesim。

终端输入:roscore,启动ros_master。

终端输入:rosrun turtlesim turtlesim_node,启动海龟仿真器,界面如下。

ROS通信机制——发布/订阅者模型_第2张图片

终端输入:rostopic list,查看当前环境的话题消息。

ROS通信机制——发布/订阅者模型_第3张图片

可见,当前环境中已经有了话题/turtle1/cmd_velturtle1表示仿真器中产生的第一只海龟,cmd_vel表示速度指令。

终端输入:rostopic info /turtle1/cmd_vel,获取该话题的具体信息。
ROS通信机制——发布/订阅者模型_第4张图片

通过终端输出,我们可以知道话题的三个重要信息:消息类型、发布者和订阅者。

turtle1/cmd_vel话题消息的类型是geometry_msgs/Twist,发布者无,订阅者是/turtlesim。即,/turtlesim已经自动订阅了话题消息/turtle/cmd_vel,我们需要做的仅仅是编写一个发布/turtle1/cmd_vel的节点。

终端输入:rosmsg show geometry_msgs/Twist ,获取Twist消息的具体消息格式。

ROS通信机制——发布/订阅者模型_第5张图片

Twist消息格式主要分为两部分:linearangular。其中,linear指海龟在空间三个维度上的线速度,angular指海龟在空间三个维度上的角速度。

首先,我们选择C++来编写程序,直接上代码,,详细可参考代码中的注释。

#include               //导入ros功能包
#include   //导入geometry_msgs::Twist消息类型

/*
  功能:创建一个名为“velocity_publisher”的节点,负责发布海龟的速度指令
*/

int main(int argc,char **argv)
{
   //创建一个名为“velocity_publisher”的节点
   ros::init(argc,argv,"velocity_publisher");

   //创建节点句柄,用于管理节点对象
   ros::NodeHandle n;
   /*
      创建一个发布者对象: turtle_vel_pub,负责发布海龟速度消息
      消息类型:geometry_msgs::Twist(ros定义的关于海龟的速度消息类型)
      消息名字:/turtle1/cmd_vel
      消息队列长度:10
   */
   ros::Publisher turtle_vel_pub = n.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel",10);

   //创建Rate对象,设置速度消息发布频率 单位Hz 10次/s 
   ros::Rate pub_rate(10);

   while (ros::ok())
   {
      //创建消息对象,并初始化数据
      geometry_msgs::Twist vel_msg;
      vel_msg.linear.x = 0.3;
      vel_msg.angular.z = 0.3;

      //发布速度消息
      turtle_vel_pub.publish(vel_msg);

      //打印日志信息
      ROS_INFO("Publishing turtle velocity command:[%0.2f  m/s,%0.2f rad/s]",vel_msg.linear.x,vel_msg.angular.z);

      //配合发布频率进行自适应延时
      pub_rate.sleep();
   }
   return 0;
}

编写好C++代码后,我们还需要在CMakeLists.txt中配置编译规则。

  • 生成可执行文件
  • 设置链接库
add_executable(velocity_publisher src/velocity_publisher.cpp)
target_link_libraries(velocity_publisher ${catkin_LIBRARIES})

终端输入:

roscore

rosrun turtlesim turtlesim_node

rosrun learning_topic velocity_publisher

运行结果:

ROS通信机制——发布/订阅者模型_第6张图片

发现了嘛,我们的小海龟已经在做圆周运动了。

终端输入:rqt_graph ,利用Qt工具箱得到当前环境的计算图。

ROS通信机制——发布/订阅者模型_第7张图片

通过rqt_graph我们可以直观的了解当前环境中各个节点之间的关系。

python 版代码

#!/usr/bin/env python
# _*_ coding:utf-8 _*_

"""
func: creating a node to publish a topic of /turtle1/cm_vel
"""

import rospy    # 导入rospy库
from geometry_msgs.msg import Twist  # 导入Twist消息类型


def velocity_publisher():
    # 创建一个名为“velocity_publisher”的节点
    rospy.init_node("velocity_publisher", anonymous=True)

    # 创建一个发布者:turtle_vel_pub
    # 消息名称:/turtle1/cmd_vel
    # 消息类型:Twist
    # 消息队列长度:10
    turtle_vel_pub = rospy.Publisher("/turtle1/cmd_vel", Twist, queue_size=10)

    # 创建消息发布频率对象,10次/s
    pub_rate = rospy.Rate(10)

    while not rospy.is_shutdown():
        # 创建消息,并初始化数据
        vel_msg = Twist()
        vel_msg.linear.x = 0.3
        vel_msg.angular.z = 0.3

        # 发布消息
        turtle_vel_pub.publish(vel_msg)

        # 打印日志信息
        rospy.loginfo("Publishing turtle velocity commanding: [% 0.2f m/s, %0.2f rad/s]", vel_msg.linear.x,
                      vel_msg.angular.z)

        # 按照发布频率进行延时
        pub_rate.sleep()


if __name__ == "__main__":
    try:
        velocity_publisher()
    except rospy.ROSInterruptException:
        pass

小提示:在运行.py文件之前,确保右击文件,在属性中修改.py可作为可执行文件执行。

这里,我们小结一下编写节点,发布话题的步骤:

  • 创建节点(节点名字)
  • 创建话题发布者对象(话题名,话题类型,队列长度等)
  • 设置话题发布频率
  • 创建话题消息,并初始化数据
  • 发布者发布话题
  • 延时
(二)订阅者

前面我们已经实现了编写一个节点用来发布海龟的速度指令信息。这一节,我们将编写一个节点,负责订阅海龟的位置信息。

ROS通信机制——发布/订阅者模型_第8张图片

不知道你们有没有发现在启动海龟仿真器turtlesim后,话题消息列表(见前面)中出现了/turtle1/pose这个话题?那我们来看看它的具体信息。

终端输入:

roscore

rosrun turtlesim turtlesim_node

rostopic info /turtle1/pose

终端输出结果如下:

ROS通信机制——发布/订阅者模型_第9张图片

可知,话题消息/turtle1/pose的发布者是turtlesim,订阅者无。所以,我们需要做的仅仅是编写一个节点,负责订阅海龟的位置信息即可。

终端输入:rosmsg show geometry_msgs/Pose

终端输出:

ROS通信机制——发布/订阅者模型_第10张图片

可知,Pose话题消息格式包含两个部分:positionorientation,其中position代表三维空间中的位置,orientation代表的是三维空间中的方位。

C++代码如下:

#include           // 导入ros cpp库
#include "turtlesim/Pose.h"   // 导入Pose消息类型

// 订阅者注册的回调函数
void poseCallback(const turtlesim::Pose::ConstPtr &msg)
{
   //将接收到的消息打印出来,这里只显示平面二维坐标
   ROS_INFO("The turtle pose: x:%0.6f, y: %0.6f",msg->x,msg->y);
}


int main(int argc,char **argv)
{
    // 创建节点
    ros::init(argc,argv,"pose_subscriber");

    // 创建节点句柄
    ros::NodeHandle n;
    /*
      创建一个订阅者:turtle_pose_sub
      订阅消息名称:/turtle1/pose
      订阅消息类型:Pose
      消息队列长度:10
      回调函数:poseCallback
    */
    ros::Subscriber turtle_pose_sub = n.subscribe("/turtle1/pose",10,poseCallback);

    //循环等待回调函数
    ros::spin();

    return 0;
}

同样,在CMakeLists.txt配置编译规则

add_executable(pose_subscriber src/pose_subscriber.cpp)
target_link_libraries(pose_subscriber ${catkin_LIBRARIES})

终端输入:

roscore

rosrun turtlesim turtlesim_node

rosrun learning_topic velocity_publisher

rosrun learning_topic pose_subscriber

终端输出:

ROS通信机制——发布/订阅者模型_第11张图片

我们可以看到,小海龟在做圆周运动,同时它的位置也被实时输出到终端。

终端输入:rqt_graph,查看计算图。

ROS通信机制——发布/订阅者模型_第12张图片

现在来总结一下,编写一个节点,实现订阅者的步骤:

  • 创建节点(配置节点名)
  • 创建订阅者,订阅话题消息(话题名,话题类型,回调函数)
  • 编写回调函数
  • 循环等待回调函数

python版本代码

#!/usr/bin/env python
# _*_ coding:utf-8 _*_

"""
func: subscribing the pose information of a turtle
"""

import rospy   # 导入rospy
from turtlesim.msg import Pose    # 导入Pose类型


def poseCallback(msg):
    # 打印位置信息
    rospy.loginfo("The turtle's pose: x: %0.6f, y:%0.6f", msg.x, msg.y)


def pose_subscriber():
    # 创建一个名为"pose_subscriber"的节点
    rospy.init_node("pose_subscriber", anonymous=True)

    # 订阅位置信息
    rospy.Subscriber("/turtle1/pose", Pose, poseCallback)

    # 循环等待回调函数
    rospy.spin()


if __name__ == "__main__":
    pose_subscriber()
(三)综合

前面,我们分别单独介绍了如何编写一个节点实现发布者发布消息和订阅者订阅消息。但是,我们使用的都是ROS库封装好的消息结构,那我们可不可以定义自己的话题消息结构,然后实现发布和订阅呢?

答案是可以的!

ROS提供了一套机制,让用户可以定义自己想要的话题消息结构。

首先,我们在功能包中创建一个msg文件夹,然后在该文件夹下创建一个Person.msg文件,在该文件中我们添加以下内容:

string name
string sex
uint8 age

Person.msg中我们定义了三个数据:name 、sex、age,用来表征一个人的信息。

接下来我们需要在CMakeLists.txt和package.xml中添加相关内容进行配置。

在CMakeLists.txt中添加:

find_package( ...... message_generation)

add_message_files(FILES Person.msg)
generate_messages(DEPENDENCIES std_msgs)

catkin_package(...... message_runtime)

在package.xml中添加:

<build_depend>message_generationbuild_depend>
<exec_depend>message_runtimeexec_depend>

到此,我们的自定义消息结构便配置完成了,然后在工作空间下进行编译。

终端输入:catkin_make

编译后,我们在/devel/include/learning_topic目录下可以发现ROS生成了一个Person.h头文件,该文件中的内容就是一些关于我们自定义消息结构的相关配置说明。

ROS通信机制——发布/订阅者模型_第13张图片

现在我们来检查以下自己创建的消息类型是否成功。

终端输入:rosmsg show learning_topic/Person

在这里插入图片描述

可见,我们已经成功定义了自己的话题消息结构learning_topic/Person

接下来,便是编写发布者节点和订阅者节点了,具体操作步骤同(一)(二),这里就不详细介绍了,具体参考代码。

发布者:person_publisher.cpp

/*
  func: 发布/person_info话题消息,自定义消息类型learning_topic::Person
*/
#include 
#include "learning_topic/Person.h"  //加入自定义消息结构头文件Person.h

int main(int argc,char **argv)
{
   //创建节点
   ros::init(argc,argv,"person_publisher");
   //创建节点句柄
   ros::NodeHandle n;
   /*
   创建发布者:person_info_pub
   消息名称:/person_info
   消息类型:learning_topic
   消息队列长度:10
   */
   ros::Publisher person_info_pub = n.advertise<learning_topic::Person>("/person_info",10);

   //设置消息发布频率
   ros::Rate pub_rate(10);

   while(ros::ok())
   {
       //创建消息,并初始化数据
       learning_topic::Person person_msg;
       person_msg.name = "Steve";
       person_msg.sex = "male";
       person_msg.age = 25;

       //发布消息
       person_info_pub.publish(person_msg);

       //打印日志信息
       ROS_INFO("publishing person info: name:%s sex: %s age:%d",person_msg.name.c_str(),person_msg.sex.c_str(),person_msg.age);

       //按照发布频率延时
       pub_rate.sleep();
   }
   return 0;
}

订阅者:person_subscriber.cpp

/*
  func:订阅话题消息/person_info
*/

#include 
#include "learning_topic/Person.h"

void personCallback(const learning_topic::Person::ConstPtr &msg)
{
   //打印接收到的信息
   ROS_INFO("Subscribing peron info: name %s  sex:%s  age:%d",msg->name.c_str(),msg->sex.c_str(),msg->age);
}


int main(int argc,char **argv)
{
    //创建节点
    ros::init(argc,argv,"person_subscriber");

    //创建节点句柄
    ros::NodeHandle n;
    /*
      创建订阅者:person_info_sub
      消息名称:/person_info
      回调函数:personCallback
    */
    ros::Subscriber person_info_sub = n.subscribe("/person_info",10,personCallback);

    //循环等待回调函数
    ros::spin();

    return 0;
}

配置编译规则

add_executable(person_publisher src/person_publisher.cpp)
target_link_libraries(person_publisher ${catkin_LIBRARIES})
add_dependencies(person_publisher ${PROJECT_NAME}_generate_messages_cpp)

add_executable(person_subscriber src/person_subscriber.cpp)
target_link_libraries(person_subscriber ${catkin_LIBRARIES})
add_dependencies(person_subscriber ${PROJECT_NAME}_generate_messages_cpp)

在工作空间目录下编译

catkin_make

终端输入

roscore
rosrun learning_topic person_subscriber
rosrun learning_topic person_publisher

运行效果:

ROS通信机制——发布/订阅者模型_第14张图片

终端输入:rqt_graph,查看当前环境的计算图。

ROS通信机制——发布/订阅者模型_第15张图片

到此,我们便是简单地介绍了ROS的发布/订阅者模型。接下来,我们将会学习ROS的另外一大通信模型:服务/客户端模型。

参考资料

古月居

你可能感兴趣的:(ROS,ROS,通信机制,发布/订阅者模型)