Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding ability to add parameters through yaml at runtime #2406

Open
wants to merge 5 commits into
base: rolling
Choose a base branch
from

Conversation

CursedRock17
Copy link
Contributor

This is a pull request meant to address #1029 and may start as a draft depending on the issue actually needing to be addressed. It adds the load_parameters function to NodeParametersInterface and all of its children; a way to add parameters during runtime through a yaml file.

Signed-off-by: CursedRock17 <[email protected]>
Signed-off-by: CursedRock17 <[email protected]>
@CursedRock17 CursedRock17 changed the title Load params Adding ability to add parameters through yaml at runtime Jan 17, 2024
Signed-off-by: CursedRock17 <[email protected]>
Copy link
Member

@wjwwood wjwwood left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We had a look at this in our issue triage meeting and the consensus was that "loading" parameters based on a yaml file is useful, however we don't think we should continue to expand the methods in the Node class or the NodeParametersInterface class and we'd prefer to see a free-function or similar that uses a Node and a yaml file to perform the operations.

To this point, your pull request doesn't add these methods to the LifecycleNode class, but if it was a free-function that took a rclcpp::NodeInterface<...> template argument (see

/// Create a new NodeInterfaces object using the given node-like object's interfaces.
/**
* Specify which interfaces you need by passing them as template parameters.
*
* This allows you to aggregate interfaces from different sources together to pass as a single
* aggregate object to any functions that take node interfaces or node-likes, without needing to
* templatize that function.
*
* You may also use this constructor to create a NodeInterfaces that contains a subset of
* another NodeInterfaces' interfaces.
*
* Finally, this class supports implicit conversion from node-like objects, allowing you to
* directly pass a node-like to a function that takes a NodeInterfaces object.
*
* Usage examples:
* ```cpp
* // Suppose we have some function:
* void fn(NodeInterfaces<NodeBaseInterface, NodeClockInterface> interfaces);
*
* // Then we can, explicitly:
* rclcpp::Node node("some_node");
* auto ni = NodeInterfaces<NodeBaseInterface, NodeClockInterface>(node);
* fn(ni);
*
* // But also:
* fn(node);
*
* // Subsetting a NodeInterfaces object also works!
* auto ni_base = NodeInterfaces<NodeBaseInterface>(ni);
*
* // Or aggregate them (you could aggregate interfaces from disparate node-likes)
* auto ni_aggregated = NodeInterfaces<NodeBaseInterface, NodeClockInterface>(
* node->get_node_base_interface(),
* node->get_node_clock_interface()
* )
*
* // And then to access the interfaces:
* // Get with get<>
* auto base = ni.get<NodeBaseInterface>();
*
* // Or the appropriate getter
* auto clock = ni.get_clock_interface();
* ```
*
* You may use any of the standard node interfaces that come with rclcpp:
* - rclcpp::node_interfaces::NodeBaseInterface
* - rclcpp::node_interfaces::NodeClockInterface
* - rclcpp::node_interfaces::NodeGraphInterface
* - rclcpp::node_interfaces::NodeLoggingInterface
* - rclcpp::node_interfaces::NodeParametersInterface
* - rclcpp::node_interfaces::NodeServicesInterface
* - rclcpp::node_interfaces::NodeTimeSourceInterface
* - rclcpp::node_interfaces::NodeTimersInterface
* - rclcpp::node_interfaces::NodeTopicsInterface
* - rclcpp::node_interfaces::NodeTypeDescriptionsInterface
* - rclcpp::node_interfaces::NodeWaitablesInterface
*
* Or you use custom interfaces as long as you make a template specialization
* of the rclcpp::node_interfaces::detail::NodeInterfacesSupport struct using
* the RCLCPP_NODE_INTERFACE_HELPERS_SUPPORT macro.
*
* Usage example:
* ```RCLCPP_NODE_INTERFACE_HELPERS_SUPPORT(rclcpp::node_interfaces::NodeBaseInterface, base)```
*
* If you choose not to use the helper macro, then you can specialize the
* template yourself, but you must:
*
* - Provide a template specialization of the get_from_node_like method that gets the interface
* from any node-like that stores the interface, using the node-like's getter
* - Designate the is_supported type as std::true_type using a using directive
* - Provide any number of getter methods to be used to obtain the interface with the
* NodeInterface object, noting that the getters of the storage class will apply to all
* supported interfaces.
* - The getter method names should not clash in name with any other interface getter
* specializations if those other interfaces are meant to be aggregated in the same
* NodeInterfaces object.
*
* \param[in] node Node-like object from which to get the node interfaces
*/
), then this free function could operate on Node as well as LifecycleNode or any other kinds of "node-like" objects in the future. This is not only more reusable, but it helps us keep the size of the Node class manageable and therefore helps with maintainability.

Copy link
Collaborator

@fujitatomoya fujitatomoya left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wjwwood

we don't think we should continue to expand the methods in the Node class or the NodeParametersInterface class

i understand the concern and benefits for having this as free functions.
on the other hand, i also feel like load_parameters method can belong to NodeParameterInterface.

  // pseudo code

  // list parameters belong to the node
  auto parameter_list = node->list_parameters({}, 0);

  // load parameters to the node
  results = rclcpp::load_parameters(node, yaml_file);

i do not have strong opinion, but it would be nice if we can provide the consistent way to manage parameters?

it would be probably nice to refactor the existed methods to use free function like load_parameters above? what do you think?

*/
RCLCPP_PUBLIC
std::vector<rcl_interfaces::msg::SetParametersResult>
load_parameters(const std::string & yaml_filepath, const std::string & node_name_);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aside the on-going discussion on how to implement, do we need to have 2nd argument node_name_? what about load_parameters takes yaml file and internally calls get_fully_qualified_name(). if load_parameters belong to the Node class, i think it should load the parameters from yaml file in the context of the Node?

@CursedRock17
Copy link
Contributor Author

it would be nice if we can provide the consistent way to manage parameters?

I would have to agree with this statement, I feel that too much separation will create inconsistency thus a worse DX. Furthermore, filing a free function doesn't seem clear right now, as right now in testing I just stuck it in copy_all_parameter_values.hpp and was planning on possibly renaming the file to make it more discoverable. That function declaration would look something like this:

template<typename NodeT>
std::vector<rcl_interfaces::msg::SetParametersResult>
load_parameters(
  const std::string & yaml_filepath, 
  rclcpp::node_interfaces::NodeInterfaces<NodeT> node_interface)

Though the benefits of a free function would be its variability to take various NodeInterfaces, if we have a limited amount of nodes that we care about; for example if we only care about Node and LifecycleNode opposed to caring about 6 different nodes or whatnot.

@clalancette
Copy link
Contributor

clalancette commented Feb 2, 2024

I would have to agree with this statement, I feel that too much separation will create inconsistency thus a worse DX.

Looking at this holistically, I think my other problem with this is that our developer experience around parameters is already poor. We have too many node APIs that deal with parameters. Right now we have (all from

/// Declare and initialize a parameter, return the effective value.
/**
* This method is used to declare that a parameter exists on this node.
* If, at run-time, the user has provided an initial value then it will be
* set in this method, otherwise the given default_value will be set.
* In either case, the resulting value is returned, whether or not it is
* based on the default value or the user provided initial value.
*
* If no parameter_descriptor is given, then the default values from the
* message definition will be used, e.g. read_only will be false.
*
* The name and type in the given rcl_interfaces::msg::ParameterDescriptor
* are ignored, and should be specified using the name argument to this
* function and the default value's type instead.
*
* If `ignore_override` is `true`, the parameter override will be ignored.
*
* This method will result in any callback registered with
* `add_on_set_parameters_callback` and `add_post_set_parameters_callback`
* to be called for the parameter being set.
*
* If a callback was registered previously with `add_on_set_parameters_callback`,
* it will be called prior to setting the parameter for the node.
* If that callback prevents the initial value for the parameter from being
* set then rclcpp::exceptions::InvalidParameterValueException is thrown.
*
* If a callback was registered previously with `add_post_set_parameters_callback`,
* it will be called after setting the parameter successfully for the node.
*
* This method will _not_ result in any callbacks registered with
* `add_pre_set_parameters_callback` to be called.
*
* The returned reference will remain valid until the parameter is
* undeclared.
*
* \param[in] name The name of the parameter.
* \param[in] default_value An initial value to be used if at run-time user
* did not override it.
* \param[in] parameter_descriptor An optional, custom description for
* the parameter.
* \param[in] ignore_override When `true`, the parameter override is ignored.
* Default to `false`.
* \return A const reference to the value of the parameter.
* \throws rclcpp::exceptions::ParameterAlreadyDeclaredException if parameter
* has already been declared.
* \throws rclcpp::exceptions::InvalidParametersException if a parameter
* name is invalid.
* \throws rclcpp::exceptions::InvalidParameterValueException if initial
* value fails to be set.
* \throws rclcpp::exceptions::InvalidParameterTypeException
* if the type of the default value or override is wrong.
*/
RCLCPP_PUBLIC
const rclcpp::ParameterValue &
declare_parameter(
const std::string & name,
const rclcpp::ParameterValue & default_value,
const rcl_interfaces::msg::ParameterDescriptor & parameter_descriptor =
rcl_interfaces::msg::ParameterDescriptor(),
bool ignore_override = false);
/// Declare and initialize a parameter, return the effective value.
/**
* Same as the previous one, but a default value is not provided and the user
* must provide a parameter override of the correct type.
*
* \param[in] name The name of the parameter.
* \param[in] type Desired type of the parameter, which will enforced at runtime.
* \param[in] parameter_descriptor An optional, custom description for
* the parameter.
* \param[in] ignore_override When `true`, the parameter override is ignored.
* Default to `false`.
* \return A const reference to the value of the parameter.
* \throws Same as the previous overload taking a default value.
* \throws rclcpp::exceptions::InvalidParameterTypeException
* if an override is not provided or the provided override is of the wrong type.
*/
RCLCPP_PUBLIC
const rclcpp::ParameterValue &
declare_parameter(
const std::string & name,
rclcpp::ParameterType type,
const rcl_interfaces::msg::ParameterDescriptor & parameter_descriptor =
rcl_interfaces::msg::ParameterDescriptor{},
bool ignore_override = false);
/// Declare and initialize a parameter with a type.
/**
* See the non-templated declare_parameter() on this class for details.
*
* If the type of the default value, and therefore also the type of return
* value, differs from the initial value provided in the node options, then
* a rclcpp::exceptions::InvalidParameterTypeException may be thrown.
* To avoid this, use the declare_parameter() method which returns an
* rclcpp::ParameterValue instead.
*
* Note, this method cannot return a const reference, because extending the
* lifetime of a temporary only works recursively with member initializers,
* and cannot be extended to members of a class returned.
* The return value of this class is a copy of the member of a ParameterValue
* which is returned by the other version of declare_parameter().
* See also:
*
* - https://en.cppreference.com/w/cpp/language/lifetime
* - https://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/
* - https://www.youtube.com/watch?v=uQyT-5iWUow (cppnow 2018 presentation)
*/
template<typename ParameterT>
auto
declare_parameter(
const std::string & name,
const ParameterT & default_value,
const rcl_interfaces::msg::ParameterDescriptor & parameter_descriptor =
rcl_interfaces::msg::ParameterDescriptor(),
bool ignore_override = false);
/// Declare and initialize a parameter with a type.
/**
* See the non-templated declare_parameter() on this class for details.
*/
template<typename ParameterT>
auto
declare_parameter(
const std::string & name,
const rcl_interfaces::msg::ParameterDescriptor & parameter_descriptor =
rcl_interfaces::msg::ParameterDescriptor(),
bool ignore_override = false);
/// Declare and initialize several parameters with the same namespace and type.
/**
* For each key in the map, a parameter with a name of "namespace.key"
* will be set to the value in the map.
* The resulting value for each declared parameter will be returned.
*
* The name expansion is naive, so if you set the namespace to be "foo.",
* then the resulting parameter names will be like "foo..key".
* However, if the namespace is an empty string, then no leading '.' will be
* placed before each key, which would have been the case when naively
* expanding "namespace.key".
* This allows you to declare several parameters at once without a namespace.
*
* The map contains default values for parameters.
* There is another overload which takes the std::pair with the default value
* and descriptor.
*
* If `ignore_overrides` is `true`, all the overrides of the parameters declared
* by the function call will be ignored.
*
* This method will result in any callback registered with
* `add_on_set_parameters_callback` and `add_post_set_parameters_callback`
* to be called once for each parameter.
*
* This method, if successful, will result in any callback registered with
* `add_on_set_parameters_callback` to be called, once for each parameter.
* If that callback prevents the initial value for any parameter from being
* set then rclcpp::exceptions::InvalidParameterValueException is thrown.
*
* If a callback was registered previously with `add_post_set_parameters_callback`,
* it will be called after setting the parameters successfully for the node,
* once for each parameter.
*
* This method will _not_ result in any callbacks registered with
* `add_pre_set_parameters_callback` to be called.
*
* \param[in] namespace_ The namespace in which to declare the parameters.
* \param[in] parameters The parameters to set in the given namespace.
* \param[in] ignore_overrides When `true`, the parameters overrides are ignored.
* Default to `false`.
* \throws rclcpp::exceptions::ParameterAlreadyDeclaredException if parameter
* has already been declared.
* \throws rclcpp::exceptions::InvalidParametersException if a parameter
* name is invalid.
* \throws rclcpp::exceptions::InvalidParameterValueException if initial
* value fails to be set.
*/
template<typename ParameterT>
std::vector<ParameterT>
declare_parameters(
const std::string & namespace_,
const std::map<std::string, ParameterT> & parameters,
bool ignore_overrides = false);
/// Declare and initialize several parameters with the same namespace and type.
/**
* This version will take a map where the value is a pair, with the default
* parameter value as the first item and a parameter descriptor as the second.
*
* See the simpler declare_parameters() on this class for more details.
*/
template<typename ParameterT>
std::vector<ParameterT>
declare_parameters(
const std::string & namespace_,
const std::map<
std::string,
std::pair<ParameterT, rcl_interfaces::msg::ParameterDescriptor>
> & parameters,
bool ignore_overrides = false);
/// Undeclare a previously declared parameter.
/**
* This method will _not_ cause a callback registered with any of the
* `add_pre_set_parameters_callback`, `add_on_set_parameters_callback` and
* `add_post_set_parameters_callback` to be called.
*
* \param[in] name The name of the parameter to be undeclared.
* \throws rclcpp::exceptions::ParameterNotDeclaredException if the parameter
* has not been declared.
* \throws rclcpp::exceptions::ParameterImmutableException if the parameter
* was create as read_only (immutable).
*/
RCLCPP_PUBLIC
void
undeclare_parameter(const std::string & name);
/// Return true if a given parameter is declared.
/**
* \param[in] name The name of the parameter to check for being declared.
* \return true if the parameter name has been declared, otherwise false.
*/
RCLCPP_PUBLIC
bool
has_parameter(const std::string & name) const;
/// Set a single parameter.
/**
* Set the given parameter and then return result of the set action.
*
* If the parameter has not been declared this function may throw the
* rclcpp::exceptions::ParameterNotDeclaredException exception, but only if
* the node was not created with the
* rclcpp::NodeOptions::allow_undeclared_parameters set to true.
* If undeclared parameters are allowed, then the parameter is implicitly
* declared with the default parameter meta data before being set.
* Parameter overrides are ignored by set_parameter.
*
* This method will result in any callback registered with
* `add_pre_set_parameters_callback`, add_on_set_parameters_callback` and
* `add_post_set_parameters_callback` to be called once for the parameter
* being set.
*
* This method will result in any callback registered with
* `add_on_set_parameters_callback` to be called.
* If the callback prevents the parameter from being set, then it will be
* reflected in the SetParametersResult that is returned, but no exception
* will be thrown.
*
* If a callback was registered previously with `add_pre_set_parameters_callback`,
* it will be called once prior to the validation of the parameter for the node.
* If this callback makes modified parameter list empty, then it will be reflected
* in the returned result; no exceptions will be raised in this case.
*
* If a callback was registered previously with `add_post_set_parameters_callback`,
* it will be called once after setting the parameter successfully for the node.
*
* If the value type of the parameter is rclcpp::PARAMETER_NOT_SET, and the
* existing parameter type is something else, then the parameter will be
* implicitly undeclared.
* This will result in a parameter event indicating that the parameter was
* deleted.
*
* \param[in] parameter The parameter to be set.
* \return The result of the set action.
* \throws rclcpp::exceptions::ParameterNotDeclaredException if the parameter
* has not been declared and undeclared parameters are not allowed.
*/
RCLCPP_PUBLIC
rcl_interfaces::msg::SetParametersResult
set_parameter(const rclcpp::Parameter & parameter);
/// Set one or more parameters, one at a time.
/**
* Set the given parameters, one at a time, and then return result of each
* set action.
*
* Parameters are set in the order they are given within the input vector.
*
* Like set_parameter, if any of the parameters to be set have not first been
* declared, and undeclared parameters are not allowed (the default), then
* this method will throw rclcpp::exceptions::ParameterNotDeclaredException.
*
* If setting a parameter fails due to not being declared, then the
* parameters which have already been set will stay set, and no attempt will
* be made to set the parameters which come after.
*
* If a parameter fails to be set due to any other reason, like being
* rejected by the user's callback (basically any reason other than not
* having been declared beforehand), then that is reflected in the
* corresponding SetParametersResult in the vector returned by this function.
*
* This method will result in any callback registered with
* `add_pre_set_parameters_callback`, `add_on_set_parameters_callback` and
* `add_post_set_parameters_callback` to be called once for each parameter.
* If a callback was registered previously with `add_pre_set_parameters_callback`,
* it will be called prior to the validation of parameters for the node,
* once for each parameter.
* If this callback makes modified parameter list empty, then it will be reflected
* in the returned result; no exceptions will be raised in this case.
*
* This method will result in any callback registered with
* `add_on_set_parameters_callback` to be called, once for each parameter.
* If the callback prevents the parameter from being set, then, as mentioned
* before, it will be reflected in the corresponding SetParametersResult
* that is returned, but no exception will be thrown.
*
* If a callback was registered previously with `add_post_set_parameters_callback`,
* it will be called after setting the parameters successfully for the node,
* once for each parameter.
*
* Like set_parameter() this method will implicitly undeclare parameters
* with the type rclcpp::PARAMETER_NOT_SET.
*
* \param[in] parameters The vector of parameters to be set.
* \return The results for each set action as a vector.
* \throws rclcpp::exceptions::ParameterNotDeclaredException if any parameter
* has not been declared and undeclared parameters are not allowed.
*/
RCLCPP_PUBLIC
std::vector<rcl_interfaces::msg::SetParametersResult>
set_parameters(const std::vector<rclcpp::Parameter> & parameters);
/// Set one or more parameters, all at once.
/**
* Set the given parameters, all at one time, and then aggregate result.
*
* Behaves like set_parameter, except that it sets multiple parameters,
* failing all if just one of the parameters are unsuccessfully set.
* Either all of the parameters are set or none of them are set.
*
* Like set_parameter and set_parameters, this method may throw an
* rclcpp::exceptions::ParameterNotDeclaredException exception if any of the
* parameters to be set have not first been declared.
* If the exception is thrown then none of the parameters will have been set.
*
* This method will result in any callback registered with
* `add_pre_set_parameters_callback`, `add_on_set_parameters_callback` and
* `add_post_set_parameters_callback` to be called only 'once' for all parameters.
*
* If a callback was registered previously with `add_pre_set_parameters_callback`,
* it will be called prior to the validation of node parameters, just one time
* for all parameters.
* If this callback makes modified parameter list empty, then it will be reflected
* in the returned result; no exceptions will be raised in this case.
*
* This method will result in any callback registered with
* 'add_on_set_parameters_callback' to be called, just one time.
* If the callback prevents the parameters from being set, then it will be
* reflected in the SetParametersResult which is returned, but no exception
* will be thrown.
*
* If a callback was registered previously with `add_post_set_parameters_callback`,
* it will be called after setting the node parameters successfully, just one time
* for all parameters.
*
* If you pass multiple rclcpp::Parameter instances with the same name, then
* only the last one in the vector (forward iteration) will be set.
*
* Like set_parameter() this method will implicitly undeclare parameters
* with the type rclcpp::PARAMETER_NOT_SET.
*
* \param[in] parameters The vector of parameters to be set.
* \return The aggregate result of setting all the parameters atomically.
* \throws rclcpp::exceptions::ParameterNotDeclaredException if any parameter
* has not been declared and undeclared parameters are not allowed.
*/
RCLCPP_PUBLIC
rcl_interfaces::msg::SetParametersResult
set_parameters_atomically(const std::vector<rclcpp::Parameter> & parameters);
/// Return the parameter by the given name.
/**
* If the parameter has not been declared, then this method may throw the
* rclcpp::exceptions::ParameterNotDeclaredException exception.
* If the parameter has not been initialized, then this method may throw the
* rclcpp::exceptions::ParameterUninitializedException exception.
*
* If undeclared parameters are allowed, see the node option
* rclcpp::NodeOptions::allow_undeclared_parameters, then this method will
* not throw the rclcpp::exceptions::ParameterNotDeclaredException exception,
* and instead return a default initialized rclcpp::Parameter, which has a type of
* rclcpp::ParameterType::PARAMETER_NOT_SET.
*
* \param[in] name The name of the parameter to get.
* \return The requested parameter inside of a rclcpp parameter object.
* \throws rclcpp::exceptions::ParameterNotDeclaredException if the parameter
* has not been declared and undeclared parameters are not allowed.
* \throws rclcpp::exceptions::ParameterUninitializedException if the parameter
* has not been initialized.
*/
RCLCPP_PUBLIC
rclcpp::Parameter
get_parameter(const std::string & name) const;
/// Get the value of a parameter by the given name, and return true if it was set.
/**
* This method will never throw the
* rclcpp::exceptions::ParameterNotDeclaredException exception, but will
* instead return false if the parameter has not be previously declared.
*
* If the parameter was not declared, then the output argument for this
* method which is called "parameter" will not be assigned a value.
* If the parameter was declared, and therefore has a value, then it is
* assigned into the "parameter" argument of this method.
*
* \param[in] name The name of the parameter to get.
* \param[out] parameter The output storage for the parameter being retrieved.
* \return true if the parameter was previously declared, otherwise false.
*/
RCLCPP_PUBLIC
bool
get_parameter(const std::string & name, rclcpp::Parameter & parameter) const;
/// Get the value of a parameter by the given name, and return true if it was set.
/**
* Identical to the non-templated version of this method, except that when
* assigning the output argument called "parameter", this method will attempt
* to coerce the parameter value into the type requested by the given
* template argument, which may fail and throw an exception.
*
* If the parameter has not been declared, it will not attempt to coerce the
* value into the requested type, as it is known that the type is not set.
*
* \throws rclcpp::ParameterTypeException if the requested type does not
* match the value of the parameter which is stored.
*/
template<typename ParameterT>
bool
get_parameter(const std::string & name, ParameterT & parameter) const;
/// Get the parameter value, or the "alternative_value" if not set, and assign it to "parameter".
/**
* If the parameter was not set, then the "parameter" argument is assigned
* the "alternative_value".
*
* Like the version of get_parameter() which returns a bool, this method will
* not throw the rclcpp::exceptions::ParameterNotDeclaredException exception.
*
* In all cases, the parameter is never set or declared within the node.
*
* \param[in] name The name of the parameter to get.
* \param[out] parameter The output where the value of the parameter should be assigned.
* \param[in] alternative_value Value to be stored in output if the parameter was not set.
* \returns true if the parameter was set, false otherwise.
*/
template<typename ParameterT>
bool
get_parameter_or(
const std::string & name,
ParameterT & parameter,
const ParameterT & alternative_value) const;
/// Return the parameter value, or the "alternative_value" if not set.
/**
* If the parameter was not set, then the "alternative_value" argument is returned.
*
* This method will not throw the rclcpp::exceptions::ParameterNotDeclaredException exception.
*
* In all cases, the parameter is never set or declared within the node.
*
* \param[in] name The name of the parameter to get.
* \param[in] alternative_value Value to be stored in output if the parameter was not set.
* \returns The value of the parameter.
*/
template<typename ParameterT>
ParameterT
get_parameter_or(
const std::string & name,
const ParameterT & alternative_value) const;
/// Return the parameters by the given parameter names.
/**
* Like get_parameter(const std::string &), this method may throw the
* rclcpp::exceptions::ParameterNotDeclaredException exception if the
* requested parameter has not been declared and undeclared parameters are
* not allowed, and may throw the rclcpp::exceptions::ParameterUninitializedException exception.
*
* Also like get_parameter(const std::string &), if undeclared parameters are allowed and the
* parameter has not been declared, then the corresponding rclcpp::Parameter
* will be default initialized and therefore have the type
* rclcpp::ParameterType::PARAMETER_NOT_SET.
*
* \param[in] names The names of the parameters to be retrieved.
* \return The parameters that were retrieved.
* \throws rclcpp::exceptions::ParameterNotDeclaredException if any of the
* parameters have not been declared and undeclared parameters are not
* allowed.
* \throws rclcpp::exceptions::ParameterUninitializedException if any of the
* parameters have not been initialized.
*/
RCLCPP_PUBLIC
std::vector<rclcpp::Parameter>
get_parameters(const std::vector<std::string> & names) const;
/// Get the parameter values for all parameters that have a given prefix.
/**
* The "prefix" argument is used to list the parameters which are prefixed
* with that prefix, see also list_parameters().
*
* The resulting list of parameter names are used to get the values of the
* parameters.
*
* The names which are used as keys in the values map have the prefix removed.
* For example, if you use the prefix "foo" and the parameters "foo.ping" and
* "foo.pong" exist, then the returned map will have the keys "ping" and
* "pong".
*
* An empty string for the prefix will match all parameters.
*
* If no parameters with the prefix are found, then the output parameter
* "values" will be unchanged and false will be returned.
* Otherwise, the parameter names and values will be stored in the map and
* true will be returned to indicate "values" was mutated.
*
* This method will never throw the
* rclcpp::exceptions::ParameterNotDeclaredException exception because the
* action of listing the parameters is done atomically with getting the
* values, and therefore they are only listed if already declared and cannot
* be undeclared before being retrieved.
*
* Like the templated get_parameter() variant, this method will attempt to
* coerce the parameter values into the type requested by the given
* template argument, which may fail and throw an exception.
*
* \param[in] prefix The prefix of the parameters to get.
* \param[out] values The map used to store the parameter names and values,
* respectively, with one entry per parameter matching prefix.
* \returns true if output "values" was changed, false otherwise.
* \throws rclcpp::ParameterTypeException if the requested type does not
* match the value of the parameter which is stored.
*/
template<typename ParameterT>
bool
get_parameters(
const std::string & prefix,
std::map<std::string, ParameterT> & values) const;
/// Return the parameter descriptor for the given parameter name.
/**
* Like get_parameters(), this method may throw the
* rclcpp::exceptions::ParameterNotDeclaredException exception if the
* requested parameter has not been declared and undeclared parameters are
* not allowed.
*
* If undeclared parameters are allowed, then a default initialized
* descriptor will be returned.
*
* \param[in] name The name of the parameter to describe.
* \return The descriptor for the given parameter name.
* \throws rclcpp::exceptions::ParameterNotDeclaredException if the
* parameter has not been declared and undeclared parameters are not
* allowed.
* \throws std::runtime_error if the number of described parameters is more than one
*/
RCLCPP_PUBLIC
rcl_interfaces::msg::ParameterDescriptor
describe_parameter(const std::string & name) const;
/// Return a vector of parameter descriptors, one for each of the given names.
/**
* Like get_parameters(), this method may throw the
* rclcpp::exceptions::ParameterNotDeclaredException exception if any of the
* requested parameters have not been declared and undeclared parameters are
* not allowed.
*
* If undeclared parameters are allowed, then a default initialized
* descriptor will be returned for the undeclared parameter's descriptor.
*
* If the names vector is empty, then an empty vector will be returned.
*
* \param[in] names The list of parameter names to describe.
* \return A list of parameter descriptors, one for each parameter given.
* \throws rclcpp::exceptions::ParameterNotDeclaredException if any of the
* parameters have not been declared and undeclared parameters are not
* allowed.
* \throws std::runtime_error if the number of described parameters is more than one
*/
RCLCPP_PUBLIC
std::vector<rcl_interfaces::msg::ParameterDescriptor>
describe_parameters(const std::vector<std::string> & names) const;
/// Return a vector of parameter types, one for each of the given names.
/**
* Like get_parameters(), this method may throw the
* rclcpp::exceptions::ParameterNotDeclaredException exception if any of the
* requested parameters have not been declared and undeclared parameters are
* not allowed.
*
* If undeclared parameters are allowed, then the default type
* rclcpp::ParameterType::PARAMETER_NOT_SET will be returned.
*
* \param[in] names The list of parameter names to get the types.
* \return A list of parameter types, one for each parameter given.
* \throws rclcpp::exceptions::ParameterNotDeclaredException if any of the
* parameters have not been declared and undeclared parameters are not
* allowed.
*/
RCLCPP_PUBLIC
std::vector<uint8_t>
get_parameter_types(const std::vector<std::string> & names) const;
/// Return a list of parameters with any of the given prefixes, up to the given depth.
/**
* Parameters are separated into a hierarchy using the "." (dot) character.
* The "prefixes" argument is a way to select only particular parts of the hierarchy.
*
* \param[in] prefixes The list of prefixes that should be searched for within the
* current parameters. If this vector of prefixes is empty, then list_parameters
* will return all parameters.
* \param[in] depth An unsigned integer that represents the recursive depth to search.
* If this depth = 0, then all parameters that fit the prefixes will be returned.
* \returns A ListParametersResult message which contains both an array of unique prefixes
* and an array of names that were matched to the prefixes given.
*/
RCLCPP_PUBLIC
rcl_interfaces::msg::ListParametersResult
list_parameters(const std::vector<std::string> & prefixes, uint64_t depth) const;
using PreSetParametersCallbackHandle =
rclcpp::node_interfaces::PreSetParametersCallbackHandle;
using PreSetParametersCallbackType =
rclcpp::node_interfaces::NodeParametersInterface::PreSetParametersCallbackType;
using OnSetParametersCallbackHandle =
rclcpp::node_interfaces::OnSetParametersCallbackHandle;
using OnSetParametersCallbackType =
rclcpp::node_interfaces::NodeParametersInterface::OnSetParametersCallbackType;
using OnParametersSetCallbackType [[deprecated("use OnSetParametersCallbackType instead")]] =
OnSetParametersCallbackType;
using PostSetParametersCallbackHandle =
rclcpp::node_interfaces::PostSetParametersCallbackHandle;
using PostSetParametersCallbackType =
rclcpp::node_interfaces::NodeParametersInterface::PostSetParametersCallbackType;
/// Add a callback that gets triggered before parameters are validated.
/**
* This callback can be used to modify the original list of parameters being
* set by the user.
*
* The modified list of parameters is then forwarded to the "on set parameter"
* callback for validation.
*
* The callback is called whenever any of the `set_parameter*` methods are called
* or when a set parameter service request is received.
*
* The callback takes a reference to the vector of parameters to be set.
*
* The vector of parameters may be modified by the callback.
*
* One of the use case of "pre set callback" can be updating additional parameters
* conditioned on changes to a parameter.
*
* Users should retain a copy of the returned shared pointer, as the callback
* is valid only as long as the smart pointer is alive.
*
* For an example callback:
*
*```cpp
* void
* preSetParameterCallback(std::vector<rclcpp::Parameter> & parameters)
* {
* for (auto & param : parameters) {
* if (param.get_name() == "param1") {
* parameters.push_back(rclcpp::Parameter("param2", 4.0));
* }
* }
* }
* ```
* The above callback appends 'param2' to the list of parameters to be set if
* 'param1' is being set by the user.
*
* All parameters in the vector will be set atomically.
*
* Note that the callback is only called while setting parameters with `set_parameter`,
* `set_parameters`, `set_parameters_atomically`, or externally with a parameters service.
*
* The callback is not called when parameters are declared with `declare_parameter`
* or `declare_parameters`.
*
* The callback is not called when parameters are undeclared with `undeclare_parameter`.
*
* An empty modified parameter list from the callback will result in "set_parameter*"
* returning an unsuccessful result.
*
* The `remove_pre_set_parameters_callback` can be used to deregister the callback.
*
* \param callback The callback to register.
* \returns A shared pointer. The callback is valid as long as the smart pointer is alive.
* \throws std::bad_alloc if the allocation of the PreSetParametersCallbackHandle fails.
*/
RCLCPP_PUBLIC
RCUTILS_WARN_UNUSED
PreSetParametersCallbackHandle::SharedPtr
add_pre_set_parameters_callback(PreSetParametersCallbackType callback);
/// Add a callback to validate parameters before they are set.
/**
* The callback signature is designed to allow handling of any of the above
* `set_parameter*` or `declare_parameter*` methods, and so it takes a const
* reference to a vector of parameters to be set, and returns an instance of
* rcl_interfaces::msg::SetParametersResult to indicate whether or not the
* parameter should be set or not, and if not why.
*
* Users should retain a copy of the returned shared pointer, as the callback
* is valid only as long as the smart pointer is alive.
*
* For an example callback:
*
* ```cpp
* rcl_interfaces::msg::SetParametersResult
* my_callback(const std::vector<rclcpp::Parameter> & parameters)
* {
* rcl_interfaces::msg::SetParametersResult result;
* result.successful = true;
* for (const auto & parameter : parameters) {
* if (!some_condition) {
* result.successful = false;
* result.reason = "the reason it could not be allowed";
* }
* }
* return result;
* }
* ```
*
* You can see that the SetParametersResult is a boolean flag for success
* and an optional reason that can be used in error reporting when it fails.
*
* This allows the node developer to control which parameters may be changed.
*
* It is considered bad practice to reject changes for "unknown" parameters as this prevents
* other parts of the node (that may be aware of these parameters) from handling them.
*
* Note that the callback is called when declare_parameter() and its variants
* are called, and so you cannot assume the parameter has been set before
* this callback, so when checking a new value against the existing one, you
* must account for the case where the parameter is not yet set.
*
* The callback is not called when parameters are undeclared with `undeclare_parameter`.
*
* Some constraints like read_only are enforced before the callback is called.
*
* The callback may introspect other already set parameters (by calling any
* of the {get,list,describe}_parameter() methods), but may *not* modify
* other parameters (by calling any of the {set,declare}_parameter() methods)
* or modify the registered callback itself (by calling the
* add_on_set_parameters_callback() method). If a callback tries to do any
* of the latter things,
* rclcpp::exceptions::ParameterModifiedInCallbackException will be thrown.
*
* The callback functions must remain valid as long as the
* returned smart pointer is valid.
* The returned smart pointer can be promoted to a shared version.
*
* Resetting or letting the smart pointer go out of scope unregisters the callback.
* `remove_on_set_parameters_callback` can also be used.
*
* The registered callbacks are called when a parameter is set.
* When a callback returns a not successful result, the remaining callbacks aren't called.
* The order of the callback is the reverse from the registration order.
*
* \param callback The callback to register.
* \returns A shared pointer. The callback is valid as long as the smart pointer is alive.
* \throws std::bad_alloc if the allocation of the OnSetParametersCallbackHandle fails.
*/
RCLCPP_PUBLIC
RCUTILS_WARN_UNUSED
OnSetParametersCallbackHandle::SharedPtr
add_on_set_parameters_callback(OnSetParametersCallbackType callback);
/// Add a callback that gets triggered after parameters are set successfully.
/**
* The callback is called when any of the `set_parameter*` or `declare_parameter*`
* methods are successful.
*
* Users should retain a copy of the returned shared pointer, as the callback
* is valid only as long as the smart pointer is alive.
*
* The callback takes a reference to a const vector of parameters that have been
* set successfully.
*
* The post callback can be valuable as a place to cause side-effects based on
* parameter changes.
* For instance updating internally tracked class attributes once parameters
* have been changed successfully.
*
* For an example callback:
*
* ```cpp
* void
* postSetParameterCallback(const std::vector<rclcpp::Parameter> & parameters)
* {
* for(const auto & param:parameters) {
* // the internal class member can be changed after
* // successful change to param1 or param2
* if(param.get_name() == "param1") {
* internal_tracked_class_parameter_1_ = param.get_value<double>();
* }
* else if(param.get_name() == "param2") {
* internal_tracked_class_parameter_2_ = param.get_value<double>();
* }
* }
* }
* ```
*
* The above callback takes a const reference to list of parameters that have been
* set successfully and as a result of this updates the internally tracked class attributes
* `internal_tracked_class_parameter_1_` and `internal_tracked_class_parameter_2_`
* respectively.
*
* This callback should not modify parameters.
*
* The callback is called when parameters are declared with `declare_parameter`
* or `declare_parameters`. See `declare_parameter` or `declare_parameters` above.
*
* The callback is not called when parameters are undeclared with `undeclare_parameter`.
*
* If you want to make changes to parameters based on changes to another, use
* `add_pre_set_parameters_callback`.
*
* The `remove_post_set_parameters_callback` can be used to deregister the callback.
*
* \param callback The callback to register.
* \returns A shared pointer. The callback is valid as long as the smart pointer is alive.
* \throws std::bad_alloc if the allocation of the OnSetParametersCallbackHandle fails.
*/
RCLCPP_PUBLIC
RCUTILS_WARN_UNUSED
PostSetParametersCallbackHandle::SharedPtr
add_post_set_parameters_callback(PostSetParametersCallbackType callback);
/// Remove a callback registered with `add_pre_set_parameters_callback`.
/**
* Delete a handler returned by `add_pre_set_parameters_callback`.
*
* \param handler The callback handler to remove.
* \throws std::runtime_error if the handler was not created with `add_pre_set_parameters_callback`,
* or if it has been removed before.
*/
RCLCPP_PUBLIC
void
remove_pre_set_parameters_callback(const PreSetParametersCallbackHandle * const handler);
/// Remove a callback registered with `add_on_set_parameters_callback`.
/**
* Delete a handler returned by `add_on_set_parameters_callback`.
*
* e.g.:
*
* `remove_on_set_parameters_callback(scoped_callback.get())`
*
* As an alternative, the smart pointer can be reset:
*
* `scoped_callback.reset()`
*
* Supposing that `scoped_callback` was the only owner.
*
* Calling `remove_on_set_parameters_callback` more than once with the same handler,
* or calling it after the shared pointer has been reset is an error.
* Resetting or letting the smart pointer go out of scope after calling
* `remove_on_set_parameters_callback` is not a problem.
*
* \param handler The callback handler to remove.
* \throws std::runtime_error if the handler was not created with `add_on_set_parameters_callback`,
* or if it has been removed before.
*/
RCLCPP_PUBLIC
void
remove_on_set_parameters_callback(const OnSetParametersCallbackHandle * const handler);
/// Remove a callback registered with `add_post_set_parameters_callback`.
/**
* Delete a handler returned by `add_post_set_parameters_callback`.
*
* \param handler The callback handler to remove.
* \throws std::runtime_error if the handler was not created with `add_post_set_parameters_callback`,
* or if it has been removed before.
*/
RCLCPP_PUBLIC
void
remove_post_set_parameters_callback(const PostSetParametersCallbackHandle * const handler);
):

  1. declare_parameter that takes a ParameterValue
  2. declare_parameter that takes a ParameterType
  3. declare_parameter that takes a templated default type
  4. declare_parameter that is templated on just the type
  5. declare_parameters that takes a std::map of <std::string, ParameterT>
  6. declare_parameters that takes a std::map of <std::string, std::pair<ParameterT, ParameterDescriptor>>
  7. undeclare_parameter
  8. has_parameter
  9. set_parameter
  10. set_parameters
  11. set_parameters_atomically
  12. get_parameter
  13. get_parameter, returning a bool if it was set
  14. get_parameter, returning a bool if set and coercing the output to the templated type
  15. get_parameter_or, which gets a parameter or a default value and returns a bool
  16. get_parameter_or, which returns a templated type
  17. get_parameters, which takes a vector of names and returns a vector of Parameter structures
  18. get_parameters, which, given a prefix returns all parameters with that prefix
  19. describe_parameter, which gets the ParameterDescriptor for a parameter
  20. describe_parameters, which gets the ParameterDescriptros for a vector of parameter names
  21. get_parameter_types, which gets the enum type for a vector of parameter names
  22. list_parameters, which gets all parameters up to a depth
  23. add_pre_set_parameters_callback, which registers a callback to be called before parameters are set
  24. add_on_set_parameters_callback, which registers a callback to accept/reject parameters before they are set
  25. add_post_set_parameters_callback, which registers a callback to be called after parameters are set
  26. remove_pre_set_parameters_callback, which unregisters a pre callback
  27. remove_on_set_parameters_callback, which unregisters an accept callback
  28. remove_post_set_parameters_callback, which unregisters a post callback

And this list ignores the ParametersClient, ParameterEvents, ParameterEventsFilter, etc, that also revolve around parameters.

I think we need to think long and hard before we add to this mess, because I don't think it is sustainable to keep adding APIs here. Instead I'm leaning towards just documenting how to do this, since I think this is completely achievable with the current APIs.

@alsora
Copy link
Collaborator

alsora commented Feb 2, 2024

+1 on documenting and/or add utility functions (that take node interfaces as input) as opposed to add APIs to the node class itself

Signed-off-by: CursedRock17 <[email protected]>
rclcpp/include/rclcpp/copy_all_parameter_values.hpp Outdated Show resolved Hide resolved
*/
template<typename NodeT>
std::vector<rcl_interfaces::msg::SetParametersResult>
load_parameters(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this file name should be changed into something else like parameter_interfaces.hpp? copy_all_parameter_values.hpp sounds dedicated to rclcpp::copy_all_parameter_values?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea I agree, I just didn't want to change it too fast. parameter_interfaces.hpp sounds good to me.

rclcpp/include/rclcpp/copy_all_parameter_values.hpp Outdated Show resolved Hide resolved
template<typename NodeT>
std::vector<rcl_interfaces::msg::SetParametersResult>
load_parameters(
const std::string & yaml_filepath, NodeT node_interface)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer if we didn't use a template here, but rather the NodeInterfaces class or directly passing the two required interface (node_base_interface and node_parameters_interface)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that would be better for compile time.

const std::string & yaml_filepath, NodeT node_interface)
{
rclcpp::ParameterMap parameter_map =
rclcpp::parameter_map_from_yaml_file(yaml_filepath, node_interface->get_name());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be get_fully_qualified_name ?

TEST_F(TestNode, TestFileImport)
{
const uint64_t expected_param_count = 4;
auto load_node = std::make_shared<rclcpp::Node>(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can add LifecycleNode test case here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would that not make a circular testing structure, as LifecycleNode in lifecycle_rclcpp needs to include rclcpp.

Signed-off-by: CursedRock17 <[email protected]>
* \throws InvalidParametersException if no valid parameters found.
*/
std::vector<rcl_interfaces::msg::SetParametersResult>
load_parameters(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be clear; further to my comments in #2406 (comment) , this PR is currently a NAK from me.

@CursedRock17
Copy link
Contributor Author

NAK

Does this mean Negative Acknowledgement? I'm not familiar with the acronym.

If it does, I can see that point and would assume that in the context of:

+1 on documenting and/or add utility functions (that take node interfaces as input

you're mainly on board with just documentation. Though, how detrimental would a utility function - which uses NodeInterfaces - be to organization of the parameter API. Especially if it's separated from Nodes and ParameterClients then it shouldn't get in the way of any restructoring. Furthermore, if we're just documenting this function, where would we put something like this? Would it be just denoted near AsyncParametersClient::load_parameters

Then, as a sidenote, would it be worth creating an issue for the long list of parameter APIs, if

We have too many node APIs that deal with parameters.

It sounds like, some of them may not be necessary, as suggested with this message. That issue would just highlight the process for removing certain methods.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants