为什么需要
在机器人开发中,会有很多参数和设置可以后期需要调整的,如果都放到源码里很难实现动态修改和管理,ROS2为了解决这一问题,提出了参数这一通信机制。
是什么
如何理解“参数”?
- ROS2的参数就是节点的设置
- 参数类似于ROS中的全局变量,由ROS Master进行管理,其通信机制较为简单,不涉及TCP/UDP的通信。
参数服务器是一种特殊的“通信方式”。特殊点在于参数服务器是节点存储参数的地方、用于配置参数、全局共享参数。
- 参数服务器使用互联网传输,在节点管理器中运行,实现整个通信过程
- 参数服务器,作为ROS中另外一种数据传输方式,有别于topic和service,它更加静态。
- 参数服务器维护者一个数据字典,字典里存储着各种参数和配置
- 参数服务器在ROS中主要用于实现不同节点之间的数据共享。一般用于存储一些多节点共享的数据,类似于全局变量。
- 参数服务器相当于是独立于所有节点的一个公共容器,可以将数据存储在该容器中,被不同的节点调用,当然不同的节点也可以往其中存储数据。
维护方式
参数服务器的维护方式非常的简单灵活,总的来讲有三种方式:
- 命令行维护
- launch文件内读写
- node源码
命令行维护
使用命令行来维护参数服务器,主要使用rosparam语句来进行操作的各种命令,如下表:
ROS1:rosparam 命令 | 作用 |
---|---|
rosparam set param_key param_value | 设置参数 |
rosparam get param_key | 显示参数 |
rosparam load file_name | 从文件加载参数 |
rosparam dump file_name | 保存参数到文件 |
rosparam delete | 删除参数 |
rosparam list | 列出参数名称 |
load&&dump文件
load和dump文件需要遵守YAML格式,一般格式如下:
key : value
具体示例如下:
name:'Zhangsan'
age:20
gender:'M'
score{Chinese:80,Math:90}
score_history:[85,82,88,90]
一般格式如下:
launch文件内读写
launch文件中有很多标签,而与参数服务器相关的标签只有两个,一个是< param>,另一个是< rosparam>
node源码
除了上述最常用的两种读写参数服务器的方法,还有一种就是修改ROS的源码,也就是利用API来对参数服务器进行操作。具体内容我们学习完后面章节再进行介绍。
ROS2:体验一下
(1)运行小乌龟模拟器节点和小乌龟控制节点
# 终端一
ros2 run turtlesim turtlesim_node
# 终端二
ros2 run turtlesim turtle_teleop_key
(2)查看参数
- 查看节点有哪些参数(设置)
ros2 param list
- 看参数的详情信息:参数的名字,参数的描述,参数的类型,还有对参数的约束,最大值最小值等。
# ---- 语法--------
ros2 param describe <node_name> <param_name>
# ------ 举例 ---
ros2 param describe /turtlesim background_b
- 获取参数名
param list
- 获取参数值
ros2 param get /turtlesim background_b
- 设置参数值:注意这里只是临时修改
# ------------语法
ros2 param set <node_name> <parameter_name> <value>
# ---------- 举个例子
ros2 param set /turtlesim background_r 44
ros2 param set /turtlesim background_g 156
ros2 param set /turtlesim background_b 10
- 把参数存起来:相当去把当前的参数值拍一张快照,然后保存下来,后面可以用于恢复参数到当前的数值。
# ------------语法
ros2 param dump <node_name>
# ---------- 举个例子
ros2 param dump /turtlesim
# 文件被保存成了yaml格式,用cat指令看一看
文件被保存成了yaml格式,用cat指令看一看
# 恢复参数:
# 方法一:
# 我们Ctrl+C关闭乌龟模拟器,然后再重新运行。
ros2 run turtlesim turtlesim_node
# 着通过param的load的方法把参数值恢复成我们之前存储的。
ros2 param load /turtlesim ./turtlesim.yaml
# 方法二:启动节点时加载参数快照
# 语法:
ros2 run <package_name> <executable_name> --ros-args --params-file <file_name>
# 先关闭乌龟,然后重新运行
ros2 run turtlesim turtlesim_node --ros-args --params-file ./turtlesim.yaml
三个角色
- ROS Master (管理者):管理者作为一个公共的容器保存数据
- Talker (参数设置者):参数设置者往容器中存储数据
- Listener (参数调用者): 参数调用者读取容器中所需的数据
建立流程
- step1:
- 参数设置者使用RPC向参数服务器发送参数(包括参数名与参数值)
- ROS Master 将参数保存到参数列表中。
- step2:
- 参数调用者使用RPC向参数服务器发送参数查找请求,请求中包含要查找的参数名。
- step3:
- ROS Master 根据步骤2请求提供的参数名查找参数值,并使用RPC将查询结果发送给参数调用者。
参数组成成分
ROS2参数是由键值对组成的.
- 名字的数据类型是字符串
- 值的数据类型可以是:
- bool 和bool[],布尔类型用来表示开关,比如我们可以控制雷达控制节点,开始扫描和停止扫描。
- int64 和int64[],整形表示一个数字,含义可以自己来定义
- float64 和float64[],浮点型,可以表示小数类型的参数值
- string 和string[],字符串,可以用来表示雷达控制节点中真实雷达的ip地址
- byte[],字节数组,这个可以用来表示图片,点云数据等信息
代码实现
ROS2将日志分为五个级别,在RCLCPP中通过不同的宏可以实现不同日志级别日志的打印,例程如下
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级别的日志,我被打印出来了!");
有时候日志太多,会让人眼花缭乱找不到重要信息,所以我们需要对日志的级别进行过滤,比如只看INFO以上级别的,ROS2中可以通过已有的API设置日志的级别,RCLCPP中API如下:
this->get_logger().set_level(log_level);
目标:声明参数并实现动态修改打印的日志级别功能。
RCLCPP实现
创建功能包和节点
mkdir -p chapt4/chapt4_ws/
ros2 pkg create example_parameters_rclcpp --build-type ament_cmake --dependencies rclcpp --destination-directory src --node-name parameters_basic --maintainer-name "fishros" --maintainer-email "fishros@foxmail.com"
parameters_basic.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());
}
private:
};
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;
}
构建测试:
colcon build --packages-select example_parameters_rclcpp
source install/setup.bash
ros2 run example_parameters_rclcpp parameters_basic
参数API
在RCLCPP的API中,关于参数相关的函数比较多些,但都是围绕参数获取、参数设置、参数描述、列出参数、添加和移除参数回调事件。
使用参数控制节点日志级别
#include <chrono>
#include "rclcpp/rclcpp.hpp"
/*
# declare_parameter 声明和初始化一个参数
# describe_parameter(name) 通过参数名字获取参数的描述
# get_parameter 通过参数名字获取一个参数
# set_parameter 设置参数的值
*/
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;
}
上面set_level,设置日志级别,ROS2的日志级别定义在文件/opt/ros/humble/include/rcutils/rcutils/logging.h的167-175行
/// 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
};
编译测试
colcon build --packages-select example_parameters_rclcpp
source install/setup.bash
ros2 run example_parameters_rclcpp parameters_basic
运行后你会发现DEBUG级别的日志并没有被打印出来,原因在于我们将节点的日志级别设置为了0,0对应的日志级别为RCUTILS_LOG_SEVERITY_UNSET即未设置使用默认级别,节点默认的日志级别就是INFO级别的,所以只能打印INFO以上的日志信息。
运行节点的时候可以指定参数的值,我们尝试将log_level的值改成10即DEBUG级别。
ros2 run example_parameters_rclcpp parameters_basic --ros-args -p rcl_log_level:=10
除了在节点运行前通过CLI传递参数,在运动的过程中也可以动态的修改参数
#查看参数列表
ros2 param list
#设置参数级别
ros2 param set /parameters_basic rcl_log_level 10
补充
上面我们通过参数实现了动态控制节点日志级别的功能,其实像这样的功能ROS2早已为我们准备好了,在运行任意节点时候可以通过CLI传递日志级别配置。
ros2 run package-name node-name --ros-args --log-level debug
除了命令行设置参数和查看日志,通过rqt也可以可视化设置和查看