Git Product home page Git Product logo

generate_parameter_library's Introduction

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 in 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 the 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 accessed with params.nest1.nest2.param_name

Mapped parameters

You can use parameter maps, where a map with keys from another string_array parameter is created. Add the __map_ prefix followed by the key parameter name as follows:

cpp_name_space:
  joints:
    type: string_array
    default_value: ["joint1", "joint2", "joint3"]
    description: "specifies which joints will be used by the controller"
  interfaces:
    type: string_array
    default_value: ["position", "velocity", "acceleration"]
    description: "interfaces to be used by the controller"
  # nested mapped example
  gain:
    __map_joints: # create a map with joints as keys
      __map_interfaces:  # create a map with interfaces as keys
        value:
          type: double
  # simple mapped example
  pid:
    __map_joints: # create a map with joints as keys
      values:
        type: double_array

The generated parameter value for the nested map example can then be accessed with params.gain.joints_map.at("joint1").interfaces_map.at("position").value.

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 cases, 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 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 an exception if a 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.

generate_parameter_library's People

Contributors

agonzat avatar brunob81hk avatar chancecardona avatar chriseichmann avatar christhrasher avatar christophfroehlich avatar cottsay avatar destogl avatar fmauch avatar griswaldbrooks avatar guihomework avatar jaagut avatar kentakato avatar light-tech avatar mikewrock avatar mosfet80 avatar pac48 avatar sea-bass avatar sprenger120 avatar timple avatar tylerjw avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

generate_parameter_library's Issues

Examples showing proper use within a Component library

Are there any examples of using this within a component library in a public repo?

The usage within code is straightforward in this case, but I am trying to figure out:
1.) what a good project structure would look like when creating a publicly consumable Component package, and
2.) what steps need to be taken in CMake to properly expose dependencies for both static and runtime composition in this scenario

Mapped parameters can't refer to base class parameters

This is probably not a bug but a feature request. In any case, I had a situation where I had joints parameter defined in a base class. In the derived class, I needed to use a mapped parameter that referred to the base class joints parameters. This would not compile.. I got around it by re-declaring the same joints parameter in the derived class yaml file. This worked but is not ideal since the joints parameter is now declared in two places.

Markdown: Support for nested parameters

I kindly submit another feature request for the markdown generation 😇

Currently, nested parameters are given with their leaf name, but without its parent(s).

One could simply change self.declare_parameters.code_gen_variable.name to self.declare_parameters.parameter_name, but maybe it can also be exported with this hierarchy to consider that in the jinja2 template?

Something like we have with the (manual) JTC docs would be great.
image

Is it even possible to add a description to a structure parent having no variable at this level?

My understanding of the python/jinja scripts is too low. But if someone points me in the direction I could help to implement these.

Parameter namespaces that map to other parameters

Use Case

Currently, in ros2_control's JTC there is this pattern:

joints:
  - A
  - B
pid:
  A:
    p: 0.2
    i: 0.4
    d: 0.0
  B:
    p: 0.2
    i: 0.3
    d: 0.1

The basic idea is that there is some string array parameter, in this case joint names, that become part of the namespace of other parameters. This is done in two separate places in JTC and seems to be a generally useful feature.

Design

Make namespaces that come from other string-array parameters a first-class feature of the declarative yaml symtax for parameters.

Because the names of these depend on other parameters they should always be handled after all other parameters as the second type of parameter so updates to the string array or the map can be checked correctly.

YAML syntax

For the yaml syntax use the name ___map_<string_array_parameter_name> to represent the special type. Here is an example:

joints: {
  type: string_array
}
pid:
  rate: {
    type: double
  }
  ___map_joints:
    p: {
      type: double
    }
    i: {
      type: double
    }
    d: {
      type: double
    }

C++ Struct

The structure that results from the example is:

std::vector<std::string> joints;
struct PID {
  double rate;
  struct JOINTS_MAP_VALUE {
    double p;
    double i;
    double d;
  };
  std::map<std::string, JOINTS_MAP_VALUE> joints_map;
};
PID pid;

Declaring/Getting Parameters

declare_params method will be split into two sections, the first will declare, get, and validate all normal non-refering parameters then it will declare, get, and validate and reffering types. This is because the names needed to declare the referring types can only be known after the normal types have been read.

Special Validation

  1. Name in joints does not exist as sub-name of pid:
  • this is only an error if all real parameters don't have defaults and require the user to set these parameters
  • the normal way declaration works covers this
  1. Sub-name of pid does not exist in joints:
  • This is the same as the user setting a parameter that wasn't declared

Update

Updating is similar to declaring/getting in that all mapped parameters should be handled after all normal parameters.

If the base string_array (joints in the example) was modified by the update we need to handle it.

  1. Value was removed from joints:
  • remove the entry from the c++ map
  • undeclare parameter name
  1. Value was added to 'joints':
  • declare parameters

After all the updating of the structure is done (declaration) there should be the normal checking if any of the declared names have been updated and get/validate them.

Markdown: __map_ parameters

I kindly submit a feature request for the markdown generation 😇

  • __map_ : these ones are completely ignored now

My understanding of the python/jinja scripts is too low. But if someone points me in the direction I could help to implement these.

Potentially misleading error messages in the library

When working to add a new controller that uses this library, I have found few misleading issues.

The code used is under: destogl/tesko_controller repository. Download it and compile it.

When running tests (without YAML file) some parameters are not set, and it is expected for tests to fail. Nevertheless, the error messages in the test results are misleading.

For example, not having any joint in joints parameters (array) set results in a message

Exception thrown during controller's init stage with message: parameter 'joints' has invalid type: expected [string] got [string_array]

And the error happens in line 172-175 or file testko_controller_parameters.hpp (generated file) where it is checked if string array is empty.

Commenting out those lines, the same issue (but opposite output) happens when interface_name parameter (string) is empty, i.e.:

Exception thrown during controller's init stage with message: parameter 'interface_name' has invalid type: expected [string_array] got [string]

Looking at the code, I can not really understand why is this happening.

Hopefully, with this minimal example, you will have an idea about that.

Array of custom sub-structs

Is it somehow possible to declare an array of a custom sub-structure? Or how could the library be extended in this way?

Something like

cpp_name_space:
  sub_array:
    sub_struct:
      a_parameter: {
        type: double
      }
      another_parameter: {
        type: double
      }

and

  ros__parameters:
    sub_array:
      - a_parameter: 1.
        another_parameter: 2.0
      - a_parameter: 2.
        another_parameter: 3.0

used as params.sub_array.at(0).a_parameter

Python Parameter Interface

Are there any plans regarding a Python integration?

The current handling of parameters in Python under stock ROS2 suffers from similar issues compared to C++.
While Python is dynamic enough to handle generation of a config class during runtime, code generation during build time could improve the experience while autocompleting parameter names in the IDE etc..

I assume integration of CMakeLists.txt based (python) packages is easier compared to setup.py based ones.

Incorrect rclcpp::ParameterType generated for integers

I was trying this out in a test project using the latest official Humble base docker image and got the following error message for a parameter of type int in the project's parameters.yaml file:

parameters.hpp:202:51: error: ‘PARAMETER_INT’ is not a member of ‘rclcpp::ParameterType’
  202 |           auto parameter = rclcpp::ParameterType::PARAMETER_INT;

The parameter descriptor:

port: {
        type: int,
        description: "UDP listener port",
        default: 14540,
        readonly: true,
        validation: {
          bounds<>: [ 1024, 65535 ]
        }
      }

It appears that the correct enum name (at least in Humble) is PARAMETER_INTEGER:
https://github.com/ros2/rclcpp/blob/33dae5d679751b603205008fcb31755986bcee1c/rclcpp/include/rclcpp/parameter_value.hpp#L36

CMake 3.22 requirement prevents build on ROS 2 Humble recommended platforms

The packages generate_parameter_library and parameter_traits in this repository declare a requirement on CMake 3.22.
While this version is available on the required platforms (Ubuntu 22.04 and Windows 10) listed in REP-2000, it is not available for the recommended platforms. In particular RHEL 8 which we build binary packages for by default on the build farm.

If it's possible to relax this CMake version constraint to CMake 3.16 it would add support for Ubuntu Focal, Debian Bullseye, and RHEL 8. If not, I'll go ahead and block this package from building on RHEL 8.

Undeclaring a parameter throws an unexpected error.

According to the ROS 2 documentation, the undeclare_parameter(const std::string & name) method of rclcpp::node_interfaces::NodeParametersInterface should only throw two types of exceptions:

  1. rclcpp::exceptions::ParameterNotDeclaredException: if the parameter has not been declared.
  2. rclcpp::exceptions::ParameterImmutableException: if the parameter was create as read_only (immutable).

I get the following exception when I try to undeclare a parameter.

terminate called after throwing an instance of 'rclcpp::exceptions::InvalidParameterTypeException'
  what():  parameter 'pid.elbow_joint.p' has invalid type: cannot undeclare an statically typed parameter

This occurs when setting the parameter like this:

double val = 1.5;
auto parameter = rclcpp::ParameterValue(val);
rcl_interfaces::msg::ParameterDescriptor descriptor;
descriptor.description = "my  parameter";
parameters_interface_->declare_parameter("pid.elbow_joint.p", parameter, descriptor);

Compile error under debian bullseye

--- stderr: generate_parameter_library_example
CMake Error at /home/r/ws_moveit2/install/generate_parameter_library/share/generate_parameter_library/cmake/generate_parameter_library.cmake:33 (message):
  generate_parameter_library_py() variable
  'generate_parameter_library_py_BIN' must not be empty
Call Stack (most recent call first):
  CMakeLists.txt:9 (generate_parameter_library)

Parameters and gmock

I added not_empty<> to ros-controls/ros2_controllers#703, but got error messages that the command_interface parameter cannot be empty:
https://github.com/ros-controls/ros2_controllers/actions/runs/5555108925/jobs/10145808720?pr=703

In the readme of this repo, it is suggested to use size_gt, but that gives the same result

```yaml
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",]],
}
}
```

How does this library work together with gmock, and why does the parameter-test fail but the test itself is fine without not_empty<>?

Error building moveit on rolling with latest sync

--- stderr: moveit_core
Traceback (most recent call last):
  File "/opt/ros/humble/bin/generate_parameter_library_cpp", line 33, in <module>
    sys.exit(load_entry_point('generate-parameter-library-py==0.3.6', 'console_scripts', 'generate_parameter_library_cpp')())
  File "/opt/ros/humble/bin/generate_parameter_library_cpp", line 25, in importlib_load_entry_point
    return next(matches).load()
StopIteration
gmake[2]: *** [online_signal_smoothing/CMakeFiles/moveit_butterworth_parameters.dir/build.make:71: online_signal_smoothing/moveit_butterworth_parameters/include/moveit_butterworth_parameters.hpp] Error 1
gmake[1]: *** [CMakeFiles/Makefile2:1768: online_signal_smoothing/CMakeFiles/moveit_butterworth_parameters.dir/all] Error 2
gmake[1]: *** Waiting for unfinished jobs....
gmake: *** [Makefile:146: all] Error 2

TypeError: isinstance()

I try to compile ros2_controllers within a freshly built ros:rolling docker container, but generate_parameter_library (installed as binary) fails.

--- stderr: forward_command_controller
Traceback (most recent call last):
File "/opt/ros/rolling/bin/generate_parameter_library_py", line 33, in
sys.exit(load_entry_point('generate-parameter-library-py==0.3.3', 'console_scripts', 'generate_parameter_library_py')())
File "/opt/ros/rolling/lib/python3.10/site-packages/generate_parameter_library_py/main.py", line 1035, in main
gen_param_struct.run()
File "/opt/ros/rolling/lib/python3.10/site-packages/generate_parameter_library_py/main.py", line 1023, in run
self.parse_dict(self.namespace, doc[self.namespace], [])
File "/opt/ros/rolling/lib/python3.10/site-packages/generate_parameter_library_py/main.py", line 949, in parse_dict
self.parse_dict(key, root_map[key], nested_name)
File "/opt/ros/rolling/lib/python3.10/site-packages/generate_parameter_library_py/main.py", line 955, in parse_dict
self.parse_params(name, root_map, nested_name)
File "/opt/ros/rolling/lib/python3.10/site-packages/generate_parameter_library_py/main.py", line 861, in parse_params
) = preprocess_inputs(name, value, nested_name_list)
File "/opt/ros/rolling/lib/python3.10/site-packages/generate_parameter_library_py/main.py", line 807, in preprocess_inputs
code_gen_variable = CodeGenVariable(
File "/opt/ros/rolling/lib/python3.10/site-packages/generate_parameter_library_py/main.py", line 338, in init
def init(
File "/usr/local/lib/python3.10/dist-packages/typeguard/_functions.py", line 113, in check_argument_types
check_type_internal(value, expected_type, memo=memo)
File "/usr/local/lib/python3.10/dist-packages/typeguard/_checkers.py", line 679, in check_type_internal
if not isinstance(value, origin_type):
TypeError: isinstance() arg 2 must be a type, a tuple of types, or a union
gmake[2]: *** [CMakeFiles/forward_command_controller_parameters.dir/build.make:71: forward_command_controller_parameters/include/forward_command_controller_parameters.hpp] Error 1
gmake[1]: *** [CMakeFiles/Makefile2:186: CMakeFiles/forward_command_controller_parameters.dir/all] Error 2
gmake: *** [Makefile:146: all] Error 2

If I try to compile it from source, the generate_parameter_module_example throws the same errors.
What can be the cause here?

Python 3.10.6
generate-parameter-library-py 0.3.3
type-description-interfaces 1.7.0
typeguard 3.0.2
typing_extensions 4.6.3

Array parameters wierdness

There seem to be some issues with the handling of array parameters. I'm encountering problems with setting parameters and getting my application terminated, because of

Wrong parameter type, parameter {myParam} is of type {integer}, setting it to {integer_array} is not allowed.

Working configuration:

parameters.yaml

  myParam: {
    type: int_array_fixed_2,
    description: "myDescription",
    default_value: [ 0, 0],
    validation: {
      element_bounds<>: [ 0, 10000 ]
    }
  }

config.yaml

node:
  ros__parameters:
    myParam: [0, 1]

bounds demoting array to single integer

parameters.yaml

  myParam: {
    type: int_array_fixed_2,
    description: "myDescription",
    default_value: [ 0, 0],
    validation: {
      bounds<>: [ 0, 10000 ]
    }
  }

Changing out element_bounds to bounds will demote the parameter to a single integer and cause the error at the start. Removing the validation block will fix the problem.

No default_value demotes to single integer

parameters.yaml

  myParam: {
    type: int_array_fixed_2,
    description: "myDescription",
    validation: {
      element_bounds<>: [ 0, 10000 ]
    }
  }

Removing the default_value key will also demote the array. Removing the validation block will change nothing.

Something fishy is going on here, could you have a look? Thank you <3

Option to print parameter values

I have been using this library and I am really happy with it - thanks for the contribution!

This issue is more of a question/feature request: can we log the parameters that are loaded by the parameter listener? This would be very useful for debugging purposes.

Register async callback for updates

Provide an interface for registering async callbacks for parameters being updated. Be careful that the mutex is not locked when these functions are called so if they call get() it doesn't create a deadlock.

ParameterListener::update()
{
  {
    std::lock_guard lock(mutex_);
    // modify params_ variable
  }
  std::lock_guard lock(update_callbacks_);
  for (auto const& f : update_callbacks_) f(params_);
}

class ParameterListener {
 public:
  void register_callback(std::function<void(Params const&)> cb) {
    std::lock_guard lock(update_callbacks_);
    update_callbacks_.push_back(cb);
  }
 private:
  std::mutex callbacks_mutex_;
  std::vecotr<std::function<void(Params const&)>> update_callbacks_;

Warn users that the lifetime of the callback function has to be at least as long as the lifetime of the ParameterListener object.

Markdown Template

Has the markdown template a flavor for a specific platform?
I'm asking because the following markdown table is not rendered correctly with vscode plugins

| Type | bool |
| Default Value | true |

I think the header separator is missing.

| Type | bool |
| Default Value | true |

vs

Type bool
Default Value true

But this table is pivoted anyways -> one would need a different representation then.

map for dynamic parameters doesn't work

System:
Ubuntu 22.04
ROS Humble
generate-parameter-library 0.2.8

I can't get the dynamic parameters I setted, only get the default value.
Then I ckecked the header file generated by this lib, found the veriable used to point to the value of dynamic parameters map is not using a reference.

For example, Part of the generated code for setting dynamic params as follows

  1    // declare and set all dynamic parameters
  2   for (const auto & value : updated_params.name_array){
  3        auto entry = updated_params.test.name_array_map[value];
  4        auto param_name = fmt::format("{}{}.{}.{}", prefix_, "test", value, "test_1");
  5       if (!parameters_interface_->has_parameter(param_name)) {
  6            rcl_interfaces::msg::ParameterDescriptor descriptor;
  7            descriptor.description = "";
  8            descriptor.read_only = false;
  9            auto parameter = rclcpp::ParameterValue(entry.test_1);
  10          parameters_interface_->declare_parameter(param_name, parameter, descriptor);
  11     }
  12    param = parameters_interface_->get_parameter(param_name);
  13    entry.test_1 = param.as_bool();}

Replace line 3 as below and use the modified header as source code to build, it works.

`auto & entry = updated_params.test.name_array_map[value];`

But I don't know how to modify this library to make the generated header correct.
Is it only me having this problem? Or maybe I'm misunderstanding something?

Cannot build generate_parameter_library_example on RHEL 8

Hi,

We are trying to build MoveIT2 on RHEL 8 and we got problems with this generate_parameter_library. Can you help us to solve it and what is the minimun required Python version to build this? Our error message is following:

Traceback (most recent call last):
  File "/wrk1/ws_moveit2/install/generate_parameter_library_py/bin/generate_parameter_library_py", line 33, in <module>
    sys.exit(load_entry_point('generate-parameter-library-py==0.2.7', 'console_scripts', 'generate_parameter_library_py')())
  File "/wrk1/ws_moveit2/install/generate_parameter_library_py/bin/generate_parameter_library_py", line 25, in importlib_load_entry_point
    return next(matches).load()
  File "/usr/local/lib/python3.6/site-packages/importlib_metadata/__init__.py", line 194, in load
    module = import_module(match.group('module'))
  File "/usr/lib64/python3.6/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 994, in _gcd_import
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 665, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 678, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/wrk1/ws_moveit2/install/generate_parameter_library_py/lib/python3.6/site-packages/generate_parameter_library_py/main.py", line 419, in <module>
    class DeclareStruct:
  File "/wrk1/ws_moveit2/install/generate_parameter_library_py/lib/python3.6/site-packages/generate_parameter_library_py/main.py", line 421, in DeclareStruct
    def __init__(self, struct_name: str, fields: list[VariableDeclaration]):
TypeError: 'type' object is not subscriptable
gmake[2]: *** [CMakeFiles/admittance_controller_parameters.dir/build.make:75: admittance_controller_parameters/include/admittance_controller_parameters.hpp] Error 1
gmake[1]: *** [CMakeFiles/Makefile2:181: CMakeFiles/admittance_controller_parameters.dir/all] Error 2
gmake: *** [Makefile:146: all] Error 2
---

Command to build is: PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:/usr/local/lib64/pkgconfig colcon build --event-handlers desktop_notification- status- --cmake-args -DCMAKE_BUILD_TYPE=Release

Markdown IndexError: list index out of range

Running with the JTC parameter yaml

ros2 run generate_parameter_library_py generate_parameter_library_markdown --output_markdown_file out/output.md --input_yaml_file joint_trajectory_controller/src/joint_trajectory_controller_parameters.yaml

fails with the following error:

File "/workspaces/ros2_rolling_ws/install/generate_parameter_library_py/lib/python3.10/site-packages/generate_parameter_library_py/generate_markdown.py", line 79, in str
validation += ": " + str(arguments[0])
IndexError: list index out of range

Other files are processed with success. I could not find out immediately which parameter causes the error.

[Feature Request] Support for custom known structures in the parameters

Hello, I have a question about adding known complex structures under parameters, when defined with generate_parameter_library.

Problem

I would like to define a structure of known parameters and extract a map of string and known structure out of it. The purpose of this is to enable a flexible setup of an arbitrary number of options that only makes sense when they are together. For instance, I want to have the joints and their types in the parameters, right now we can only have them as 2 separate vectors of elements and the problem here is they have to be the same size and then each corresponding element of these vectors has a relationship. If we can add support to such known structures, then it would be cool to have something like a map so all the elements such as joint_type correspond to the joint information.

Current structure limitation:

controller_1:
    joint_names: ["joint_1", "joint_2", "joint_3"]
    joint_type: ["prismatic", "rotary", "rotary"]
    joint_reduction_ratio: [100.0, 10.0, 20.0]

Target structure:

controller_1:
    joint_parameters:
        joint_1:
            type: prismatic
            reduction_ratio: 100.0

        joint_2:
            type: rotary
            reduction_ratio: 10.0

        joint_3:
            type: rotary
            reduction_ratio: 20.0

If the structure is this way, maybe we can have some generated API like parameters.joint_parameters["joint_1"].type and parameters.joint_parameters["joint_1"].reduction_ratio . I think this way it is better and clear as all the parameters are expressed without losing much of their significance. This would allow us to skip the usual checks of parameter sizes being the same or not. For instance, the same could also be useful for the PIDs of the controller, as they belong per joint and they only make sense together.

If this can be done, then in the future, It would be really cool to reuse the parameter structures from another package yaml information and generate the API.

What do you think about this kind of feature?

Specify range constraint in parameter descriptor when bounds<> validator is present

The ParameterDescriptor message contains fields for specifying the range for float and int parameters:

https://github.com/ros2/rcl_interfaces/blob/43ffb7b6df6a98a1a35d0932d148ad12de549594/rcl_interfaces/msg/ParameterDescriptor.msg#L27-L36

This is used, for example, in rqt_reconfigure to display sliders for choosing the parameter value.

I would love it if these fields were actually used in this library,. Seems like they can be easily populated from the values passed to the bounds<> validator.

Error output from subset_of

While playing around with the validators for ros-controls/ros2_controllers#703, I found out that the error message of subset_of is not really helpful. Launching the JTC from demo 1 with

    command_interfaces:
      - test

gives

Exception thrown during init stage with message: argument not found

If I delete this section, I get a useful error from not_empty:

Exception thrown during init stage with message: Invalid value set during initialization for parameter 'command_interfaces': Parameter 'command_interfaces' cannot be empty

@tylerjw Is there something wrong with the validator configuration or is this an issue with this library?

Custom Unknown Structures under Parameters

Hi, question / idea about adding unknown complex structures under parameters defined with generate_parameter_library.

Problem description

I would like to define this structure of filters - see bellow. The purpose of filter chain is to enable flexible setup of arbitrary number of filters that automatically pipe data through it. The filters are plugins and have their own set of parameters, which I am planning to define using generate_parameter_library. But we don't know in advance how many filters there are nor which parameters they have. The filters can take care about their parameters so the outside class (in this case controller) doesn't have to take care about it.

Target structure:

    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
          divider: 1

      filter2:
        type: control_filters/GravityCompensationWrench
        name: tool_gravity_compensation
        params:
          CoG:
            x: 0.0
            y: 0.0
            z: 0.1
          force: 0.0        # mass * 9.81
          force_frame: wrist_3_link
          sensor_frame: ft_frame
          world_frame: base_link

How it could be done and what is missing.

The only thing I am missing here is: how can one define “sensor_filter_chain” parameter which is like a “namespace” of parameters under it in such a way that generate_parameters_library accepts that at the outer class/controller level. I could live without generate_parameters_library automatically checking those parameters, it should just “skip” (accept) them for now.

Currently, we can define only parameter type there, but not something “unknown”. Would it make sense or be possible to add something like this:

some_controller:
  filter_chain: {
    type: ExternalParameterStructure,
    description: "Map of values that defined filter chain, containing filterN as key and underlying map of parameters needed for a specific filter. See <some docs> for more details.",
  }

Possible workaround (would like to avoid)

Of course, we can simply declare manually this parameter “prefix”/“namespace”, but I would like to avoid that because of mixing two concepts in one node that would be confusing.

Fixed size string

template<size_t S>
class FixedSizeString {
public:
  FixedSizeString() = default;
  FixedSizeString(std::string_view view)
  {
    std::copy(view.cbegin(), view.cend(), data_.begin());
    view_ = // string_view constructged to point at data_
  }

  std::string_view operator() const { return view_; }

private:
  std::string_view view_;
  std::array<char, S> data_;
};  


auto empty_fixed_size = FixedSizeString<500>();
auto f1 = FixedSizeString<500>("foo");

if (f1 == "foo") {

}

if (f1.empty())

Could not find a package configuration file provided by "rsl" with any of the following names: rslConfig.cmake, rsl-config.cmake

While building the pkg, getting this issue.

Can anyone help in this regard?
Thank you

-- stderr: parameter_traits
CMake Error at CMakeLists.txt:8 (find_package):
By not providing "Findrsl.cmake" in CMAKE_MODULE_PATH this project has
asked CMake to find a package configuration file provided by "rsl", but
CMake did not find one.

Could not find a package configuration file provided by "rsl" with any of
the following names:

rslConfig.cmake
rsl-config.cmake

Add the installation prefix of "rsl" to CMAKE_PREFIX_PATH or set "rsl_DIR"
to a directory containing one of the above files. If "rsl" provides a
separate development package or SDK, be sure it has been installed.

Use parameter events?

I should read about and understand the parameter events from ROS and update how generate_parameter_library to use them if appropriate.

Use node logger

The logging generated by generate_parameter_library uses namespaced but not node logger. There are downsides to this and we should update to using the node logger as called out here:

70c917d#r111546330

Thank you @bijoua29 for calling this out. I'm sorry it took me so long to see your comment. I'll fix this eventually but it is not a priority for me so if you or someone else gets to it first I'd welcome the PR and will try to merge it quickly.

ModuleNotFoundError when building with --simlink-install

When building the example_python package inside my workspace, I get the ModuleNotFoundError. This happens everytime I try to build with --symlink-install. I first encountered this bug with my package and I decided to try the example directly to see if it happened with it too and it does. here is the traceback:

Traceback (most recent call last):
  File "/home/user/colcon_ws/install/generate_parameter_module_example/lib/generate_parameter_module_example/test_node", line 33, in <module>
    sys.exit(load_entry_point('generate-parameter-module-example', 'console_scripts', 'test_node')())
  File "/home/user/colcon_ws/install/generate_parameter_module_example/lib/generate_parameter_module_example/test_node", line 25, in importlib_load_entry_point
    return next(matches).load()
  File "/usr/lib/python3.10/importlib/metadata/__init__.py", line 171, in load
    module = import_module(match.group('module'))
  File "/usr/lib/python3.10/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 883, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/home/user/colcon_ws/build/generate_parameter_module_example/generate_parameter_module_example/minimal_publisher.py", line 33, in <module>
    from generate_parameter_module_example.admittance_parameters import (
ModuleNotFoundError: No module named 'generate_parameter_module_example.admittance_parameters'

I also checked my PYTHONPATH and I think that it may be related to name collisions.

I saw that @pac48 was referencing to some side-effects when using --symlink-install in #122, is this related?

Move cmake functions for testing with YAML files upstream

The methods from #57 should be moved into corresponding upstream repositories:

  • ament_cmake_gtest
  • ament_cmake_gmock

Should this be add_rostest_with_parameters_gtest or something to make it clear that the input yaml is for parameters and that these are a ros-test in that they only work if your test has ros in it (rclcpp::init(argc, argv);)?

Originally posted by @tylerjw in #57 (comment)

Read-only parameters are const

Could we make read-only parameters const?

We should think through the implications of this for how the user would use it.

Test with C++20 in CI

This PR #127 found that if you build with C++20 the generated code didn't compile. To ensure we don't have that problem, I'd like to add a CI job that tests with C++20. I think this can probably be done through CMake arguments.

How to set read-only mandatory parameters during tests

Is there any chance to set read-only mandatory parameters during tests except for creating yaml files?

I'm working on deactivating some dynamic parameters for joint_trajectory_controller (ros-controls/ros2_controllers#771), but the tests rely heavily on parameter reconfiguration. Some of them can be defined with a yaml file, but some are changed with the parameterized tests and would have to be compiled with different yaml files to different test executables to avoid duplicating code.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.