ROS基础(一):ROS通讯之话题(topic)通讯

第一章:ROS通讯之话题(topic)通讯

ROS提供的通讯系统分为两大类:基于TCPROS/UDPROS的Node节点间的进程通讯;基于Nodelet的进程内通讯。所有的软件功能和相关工具的实现都是基于以上通讯。

ros的底层与核心是分布式通讯机制。ROS所有的软件功能和工具都建立在这种分布式通讯机制上。ROS下最核心的三种核心通讯机制:

  • 话题通讯机制
  • 服务通讯机制
  • 参数管理机制

本章我们详解最最最常见的,做ros开发每天都会打交道的话题通讯机制

一、topic通讯之基础篇

topic通讯的具体实现是通过node之间传递message进行信息传输。

1. Node Master大管家

node master在整个通讯架构中相当于管理中心,是管理整个node通讯的大管家。node首先在master处进行注册,之后master会将该node纳入整个ROS程序中。当ROS程序启动时,第一步首先启动master,然后节点管理器处理依次启动node。

master

功能:

  • 节点间的相互查找,建立连接
  • 为系统提供参数服务器,管理全局参数

在终端启动node master的命令:

roscore

roscore

启动后可以看到对应参数与节点信息。

2. Node节点

节点就是ros中执行运算任务的进程,一个功能模块通常由多个节点组成。多个节点同时运行,之间进行信息交换与运行时,构成节点关系图。

就topic通讯而言,所对应的节点具体分为发布节点publisher和订阅节点subscriber.

publisher用于发布对应的topic话题,subscriber用于订阅publisher发布的消息。

3. message 与topic

massage是节点之间传递的信息的数据格式。ros支持标准数据类型,同样也支持数据类型的嵌套以及自定义消息类型。ros本身也定义了一整套用于表征机器人的数据格式,包括速度、位姿、轨迹、点云等等。

topic是传递信息的名称。无论node是发布者还是订阅者,他们之间相互并不了解,他们在各取所需的时候,寻找的都是topic名称。多个node可以往同一个topic话题上发布信息,同样地,多个node也可以订阅同一个topic。他们的关系如下如所示:
多个节点发布与订阅

4. 小结

在这里插入图片描述

两个node之间建立数据通讯需要经过上图所示的7步骤:

  1. talker注册:通过1234端口想master注册信息,其中包括话题名
  2. listener注册
  3. ros master信息匹配:通过listener的订阅信息,在注册列表中查找,没有找到发布者,就等待发布者的加入;找到的话,就通过RPC向listener发布talker的RPC地址信息。
  4. listener发送连接请求:根据master给的地址,通过RPC向talker发送连接请求、话题名、消息类型、通讯协议
  5. talker确认连接请求:通过RPC向listener确认连接信息,其中包含TCP地址
  6. listener尝试与talker建立网络连接
  7. talker向listener发布数据

两点需要注意

  1. 前五个步骤都是RPC通信协议,最后才用到TCP
  2. master在节点建立连接的过程中起到关键作用,但是不参与节点间最终的数据传输

5. 实例

接下来,我们通过操纵小海龟案例,具体学习日常使用topic通讯过程中常用到的几个命令:

首先roscore将大管家启动,然后分别使用rosrun命令启动两个node节点:

rosrun turtlesim turtlesim_node 
rosrun turtlesim turtle_teleop_key 

启动之后,我们通过命令rosnode list来查看所有启动的节点:

在这里插入图片描述
可以看到其中除了master大管家负责的/rosout节点外,我们依次启动了另外的两个节点:
分别名为**/turtlesim节点和/teleop_turtle**节点,第一个节点作用是启动小海龟仿真,第二个节点作用是通过发布对应topic控制小海龟移动,效果如下:

在这里插入图片描述
到底是怎么实现的控制呢,我们来详细分析。

首先输入rosrun rqt_graph rqt_graph查看节点连接图:
node_connect
如上图所示,很清晰表明,第二个节点**/teleop_turtle通过发布名为/turtle1/cmd_vel的topic来给到订阅者/tuetlesim**.

其中关键topic的名字是/turtle1/cmd_vel,通过命令rostopic info /turtle1/cmd_vel具体查看该topic的相关信息:

在这里插入图片描述
上图具体说明了,该topic的数据类型为geometry_msgs/Twist,对应的publisher和subscribers也和节点流通图一一对应。

如果我们想知道具体数据类型中包括那些信息,可以通过如下命令查询:

rosmsg show geometry_msgs/Twist

在这里插入图片描述该命令输出结果如上,数据中具体包括三轴线速度和三轴角速度,以此用来控制小乌龟行动。

到现在为止,我想我已经说清楚ros话题通讯模式了,同样,我们学习了几个非常有用的命令,来启动node,查询node节点图,查看topic和msg等。

最后,我们通过命令来实现往对应的topic上pub消息控制机器人运动。

键入如下命令让小乌龟持续转起来,最后-r 1的参数含义是,按照1hz的频率pub这条topic:

rostopic pub /turtle1/cmd_vel geometry_msgs/Twist "linear:
  x: 1.0
  y: 0.0
  z: 0.0
angular:
  x: 0.0
  y: 0.0
  z: 1.0" -r 1

那最终实现的样子如下:

在这里插入图片描述

二、topic通讯之进阶篇

本部分,我们自定义一个message格式,然后使用ros提供的python和c++接口分别创建两个node节点作为publisher和listener,用来学习python接口下和c++接口下,如何创建node节点,如何订阅和发布topic

1. 创建learn_ topic功能包

首先需要创建对应的功能包,在home目录下输入如下命令:

mkdir -p catkin_ws/src
cd catkin_ws/src
catkin_create_pkg learn_topic std_msgs rospy roscpp
cd ..
catkin_make

我们建立了名为catkin_ws的工作空间,其中包含名为learn_topic的功能包,功能包依赖std_msgsrospyroscpp三个功能包。

最后我们使用catkin_make命令编译环境,自动在工作空间目录下生成develbuild两个文件夹。

创建好功能包之后,如果想运行,需要设置环境变量

 source devel/setup.bash

为测试是否已经添加环境变量,使用如下命令查询:

rospack find learn_topic

在这里插入图片描述
如果出现上述对应路径,说明环境变量设置成功。

上述设置环境变量的方法,仅仅在当前终端环境下有效,当我们重开新的terminal就需要重新source

为实现一劳永逸的配置,我们可以使用如下命令将之写道bashrc文件中:

 echo "source ~/catkin_ws/devel/setup.bash" >> ~/.bashrc

2. 自定义message

我们来构建一个名称为person.msg的message文件,其中包括两个信息:

string	name
uint8	age

然后在工作空间下的src文件夹中新建msg文件夹,专门来放我们的自定义message. 当前空间目录如下:

在这里插入图片描述要想使自定义的msg工作起来,还需要修改CMakeLists.txt文件和package.xml文件。

package.xml文件的文件末尾添加如下语句,实现对应功能包的编译和运行的相关依赖:

  <build_depend>message_generation</build_depend>
  <exec_depend>message_runtime</exec_depend>

CMakeLists.txt文件中,需要进行如下修改:

第一块:修改find_package部分,确保编译时找到对应文件


find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
)

第二块:设置msg文件

 add_message_files(
   FILES
   person.msg
 )

 generate_messages(
   DEPENDENCIES
   std_msgs
 )

第三块:catkin依赖部分

如果我们编写的ros程序不打算给别人使用,这块就无所谓。

catkin_package(
...
  CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
...
)

上述修改完成之后,回到工作空间下,再次运行命令catkin_make进行编译

最后查询是否成功,可以使用如下命令:

rosmsg show learn_topic/person 

一切正常的话,应该会出现下图:

在这里插入图片描述

关于ros中cmakelists文件中内容的具体含义,建议参考我之前的这篇博文:

https://blog.csdn.net/allenhsu6/article/details/112345029

3. 创建node实现message的发布与订阅(C++版本)

接下来,我们依赖上述定义好的person.msg来实现一个简单的publisher和subscriber

在功能包中src文件下分别建立两个cpp文件,如图所示:
在这里插入图片描述

publisher

编辑cpp_talker.cpp文件:


#include <ros/ros.h>
#include <learn_topic/person.h>

int main(int argc, char **argv){
    
    

    ros::init(argc, argv, "cpp_talker");
    ros::NodeHandle nh;

    ros::Publisher pub = nh.advertise<learn_topic::person>("/person_topic", 1);

    ros::Rate loop_rate(1);

    learn_topic::person man;
    man.age = 0;
    man.name = "allen";
    ROS_INFO("pub: Here's a man named, how old is he? %s", man.name.data());
    while (ros::ok()){
    
    
        man.age++;
        pub.publish(man);
        loop_rate.sleep();
    }
    return 0;
}

subscriber

编辑cpp_listener.cpp文件:

#include <ros/ros.h>
#include "learn_topic/person.h"

void getAgeCallback(const learn_topic::personConstPtr &person){
    
    

    ROS_INFO("answer: %s's age is %d", person->name.data(), person->age);
}

int main(int argc, char **argv){
    
    

    ros::init(argc, argv, "cpp_listener");
    ros::NodeHandle nh;

    ros::Subscriber sub = nh.subscribe("/person_topic",1, getAgeCallback);
    ros::spin();
    return 0;
}

运行两个node

首先在cmakelists文件中输入如下内容:

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

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

然后先在工作空间catkin_make, 再在两个终端分别启动talker和listener节点:

rosrun learn_topic cpptalker_node
rosrun learn_topic cpplistener_node 

终端输出对应结果:

在这里插入图片描述

查看node节点图,没有问题
在这里插入图片描述

4. 创建node实现message的发布与订阅(python版本)

ros同样提供了python接口,我们用python再将上述实验实现一次,方便大家学习用python实现node节点的时候应该注意的事项

首先,在scripts文件夹中新建两个文件,分别命名为python_talker.pypython_listener.py,之后的整个功能包文件目录如下:

在这里插入图片描述

publisher

#!/usr/bin/env python
#coding=utf-8
import rospy
#倒入自定义的数据类型
from learn_topic.msg import person

def talker():
    pub = rospy.Publisher('person_topic', person, queue_size=10)
    rospy.init_node('pytalker', anonymous=True)
    rate = rospy.Rate(1)
    name='allen'
    rospy.loginfo('Talker: this man named %s', name)
    i = 0
    while not rospy.is_shutdown():
            pub.publish(person('allen',i))
            i = i+1
            rate.sleep()

if __name__ == '__main__':
    talker()

subscriber

#!/usr/bin/env python
#coding=utf-8
import rospy
#倒入自定义的数据类型
from learn_topic.msg import person

def callback(person):
    rospy.loginfo('Listener: %s''s age is %d', person.name, person.age)

def listener():
    rospy.init_node('pylistener', anonymous=True)
    rospy.Subscriber('person_topic', person, callback)
    rospy.spin()

if __name__ == '__main__':
    listener()


运行俩个node节点

python是脚本语言不需要编译,运行前将scripts文件夹下的py文件都给执行权限:

sudo chmod +x *.py

然后分别运行两个node节点:

rosrun lea_topic python_talker.py 

 rosrun learn_topic python_listener.py 

运行结果:

在这里插入图片描述

查看node节点图,没有问题
在这里插入图片描述

三、总结

关于topic通讯的学习,我们告一段落,基本能够涉及到的都已经覆盖,接下来,我们会进入第二章service通讯的学习。

ROS基础(二):ros通讯之服务(service)机制

猜你喜欢

转载自blog.csdn.net/allenhsu6/article/details/112334048