前言
本人ROS小白,利用寒假时间学习ROS,在此以笔记的方式记录自己每天的学习过程。争取写满15篇(2/15)。
环境:Ubuntu20.04、ROS1:noetic
环境配置:严格按照下方学习链接的教程配置,基本一次成功。
学习链接:【Autolabor初级教程】ROS机器人入门
对应链接文档:ROS机器人入门课程《ROS理论与实践》
笔记绝大部分代码使用Python语言编写。
本期关键词:理论模型,发布,订阅,自定义msg
话题通信理论模型
- 理论模型主要分为三个部分:
- ROS Master(管理者)
- Talker(发布者,对应publish)
- Listener(订阅者,对应subscribe)
- 形象了解话题通信:ROS的主要通讯方式:Topic话题与Message消息
- 涉及到7个步骤:
- 第0步:Talker向Master注册
- 第1步:Listener向Master注册
- 第2步:Master向Listener发送匹配信息
- 第3步:Listener向Talker发送请求
- 第4步:Talker确认请求(以上均是RPC协议)
- 第5步:Listener与Talker件里连接(以下均是TCP协议)
- 第6步:Talker向Listener发送消息
- 以上步骤了解即可,编程时基本不会用到,我们需要关注的主要是以下三个内容:
- 发布者
- 订阅者
- 消息载体
自定义消息类型
这里先简要概述话题通信自定义msg的内容,接下来的“发布者实现“和“订阅者实现”会结合代码来演示如何使用自定义消息类型msg。
- ROS中有原装的数据类型,在
std_msgs
依赖包里,例如String
、Int32
、Char
等(该依赖包里的数据类型首字母就是大写的)。 - 在某些情况下,
std_msgs
包里的数据类型是不能满足我们的要求的,ROS允许用户自定义数据类型,来满足用户特定场景下的需求。 - 自定义消息类型的创建方式:
- 在功能包下新建
msg
目录,添加Person.msg
文件,名字是自定义的,你也可以叫Man.msg
。(msg文件名称的首字母最好大写。) - 编辑.msg文件内容,很简单,根据需求来添加就行,数据类型就是
string
、int16
、float64
这些。注意:不要直接int
、float
、char
这几个C语言的数据类型,msg文件和这些还是有些区别的,下面列举一些msg文件允许的字段类型:- int8, int16, int32, int64 (或者无符号类型: uint16、uint32等)
- float32, float64
- string
- time, duration
- Header
- 编辑配置文件,这一步包含的步骤较多,比较繁琐:
- 在功能包下新建
package.xml文件修改:
在原来文件的基础上添加两行,即
<build_depend>message_generation</build_depend>
和<exec_depend>message_runtime</exec_depend>
整体如下:roscpp等包是原来添加依赖库时自动创建的
<buildtool_depend>catkin</buildtool_depend>
<build_depend>roscpp</build_depend>
<build_depend>rospy</build_depend>
<build_depend>std_msgs</build_depend>
<build_depend>message_generation</build_depend>
<build_export_depend>roscpp</build_export_depend>
<build_export_depend>rospy</build_export_depend>
<build_export_depend>std_msgs</build_export_depend>
<exec_depend>roscpp</exec_depend>
<exec_depend>rospy</exec_depend>
<exec_depend>std_msgs</exec_depend>
<exec_depend>message_runtime</exec_depend>
CMakeLists.txt文件修改
第一部分:
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
第二部分:
add_message_files(
FILES
Person.msg
)
第三部分:这个不用添加东西,直接取消注释就行
enerate_messages(
DEPENDENCIES
std_msgs
)
第四部分:只用取消一行的注释
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES helloworld
CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
# DEPENDS system_lib
)
- 配置好以上,用
catkin_make
指令编译一下,没有报错就问题不大。 - 在
devel/lib/python3/msg/
目录下能找到_Person.py
文件,则表示msg文件配置成功。(记住这个路径的文件,待会会用到。) - 接下来,在
_Person.py
所在目录下打开终端,执行pwd
指令查看当前目录路径,选中/home/用户名/工作空间/devel/lib/python3/dist-packages
部分,添加到settings.json
文件中,如下所示。这一步的目的是方便接下来的代码编写(会自动补齐内容),不添加也不会影响编译运行。
{
"python.autoComplete.extraPaths": [
"/opt/ros/noetic/lib/python3/dist-packages",
"/home/用户名/工作空间/devel/lib/python3/dist-packages"
],
"python.analysis.extraPaths": [
"/opt/ros/noetic/lib/python3/dist-packages",
"/home/用户名/工作空间/devel/lib/python3/dist-packages"
]
}
小tips
我们在执行代码的过程中,会经常用到
source ./devel/setup.bash
指令,虽然指令并不复杂,但是每新开一次终端都得输入一次,效率还是蛮低的。除此之外,使用Python编写代码,我们需要对新建的Python文件进行权限修改操作:chmod +x *.py
,这条指令也不复杂,但是输入多了效率也挺低的。
为了解决以上问题,我修改主目录下的.bashrc文件,在其末尾添加以下内容:
alias g-path='source ./devel/setup.bash'
alias get-x='chmod +x *.py'
(添加完之后要执行source .bashrc
指令让其生效)
添加之后,以后要输入source ./devel/setup.bash
指令就可以用g-path
代替,同理,chmod +x *.py
可以用get-x
代替。
发布者实现
基本消息类型
- 在ROS功能包下的scripts目录下新建Python文件:
demo_pub.py
。 - 代码内容如下:
#! /usr/bin/env python
import rospy
from std_msgs.msg import String
if __name__ == "__main__":
rospy.init_node("talker_pub")
pub = rospy.Publisher("chat",String,queue_size=10)
msg=String()
cont="hello! How are you? "
count=0
rate=rospy.Rate(1)
rospy.sleep(3.0)
while not rospy.is_shutdown():
msg.data=cont+str(count)
pub.publish(msg)
rate.sleep()
rospy.loginfo("output data is: %s",msg.data)
count+=1
- 修改CMakeLists.txt文件,将新创建的Python文件路径添加到
catkin_install_python
内容下:(由于CMakeLists.txt文件的修改方法比较简单,之后的文章可能不会提及。)
catkin_install_python(PROGRAMS
scripts/hello_p.py # 这是之前添加的
scripts/demo_pub.py
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
- 编译后执行即可。
自定义消息类型
- 创建并编写
Person.msg
文件,配置方法参考上文。
string name
int16 age
float64 height
- 创建Python文件,代码如下:
#! /usr/bin/env python
import rospy
from helloworld.msg import Person
if __name__ == "__main__":
rospy.init_node("pub_msg")
pub = rospy.Publisher("msg_test",Person,queue_size=10)
p=Person()
p.name="Zhang San"
p.age=18
p.height=1.78
rate=rospy.Rate(1)
while not rospy.is_shutdown():
pub.publish(p)
rate.sleep()
rospy.loginfo("name is: %s, age is: %d, height is: %f", p.name,p.age,p.height)
- 修改CMakeLists.txt文件后,编译执行即可
订阅者实现
基本消息类型
代码如下:
#! /usr/bin/env python
import rospy
from std_msgs.msg import String
def doMsg(msg):
rospy.loginfo("listener get: %s",msg.data)
if __name__=="__main__":
rospy.init_node("listener_sub")
sub = rospy.Subscriber("chat",String,doMsg,queue_size=10)
rospy.spin()
自定义消息类型
代码如下:(msg文件已经创建编译,直接用就行)
#! /usr/bin/env python
import rospy
from helloworld.msg import Person
def doMsg(p):
rospy.loginfo("I get: name is: %s, age is: %d, height is: %2f",p.name,p.age,p.height)
if __name__=="__main__":
rospy.init_node("sub_msg")
sub = rospy.Subscriber("msg_test",Person,doMsg,queue_size=10)
rospy.spin()
注意事项
- 订阅方订阅的话题必须与发布方发布的话题一致,否则无法接收消息。
rospy.Subscriber
或rospy.Publisher
的第一个参数就是话题名。 - 发布方名称和订阅方名称不能一致,否则无法同时执行。
rospy.init_node()
指定的就是发布方或订阅方。 rospy.spin()
的作用:让ROS节点保持运行状态,以便能够处理消息和回调。如果不调用rospy.spin()
,你的程序可能会在执行完所有初始化代码后立即退出,这样回调函数就无法被触发。