一、参数
1.概念
在ROS2中,参数通信是一种用于配置和管理节点行为的机制。节点可以通过参数动态地调整配置或逻辑,而无需修改代码或重新编译,从而提高系统的灵活性。
参数就是键值对,一个名称对应一个值,如:background_b =255
参数的类型:
bool 和bool[],布尔类型用来表示开关,比如我们可以控制雷达控制节点,开始扫描和停止扫描。
int64 和int64[],整形表示一个数字,含义可以自己来定义,这里我们可以用来表示李四节点写小说的周期值
float64 和float64[],浮点型,可以表示小数类型的参数值
string 和string[],字符串,可以用来表示雷达控制节点中真实雷达的ip地址
byte[],字节数组,这个可以用来表示图片,点云数据等信息
2、参数管理
1.参数服务器
在ROS2中,没有专门的“参数服务器”,每个节点独立管理自己的参数。节点可以声明、读取和更新自己的参数,其他节点或工具可以通过参数通信接口查询或修改节点的参数。
2.常用命令
查看参数描述:
ros2 param describe <node_name> <param_name>
示例:
ros2 param describe /turtlesim background_b
获取和设置参数:
ros2 param get <node_name> <param_name>
ros2 param set <node_name> <param_name> <value>
批量设置参数
可以使用YAML文件批量加载和设置参数,简化配置过程。
ros2 run <package_name> <executable_name> --ros-args --params-file <file_name>
示例:
ros2 run turtlesim turtlesim_node --ros-args --params-file ./turtlesim.yaml
3、参数通信的特点
灵活性:运行时可以动态调整参数,调试更方便。
模块化:不同节点可以独立管理参数,易于配置和维护。
便捷性:支持通过命令行和YAML文件批量设置参数,简化开发流程。
无重启修改:无需重新启动节点即可修改参数值,提高系统的实时性。
二、参数通信CPP
1.代码的功能
参数相关的API实现对ROS2打印的日志级别控制
/// The severity levels of log messages / loggers.
enum RCUTILS_LOG_SEVERITY
{
RCUTILS_LOG_SEVERITY_UNSET = 0, ///< The unset log level
RCUTILS_LOG_SEVERITY_DEBUG = 10, ///< The debug log level
RCUTILS_LOG_SEVERITY_INFO = 20, ///< The info log level
RCUTILS_LOG_SEVERITY_WARN = 30, ///< The warn log level
RCUTILS_LOG_SEVERITY_ERROR = 40, ///< The error log level
RCUTILS_LOG_SEVERITY_FATAL = 50, ///< The fatal log level
};
这些是日志的级别,通过设置get_logger()的参数,打印不同级别的日志。
2.cpp代码
#include <chrono>
#include "rclcpp/rclcpp.hpp"
class ParametersBasicNode : public rclcpp::Node {
public:
// 构造函数,有一个参数为节点名称
explicit ParametersBasicNode(std::string name) : Node(name) {
RCLCPP_INFO(this->get_logger(), "节点已启动:%s.", name.c_str());
this->declare_parameter("rcl_log_level", 0); /*声明参数*/
this->get_parameter("rcl_log_level", log_level); /*获取参数*/
/*设置日志级别*/
this->get_logger().set_level((rclcpp::Logger::Level)log_level);
using namespace std::literals::chrono_literals;
timer_ = this->create_wall_timer(
500ms, std::bind(&ParametersBasicNode::timer_callback, this));
}
private:
int log_level;
rclcpp::TimerBase::SharedPtr timer_;
void timer_callback() {
this->get_parameter("rcl_log_level", log_level); /*获取参数*/
/*设置日志级别*/
this->get_logger().set_level((rclcpp::Logger::Level)log_level);
std::cout<<"======================================================"<<std::endl;
RCLCPP_DEBUG(this->get_logger(), "我是DEBUG级别的日志,我被打印出来了!");
RCLCPP_INFO(this->get_logger(), "我是INFO级别的日志,我被打印出来了!");
RCLCPP_WARN(this->get_logger(), "我是WARN级别的日志,我被打印出来了!");
RCLCPP_ERROR(this->get_logger(), "我是ERROR级别的日志,我被打印出来了!");
RCLCPP_FATAL(this->get_logger(), "我是FATAL级别的日志,我被打印出来了!");
}
};
int main(int argc, char** argv) {
rclcpp::init(argc, argv);
/*创建对应节点的共享指针对象*/
auto node = std::make_shared<ParametersBasicNode>("parameters_basic");
/* 运行节点,并检测退出信号*/
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
整个代码的流程为:
ROS2初始化
创建参数节点ParametersBasicNode类对象,名称为parameters_basic
定时器启动,每500ms周期进入回调函数
程序执行与结束
+---------------------------+
| ROS2 初始化 (main) |
+---------------------------+
|
v
+---------------------------+
| 创建 ParametersBasicNode |
| - 声明参数 rcl_log_level |
| - 初始化日志级别 |
+---------------------------+
|
v
+---------------------------+
| 500ms 定时器启动 |
| 进入节点 spin 循环 |
+---------------------------+
|
v
+---------------------------+
| 定时器回调函数执行 |
| 1. 获取 rcl_log_level |
| 2. 动态设置日志级别 |
| 3. 输出各级别日志信息 |
+---------------------------+
|
v
+---------------------+
| 等待下一次回调 |
+---------------------+
1.ROS初始化
rclcpp::init(argc, argv);
2.创建参数节点
auto node = std::make_shared<ParametersBasicNode>("parameters_basic");
通过这句话创建了ParametersBasicNode类型的节点node,名称为"parameters_basic",这里调用了ParametersBasicNode类的构造函数,
explicit ParametersBasicNode(std::string name) : Node(name) {
RCLCPP_INFO(this->get_logger(), "节点已启动:%s.", name.c_str());
this->declare_parameter("rcl_log_level", 0); /*声明参数*/
this->get_parameter("rcl_log_level", log_level); /*获取参数*/
/*设置日志级别*/
this->get_logger().set_level((rclcpp::Logger::Level)log_level);
using namespace std::literals::chrono_literals;
timer_ = this->create_wall_timer(
500ms, std::bind(&ParametersBasicNode::timer_callback, this));
}
这里使用了explicit关键字,因为该构造函数是单参数的函数,使用explicit防止单参数构造函数发生隐式转换,确保对象只能通过显式传参创建。
进入构造函数后,首先输出日志信息到终端,接着调用 declare_parameter 方法声明参数"rcl_log_level"的值是0,rcl_log_level参数用来表示节点的日志级别。
之后调用 get_parameter 方法读取参数 rcl_log_level 的值,并存储在 log_level 成员变量中,该变量就存储的之前我们声明给参数"rcl_log_level"的值。
this->get_logger().set_level((rclcpp::Logger::Level)log_level);
使用get_logger().set_level() 的方法,根据参数 log_level 的值来设置日志级别,使用(rclcpp::Logger::Level)对log_level进行类型转换,变为ROS2 日志级别枚举值。这里在创建节点时,默认的log_level的值是0,即INFO级别,DEBUG的内容不会被输出。
using namespace std::literals::chrono_literals;
timer_ = this->create_wall_timer(
500ms, std::bind(&ParametersBasicNode::timer_callback, this));
这里是创建定时器来周期性进入回调函数。
使用this->create_wall_timer方法创建一个定时器对象,传给成员变量timer_,timer_是一个TimeBase类型的智能指针。这里传入两个参数,第一个参数是定时器周期500ms,第二个参数std::bind(&ParametersBasicNode::timer_callback, this),使用 std::bind 将类的成员函数 timer_callback 绑定到定时器事件(this)上,确保定时器触发时执行该函数。
3.进入回调函数
void timer_callback() {
this->get_parameter("rcl_log_level", log_level); /*获取参数*/
/*设置日志级别*/
this->get_logger().set_level((rclcpp::Logger::Level)log_level);
std::cout<<"======================================================"<<std::endl;
RCLCPP_DEBUG(this->get_logger(), "我是DEBUG级别的日志,我被打印出来了!");
RCLCPP_INFO(this->get_logger(), "我是INFO级别的日志,我被打印出来了!");
RCLCPP_WARN(this->get_logger(), "我是WARN级别的日志,我被打印出来了!");
RCLCPP_ERROR(this->get_logger(), "我是ERROR级别的日志,我被打印出来了!");
RCLCPP_FATAL(this->get_logger(), "我是FATAL级别的日志,我被打印出来了!");
}
定时器周期进入回调函数中,首先获取rcl_log_level的值,存放在log_level 成员变量中,之后同样
this->get_logger().set_level((rclcpp::Logger::Level)log_level);
使用set_level的方法设置输出的level,然后不同级别的日志进行输出,低于当前级别的日志不会输出出来。即每500ms进行一次日志级别的更新和输出。
4.程序运行并结束
rclcpp::spin(node);
rclcpp::shutdown();
保持节点的运行,检测到退出后退出运行。
编译运行该节点后,会持续进行输出,这时我们使用命令行改变参数
ros2 run example_parameters_rclcpp parameters_basic --ros-args -p rcl_log_level:=10
将rcl_log_level参数设置为10,不用重新进行节点的编译,节点的配置就会被更改,输出的日志内容就会发送变化。
由此我们来理解参数通信的作用:
创建节点时,可以在代码中通过 declare_parameter 来声明参数,并给参数一个默认值,如:
this->declare_parameter("rcl_log_level", 0);
通过这种方式,节点可以拥有 默认参数值,即使运行时没有指定任何参数,节点依然可以正常工作。
当节点运行的时候,无需重新启动节点,可以动态修改节点的参数。使用方法就是我们之前的命令行的指令,
ros2 param get <node_name> <param_name> #获取参数的值
ros2 param set <node_name> <param_name> <value> #设置参数的值
这样修改后,节点的回调函数(如 timer_callback)中会再次调用 get_parameter 获取最新值,并修改节点的配置,实现了动态的更新。