• Stars
    star
    228
  • Rank 174,295 (Top 4 %)
  • Language
    Python
  • License
    BSD 3-Clause "New...
  • Created about 2 years ago
  • Updated about 1 month ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Declarative ROS 2 Parameters

generate_parameter_library

Generate C++ or Python code for ROS 2 parameter declaration, getting, and validation using declarative YAML. The generated library contains a C++ struct with specified parameters. Additionally, dynamic parameters and custom validation are made easy.

Killer Features

  • Declarative YAML syntax for ROS 2 Parameters converted into C++ or Python struct
  • Declaring, Getting, Validating, and Updating handled by generated code
  • Dynamic ROS 2 Parameters made easy
  • Custom user specified validator functions
  • Automatically create documentation of parameters

Basic Usage

  1. Create YAML parameter codegen file
  2. Add parameter library generation to project
  3. Use generated struct into project source code

Create yaml parameter codegen file

Write a yaml file to declare your parameters and their attributes.

src/turtlesim_parameters.yaml

turtlesim:
  background:
    r: {
      type: int,
      default_value: 0,
      description: "Red color value for the background, 8-bit",
      validation: {
        bounds<>: [0, 255]
      }
    }
    g: {
      type: int,
      default_value: 0,
      description: "Green color value for the background, 8-bit",
      validation: {
        bounds<>: [0, 255]
      }
    }
    b: {
      type: int,
      default_value: 0,
      description: "Blue color value for the background, 8-bit",
      validation: {
        bounds<>: [0, 255]
      }
    }

Add parameter library generation to project

package.xml

<depend>generate_parameter_library</depend>

CMakeLists.txt

find_package(generate_parameter_library REQUIRED)

generate_parameter_library(
  turtlesim_parameters # cmake target name for the parameter library
  src/turtlesim_parameters.yaml # path to input yaml file
)

add_executable(minimal_node src/turtlesim.cpp)
target_link_libraries(minimal_node PRIVATE
  rclcpp::rclcpp
  turtlesim_parameters
)

setup.py

from generate_parameter_library_py.setup_helper import generate_parameter_module

generate_parameter_module(
  "turtlesim_parameters", # python module name for parameter library
  "turtlesim/turtlesim_parameters.yaml", # path to input yaml file
)

Use generated struct into project source code

src/turtlesim.cpp

#include <rclcpp/rclcpp.hpp>
#include "turtlesim_parameters.hpp"

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  auto node = std::make_shared<rclcpp::Node>("turtlesim");
  auto param_listener = std::make_shared<turtlesim::ParamListener>(node);
  auto params = param_listener->get_params();

  auto color = params.background;
  RCLCPP_INFO(node->get_logger(),
    "Background color (r,g,b): %d, %d, %d",
    color.r, color.g, color.b);

  return 0;
}

turtlesim/turtlesim.py

import rclpy
from rclpy.node import Node
from turtlesim_pkg.turtlesim_parameters import turtlesim_parameters

def main(args=None):
  rclpy.init(args=args)
  node = Node("turtlesim")
  param_listener = turtlesim_parameters.ParamListener(node)
  params = param_listener.get_params()

  color = params.background
  node.get_logger().info(
    "Background color (r,g,b): %d, %d, %d" %
    color.r, color.g, color.b)

Use example yaml files in tests

When using parameter library generation it can happen that there are issues when executing tests since parameters are not defined and the library defines them as mandatory. To overcome this it is recommended to define example yaml files for tests and use them as follows:

find_package(ament_cmake_gtest REQUIRED)
add_rostest_with_parameters_gtest(test_turtlesim_parameters test/test_turtlesim_parameters.cpp
  ${CMAKE_CURRENT_SOURCE_DIR}/test/example_turtlesim_parameters.yaml)
target_include_directories(test_turtlesim_parameters PRIVATE include)
target_link_libraries(test_turtlesim_parameters turtlesim_parameters)
ament_target_dependencies(test_turtlesim_parameters rclcpp)

when using gtest, or:

find_package(ament_cmake_gmock REQUIRED)
add_rostest_with_parameters_gmock(test_turtlesim_parameters test/test_turtlesim_parameters.cpp
  ${CMAKE_CURRENT_SOURCE_DIR}/test/example_turtlesim_parameters.yaml)
target_include_directories(test_turtlesim_parameters PRIVATE include)
target_link_libraries(test_turtlesim_parameters turtlesim_parameters)
ament_target_dependencies(test_turtlesim_parameters rclcpp)

when using gmock test library.

🤖 P.S. having this example yaml files will make your users very grateful because they will always have a working example of a configuration for your node.

Detailed Documentation

Cpp namespace

The root element of the YAML file determines the namespace used in the generated C++ code. We use this to put the Params struct in the same namespace as your C++ code.

cpp_namespace:
# additionally fields  ...

Parameter definition

The YAML syntax can be thought of as a tree since it allows for arbitrary nesting of key-value pairs. For clarity, the last non-nested value is referred to as a leaf. A leaf represents a single parameter and has the following format.

cpp_namespace:
  param_name: {
    type: int,
    default_value: 3,
    read_only: true,
    description: "A read only  integer parameter with a default value of 3",
    validation:
      # validation functions ...
  }

A parameter is a YAML dictionary with the only required key being type.

Field Description
type The type (string, double, etc) of the parameter.
default_value Value for the parameter if the user does not specify a value.
read_only Can only be set at launch and are not dynamic.
description Displayed by ros2 param describe.
validation Dictionary of validation functions and their parameters.

The types of parameters in ros2 map to C++ types.

Parameter Type C++ Type
string std::string
double double
int int
bool bool
string_array std::vector<std::string>
double_array std::vector<double>
int_array std::vector<int>
bool_array std::vector<bool>
string_fixed_XX FixedSizeString<XX>
none NO CODE GENERATED

Fixed size types are denoted with a suffix _fixed_XX, where XX is the desired size. The corresponding C++ type is a data wrapper class for conveniently accessing the data. Note that any fixed size type will automatically use a size_lt validator. Validators are explained in the next section.

The purpose of none type is purely documentation, and won't generate any C++ code. See Parameter documentation for details.

Built-In Validators

Validators are C++ functions that take arguments represented by a key-value pair in yaml. The key is the name of the function. The value is an array of values that are passed in as parameters to the function. If the function does not take any values you write null or [] to for the value.

joint_trajectory_controller:
  command_interfaces: {
    type: string_array,
    description: "Names of command interfaces to claim",
    validation: {
      size_gt<>: [0],
      unique<>: null,
      subset_of<>: [["position", "velocity", "acceleration", "effort",]],
    }
  }

Above are validations for command_interfaces from ros2_controllers. This will require this string_array to have these properties:

  • There is at least one value in the array
  • All values are unique
  • Values are only in the set ["position", "velocity", "acceleration", "effort",]

You will note that some validators have a suffix of <>, this tells the code generator to pass the C++ type of the parameter as a function template. Some of these validators work only on value types, some on string types, and others on array types. The built-in validator functions provided by this package are:

Value validators

Function Arguments Description
bounds<> [lower, upper] Bounds checking (inclusive)
lt<> [value] parameter < value
gt<> [value] parameter > value
lt_eq<> [value] parameter <= value
gt_eq<> [value] parameter >= value
one_of<> [[val1, val2, ...]] Value is one of the specified values

String validators

Function Arguments Description
fixed_size<> [length] Length string is specified length
size_gt<> [length] Length string is greater than specified length
size_lt<> [length] Length string is less less specified length
not_empty<> [] String parameter is not empty
one_of<> [[val1, val2, ...]] String is one of the specified values

Array validators

Function Arguments Description
unique<> [] Contains no duplicates
subset_of<> [[val1, val2, ...]] Every element is one of the list
fixed_size<> [length] Number of elements is specified length
size_gt<> [length] Number of elements is greater than specified length
size_lt<> [length] Number of elements is less less specified length
not_empty<> [] Has at-least one element
element_bounds<> [lower, upper] Bounds checking each element (inclusive)
lower_element_bounds<> [lower] Lower bound for each element (inclusive)
upper_element_bounds<> [upper] Upper bound for each element (inclusive)

Custom validator functions

Validators are functions that return a tl::expected<void, std::string> type and accept a rclcpp::Parameter const& as their first argument and any number of arguments after that can be specified in YAML. Validators are C++ functions defined in a header file similar to the example shown below.

Here is an example custom validator.

#include <rclcpp/rclcpp.hpp>

#include <fmt/core.h>
#include <tl_expected/expected.hpp>

namespace my_project {

tl::expected<void, std::string> integer_equal_value(
    rclcpp::Parameter const& parameter, int expected_value) {
  int param_value = parameter.as_int();
    if (param_value != expected_value) {
        return tl::make_unexpected(fmt::format(
            "Invalid value {} for parameter {}. Expected {}",
            param_value, parameter.get_name(), expected_value);
    }

  return {};
}

}  // namespace my_project

To configure a parameter to be validated with the custom validator function integer_equal_value with an expected_value of 3 you could would this to the YAML.

validation: {
  "my_project::integer_equal_value": [3]
}

Nested structures

After the top level key, every subsequent non-leaf key will generate a nested c++ struct. The struct instance will have the same name as the key.

cpp_name_space:
  nest1:
    nest2:
      param_name: { # this is a leaf
        type: string_array
      }

The generated parameter value can then be access with params.nest1.nest2.param_name

Use generated struct in Cpp

The generated header file is named based on the target library name you passed as the first argument to the cmake function. If you specified it to be turtlesim_parameters you can then include the generated code with #include "turtlesim_parameters.hpp".

#include "turtlesim_parameters.hpp"

In your initialization code, create a ParamListener which will declare and get the parameters. An exception will be thrown if any validation fails or any required parameters were not set. Then call get_params on the listener to get a copy of the Params struct.

auto param_listener = std::make_shared<turtlesim::ParamListener>(node);
auto params = param_listener->get_params();

Dynamic Parameters

If you are using dynamic parameters, you can use the following code to check if any of your parameters have changed and then get a new copy of the Params struct.

if (param_listener->is_old(params_)) {
  params_ = param_listener->get_params();
}

Parameter documentation

In some case, parameters might be unknown only at compile-time, and cannot be part of the generated C++ code. However, for documentation purpose of such parameters, the type none was introduced.

Parameters with none type won't generate any C++ code, but can exist to describe the expected name or namespace, that might be declared by an external piece of code and used in an override.

A typical use case is a controller, loading pluginlib-based filters, that themselves require (and declare) parameters in a known structure.

Example of declarative YAML

force_torque_broadcaster_controller:
  sensor_name: {
    type: string,
    default_value: "",
    description: "Name of the sensor used as prefix for interfaces if there are no individual interface names defined.",
  }
  frame_id: {
    type: string,
    default_value: "",
    description: "Sensor's frame_id in which values are published.",
  }
  sensor_filter_chain: {
    type: none,
    description: "Map of parameters that defines a filter chain, containing filterN as key and underlying map of parameters needed for a specific filter. See <some docs> for more details.",
  }

Example of parameters for that controller

force_torque_broadcaster_controller:
  ros__parameters:
    sensor_name: "fts_sensor"
    frame_id: "fts_sensor_frame"
    sensor_filter_chain:
      filter1:
        type: "control_filters/LowPassFilterWrench"
        name: "low_pass_filter"
        params:
          sampling_frequency: 200.0
          damping_frequency: 50.0
          damping_intensity: 1.0

Example Project

See cpp example or python example for complete examples of how to use the generate_parameter_library.

Generated code output

The generated code is primarily consists of two major components:

  1. struct Params that contains values of all parameters and
  2. class ParamListener that handles parameter declaration, updating, and validation. The general structure is shown below.
namespace cpp_namespace {

struct Params {
  int param_name = 3;
  struct {
    struct{
      std::string param_name;
      // arbitrary nesting depth...
    } nest2;
  } nest1;
  // for detecting if the parameter struct has been updated
  rclcpp::Time __stamp;
};

class ParamListener {
 public:
  ParamListener(rclcpp::ParameterInterface);
  ParamListener(rclcpp::Node::SharedPtr node)
    : ParameterListener(node->get_parameters_interface()) {}
  ParamListener(rclcpp_lifecycle::LifecycleNode::SharedPtr node)
    : ParameterListener(node->get_parameters_interface()) {}

  // create a copy of current parameter values
  Params get_params() const;

  // returns true if parameters have been updated since last time get_params was called
  bool is_old(Params const& other) const;

  // loop over all parameters: perform validation then update
  rcl_interfaces::msg::SetParametersResult update(const std::vector<rclcpp::Parameter> &parameters);

  // declare all parameters and throw exception if non-optional value is missing or validation fails
  void declare_params(const std::shared_ptr<rclcpp::node_interfaces::NodeParametersInterface>& parameters_interface);

 private:
  Params params_;
};

} // namespace cpp_namespace

The structure of the Params struct and the logic for declaring and updating parameters is generated from a YAML file specification.

Generate markdown documentation

Using generate_parameter_library you can generate a Markdown-file for your parameters.yaml file.

generate_parameter_library_markdown --input_yaml example/src/parameters.yaml --output_markdown_file parameters.md

This will generate a file parameters.md in the current folder that contains a markdown representation of the parameters.yaml file that you can directly include into your documentation.

FAQ

Q. What happens if I declare a parameter twice? Will I get an error at runtime? A. The declare routine that is generated checks to see if each parameter has been declared first before declaring it. Because of this you can declare a parameter twice but it will only have the properties of the first time you declared it. Here is some example generated code.

if (!parameters_interface_->has_parameter(prefix_ + "scientific_notation_num")) {
    rcl_interfaces::msg::ParameterDescriptor descriptor;
    descriptor.description = "Test scientific notation";
    descriptor.read_only = false;
    auto parameter = to_parameter_value(updated_params.scientific_notation_num);
    parameters_interface_->declare_parameter(prefix_ + "scientific_notation_num", parameter, descriptor);
}

Q: How do I log when parameters change? A. The generated library outputs debug logs whenever a parameter is read from ROS.

More Repositories

1

rviz_visual_tools

C++ API wrapper for displaying shapes and meshes in Rviz
C++
747
star
2

ros_control_boilerplate

Provides a simple simulation interface and template for setting up a hardware interface for ros_control
C++
244
star
3

roscpp_code_format

Auto formatting script for ROS C++ Style Guidelines
226
star
4

data_tamer

C++ library for Fearless Timeseries Logging
C++
196
star
5

deep_grasp_demo

Deep learning for grasp detection within MoveIt.
C++
104
star
6

abb_ros2

C++
80
star
7

pick_ik

Inverse Kinematics solver for MoveIt
C++
68
star
8

topic_based_ros2_control

ros2_control hardware interface that uses topics to command the robot and publish its state
C++
58
star
9

tf_visual_tools

Manually manipulate TFs in ROS using this Rviz plugin.
C++
51
star
10

ros2_robotiq_gripper

C++
48
star
11

RSL

ROS Support Library
C++
46
star
12

rosparam_shortcuts

Quickly load variables from rosparam with good command line error checking.
C++
38
star
13

descartes_capability

Drop-in capability for MoveIt's move_group that uses Descartes
C++
37
star
14

ros_testing_templates

C++
28
star
15

tf_keyboard_cal

Allows manual control of a TF through the keyboard or interactive markers
C++
27
star
16

quick-ros-install

Instant install script for ROS
Shell
25
star
17

ros2_package_template

A ROS2 package cookiecutter template
C++
22
star
18

moveit_studio_ur_ws

Workspace containing example MoveIt Studio configuration packages.
C++
21
star
19

UR10e_welding_demo

C++
21
star
20

crac_build_system

The unified Catin / ROSBuild / Ament / Colcon build system
Shell
21
star
21

snowbot_operating_system

The weather outside is frightful
C++
18
star
22

moveit_sim_controller

A simulation interface for a hardware interface for ros_control, and loads default joint values from SRDF
C++
14
star
23

ik_benchmarking

Utilities for IK solver benchmarking with MoveIt 2
C++
13
star
24

moveit_shelf_picking

Benchmarking suite for dual arm manipulation
C++
12
star
25

ros2_epick_gripper

ROS2 driver for the Robotiq EPick gripper.
C++
11
star
26

ROStoROS2

A living documentation of automate-able and manual steps for porting ROS packages to ROS2
10
star
27

moveit_studio_sdk

MoveIt Studio SDK
Python
9
star
28

trackjoint

C++
9
star
29

ros_reflexxes

Reflexxes Type II provides acceleration-limited trajectory smoothing
C++
9
star
30

ruckig_traj_smoothing

A jerk-limited trajectory smoothing plugin
8
star
31

graph_msgs

ROS messages for publishing graphs of different data types
CMake
8
star
32

launch_param_builder

Python library for loading parameters in launch files
Python
8
star
33

moveit_boilerplate

Basic functionality for easily building applications on MoveIt! in C++
C++
6
star
34

stretch_moveit_plugins

Package for the inverse kinematics solver for stretch from Hello Robot Inc.
C++
5
star
35

tool_changing

MoveGroup capability to dynamically change end-effectors
C++
4
star
36

test_examples

Examples of different test techniques (integration, unit, gtest, dependency injection...)
Python
4
star
37

moveit_studio_example_behaviors

C++
4
star
38

CMakeGuidelines

Collection of useful cmake tips
4
star
39

moveit_differential_ik_plugin

A plugin to provide differential kinematics. Used in admittance control.
C++
4
star
40

pronto

C++
4
star
41

light_painting_demo

C++
3
star
42

cpp_polyfills

Vendored Pollyfills for C++
C++
3
star
43

ocs2_robotic_assets

CMake
2
star
44

rqt2_setup

2
star
45

Moveit2_Tutorials

test deployment of moveit 2 tutorials
HTML
2
star
46

cartesian_msgs-release

1
star
47

moveit_sim_controller-release

1
star
48

ros_control_boilerplate-release

1
star
49

cirs_girona_cala_viuda

ROS2 version of girona cala viuda dataset
Python
1
star
50

picknik_controllers

PickNik's ros2_controllers
C++
1
star
51

rviz_visual_tools-release

1
star
52

rosparam_shortcuts-release

1
star
53

tf_keyboard_cal-release

1
star
54

rqt2_demo_nodes

Python
1
star
55

picknik_accessories

Various accessories used for studio configuration packages
CMake
1
star
56

moveit_studio_kortex_ws

C++
1
star
57

moveit_camera_survey

Code for survey of RGB-D cameras for manipulation
Dockerfile
1
star
58

picknik_ur5_moveit_config

CMake
1
star
59

beta_moveit_website

Demo site for major changes to moveit.ros.org. Found at http://moveit_beta.picknik.ai/
JavaScript
1
star
60

robot_on_rails

MoveIt Studio package for a UR robot on a linear rail
C++
1
star
61

c3pzero

Python
1
star
62

picknik_ament_copyright

Python
1
star
63

boost_sml

State Machine Library ROS Package
C++
1
star
64

cartesian_msgs

Experimental messages for sending Cartesian commands to a robot.
CMake
1
star