ROS2开发笔记——Topic样例(二)
一、新建工作空间
ROS采用工作空间的概念,在工作空间目录下一般设src目录存放源代码。ROS采用包的形式管理项目,所有工作空间src目录下一般有多个包。ROS可以采用python或C++进行开发,也可以同时采用这两种语言混合开发,因此一个典型ROS工作空间如下所示,
workspace_folder/
src/
cpp_package_1/
CMakeLists.txt
include/cpp_package_1/
package.xml
src/
py_package_1/
package.xml
resource/py_package_1
setup.cfg
setup.py
py_package_1/
...
cpp_package_n/
CMakeLists.txt
include/cpp_package_n/
package.xml
src/
下面,我们新建自己的工作空间,
$ mkdir -p ~/ros2_ws/src
$ cd ~/ros2_ws/src
二、新建包
ROS 提供了工具帮助我们按照模板新建包, 指定了编译类型,也就是指定了我们使用什么语言进行开发,使用C++则为 ament_cmake, 而使用python则是ament_python。是我们的包名字。
ros2 pkg create --build-type <build-type> <package_name>
下面,我们新建一个py_pubsub包,学习使用python进行ROS主题通信的开发,
$ ros2 pkg create --build-type ament_python py_pubsub
我们看一下新建的py_pubsub的目录结构,
py_pubsub/
py_pubsub/
resource/py_pubsub
test/
package.xml
setup.cfg
setup.py
2.1、package.xml
首先,查看第一个package.xml文件,首先它包含了一些包的名字、版本号、描述信息、许可以及维护者的信息。更重要的是package.xml指定了这个包的依赖,方便编译器在编译的时候能够找到对应的依赖。依赖以_depend结尾的标签指定,顾名思义是测试时用到的依赖,还有编译依赖,执行依赖,以及导出依赖等。
<?xml version="1.0"?>
<?xml-model
href="http://download.ros.org/schema/package_format3.xsd"
schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>py_pubsub</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="user@todo.todo">user</maintainer>
<license>TODO: License declaration</license>
<test_depend>ament_copyright</test_depend>
<test_depend>ament_flake8</test_depend>
<test_depend>ament_pep257</test_depend>
<test_depend>python3-pytest</test_depend>
<export>
<build_type>ament_python</build_type>
</export>
</package>
加入我们项目中需要用到一个包,但是我们系统上没有,那么编译的时候必然会报错,那么我们怎么安装这些外部依赖包呢?这需要用到rosdep这个工具。使用rosdep之前,需要先对它进行初始化并更新,
$ sudo rosdep init
$ rosdep update
但是sudo rosdep init需要从GitHub下载数据,由于国内的原因,运行这个命令必然会失败。这成为了每个学习ros的人必然要面对的一个问题。国情原因我理解,只是不知道为什么国内ros社区没有去维护一个国内版本,可能是许可的原因吧,以致大家需要使用一些邪魔外道的手段才可以解决这个问题。
不过,现在博主鱼香ROS,自己维护了国内版rosdepc,算是解决了这个问题,rosdepc的安装与使用
$ pip install rosdepc
$ rosdepc init
$ rosdepc update
检查并安装依赖,
rosdepc install --from-paths src -y --ignore-src
--from-paths src 表示检查src目录下的package.xml中的依赖,-y就是默认yes,--ignore-src表示加入package.xml中指定的某个依赖是当前工作空间中的就忽略,因为编译之后必然会安装。
2.2、setup.py
setup.py是python包的安装指令文件,指定了怎么安装这个包,同样也包含该包的一些相关信息,我们需要注意的是执行程序入口entry_points,示例中定义了一个执行程序入口为my_node,该入口指向my_py_pkg.my_node:main函数,后面写代码的时候会有具体示例。
from setuptools import setup
package_name = 'py_pubsub'
setup(
name=package_name,
version='0.0.0',
packages=[package_name],
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='TODO',
maintainer_email='TODO',
description='TODO: Package description',
license='TODO: License declaration',
tests_require=['pytest'],
entry_points={
'console_scripts': [
# 'my_node = my_py_pkg.my_node:main'
],
},
)
2.3、setup.cfg
setup.cfg是为了执行ros2 run命令时能够找到对应的可执行程序,
[develop]
script_dir=$base/lib/py_pubsub
[install]
install_scripts=$base/lib/py_pubsub
2.4、其他
剩下的是三个目录,一个是跟包同名的目录,用于存放我们的源代码文件,该目录下还有__init__.py文件。另外一个是resource目录,底下也是跟包同名的目录,用于存放相关资源文件,比如图片啥的。最后就是自动的测试脚本了,暂时不用管。
三、编写publisher节点
在源代码的存放目录新建一个名为publisher_member_function.py的python文件,publisher节点示例如下,
import rclpy # ROS提供的python接口
from rclpy.node import Node # 应该是ROS节点的父类了
from std_msgs.msg import String # 从标准信息类型库中导入字符串类型,其实就是数据类型
class MinimalPublisher(Node): # 定义类并继承Node
def __init__(self):
super().__init__('minimal_publisher')
self.publisher_ = self.create_publisher(String, 'topic', 10) # 创建一个发布者,该发布者在一个名为'topic'(可以定义为其他名字)的主题上发布一个String类型的信息,10表示缓冲队列的大小,队列大小是一项必需的 QoS(服务质量)设置,如果订阅者接收消息的速度不够快,它会限制排队消息的数量。
timer_period = 0.5 # seconds
self.timer = self.create_timer(timer_period, self.timer_callback) # 创建一个计时器,该计时器按一定时间间隔(0.5秒)调用回调函数self.timer_callback
self.i = 0
def timer_callback(self): # 回调函数,构造并发布信息
msg = String()
msg.data = 'Hello World: %d' % self.i
self.publisher_.publish(msg) # 发布信息(这是给订阅者发布的)
self.get_logger().info('Publishing: "%s"' % msg.data) # 同时还发布到终端 console
self.i += 1
def main(args=None):
rclpy.init(args=args) # 初始化节点
minimal_publisher = MinimalPublisher() # 实例化发布者节点
rclpy.spin(minimal_publisher) # 启动发布者节点
# Destroy the node explicitly
# (optional - otherwise it will be done automatically
# when the garbage collector destroys the node object)
minimal_publisher.destroy_node() # 销毁发布者节点
rclpy.shutdown() # 关闭节点
if __name__ == '__main__':
main()
因为我们在发布者节点中调用了两个库:rclpy和std_msgs,这两个库是运行时需要的,数据执行依赖,所以我们打开这个包下的package.xml文件,添加以下两行,
<exec_depend>rclpy</exec_depend>
<exec_depend>std_msgs</exec_depend>
然后,我们需要为程序调用定义入口,打开setup.py文件,添加如下代码,
entry_points={
'console_scripts': [
'talker = py_pubsub.publisher_member_function:main',
],
},
其中,talker是自定义的名字,调用时如下所示,
$ ros2 run py_pubsub talker
四、编写subscriber节点
该节点简单接受publisher发布的信息并打印到终端,
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class MinimalSubscriber(Node):
def __init__(self):
super().__init__('minimal_subscriber')
self.subscription = self.create_subscription(
String,
'topic',
self.listener_callback,
10) # 新建订阅者,接收'topic'的数据并调用self.listener_callback函数
self.subscription # prevent unused variable warning
def listener_callback(self, msg):
self.get_logger().info('I heard: "%s"' % msg.data) # 打印到终端
def main(args=None):
rclpy.init(args=args)
minimal_subscriber = MinimalSubscriber()
rclpy.spin(minimal_subscriber)
# Destroy the node explicitly
# (optional - otherwise it will be done automatically
# when the garbage collector destroys the node object)
minimal_subscriber.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
由于它和发布者程序调用的依赖是一样的,所以我们不需要再一次添加依赖,但是需要给它添加执行入口,打开setup.py,按如下所示添加,
entry_points={
'console_scripts': [
'talker = py_pubsub.publisher_member_function:main',
'listener = py_pubsub.subscriber_member_function:main',
],
},
五、编译与运行
按照惯例,编译前检查一下依赖,
$ rosdepc install -i --from-path src --rosdistro humble -y
rosdepc可以自定义很多参数,我们看到多少学多少,这里--rosdistro humble是指定ROS版本,不指定也行,这是多版本混合开发才需要的。
ROS2采用的编译器是colcon,回到工作空间的根目录ros2_ws,执行编译,
$ colcon build --packages-select py_pubsub
--packages-select py_pubsub是指定只编译py_pubsub,如果不指定,直接执行colcon build会编译整个工作空间中所有的包。
编译之后,我们会发现工作空间中多了三个目录,build/用于存放编译过程的中间文件,install/就是安装目录,就是我们把刚才的py_pubsub包安装到这里了,log/顾名思义日志文件。
ros2_ws/
build/
install/
log/
src/
我们要使用刚编译的py_pubsub,还需要执行以下以下命令来配置环境变量,
$ source install/setup.bash
运行发布者节点,
$ ros2 run py_pubsub talker
[INFO] [minimal_publisher]: Publishing: "Hello World: 0"
[INFO] [minimal_publisher]: Publishing: "Hello World: 1"
[INFO] [minimal_publisher]: Publishing: "Hello World: 2"
[INFO] [minimal_publisher]: Publishing: "Hello World: 3"
[INFO] [minimal_publisher]: Publishing: "Hello World: 4"
...
运行订阅者节点,
$ ros2 run py_pubsub listener
[INFO] [minimal_subscriber]: I heard: "Hello World: 10"
[INFO] [minimal_subscriber]: I heard: "Hello World: 11"
[INFO] [minimal_subscriber]: I heard: "Hello World: 12"
[INFO] [minimal_subscriber]: I heard: "Hello World: 13"
[INFO] [minimal_subscriber]: I heard: "Hello World: 14"
好了!至此入门ros开发第一课结束了,从开发、编译、运行整个流程都走了一遍了,下一步学习服务通信的开发!