文章目录
- ROS机器人入门第五课:话题通信自定义msg
- 一、介绍
- 二、流程
- (一)定义msg文件
- (二)编辑配置文件
- (三)编译
- 三、话题通信自定义msg调用
- (一)调用流程
- 0.vscode配置
- 1.发布方
- 2.订阅方
- 3.权限设置
- 4.配置 CMakeLists.txt
- 5.执行
ROS机器人入门第五课:话题通信自定义msg
一、介绍
在 ROS 通信协议中,数据载体是一个较为重要组成部分,ROS 中通过 std_msgs 封装了一些原生的数据类型,比如:String、Int32、Int64、Char、Bool、Empty… 但是,这些数据一般只包含一个 data 字段,结构的单一意味着功能上的局限性,当传输一些复杂的数据,比如: 激光雷达的信息… std_msgs 由于描述性较差而显得力不从心,这种场景下可以使用自定义的消息类型
msgs只是简单的文本文件,每行具有字段类型和字段名称,可以使用的字段类型有:
-
int8, int16, int32, int64 (或者无符号类型: uint*)
-
float32, float64
-
string
-
time, duration
-
other msg files
-
variable-length array[] and fixed-length array[C]
ROS中还有一种特殊类型:Header
,标头包含时间戳和ROS中常用的坐标帧信息。会经常看到msg文件的第一行具有Header标头。
需求:创建自定义消息,该消息包含人的信息:姓名、身高、年龄等。
二、流程
- 按照固定格式创建 msg 文件
- 编辑配置文件
- 编译生成可以被 Python 或 C++ 调用的中间文件
(一)定义msg文件
功能包下新建 msg 目录,添加文件 person.msg
string name
uint16 age
float64 height
(二)编辑配置文件
- package.xml中添加编译依赖与执行依赖
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
<!--
exce_depend 以前对应的是 run_depend 现在非法
-->
2. CMakeLists.txt编辑 msg 相关配置
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
# 需要加入 message_generation,必须有 std_msgs
## 配置 msg 源文件
add_message_files(
FILES
Person.msg
)
# 生成消息时依赖于 std_msgs
generate_messages(
DEPENDENCIES
std_msgs
)
#执行时依赖
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES demo02_talker_listener
CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
# DEPENDS system_lib
)
(三)编译
ctrl + shift + B
编译后的中间文件查看:
Python 需要调用的中间文件(…/工作空间/devel/lib/python3/dist-packages/包名/msg)
后续调用相关 msg 时,是从这些中间文件调用的
三、话题通信自定义msg调用
需求:
编写发布订阅实现,要求发布方以1HZ(每秒1次)的频率发布自定义消息,订阅方订阅自定义消息并将消息内容打印输出。
分析:
在模型实现中,ROS master 不需要实现,而连接的建立也已经被封装了,需要关注的关键点有三个:
- 发布方
- 接收方
- 数据(此处为自定义消息)
(一)调用流程
- 编写发布方实现;
- 编写订阅方实现;
- 为python文件添加可执行权限;
- 编辑配置文件;
- 编译并执行。
0.vscode配置
为了方便代码提示以及误抛异常,需要先配置 vscode,将前面生成的 python 文件路径配置进 settings.json
{
"python.autoComplete.extraPaths": [
"/opt/ros/noetic/lib/python3/dist-packages",
"/xxx/yyy工作空间/devel/lib/python3/dist-packages"
]
}
1.发布方
这段代码是一个ROS(Robot Operating System)的Python节点,用于发布包含个人信息的消息。它导入了ROS的python库rospy
和自定义的消息类型person
。接下来,我会逐行提供详细的注释:
#! /usr/bin/env python
# - coding: utf-8 -*-
# 导入 rospy,这是编写 ROS 节点的python库。
import rospy
# 导入自定义的消息类型,person 是我们自定义的消息类型,它应该已经在 ROS 的 msg 包中定义。
from msg.msg import person
# __name__ == "__main__" 确保当这个脚本被执行而不是被导入时,以下代码块将被运行。
if __name__ == "__main__":
# 1. 初始化 ROS 节点,名为 "talker_person_p"。
rospy.init_node("talker_person_p")
# 2. 创建发布者对象,它将发布 person 类型的消息到 "chatter_person" 话题,队列大小设置为 10。
pub = rospy.Publisher("chatter_person", person, queue_size=10)
# 3. 组织要发布的消息,创建一个 person 类型的实例 p,并设置其属性。
p = person()
p.name = "zhangsan" # 设置人的名字为 "zhangsan"。
p.age = 18 # 设置年龄为 18。
p.height = 0.75 # 设置身高为 0.75 米。
# 4. 编写消息发布逻辑。
rate = rospy.Rate(1) # 设置一个发布频率,1Hz,每秒发布一次。
rospy.sleep(3) # 休眠3秒,给ROS系统一些启动时间。
# while 循环会在 ROS 节点没有被关闭的情况下持续运行。
while not rospy.is_shutdown():
pub.publish(p) # 发布消息,将 person 实例 p 发布到 "chatter_person" 话题。
rate.sleep() # 使用前面设置的频率来休眠,确保以 1Hz 的频率发布消息。
# rospy.loginfo 用于输出日志信息,这里输出了 person 实例 p 的所有信息。
rospy.loginfo("姓名:%s, 年龄:%d, 身高:%.2f", p.name, p.age, p.height)
现在,让我详细解释一下你提问中的ros
函数:
rospy.init_node("talker_person_p")
: 这个函数初始化一个ROS节点,节点的名称是"talker_person_p"
。在ROS中,节点是运行的基本单元,每个节点都有自己的名字。rospy.Publisher("chatter_person",person,queue_size=10)
: 这个函数创建一个Publisher对象,用于发布消息。它告诉ROS你想要发布“chatter_person”这个话题上的person
类型消息。queue_size
参数指定了消息队列的大小,这里设置为10,意味着如果数据处理不过来,它将缓存10条消息。rospy.Rate(1)
: 这个函数设置了一个频率(1Hz),用于定时发布消息。rospy.sleep(3)
: 使得节点休眠3秒钟。rospy.is_shutdown()
: 这个函数会检查你的ROS节点是否应该被关闭(例如,如果有人按下Ctrl+C或者如果ROS核心已经关闭)。pub.publish(p)
: 通过前面创建的pub
发布者对象,发布消息p
。rate.sleep()
: 根据之前定义的rate
来使程序休眠,以保持1Hz的发布频率。rospy.loginfo(...)
: 输出日志信息到命令行和ROS日志文件,便于调试和记录状态。
代码中需要改的地方:
from msg.msg import person
:from 包名.msg import 自定义msg文件名pub = rospy.Publisher("chatter_person", person, queue_size=10)
:pub = rospy.Publisher(“chatter_person”, 自定义msg文件名, queue_size=10)p = person()
:p = 自定义msg文件名()
2.订阅方
这段代码定义了一个ROS(Robot Operating System)的Python订阅节点,用于订阅包含个人信息的消息。它同样使用了rospy
库以及自定义的消息类型person
。让我为你逐行提供注释:
#! /usr/bin/env python
# - coding: utf-8 -*-
"""
订阅方:
订阅消息
"""
# 导入ROS的Python库rospy。
import rospy
# 从msg包导入自定义的消息类型,person必须已经在ROS的msg包中定义了。
from msg.msg import person
# 定义回调函数doPerson,它将在接收到消息时被调用。
def doPerson(p):
# 输出接收到的消息内容:人的名字、年龄和身高。
rospy.loginfo("接收到的人的信息:%s, %d, %.2f", p.name, p.age, p.height)
# 确保当脚本被直接执行时,下面的代码块会运行。
if __name__ == "__main__":
# 1. 初始化ROS节点,名称为"listener_person_p"。
rospy.init_node("listener_person_p")
# 2. 创建订阅者对象,订阅名为"chatter_person"的话题,消息类型为person。
# doPerson是收到消息时要调用的回调函数。queue_size=10指定了消息队列的大小。
sub = rospy.Subscriber("chatter_person", person, doPerson, queue_size=10)
# 4. 调用rospy.spin()使得节点保持运行直到节点被显式地关闭或者Ctrl+C被按下。
# 它让程序停留在一个循环中,等待和处理回调函数。
rospy.spin()
现在,让我解释一下代码中使用的ROS函数及其功能:
rospy.init_node("listener_person_p")
: 这个函数初始化一个ROS节点,节点的名称是"listener_person_p"。每个ROS节点都需要一个唯一的名字,这样它们就可以在ROS网络中被识别和通信。rospy.Subscriber("chatter_person", person, doPerson, queue_size=10)
: 这个函数创建一个订阅者对象,用于订阅名为"chatter_person"的话题上的消息。这里指定消息类型为person
,并且指定了一个回调函数doPerson
,该函数会在接收到消息时被自动调用。queue_size
参数用于控制消息队列的大小,防止处理速度跟不上接收速度导致的内存溢出。rospy.spin()
: 这个函数使得你的节点保持运行,等待消息的到来。它不会占用大量CPU资源,因为它会在没有消息到来时阻塞。这使得节点能够在接收到新消息时立即响应,同时也保持了低资源消耗。
简而言之,这段代码实现了一个ROS节点,它订阅了一个名为"chatter_person"的话题,用于接收包含个人信息的消息,并在接收到消息时通过日志输出这些信息。
3.权限设置
终端下进入 scripts 执行:chmod +x *.py
4.配置 CMakeLists.txt
catkin_install_python(PROGRAMS
scripts/pub.py
scripts/sub.py
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
5.执行
1.启动 roscore;
2.启动发布节点;
3.启动订阅节点。
4. 运行结果
PS:可以使用 rqt_graph 查看节点关系。