在类中使用参数(C++)
目标: 使用 C++ 创建并运行一个带有 ROS 参数的类。
教程级别: 初级
预计时间: 20 分钟
在编写自定义节点(node)时,有时需要添加可以从 launch 文件设置的参数(parameter)。
本教程将介绍如何在 C++ 类中创建这些参数,以及如何通过 launch 文件来设置它们。
在之前的教程中,你学习了如何创建工作空间(workspace)和功能包(package),也了解了参数及其在 ROS 2 系统中的作用。
1 创建功能包
Section titled “1 创建功能包”打开一个新终端并 source 你的 ROS 2 安装环境,以便 ros2 命令正常工作。
按照之前的说明创建一个名为 ros2_ws 的新工作空间。
注意,功能包应在 src 目录中创建,而非工作空间根目录。进入 ros2_ws/src,创建一个新功能包:
ros2 pkg create --build-type ament_cmake --license Apache-2.0 cpp_parameters --dependencies rclcpp终端会返回一条消息,确认功能包 cpp_parameters 及其所有必要的文件和文件夹已创建。
--dependencies 参数会自动在 package.xml 和 CMakeLists.txt 中添加必要的依赖行。
1.1 更新 package.xml
Section titled “1.1 更新 package.xml”由于在创建功能包时已使用 --dependencies 选项,因此无需手动在 package.xml 或 CMakeLists.txt 中添加依赖。不过,仍需照例在 package.xml 中填写描述、维护者姓名和邮箱以及许可证信息。
<description>C++ parameter tutorial</description><maintainer email="you@email.com">Your Name</maintainer><license>Apache-2.0</license>2 编写 C++ 节点
Section titled “2 编写 C++ 节点”在 ros2_ws/src/cpp_parameters/src 目录中,创建一个名为 cpp_parameters_node.cpp 的新文件,并粘贴以下代码:
#include <chrono> #include <functional> #include <string>
#include <rclcpp/rclcpp.hpp>
using namespace std::chrono_literals;
class MinimalParam : public rclcpp::Node { public: MinimalParam() : Node("minimal_param_node") { this->declare_parameter("my_parameter", "world");
auto timer_callback = [this](){ std::string my_param = this->get_parameter("my_parameter").as_string();
RCLCPP_INFO(this->get_logger(), "Hello %s!", my_param.c_str());
std::vector<rclcpp::Parameter> all_new_parameters{rclcpp::Parameter("my_parameter", "world")}; this->set_parameters(all_new_parameters); }; timer_ = this->create_wall_timer(1000ms, timer_callback); }
private: rclcpp::TimerBase::SharedPtr timer_; };
int main(int argc, char ** argv) { rclcpp::init(argc, argv); rclcpp::spin(std::make_shared<MinimalParam>()); rclcpp::shutdown(); return 0; }2.1 代码解析
Section titled “2.1 代码解析”顶部的 #include 语句用于引入功能包依赖。
接下来的代码创建了类和构造函数。构造函数的第一行声明了一个名为 my_parameter 的参数,默认值为 world。参数类型由默认值推断,因此在本例中会被设为字符串类型。随后,声明了一个名为 timer_callback 的 lambda 函数,它通过引用捕获当前对象 this,不接受输入参数并返回 void。timer_callback 函数的第一行从节点获取参数 my_parameter 并存入 my_param。然后 RCLCPP_INFO 函数记录一条日志。set_parameters 函数将参数 my_parameter 重置为默认字符串值 world,即使用户在外部修改了参数,也会恢复为原始值。最后,timer_ 以 1000ms 的周期初始化,使 timer_callback 函数每秒执行一次。
class MinimalParam : public rclcpp::Node { public: MinimalParam() : Node("minimal_param_node") { this->declare_parameter("my_parameter", "world");
auto timer_callback = [this](){ std::string my_param = this->get_parameter("my_parameter").as_string();
RCLCPP_INFO(this->get_logger(), "Hello %s!", my_param.c_str());
std::vector<rclcpp::Parameter> all_new_parameters{rclcpp::Parameter("my_parameter", "world")}; this->set_parameters(all_new_parameters); }; timer_ = this->create_wall_timer(1000ms, timer_callback); }最后是 timer_ 的声明。
private: rclcpp::TimerBase::SharedPtr timer_;MinimalParam 之后是 main 函数。这里先初始化 ROS 2,构造 MinimalParam 类的实例,然后 rclcpp::spin 开始处理来自节点的数据。
int main(int argc, char ** argv) { rclcpp::init(argc, argv); rclcpp::spin(std::make_shared<MinimalParam>()); rclcpp::shutdown(); return 0; }2.1.1(可选)添加 ParameterDescriptor
Section titled “2.1.1(可选)添加 ParameterDescriptor”你也可以为参数设置描述符。描述符允许你指定参数的文字描述及其约束条件,例如设为只读、指定取值范围等。为此,需要将构造函数中的代码修改为:
// ...
class MinimalParam : public rclcpp::Node { public: MinimalParam() : Node("minimal_param_node") { auto param_desc = rcl_interfaces::msg::ParameterDescriptor{}; param_desc.description = "This parameter is mine!";
this->declare_parameter("my_parameter", "world", param_desc);
auto timer_callback = [this](){ std::string my_param = this->get_parameter("my_parameter").as_string();
RCLCPP_INFO(this->get_logger(), "Hello %s!", my_param.c_str());
std::vector<rclcpp::Parameter> all_new_parameters{rclcpp::Parameter("my_parameter", "world")}; this->set_parameters(all_new_parameters); }; timer_ = this->create_wall_timer(1000ms, timer_callback);
}其余代码保持不变。运行节点后,可以运行 ros2 param describe /minimal_param_node my_parameter 来查看类型和描述。
2.2 添加可执行文件
Section titled “2.2 添加可执行文件”现在打开 CMakeLists.txt 文件,在依赖 find_package(rclcpp REQUIRED) 下方添加以下代码:
add_executable(minimal_param_node src/cpp_parameters_node.cpp) target_link_libraries(minimal_param_node rclcpp::rclcpp)
install(TARGETS minimal_param_node DESTINATION lib/${PROJECT_NAME} )3 构建与运行
Section titled “3 构建与运行”建议在工作空间(ros2_ws)的根目录下运行 rosdep,检查缺失的依赖:
Linux:
rosdep install -i --from-path src --rosdistro {DISTRO} -ymacOS:
rosdep 仅在 Linux 上运行,可直接跳到下一步。
Windows:
rosdep 仅在 Linux 上运行,可直接跳到下一步。
回到工作空间根目录 ros2_ws,构建你的新功能包:
Linux/macOS:
colcon build --packages-select cpp_parametersWindows:
colcon build --merge-install --packages-select cpp_parameters打开一个新终端,导航到 ros2_ws,并 source 环境配置文件:
Linux:
source install/setup.bashmacOS:
. install/setup.bashWindows:
call install/setup.bat现在运行节点,终端应该每秒输出以下消息:
$ ros2 run cpp_parameters minimal_param_node [INFO] [minimal_param_node]: Hello world!现在你可以看到参数的默认值了,但你可能希望自己设置它。有以下四种方法。
3.1 通过控制台更改
Section titled “3.1 通过控制台更改”本节将运用之前参数教程中的知识,将其应用到你刚创建的节点上。
确保节点正在运行:
ros2 run cpp_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!
3.2 通过 launch 文件更改
Section titled “3.2 通过 launch 文件更改”你也可以在 launch 文件中设置参数,但首先需要添加一个 launch 目录。在 ros2_ws/src/cpp_parameters/ 目录中,创建一个名为 launch 的新目录。在其中创建一个名为 cpp_parameters_launch.py 的新文件:
from launch import LaunchDescriptionfrom launch_ros.actions import Node
def generate_launch_description(): return LaunchDescription([ Node( package='cpp_parameters', executable='minimal_param_node', name='custom_minimal_param_node', output='screen', emulate_tty=True, parameters=[ {'my_parameter': 'earth'} ] ) ])可以看到,我们在启动节点 minimal_param_node 时将 my_parameter 设置为 earth。通过添加以下两行,确保输出被打印到控制台。
output="screen", emulate_tty=True,现在打开 CMakeLists.txt 文件,在之前添加的代码下方,补充以下内容:
install( DIRECTORY launch DESTINATION share/${PROJECT_NAME} )打开控制台,导航到工作空间根目录 ros2_ws,然后构建你的新功能包:
Linux/macOS:
colcon build --packages-select cpp_parametersWindows:
colcon build --merge-install --packages-select cpp_parameters然后在新终端中 source 环境配置文件:
Linux:
source install/setup.bashmacOS:
. install/setup.bashWindows:
call install/setup.bat现在用刚才创建的 launch 文件运行节点。首次运行时,终端应该返回以下消息:
$ ros2 launch cpp_parameters cpp_parameters_launch.py [INFO] [custom_minimal_param_node]: Hello earth!后续输出应该每秒显示 [INFO] [minimal_param_node]: Hello world!。
3.3 通过 launch 文件从 YAML 文件加载参数
Section titled “3.3 通过 launch 文件从 YAML 文件加载参数”你可以选择在 launch 文件中加载独立的 YAML 文件,而不是直接在其中逐个列出参数及其值。将参数放在 YAML 文件中可以更方便地组织,例如分配到不同的命名空间。你可以在这里阅读更多相关信息。
注意:在 C++ 节点中声明、获取和设置参数值时,应该使用点作为参数命名空间和名称之间的分隔符。
3.4 在节点启动时通过传递 YAML 文件作为参数更改
Section titled “3.4 在节点启动时通过传递 YAML 文件作为参数更改”请回到参数教程,回顾如何使用 CLI 在节点启动时加载参数文件。
你创建了一个带有自定义参数的节点,该参数可以通过 launch 文件或命令行设置。你还向功能包配置文件添加了依赖、可执行文件和 launch 文件,从而能够构建和运行它们,并观察参数的实际效果。
现在你已经有了一些自己的功能包和 ROS 2 系统,下一篇教程将介绍如何在遇到问题时排查环境和系统中的故障。
- 有关使用 YAML 文件加载参数的更多详细信息,请参阅管理大型项目教程中的相关章节。
- 如果你想了解如何监控和响应参数更改,请查看监控参数变化(C++)教程。