0
$\begingroup$

Rosanswers logo

This isn't a very clear question, mostly just a dump of my current understanding of how to use ros2 parameters and hopefully any misconceptions can be addressed in comments or answers.

I have many ros1 nodes that use dynamic reconfigure that I'm converting to ros2, and it appears ros2 parameters can provide the same functionality with approximately the same resource costs and lines of code.

The example code in https://github.com/ros2/demos is nice but the use of modern C++ isn't always helpful, it multiplies the cognitive load in the jump from ros1 to ros2 and the use of auto is less self-documenting except in the trivial cases where the type of the auto is on right side of the same line of code. Adding older style examples that duplicate functionality of the existing examples would be good but then doubles the number of examples to be gone through by a new user.

https://github.com/lucasw/ros2_cpp_py has some of what I think is the right way for path of least resistance in updating ros1 code:

  • set_parameter_if_not_set() followed by get_parameter_or() for all parameters in the constructor/init. (or the other way around, the order doesn't matter). This makes sure any other node can see all the parameters used internally by this node, that parameter yaml values are in use.

Putting this into the class which inherits from rclcpp::Node is roughly the same as the dynamic reconfigure boiler plate:

rclcpp::AsyncParametersClient::SharedPtr parameters_client_;
rclcpp::Subscription<rcl_interfaces::msg::ParameterEvent>::SharedPtr param_sub_;
void onParameterEvent(const rcl_interfaces::msg::ParameterEvent::SharedPtr event);

Then at constructor/init time:

parameters_client_ = std::make_shared<rclcpp::AsyncParametersClient>(this);
param_sub_ = parameters_client_->on_parameter_event(
    std::bind(&CppTest::onParameterEvent, this, std::placeholders::_1));

This is where parameter handling got confusing:

void CppTest::onParameterEvent(const rcl_interfaces::msg::ParameterEvent::SharedPtr event) {
...
}

It appears that onParameterEvent will get a message for every parameter event in the same namespace, so two nodes with parameter event handlers will get events meant for each other, which is especially bad if the multiple nodes use parameters with the same names. Leaving the node name blank in AsyncParametersClient appears identical to the following, and doesn't help with filtering out unwanted events:

const std::string full_name = std::string(get_namespace()) + std::string(get_name());
parameters_client_ = std::make_shared<rclcpp::AsyncParametersClient>(this, full_name);

(Is that behavior that might change post-crystal?)

The parameter handling behind the scenes knows which node a parameter is meant for, but in the parameter event handler the event->node string can be used to filter out unwanted updates:

const std::string full_name = std::string(get_namespace()) + std::string(get_name());
if (event->node != full_name) {
  return;
}

What to do inside onParameterEvent() is less clear. In many of my nodes there is a timer update function getting called so many times a second which runs processing code only if parameters have changed (or new messages arrived on input topics), so I could simply set a flag in onParameterEvent and then get the current value of every parameter with get_parameter_or() in the timer update if the flag is set. I think that is not too painful, but I assume I should cache the values first before using them inside a loop.

Or is it a lot more efficient to use the parameter events to update copies of every parameter and avoid get_parameter_or()?

https://github.com/lucasw/image_manip/blob/2b188674a8eaeb7853f49b603957d5922f5c8036/image_manip/src/color.cpp#L103 shows where I update the local class copy of every parameter if the dirty flag is set- at least for a small number of parameters this seems more expedient than trying to switch on parameter types etc. inside the parameter event handler.

void Color::pubImage()
{
  if (dirty_ || image_.empty()) {
    get_parameter_or("red", red_, red_);
    get_parameter_or("green", green_, green_);
    get_parameter_or("blue", blue_, blue_);
    get_parameter_or("width", width_, width_);
    get_parameter_or("height", height_, height_);
    ...
    dirty_ = false;

I think the min and max values for former dynamic reconfigure parameters can be enforced through register_param_change_callback but I haven't explored that much. The caller node won't be able to know what the restrictions are (if for example it wanted to set limits on a gui slider), but the trade off benefit is that the limits aren't fixed at compile time, don't have to exist at all or can change dynamically depending on other parameters or for any other reason.

Multiple parameter events callbacks in one node

It looks like it isn't possible to register on_parameter_event for two different remote nodes if they are in the same namespace (or at all regardless of namespace? That would be bad)- probably because the AsyncParametersClient is trying to subscribe to the same parameter_events topic twice, so only the last one gets called. The node needs the mechanism to determine that two remote nodes that the main node wants to get events for are in the same namespace and then only have one parameter event handler that they both share?

Setting parameters to the wrong type

Setting a bool to a float or even a float to an int causes a rclcpp::ParameterTypeException in the node that is trying to use get_parameter_or() on that parameter- sometimes the exception causes the whole launch to die with an 'invalid start byte' message. The solution is to wrap get_parameter_or with try catch.


Originally posted by lucasw on ROS Answers with karma: 8729 on 2019-01-10

Post score: 1

$\endgroup$

1 Answer 1

0
$\begingroup$

Rosanswers logo

I couldn't get on_parameter_event to work when the node was within a namespace, but the object is already a Node so doesn't need a parameters client for itself, that is redundant- so get rid parameters_client_ and param_sub_ and onParameterEvent above inside the class that is the receiving end of set parameters.

Instead:

class CppTest : public rclcpp::Node {
...

  register_param_change_callback(
        std::bind(&CppTest::onParameterChange, this, _1));

...
rcl_interfaces::msg::SetParametersResult CppTest::onParameterChange(
    std::vector<rclcpp::Parameter> parameters)
{
  // this doesn't do any checking, it just assumes the parameter
  // changes are good
  dirty_ = true;
  auto result = rcl_interfaces::msg::SetParametersResult();
  result.successful = true;
  return result;
}

Originally posted by lucasw with karma: 8729 on 2019-01-15

This answer was ACCEPTED on the original site

Post score: 0

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.