ROS学习笔记(三)—— 话题 topic详解

1. 话题 topic & 消息 message

参考:Understanding ROS Topics
ROS/Tutorials/ Writing a Simple Publisher and Subscriber (Python)
ROS学习笔记(三)—— 话题 topic详解_第1张图片

topic 实现了一种 发布/订阅(publish/subscribe)通信机制,这是一种在分布式系统中常用的数据交换方式。对于实时性、周期性的消息,使用topic来传输是最佳的选择。节点在发送数据到话题上之前,必须先声明(advertise)话题名和发布到该话题上的消息所具有的类型,然后就可以开始发布数据到这个话题上了。想要从话题上接收信息的节点可以通过向 ros Master 发出请求来订阅这个话题。订阅之后,该话题上的所有消息都会被转发到发出订阅请求的节点上。

一个节点可以订阅或发布多个话题,而不需要去了解另一个节点的具体细节。这种机制提供了一种方便的进程间通信方式,有效实现了不同功能节点之间的解耦。

消息是一种数据结构,支持多种数据类型(整形、浮点、布尔型、数组等),同时也支持消息的嵌套定义。ROS提供了大量的系统默认消息供用户使用,如geometry_msgs、sensor_msgs等,同时也支持用户定义专属数据结构的消息类型。

Communication on topics happens by sending ROS messages between nodes. For the publisher and subscriber to communicate, the publisher and subscriber must send and receive the same type of message. This means that a topic type is defined by the message type published on it.

话题就像一块黑板,publishers 将 message 写到黑板上,subscribers 就可以接收到黑板上的内容了。message 则规定了写到黑板上的内容的格式。

topic通信机制建立过程如下:

  1. Publisher 和 Subscriber 在节点管理器ROS Master 注册
    在系统中,节点与节点是没有任何关系的,都靠节点管理器来处理相关的请求与服务。publisher 节点需要向节点管理器ROS Master注册相关信息,包括节点的信息、需要发布的话题名等,然后节点管理器会记录下来相应的节点信息。同理 Subscriber 也需要向节点管理器ROS Master注册相关信息,这其实是启动两个节点时候就已经做了的事情。

  2. ROS Master 进行话题匹配
    因为 Publisher 与 Subscriber 节点都在 ROS Master 注册了信息,那么 ROS Master 就会发现二者有相同的话题信息,此时它就会向 Subscriber 发送 Publisher 节点的RPC地址信息,将订阅与发布话题的两个节点匹配在一起。

  3. Subscriber 向 Publisher 发送连接请求
    Subscriber 知道 Publisher 后,就会通过 RPC 主动向 Publisher 发送连接请求,传输订阅的话题名、消息类型以及通信协议。

  4. Publisher 确认连接请求
    Publisher 接收到 Subscriber 的连接请求后,继续通过RPC向 Subscriber 确认连接信息,同时发送自身相关的信息。

  5. Subscriber 尝试与 Publisher 建立网络连接
    Subscriber 接收到确认信息后,使用TCP尝试与 Publisher 建立网络连接。

  6. Publisher 向 Subscriber 发布消息
    成功建立连接后,Publisher开始向 Subscriber 发送话题消息数据。

在节点与节点建立连接前,他们的通信协议都是RPC,而这主要都是依赖于节点管理器的,在节点与节点建立连接后,就直接是使用TCP协议进行数据传输,而不需要依赖节点管理器,此时节点管理器允许被关闭,但在关闭后,其他节点就不能订阅、发布这个话题消息了。总之节点管理器ROS Master在节点建立连接的过程中起到了重要作用,但是并不参与节点之间最终的数据传输。
参考:深入了解ROS话题通信机制的过程

2. 编写一个 Publiser Node (python)

首先要在工作空间创建一个package:

cd ~/catkin_ws/src/
catkin_create_pkg topic_tutorials std_msgs rospy  # std_msgs rospy 是依赖项

例程 talker.py :创建一个 Publisher 节点 talker,持续向chatter 话题广播一条 message。

  1. 在 package 中创建一个 scripts 文件夹,来存放 python 脚本文件。
  2. 编写 talker.py
#!/usr/bin/env python
# -*- coding: utf-8
import rospy
from std_msgs.msg import String  

def talker():
    # ros 节点初始化,节点名为'talker'。
    # ros 中要求节点名是唯一的,如果启动了两个同名的 node,前一个 node 会被剔除。
    # anonymous=True 表示让 rospy 给该节点一个唯一的标记名,以确保不被新启动的 talker 节点剔除。
    rospy.init_node('talker', anonymous=True)
    # 创建一个Publisher。发布名为 `chatter` 的 topic。消息类型为 String (实际为std_msgs.msg.String),
    # 队列长度为 10. 如果订阅者没有快速接收已经排队的消息,为维持队列长度,前期进入队列的消息会被舍弃。
    pub = rospy.Publisher('chatter', String, queue_size=10)
    # 设置循环的频率
    rate = rospy.Rate(10) # 10hz
    while not rospy.is_shutdown():
        hello_str = "hello world %s" % rospy.get_time()
        rospy.loginfo(hello_str)
        # 在话题上发布消息
        pub.publish(hello_str)
        # 按照循环频率延时
        rate.sleep()

if __name__ == '__main__':  
    try:
        talker()
    except rospy.ROSInterruptException:
        pass

脚本注释:
第一行: #!/usr/bin/env python ,这就是所谓的 shebang,每个 Python ROS 节点的顶部都会有此声明。 告诉操作系统这是一个 python 文件,应该传递给一个 python 解释器。
第二行:# -*- coding: utf-8,防止因代码中出现中文注释而报错 SyntaxError: Non-ASCII character ‘\xe2’ in file。

  1. 配置 CMakeLists.txt的编译规则。确保安装了正确的 python 脚本,并且使用了正确的 python 解释器。
catkin_install_python(PROGRAMS scripts/talker.py scripts/listener.py
  DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)

上述程序顺便添加了订阅者节点程序 scripts/listener.py。

  1. 在 package.xml 添加依赖。下述语句在功能包创建时已经自动添加。
<build_depend>rospybuild_depend>
<build_depend>std_msgsbuild_depend>

<build_export_depend>rospybuild_export_depend>
<build_export_depend>std_msgsbuild_export_depend>

<exec_depend>rospyexec_depend>
<exec_depend>std_msgsexec_depend>

by the way,上述语句相当于:

<depend>rospydepend>
 <depend>std_msgdepend>
  1. 编译功能包:
cd ~/catkin_ws
catkin_make
  1. 运行 Publisher 节点。注意:如果你手写了这个节点文件,运行这个文件前需要在终端使用chmod 命令给他增加运行权限:chmod +x talker.py
roscore
# new terminal
rosrun topic_tutorials talker.py
rostopic -h # 查看 rostopic 指令的帮助信息
rostopic list  # 查看系统中有哪些可用话题
rostopic echo topic_name -n 5 # 查看话题上发布的消息, -n 5 表示只打印 5 条消息。
rostopic hz topic_name # 查看发布频率
rostopic info topic_name # 查看 topic 的属性信息

也可以使用命令行工具发布消息到一个话题上:

rostopic pub topic_name topic_type message

rosrun rqt_plot rqt_plot 绘制 topic 消息曲线。

3. 编写一个 Subscriber Node (python)

订阅者使用回调函数 (callback) 处理接收到的消息。一旦一个节点订阅了一个话题,每次消息到达时,相应的回调函数就会被调用,并使用接收到的消息作为它的参数。

例程 listener.py:创建一个 listener 节点 订阅 chatter话题。

#!/usr/bin/env python
# -*- coding: utf-8
import rospy
from std_msgs.msg import String

def my_callback(data):
    rospy.loginfo(rospy.get_caller_id() + "I heard %s", data.data)
    
def listener():
    rospy.init_node('listener', anonymous=True)
    # 创建一个 Subscriber,订阅 ”chatter“ 话题,消息类型为 String,回调函数为 my_callback。
    # 在幕后,Subscriber 会将这些信息传递给节点管理器,并尝试与 Publisher 建立连接。
    # 如果订阅的话题不存在,或者存在类型错误,将不会输出任何报错信息,节点将只是等待。
    rospy.Subscriber("chatter", String, my_callback)
    # spin() simply keeps python from exiting until this node is stopped
    # 循环等待会话题消息,若接收到消息进入 callback 回调函数
    rospy.spin()

if __name__ == '__main__':
    listener()

增加运行权限:chmod +x listener.py。运行 listener.py 文件

rosrun topic_tutorials listener.py

4. 消息的定义与使用

ROS 的 std_msgscommon_msgs 包提供了丰富的内建消息类型,在std_msgs中定义了一些基本的类型,在 ros wiki msg 页面有详细文档。其他消息类型都是由这些基本类型构成的。有时候内建消息类型不够用,我们也可以定义自己的消息类型。

ros 消息由每个 ros 包的 msg 目录中的消息定义文件说明。这些文件将会编译成与语言相关的消息文件,这样你就能在代码中使用了。这意味着 如果你定义了自己的消息类型,即使你使用的是解释型语言,如 python,你仍然需要运行 catkin_make,否则 python 将无法找到你定义的消息类型。

4.1 定义一个自己的消息类型

消息定义文件通常放在功能包的 msg 文件夹下,后缀名为.msg。消息定义文件包含一系列构成此消息的类型,这些类型可以是 ros 内建的消息类型也可以是自定义的消息类型。

自定义话题消息的主要流程:

  1. msg文件中定义好数据类型;
  2. 在package.xml中添加功能包依赖;
  3. 在CMakeLists.txt添加编译选项;
  4. catkin_make编译即可使用。

例如:定义一个描述个人信息的消息类型:

  1. 在功能包的文件夹下创建一个 Person.msg 文件(通常 ROS 消息名称都是首字母大写且没有下划线的):
string name
uint8  age
uint8  sex

uint8 unknown = 0
uint8 male    = 1
uint8 female  = 2
  1. 在package.xml中添加两个依赖:编译依赖和执行依赖。确保告知构建系统新消息的定义。
<build_depend>message_generationbuild_depend>
<run_depend>message_runtimerun_depend>
  1. 在 CMakeLists.txt 文件中添加编译选项。
    1)在find_package()末尾添加message_generation,这样 catkin 就知道去找寻message_generation 包了。
    2)告知 catkin 我们将在运行时使用消息,即在catkin_package()调用末尾添加 message_runtime
    3)在add_message_files()末尾添加消息定义文件,来告知catkin 我们想要编译的消息。
    4)确保generate_messages()包含了消息所依赖的所有依赖项。
find_package(catkin REQUIRED COMPONENTS
		roscpp
		rospy
		std_msgs
		message_generation  # add message_generation here
)
...
catkin_packages(
		CATKIN_DEPENDS message_runtime # this will not be the only thing here
...
add_message_files(FILES Person.msg)
generate_messages(DEPENDENCIES std_msgs)
  1. 到 catkin 工作空间的根目录,运行 catkin_make。这将产生一个和消息定义文件同名的消息类型。
    在工作空间 devel/include 文件夹中有了一个Person.h的头文件。

4.2使用自定义的消息类型

C++

include ”package_name/Person.h“

Python

from package_name.msg import Person 

5. 让话题发布者与订阅者协同工作

一个节点也可以同时是发布者和订阅者。或同时拥有多个订阅和发布。实际上ros节点最常做的事情技术就是传递消息,并在消息上进行运算。例如,一个节点可能会订阅一个摄像机图像的话题,检测其中的人脸后在另一个节点上发布人脸的位置。
例程:接收到一个数字后,将数字翻倍,然后发布出去。

#!/usr/bin/env python
import rospy
from std_msg.msg import Int32

rospy.init_node('doubler')

def callback(msg):
	doubled = Init32()
	doubled.data = msg.data * 2
	pub.publisth(doubled)

sub = rospy.Subscriber('number',Int32,callback)
pub = rospy.Publisher('doubled',Int32)

rospy.spin()

你可能感兴趣的:(Ros)