在类中使用参数 (Python)

目标: 用 Python 创建并运行带有参数的类.

教程等级: 初级

预计时长: 20 分钟

背景

在实现你自己的 nodes 时,有时需要添加可以从启动文件(launch file)中设置的参数。

本教程将向你展示如何在 Python 类中创建这些参数,并如何在启动文件中设置它们。

前提条件

在之前的教程中,你学习了如何 创建工作空间创建包。 你还了解了 参数 及其在 ROS 2 系统中的功能。

任务

1 创建包

打开一个新的终端并 配置你的 ROS 2 安装环境,这样 ros2 指令就能正常工作。

按照 这些指令 创建一个名为 ros2_ws 的新工作空间。

回想一下之前学到的,包应该在 src 目录中创建,而不是工作空间的根目录. 所以,进入 ros2_ws/src 目录,并运行包创建指令:

ros2 pkg create --build-type ament_python --license Apache-2.0 python_parameters --dependencies rclpy

你的终端会返回一个消息,确认了包 cpp_parameters 及其所有必要文件和文件夹已经创建。

--dependencies 参数会自动将必要的依赖添加到 package.xmlCMakeLists.txt 中。

1.1 更新 package.xml

因为在包创建过程中使用了 --dependencies 选项,所以不需要手动将依赖项添加到 package.xmlCMakeLists.txt

但是,记得将描述、维护者电子邮件和姓名,以及许可证信息添加到 package.xml 中。

<description>Python parameter tutorial</description>
<maintainer email="you@email.com">Your Name</maintainer>
<license>Apache License 2.0</license>

2 编写 Python 代码

ros2_ws/src/python_parameters/python_parameters 目录中创建一个名为 python_parameters_node.py 的新文件,并粘贴以下代码:

import rclpy
import rclpy.node

class MinimalParam(rclpy.node.Node):
    def __init__(self):
        super().__init__('minimal_param_node')

        self.declare_parameter('my_parameter', 'world')

        self.timer = self.create_timer(1, self.timer_callback)

    def timer_callback(self):
        my_param = self.get_parameter('my_parameter').get_parameter_value().string_value

        self.get_logger().info('Hello %s!' % my_param)

        my_new_param = rclpy.parameter.Parameter(
            'my_parameter',
            rclpy.Parameter.Type.STRING,
            'world'
        )
        all_new_parameters = [my_new_param]
        self.set_parameters(all_new_parameters)

def main():
    rclpy.init()
    node = MinimalParam()
    rclpy.spin(node)

if __name__ == '__main__':
    main()

2.1 检查代码

文件最开头的 import 语句用于导入包依赖项。

接下来的代码段创建了类和构造函数。 构造函数的 self.declare_parameter('my_parameter', 'world') 创建了一个名为 my_parameter 的参数,并将其默认值设置为 world。 参数类型是从默认值推断出来的,所以在这种情况下,它将被设置为字符串类型。 然后 timer 使用 1 秒的周期初始化,这会使得每秒执行一次 timer_callback 函数。

class MinimalParam(rclpy.node.Node):
    def __init__(self):
        super().__init__('minimal_param_node')

        self.declare_parameter('my_parameter', 'world')

        self.timer = self.create_timer(1, self.timer_callback)

接下来的 timer_callback 函数的第一行从节点中获取参数 my_parameter,并将其存储在 my_param 中。 后续 get_logger 函数确保事件被记录。 然后 set_parameters 函数将参数 my_parameter 重置为默认字符串值 world。 这样,即使用户在外部更改了参数,也能确保它总是被重置为原始值。

def timer_callback(self):
    my_param = self.get_parameter('my_parameter').get_parameter_value().string_value

    self.get_logger().info('Hello %s!' % my_param)

    my_new_param = rclpy.parameter.Parameter(
        'my_parameter',
        rclpy.Parameter.Type.STRING,
        'world'
    )
    all_new_parameters = [my_new_param]
    self.set_parameters(all_new_parameters)

timer_callback 后面的是 main。 这里初始化了 ROS 2,构造了 MinimalParam 类的一个实例,并启动了 rclpy.spin 来处理节点的数据。

def main():
    rclpy.init()
    node = MinimalParam()
    rclpy.spin(node)

if __name__ == '__main__':
    main()
2.1.1 (可选) 添加参数描述 (ParameterDescriptor)

你可以为参数设置对应的描述。 这样做可以指定参数的文本描述和约束,比如设置为只读,指定范围等。 为了使这个功能生效, __init__ 代码必须更改为:

# ...

class MinimalParam(rclpy.node.Node):
    def __init__(self):
        super().__init__('minimal_param_node')

        from rcl_interfaces.msg import ParameterDescriptor
        my_parameter_descriptor = ParameterDescriptor(description='This parameter is mine!')

        self.declare_parameter('my_parameter', 'world', my_parameter_descriptor)

        self.timer = self.create_timer(1, self.timer_callback)

其余代码保持不变。 运行节点后,你可以运行 ros2 param describe /minimal_param_node my_parameter 来查看类型和描述。

2.2 添加 entry point

打开 setup.py 文件。 填写 maintainermaintainer_emaildescriptionlicense 字段,使其与 package.xml 匹配:

maintainer='YourName',
maintainer_email='you@email.com',
description='Python parameter tutorial',
license='Apache License 2.0',

接下来,将以下行添加到 entry_points 字段的 console_scripts 括号内:

entry_points={
    'console_scripts': [
        'minimal_param_node = python_parameters.python_parameters_node:main',
    ],
},

别忘了保存。

3 构建和运行

在构建之前,你需要在工作空间的根目录(ros2_ws)中运行 rosdep 检查是否有缺失的依赖项。

rosdep install -i --from-path src --rosdistro humble -y

然后在工作空间的根目录(ros2_ws)构建包:

colcon build --packages-select python_parameters

打开一个新的终端,进入 ros2_ws,并 source 配置文件:

source install/setup.bash

现在运行节点:

ros2 run python_parameters minimal_param_node

终端应该每秒都会返回以下消息:

[INFO] [parameter_node]: Hello world!

现在你可以看到参数的默认值,但你希望能够自己设置它。 有两种方法可以实现这一点。

3.1 从终端修改

这部分将使用从 关于参数的教程 中学到的知识,并将其应用到你刚刚创建的节点。

确保节点正在运行:

ros2 run python_parameters minimal_param_node

打开另一个终端,再次从 ros2_ws 中 source 配置文件,然后输入以下命令:

ros2 param list

能看到自定义参数 my_parameter。 只需运行以下命令即可更改它:

ros2 param set /minimal_param_node my_parameter earth

如果输出为 Set parameter successful,则说明设置成功。 此时查看另一个终端,应该能看到输出变为 [INFO] [minimal_param_node]: Hello earth!

由于节点随后将参数设置回 world,因此后续输出显示 [INFO] [minimal_param_node]: Hello world!

3.2 从启动文件修改

你也可以在启动文件中设置参数,但首先需要添加一个启动目录。 在 ros2_ws/src/python_parameters/ 目录中创建一个名为 launch 的新目录。 创建一个名为 python_parameters_launch.py 的新文件。

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='python_parameters',
            executable='minimal_param_node',
            name='custom_minimal_param_node',
            output='screen',
            emulate_tty=True,
            parameters=[
                {'my_parameter': 'earth'}
            ]
        )
    ])

可以看到,我们在启动节点 parameter_node 时将 my_parameter 设置为 earth。 下面这两行确保输出能在控制台中打印。

output="screen",
emulate_tty=True,

打开 setup.py 文件。 在最开头的 import 语句中添加以下内容,以及在 data_files 参数中添加一些新内容,来包含所有启动文件:

import os
from glob import glob
# ...

setup(
  # ...
  data_files=[
      # ...
      (os.path.join('share', package_name), glob('launch/*launch.[pxy][yma]*')),
    ]
  )

打开一个新的终端,进入 ros2_ws,构建包:

colcon build --packages-select python_parameters

然后在新终端中 source 配置文件:

source install/setup.bash

现在使用我们刚刚创建的启动文件运行节点:

ros2 launch python_parameters python_parameters_launch.py

终端应该每秒都会返回以下消息:

[INFO] [custom_minimal_param_node]: Hello earth!

总结

你创建了一个带有自定义参数的节点,可以从启动文件或命令行中设置。 你在包配置文件中添加了依赖项、可执行文件和启动文件,以便构建和运行它们,并查看参数的作用。

下一步

现在你有了自己的包和 ROS 2 系统, 下一个教程 将向你展示如何检查环境和系统中的问题,以便在出现问题时解决。