Robotics - [ROS2 Foundation] Ros2 Parameters

Posted by Rico's Nerd Cluster on November 22, 2024

ROS2 Parameters Introduction

What are ROS2 parameters?

ROS2 Parameters are one means to change certain values in a node during runtime. They are associated initialized, set, and retrieved at the level of individual nodes, and hence their lifetimes are the same as their nodes’ lifetimes.

Each parameter has a key, value, and a descriptor.

  • The key is a string
  • The value is one of the following types: bool, int64, float64, string, byte[], bool[], int64[], float64[] string[]
  • The descriptor is a parameter’s description

Here is an example of how we can use them:

  1. Initialize / declare
    • Command Delare params?

      1
      2
      3
      4
      5
      
        from rclpy.node import Node
        class ParameterNode(Node):
            def __init__(self):
                super().__init__('parameter_node')
                self.declare_parameter('shared_value', 42.0)
      
    • When a parameter needs to take in different types, use ParameterDescriptor with the dynamic_typing member variable set to true

      1
      2
      3
      
        from rcl_interfaces.msg import ParameterDescriptor
        descriptor = ParameterDescriptor(dynamic_typing=True)
        self.declare_parameter('dynamic_param', 0, descriptor)
      
    • In CLI:

      • ros2 param list shows all parameters:

        1
        2
        3
        
          /teleop_turtle:
              qos_overrides./parameter_events.publisher.depth
              use_sim_time
        
  2. Get
  • param_value = self.get_parameter_or(param_name, None)
  • For parameters that can’t be known ahead of time, they can be instantiated as allow_undeclared_parameters = True

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
      ```python
      class MyNode(Node):
          def __init__(self):
              super().__init__('my_node', allow_undeclared_parameters=True) 
          def get_dynamic_param(self, param_name):
              param_value = self.get_parameter_or(param_name, None)
              return param_value
      def main(args=None):
          ...
          node.get_dynamic_param('undeclared_param')  # Should print a warning and return None
      ```
    
  • In CLI:
    • ros2 param get <node_name> <parameter_name>
      • ros2 param list: a CLI python helper that pings a running node to retrive their parameters. It calls list_parameter service of each node.
      • So there could be a an exception like Exception while calling service of node 'my_node': None where the node is busy
    • View all params of a node, and they can be saved into an yaml:

      1
      2
      3
      4
      
          ```
          ros2 param dump <node_name>
          ros2 param dump /turtlesim > turtlesim.yaml
          ```
      
  • Set.
    • In code?

      1
      2
      3
      4
      5
      6
      7
      
      ```python
      from rclpy.parameter import Parameter
      class MyNode(Node):
          def update_param(self, key, val):
              param = Parameter(key, Parameter.Type.INTEGER, val)
              self.set_parameters([param])
      ```
      
    • In CLI:

      • ros2 param set <node_name> <parameter_name> <value>
      • Load from a yaml file:

        1
        2
        3
        
          ```
          ros2 param load <node_name> <parameter_file>
          ```
        
      • Load parameters on start up:

        1
        2
        3
        
          ```
          ros2 run <package_name> <executable_name> --ros-args --params-file <file_name>
          ```
        

Rclcpp Parameters

Overriding ROS 2 Parameters

You can expose and override parameters in two parts:

  1. Inside your node (RunImuNode): in the constructor, declare each parameter (with an optional default) and read it into a member:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class RunImuNode : public rclcpp::Node
{
public:
  explicit RunImuNode(const rclcpp::NodeOptions &opts = {})
    : Node("run_imu_node", opts)
  {
    // 1) Declare parameters (here with defaults)
    this->declare_parameter<bool>("is_millig",      true);
    this->get_parameter("is_millig",      is_millig_);
  }

private:
  bool        is_millig_;
};
  1. From “user” or test code: when you instantiate the node, supply overrides via NodeOptions:
1
2
3
4
5
6
7
rclcpp::NodeOptions options;
options.parameter_overrides({
  {"is_millig",            false},
});

auto run_imu_node = 
  std::make_shared<RunImuNode>(options);

Passing --ros-args -p <ARG> to a ROS 2 GTest

You can make your test binary accept ROS 2 parameters (e.g. visualize) without touching your node by:

  1. Providing a custom main(): Capture the full argc/argv (including --ros-args) and use them for both rclcpp::init() and Google Test:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <gtest/gtest.h>
#include <rclcpp/rclcpp.hpp>

// Store all CLI args so we can forward them later
static std::vector<std::string> g_test_args;

int main(int argc, char ** argv)
{
 ::testing::InitGoogleTest(&argc, argv);
 // Copy original args (including --ros-args)
 g_test_args.assign(argv, argv + argc);

 rclcpp::init(argc, argv);
 int ret = RUN_ALL_TESTS();
 rclcpp::shutdown();
 return ret;
}
  1. Forwarding the ROS 2 args into your node. In your test fixture’s SetUp(), pass along g_test_args via NodeOptions::arguments(), then declare and read your test-only parameter:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
std::vector<std::string> g_test_args;
class TestIntegrateIMU : public ::testing::Test {
protected:
  bool visualize_{false};

  void SetUp() override {
    // 1) Build options with the exact same CLI args
    rclcpp::NodeOptions options;
    options.arguments(g_test_args);
    run_imu_node_ = std::make_shared<RunImuNode>(options);
    // 4) Declare & read the test-only “visualize” flag
    run_imu_node_->declare_parameter("visualize", false);
    run_imu_node_->get_parameter("visualize", visualize_);
    // …
  }
};

  1. Run test with: ./test_integrate_imu --ros-args -p visualize:=true