# ROS2基础教程 - 8 参数

在 ROS1 中,有参数服务器(Parameter Server),它是一个集中式的存储系统,用于存储和检索参数。节点可以通过这个参数服务器共享配置数据,例如常量和设置。

而在 ROS2 中,取消了 Master 和集中式的参数服务器,每个节点都有自己的参数管理系统。参数直接与节点绑定,节点可以声明、获取和更新自己的参数。这种设计使得参数管理更加分散和灵活,适应了分布式系统的需求。

那么 ROS2 中如何在节点之间共享参数呢?

除了可以使用话题、服务、共享文件等方式,还可以通过 SyncParametersClientAsyncParametersClient 访问和修改其他节点的参数。

# 8.1 查看和设置节点参数

# 1 查看参数命令

我们可以使用如下命令查看参数有哪些命令:

# 可以查看参数有哪些命令
ros2 param
1
2

将小海龟的两个节点运行起来,使用如下命令可以查看运行的节点有哪些参数:

# 查看节点有哪些参数:
ros2 param list
1
2

可以看到有两个节点,每个节点下有很多的参数:

查看到节点有哪些参数后,可以使用如下命令查看某个参数的描述信息:

# 查看turtlesim节点下background_b参数的描述性信息
ros2 param describe turtlesim background_b
1
2

# 2 获取和设置参数

我们还可以对参数值进行获取和修改。

我们可以使用如下命令查看当前参数的值:

# 查看turtlesim节点下background_b参数的值
ros2 param get turtlesim background_b
1
2

修改参数的值:

# 查看turtlesim节点下background_b参数的值
ros2 param set turtlesim background_b 100
1
2

在上面的命令中,修改了背景颜色蓝色通道的值,所以小海龟的窗口的背景颜色会发生变化。

# 3 导出和导入参数

上面对单个参数进行获取和设置,有时候节点的参数比较多,一个一个修改不太方便,我们还可以将某个节点所有的参数导出到一个 yaml 文件,然后修改这个文件,修改完成,再将这个文件导入到节点中。

turtlesim 节点的参数导入到 turtlesim.yaml 文件中:

# 将turtlesim节点所有的参数和值打印到终端
ros2 param dump turtlesim

# 将turtlesim节点所有的参数和值,导出到 turtlesim.yaml 文件中
ros2 param dump turtlesim >> turtlesim.yaml
1
2
3
4
5

所有的参数会导出到文件中,然后可以修改导出后的文件。

修改完成,可以使用如下命令导入到节点:

# 配置文件中的参数加载入节点
ros2 param load turtlesim turtlesim.yaml
1
2

下面通过代码来演示,如何在节点中声明、获取、修改、监听参数的变化。

# 8.2 Python实现参数

# 1 创建功能包

首先在 工作空间/src 创建一个功能包,我这里叫 param_python

ros2 pkg create --build-type ament_python param_python
1

# 2 创建节点文件

然后下面在 param_python/param_python 目录下创建节点文件。

例如 param_node.py,内容如下:

import rclpy
from rclpy.node import Node
from rcl_interfaces.msg import SetParametersResult
from rclpy.parameter import Parameter
import rclpy.parameter

class MyNode(Node):
    def __init__(self):
        super().__init__('param_node')  # 构造函数,节点名为"param_node"
        
        # 声明一个名为 "my_param" 的参数,默认值为 "Hello, ROS2!"
        self.declare_parameter('my_param', 'Hello, ROS2!')

        # 获取参数值并打印
        param_value = self.get_parameter('my_param').get_parameter_value().string_value
        self.get_logger().info(f'Initial parameter value: {param_value}')

        # 订阅参数变化的回调
        self.add_on_set_parameters_callback(self.parameter_callback)

        # 创建一个定时器,每秒打印参数值
        self.timer_print = self.create_timer(1.0, self.timer_print_callback)

        # 创建一个定时器,每 5 秒重置参数值
        self.timer_reset = self.create_timer(5.0, self.timer_reset_callback)

    # 每秒打印参数值的回调函数
    def timer_print_callback(self):
        param_value = self.get_parameter('my_param').get_parameter_value().string_value
        self.get_logger().info(f'Current parameter value: {param_value}')

    # 每 5 秒重置参数值的回调函数
    def timer_reset_callback(self):
        # 重置参数值为 "Hello, ROS2!"
        new_param = Parameter('my_param', Parameter.Type.STRING, 'Hello, ROS2!')
        self.set_parameters([new_param])

        # 获取参数值并打印
        param_value = self.get_parameter('my_param').get_parameter_value().string_value
        self.get_logger().info(f'Parameter reset to: {param_value}')

    # 参数变化的回调函数
    def parameter_callback(self, parameters):
        for param in parameters:
            if param.name == 'my_param':
                self.get_logger().info(f'Parameter changed to: {param.value}')
        return SetParametersResult(successful=True, reason='')

def main(args=None):
    rclpy.init(args=args)  # 初始化ROS2
    node = MyNode()  # 创建节点
    rclpy.spin(node)  # 运行节点
    rclpy.shutdown()  # 关闭ROS2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

在上面的代码中,使用 self.declare_parameter('my_param', 'Hello, ROS2!') 声明参数。然后使用 self.get_parameter('my_param') 获取参数,并创建了一个监听参数变化的回调。

创建了两个定时器,一个每隔1秒打印一次参数值,另一个每隔5秒重置一下参数。

所以上面的代码涉及到参数的声明、获取、修改 和 监听参数的变化。

# 4 配置节点

在功能包下的 setup.py 中配置两个节点,在 entry_points 中配置:

entry_points={
    'console_scripts': [
        'param_node = param_python.param_node:main',
    ],
},
1
2
3
4
5

# 5 构建项目

现在代码已经编写完成了,需要构建项目。

工作空间下执行如下命令:

colcon build
1

构建完成,会在 install 目录下生成文件。

# 6 运行节点

首先执行 source 命令,在工作空间下执行:

source install/local_setup.sh
1

上面的命令是让 ROS 找到我们的功能包,已经在 HelloWorld 章节说过了。


然后打开一个终端,启动服务端节点:

# 启动节点,不传递参数
ros2 run param_python param_node

# 在启动节点的时候,也可以给my_param传递参数
ros2 run param_python param_node --ros-args -p my_param:='hello foooor'
1
2
3
4
5

此时可以看到不停的打印参数和重置参数的日志。

再打开另一个命令行,可以使用如下命令修改上面程序中声明的 my_param 参数:

# 将my_param的值修改为Hello Foooor
ros2 param set param_node my_param 'Hello Foooor'
1
2

可以通过日志看到参数的值被修改为 Hello Foooor 了。

# 8.3 C++实现参数

# 1 创建功能包

首先在 工作空间/src 创建一个功能包,我这里叫 param_cpp

ros2 pkg create --build-type ament_cmake param_cpp
1

# 2 创建节点文件

param_cpp/src 下创建节点文件,例如创建节点文件 param_node.cpp,内容如下:

#include "rclcpp/rclcpp.hpp"
#include "rcl_interfaces/msg/set_parameters_result.hpp"

class MyNode : public rclcpp::Node
{
public:
    MyNode() : Node("param_node")  // 节点名称为 "param_node"
    {
        // 声明一个名为 "my_param" 的参数,默认值为 "Hello, ROS2!"
        this->declare_parameter<std::string>("my_param", "Hello, ROS2!");

        // 获取参数值并打印
        std::string param_value = this->get_parameter("my_param").as_string();
        RCLCPP_INFO(this->get_logger(), "Initial parameter value: %s", param_value.c_str());

        // 订阅参数变化的回调
        this->add_on_set_parameters_callback(
            std::bind(&MyNode::parameter_callback, this, std::placeholders::_1));

        // 创建定时器,每秒打印参数值
        timer_print_ = this->create_wall_timer(
            std::chrono::seconds(1),
            std::bind(&MyNode::timer_print_callback, this));

        // 创建定时器,每 5 秒重置参数值
        timer_reset_ = this->create_wall_timer(
            std::chrono::seconds(5),
            std::bind(&MyNode::timer_reset_callback, this));
    }

private:
    // 每秒打印参数值的回调函数
    void timer_print_callback()
    {
        std::string param_value = this->get_parameter("my_param").as_string();
        RCLCPP_INFO(this->get_logger(), "Current parameter value: %s", param_value.c_str());
    }

    // 每 5 秒重置参数值的回调函数
    void timer_reset_callback()
    {
        // 重置参数值为 "Hello, ROS2!"
        rclcpp::Parameter new_param("my_param", "Hello, ROS2!");
        this->set_parameters({new_param});

        // 获取参数值并打印
        std::string param_value = this->get_parameter("my_param").as_string();
        RCLCPP_INFO(this->get_logger(), "Parameter reset to: %s", param_value.c_str());
    }

    // 参数变化的回调函数
    rcl_interfaces::msg::SetParametersResult parameter_callback(const std::vector<rclcpp::Parameter> &parameters)
    {
        for (const auto &param : parameters)
        {
            if (param.get_name() == "my_param")
            {
                RCLCPP_INFO(this->get_logger(), "Parameter changed to: %s", param.as_string().c_str());
            }
        }
        rcl_interfaces::msg::SetParametersResult result;
        result.successful = true;
        result.reason = "";
        return result;
    }

    // 声明两个定时器
    rclcpp::TimerBase::SharedPtr timer_print_;
    rclcpp::TimerBase::SharedPtr timer_reset_;
};

int main(int argc, char **argv)
{
    rclcpp::init(argc, argv);
    
    rclcpp::spin(std::make_shared<MyNode>());
    rclcpp::shutdown();
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79

在上面的代码中,使用 this->declare_parameter<std::string>("my_param", "Hello, ROS2!"); 声明参数。然后使用 this->get_parameter("my_param") 获取参数,并创建了一个监听参数变化的回调。

创建了两个定时器,一个每隔1秒打印一次参数值,另一个每隔5秒重置一下参数。

所以上面的代码涉及到参数的声明、获取、修改 和 监听参数的变化。

# 4 配置CMakeLists.txt

在功能包下的 CMakeLists.txt 中,添加节点配置,可以添加在 find_package(ament_cmake REQUIRED) 下面。

find_package(rclcpp REQUIRED)

# 添加可执行文件,指向 param_node.cpp
add_executable(param_node src/param_node.cpp)
ament_target_dependencies(param_node rclcpp)

# 安装可执行文件
install(TARGETS
  param_node
  DESTINATION lib/${PROJECT_NAME}
)
1
2
3
4
5
6
7
8
9
10
11

上面配置了 param_node 节点。

# 5 构建项目

现在代码已经编写完成了,需要构建项目。

工作空间下执行如下命令:

colcon build
1

构建完成,会在 install 目录下生成文件。

# 6 运行节点

首先执行 source 命令,在工作空间下执行:

source install/local_setup.sh
1

上面的命令是让 ROS 找到我们的功能包,已经在 HelloWorld 章节说过了。


然后打开一个终端,启动服务端节点:

# 启动节点,不传递参数
ros2 run param_cpp param_node

# 在启动节点的时候,也可以给my_param传递参数
ros2 run param_cpp param_node --ros-args -p my_param:='hello foooor'
1
2
3
4
5

此时可以看到不停的打印参数和重置参数的日志。


再打开另一个命令行,可以使用如下命令修改上面程序中声明的 my_param 参数:

# 将my_param的值修改为Hello Foooor
ros2 param set param_node my_param 'Hello Foooor'
1
2

可以通过日志看到参数的值被修改为 Hello Foooor 了。