ROS:参数服务器通信

为什么需要

在机器人开发中,会有很多参数和设置可以后期需要调整的,如果都放到源码里很难实现动态修改和管理,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也可以可视化设置和查看
在这里插入图片描述

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值