1. 引言
通过上一篇,我们对ros里面的节点有了基本的了解,下面是一个关于如何在ROS1环境中创建和使用自定义话题(Topic)的介绍。我们将通过创建一个简单的发布者-订阅者系统来演示这个过程,以帮助大家理解话题。关于底层原理这里不做过多的介绍,我们通过示例来理解这一过程。
2. 创建工作空间并初始化
2.1 进入工作空间
打开终端,并输入:
cd catkin_ws/src/
前提是你已经创建好了工作空间,如果还没创建好工作空间,请看第一篇博文,链接如下:https://blog.csdn.net/winner0111/article/details/141788364?spm=1001.2014.3001.5501
2.2 创建功能包
之后我们在这个目录下创建一个功能包,在终端接着输入:
catkin_create_pkg topic_ws rospy roscpp std_msgs
2.3 编写消息类型
为了发送数据,我们需要定义一个消息类型。在catkin_ws目录下创建一个名为msg的新目录,并在此目录中创建一个.msg文件,命名为CustomMessage.msg。定义一个简单的消息结构:
string field1
int32 field2
2.4 编译工作空间
在这里我们可以先对工作空间进行编译,以便于新建的功能包能进入ROS的软件包列表 :
cd ..
catkin_make
2.5 编写发布者话题
在工作空间catkin_ws文件目录下创建文件夹scripts,进入该文件夹,在该文件夹下使用python编写发布者话题,我们可以参照以下顺序编写代码:
- 导入需要的库
- 初始化节点
- 声明需要发布的话题名称以及需要能发布消息的对象
- 开启循环,不停地使用上述对象发布消息包
因此代码可以按照以下流程来写:
(1)指示操作系统使用环境中的Python解释器来执行此脚本,指定解码方式。
#!/usr/bin/env python
#encoding=utf-8
(2)导入ROS Python接口库rospy以及自定义消息类型CustomMessage,CustomMessage是在之前创建的消息文件中定义的消息类型。
import rospy
from my_first_package.msg import CustomMessage
(3)定义一个名为talker的函数,这个函数要包含所有用于发布消息的代码。
def talker():
pub = rospy.Publisher('custom_topic', CustomMessage, queue_size=10)
这里创建一个发布者对象pub,它将消息发布到名为custom_topic的话题上,发布的消息类型为CustomMessage。queue_size参数设置了发布缓存队列的大小,这里是10个消息。
之后初始化一个名为talker的ROS节点。anonymous=True意味着如果存在多个同名节点运行,ROS会给它们加上不同的后缀来区分。
rospy.init_node('talker', anonymous=True)
再创建一个Rate对象,用于控制循环的频率。这里设置的是每秒1次,即1Hz;实例化了一个CustomMessage对象msg,并定义了一个计数变量i。
rate = rospy.Rate(1) # 1hz
msg = CustomMessage()
i = 0
定义一个循环,只要ROS没有请求关闭节点,就一直运行下去。设置msg对象的两个字段。field1是一个字符串,包含“Hello World”加上当前循环次数i;field2则直接赋值为当前循环次数i。使用ROS的日志功能打印出msg的内容。还有发布消息msg到custom_topic话题,让当前线程休眠足够的时间以达到指定的循环频率,增加循环计数器i的值,以便在下次循环中使用新的值。
while not rospy.is_shutdown():
msg.field1 = "Hello World %s" % i
msg.field2 = i
rospy.loginfo(msg)
pub.publish(msg)
rate.sleep()
i += 1
定义Python程序的标准入口点,即如果这个脚本被作为主程序运行,则调用talker()函数。try…except块用来处理ROSInterruptException异常,这种异常通常在用户请求节点退出时抛出,如按下Ctrl+C。
if __name__ == '__main__':
try:
talker()
except rospy.ROSInterruptException:
pass
最后组装形成以下代码:
#!/usr/bin/env python
#encoding=utf-8
import rospy
from my_first_package.msg import CustomMessage
def talker():
pub = rospy.Publisher('custom_topic', CustomMessage, queue_size=10)
rospy.init_node('talker', anonymous=True)
rate = rospy.Rate(1) # 1hz
msg = CustomMessage()
i = 0
while not rospy.is_shutdown():
msg.field1 = "Hello World %s" % i
msg.field2 = i
rospy.loginfo(msg)
pub.publish(msg)
rate.sleep()
i += 1
if __name__ == '__main__':
try:
talker()
except rospy.ROSInterruptException:
pass
至此,发布者节点编写完毕,我们在脚本所在目录下给对应的脚本赋权之后即可直接执行。
2.6 编写订阅者节点
同样在scripts目录中创建一个名为listener.py的Python脚本。流程与编写发布者节点的时候类似,这里不做赘述。我们编写代码如下:
#!/usr/bin/env python
#encoding=utf-8
import rospy
from my_first_package.msg import CustomMessage
def callback(data):
rospy.loginfo("I heard %s", data)
def listener():
rospy.init_node('listener', anonymous=True)
rospy.Subscriber("custom_topic", CustomMessage, callback)
rospy.spin()
if __name__ == '__main__':
listener()
同样赋权完成之后,该脚本可以直接执行。
2.7 测试发布者和订阅者
为了更新ros功能包的各项依赖,我们对工作空间继续编译,打开终端输入:
cd ~/catkin_ws
catkin_make
之后逐个启动,在终端输入:
cd scripts/
python talker.py
新建终端,启动另一个节点:
cd ~/catkin_ws/src/scripts/
python listener.py
最后我们在两个终端里面能看到发布者不断发布消息,而订阅者不断接收到消息。
3. 示例中节点和话题的区别
3.1 节点(Node)
在ROS中,节点是指一个执行特定任务的进程。每个节点都是一个独立的执行单元,可以与其他节点协作完成复杂的任务。节点是ROS应用程序的基本构建块,通过编写节点,开发者能够实现多种功能,包括但不限于:
- 发布数据:节点可以发布数据到一个或多个话题中,供其他节点订阅。
- 订阅数据:节点可以从一个或多个话题中订阅数据,处理接收到的信息。
- 提供服务:节点可以提供服务(Service),其他节点可以通过调用这些服务来请求执行特定的操作。
- 调用服务:节点也可以调用其他节点提供的服务,获取所需的结果或数据。
为了使一个进程成为ROS中的有效节点,必须通过rospy.init_node()函数来初始化它。这个函数需要一个节点的名称作为参数,并且可以设置节点是否匿名。如果设置为匿名,ROS将会自动为该节点添加一个随机的后缀,以确保每个节点的名字是唯一的,这对于调试和并发测试特别有用。
节点可以通过ROS的API与其他节点进行通信。在前面的代码示例中,talker节点通过调用rospy.init_node(‘talker’, anonymous=True)来初始化,并开始执行发布消息的任务。
3.2 话题(Topic)
话题是ROS中的通信机制之一,它允许节点之间以异步的方式发送和接收消息。话题类似于一种通信频道,节点可以通过发布或订阅的方式与话题进行互动。话题上的消息发布遵循发布-订阅模式:
- 发布者(Publisher):发布者节点向话题发送消息,这些消息可以被一个或多个订阅者接收。
- 订阅者(Subscriber):订阅者节点监听特定的话题,并接收该话题上的所有消息。
发布者并不关心是否有订阅者在接收消息,同样,订阅者也不关心是谁发布了消息。这种解耦使得系统更加灵活和健壮。此外,ROS还支持消息的持久化存储,即使没有订阅者,消息也会被暂时保存,直到有订阅者连接上来。
在示例中,custom_topic是一个话题,talker节点作为发布者,向custom_topic发布CustomMessage类型的消息。任何订阅了custom_topic的节点,都可以接收这些消息。
3.3 示例总结
在给定的代码示例中,talker节点通过rospy.Publisher创建了一个发布者对象,并指定了要发布消息的话题名称custom_topic和消息类型CustomMessage。接着,talker节点进入一个循环,不断更新消息的内容,并将其发布到话题上。订阅了custom_topic话题的节点(如listener节点)可以接收这些消息,并对其进行处理。