mrs_lib
Various reusable classes, functions and utilities for use in MRS projects
Loading...
Searching...
No Matches
param_provider.hpp
Go to the documentation of this file.
1// clang: MatousFormat
7#pragma once
8
10
11namespace mrs_lib
12{
13
14 template <typename T>
15 std::ostream& operator<<(std::ostream& os, const std::vector<T>& var)
16 {
17 for (size_t it = 0; it < var.size(); it++)
18 {
19 os << var.at(it);
20 if (it < var.size() - 1)
21 os << ", ";
22 }
23 return os;
24 }
25
26 template <typename Key, typename Value>
27 std::ostream& operator<<(std::ostream& os, const std::map<Key, Value>& var)
28 {
29 size_t it = 0;
30 for (const auto& pair : var)
31 {
32 os << pair.first << ": " << pair.second;
33 if (it < var.size() - 1)
34 os << std::endl;
35 it++;
36 }
37 return os;
38 }
39
40 /* ParamProvider::resolved_name_t //{ */
41
43 {
44 std::string str;
45
46 resolved_name_t() = default;
47 resolved_name_t(std::string&& s) : str(s)
48 {
49 }
50 resolved_name_t(const std::string& s) : str(s)
51 {
52 }
53
54 // Explicit conversion
55 explicit operator std::string() const
56 {
57 return str;
58 }
59
60 friend std::ostream& operator<<(std::ostream& os, const resolved_name_t& r)
61 {
62 return os << r.str;
63 }
64
65 friend bool operator==(const resolved_name_t& lhs, const resolved_name_t& rhs)
66 {
67 return lhs.str == rhs.str;
68 }
69 };
70
71 //}
72
73 /* to_param_type() method //{ */
74 template <typename T>
75 rclcpp::ParameterType to_param_type()
76 {
77 return rclcpp::ParameterValue{T{}}.get_type();
78 }
79 //}
80
81 /* getParam() method and overloads //{ */
82 template <typename T>
83 bool ParamProvider::getParam(const std::string& param_name, T& value_out) const
84 {
85 return getParamResult(resolveName(param_name), value_out, {}) != get_result_t::FAILED;
86 }
87
88 template <typename T>
89 ParamProvider::get_result_t ParamProvider::getParamResult(const std::string& param_name, T& value_out, const get_options_t<T>& opts) const
90 {
91 return getParamResult(resolveName(param_name), value_out, opts);
92 }
93
94 template <typename T>
95 bool ParamProvider::getParam(const resolved_name_t& resolved_name, T& value_out, const get_options_t<T>& opts) const
96 {
97 return getParamResult(resolved_name, value_out, opts) != get_result_t::FAILED;
98 }
99
100 template <typename T>
102 {
103 bool use_rosparam = m_use_rosparam;
104 // the options structure always has precedence if the parameter is set
105 if (opts.use_rosparam.has_value())
106 use_rosparam = opts.use_rosparam.value();
107
108 // first, try to load from YAML, if enabled
109 if (opts.use_yaml && loadFromYaml(resolved_name, value_out, opts))
111
112 // then, try to load from ROS, if enabled
113 if (use_rosparam && loadFromROS(resolved_name, value_out, opts))
115
116 // if both fail, check for a default value
117 if (opts.declare_options.default_value.has_value())
118 {
119 const auto& default_value = opts.declare_options.default_value.value();
120
121 if (opts.always_declare && !declareParam<T>(resolved_name, opts.declare_options))
122 {
123 RCLCPP_ERROR_STREAM(m_node->get_logger(), "Failed to declare parameter \"" << resolved_name << "\".");
125 }
126
127 value_out = default_value;
129 }
130
131 // if all options fail, return false
132 std::stringstream ss;
133 ss << "Param '" << resolved_name << "' not found:";
134 if (opts.use_yaml)
135 ss << " in YAML files,";
136 if (use_rosparam)
137 ss << " in ROS,";
138 ss << " no default value provided.";
139 RCLCPP_ERROR_STREAM(m_node->get_logger(), ss.str());
141 }
142 //}
143
144 /* setParam() method //{ */
145 template <typename T>
146 bool ParamProvider::setParam(const std::string& param_name, const T& value) const
147 {
148 return setParam(resolveName(param_name), value);
149 }
150
151 template <typename T>
152 bool ParamProvider::setParam(const resolved_name_t& resolved_name, const T& value, const set_options_t<T>& opts) const
153 {
154 // if the parameter is not yet declared, declare and set it together
155 if (!m_node->has_parameter(resolved_name.str))
156 {
157 auto declare_opts_local = opts.declare_options;
158 declare_opts_local.default_value = value;
159 const auto result = declareParam<T>(resolved_name, declare_opts_local);
160 if (!result)
161 RCLCPP_ERROR_STREAM(m_node->get_logger(), "Could not declare and set param '" << resolved_name << "'!");
162 return result;
163 }
164
165 // otherwise, try setting it
166 try
167 {
168 /* RCLCPP_INFO_STREAM(m_node->get_logger(), "Setting param '" << resolved_name << "'"); */
169 rcl_interfaces::msg::SetParametersResult res = m_node->set_parameter({resolved_name.str, value});
170 if (!res.successful)
171 {
172 RCLCPP_ERROR_STREAM(m_node->get_logger(), "Could not set param '" << resolved_name << "': " << res.reason);
173 return false;
174 }
175 }
176 // if the parameter has a wrong value, return failure
177 catch (const rclcpp::exceptions::ParameterNotDeclaredException& e)
178 {
179 RCLCPP_ERROR_STREAM(m_node->get_logger(), "Could not set param '" << resolved_name << "': " << e.what());
180 return false;
181 }
182 return true;
183 }
184 //}
185
186 /* declareParam() method and overloads //{ */
187 template <typename T>
188 bool ParamProvider::declareParam(const std::string& param_name) const
189 {
190 return declareParam<T>(resolveName(param_name), {});
191 }
192
193 template <typename T>
194 bool ParamProvider::declareParam(const std::string& param_name, const T& default_value) const
195 {
196 return declareParam<T>(resolveName(param_name), {.default_value = default_value});
197 }
198
199 template <typename T>
200 bool ParamProvider::declareParam(const resolved_name_t& resolved_name, const declare_options_t<T>& opts) const
201 {
202 try
203 {
204 rcl_interfaces::msg::ParameterDescriptor descriptor;
205 descriptor.read_only = !opts.reconfigurable;
206
207 // if the parameter range is specified, set it if applicable
208 if (opts.range.has_value())
209 {
210 const auto& opt_range = opts.range.value();
211 // set the range approprately according to the parameter type
212 if constexpr (!numeric<T>)
213 {
214 // if the type is not numerical, print an error to let the user know and fail
215 RCLCPP_ERROR_STREAM(m_node->get_logger(),
216 "Error when declaring parameter \"" << resolved_name << "\": Range cannot be set for non-numerical values! Ignoring range.");
217 return false;
218 } else if constexpr (std::integral<T>)
219 {
220 // integer range for integral types
221 rcl_interfaces::msg::IntegerRange range;
222 range.from_value = opt_range.minimum;
223 range.to_value = opt_range.maximum;
224 descriptor.integer_range.push_back(range);
225 } else if constexpr (std::floating_point<T>)
226 {
227 // floating-point range for floating-point types
228 rcl_interfaces::msg::FloatingPointRange range;
229 range.from_value = opt_range.minimum;
230 range.to_value = opt_range.maximum;
231 descriptor.floating_point_range.push_back(range);
232 }
233 }
234
235 // actually declare the parameter
236 if (opts.default_value.has_value())
237 {
238 m_node->declare_parameter(resolved_name.str, opts.default_value.value(), descriptor);
239 } else
240 {
241 // a hack to allow declaring a strong-typed parameter without a default value
242 // because ROS2 apparently fell on its head when it was a litle baby
243 // this is actually not documented in the API documentation, so see https://github.com/ros2/rclcpp/issues/1691
244 const rclcpp::ParameterType type = to_param_type<T>();
245 descriptor.type = type;
246 m_node->declare_parameter(resolved_name.str, type, descriptor);
247 }
248 }
249 catch (const std::exception& e)
250 {
251 // this can happen if (see
252 // http://docs.ros.org/en/humble/p/rclcpp/generated/classrclcpp_1_1Node.html#_CPPv4N6rclcpp4Node17declare_parameterERKNSt6stringERKN6rclcpp14ParameterValueERKN14rcl_interfaces3msg19ParameterDescriptorEb):
253 // * parameter has already been declared (rclcpp::exceptions::ParameterAlreadyDeclaredException)
254 // * parameter name is invalid (rclcpp::exceptions::InvalidParametersException)
255 // * initial value fails to be set (rclcpp::exceptions::InvalidParameterValueException, not sure what exactly this means)
256 // * type of the default value or override is wrong (rclcpp::exceptions::InvalidParameterTypeException, the most common one)
257 RCLCPP_ERROR_STREAM(m_node->get_logger(), "Could not declare param '" << resolved_name << "': " << e.what());
258
259 return false;
260 }
261 return true;
262 }
263 //}
264
265 /* loadFromYaml() method //{ */
266 template <typename T>
267 bool ParamProvider::loadFromYaml(const resolved_name_t& resolved_name, T& value_out, const get_options_t<T>& opts) const
268 {
269 T loaded_value;
270
271 const auto found_node_opt = findYamlNode(resolved_name);
272 if (!found_node_opt.has_value())
273 {
274 return false;
275 }
276
277 const auto& found_node = found_node_opt.value();
278 try
279 {
280 // try catch is the only type-generic option...
281 loaded_value = applyTag<T>(found_node);
282 }
283 catch (const YAML::BadConversion& e)
284 {
285 RCLCPP_ERROR_STREAM(m_node->get_logger(), "[" << m_node_name << "]: The YAML-loaded parameter has a wrong type: " << e.what());
286 return false;
287 }
288 catch (const YAML::Exception& e)
289 {
290 RCLCPP_ERROR_STREAM(m_node->get_logger(), "[" << m_node_name << "]: YAML-CPP threw an unknown exception: " << e.what());
291 return false;
292 }
293
294 // declare the parameter if the options specify to always define it
295 if (opts.always_declare)
296 {
297 auto declare_opts_local = opts.declare_options;
298 declare_opts_local.default_value = loaded_value;
299
300 // see https://docs.ros.org/en/jazzy/Concepts/Basic/About-Parameters.html#parameters
301 if (!m_node->has_parameter(resolved_name.str) && !declareParam<T>(resolved_name, declare_opts_local))
302 {
303 RCLCPP_ERROR_STREAM(m_node->get_logger(), "Failed to declare parameter \"" << resolved_name << "\".");
304 return false;
305 }
306 }
307
308 // if all is OK, set the output value
309 value_out = loaded_value;
310 // the parameter value was successfully loaded and the parameter was declared if required, everything is done, return true
311 return true;
312 }
313 //}
314
315 /* degrees2radians() method //{ */
316 template <typename T>
317 inline T ParamProvider::degrees2radians(const T degrees)
318 {
319 return degrees / T(180) * T(M_PI);
320 }
321 //}
322
323 /* applyTag() method //{ */
324 template <typename T>
325 T ParamProvider::applyTag(const YAML::Node& node) const
326 {
327 if constexpr (std::is_floating_point_v<T>)
328 {
329 if (node.Tag() == "!degrees")
330 return degrees2radians(node.as<T>());
331 }
332 return node.as<T>();
333 }
334 //}
335
336 /* ranges_match() method //{ */
337 template <typename T>
338 bool ParamProvider::ranges_match(const rcl_interfaces::msg::ParameterDescriptor& descriptor, const range_t<T>& declare_range) const
339 {
340 if constexpr (numeric<T>)
341 {
342 if (!descriptor.floating_point_range.empty())
343 {
344 const auto& desc_range = descriptor.floating_point_range.front();
345 if (desc_range.from_value != declare_range.minimum || desc_range.to_value != declare_range.maximum)
346 return false;
347 }
348
349 if (!descriptor.integer_range.empty())
350 {
351 const auto& desc_range = descriptor.integer_range.front();
352 if (desc_range.from_value != declare_range.minimum || desc_range.to_value != declare_range.maximum)
353 return false;
354 }
355 }
356
357 // trying to specify a range for a non-numeric type...
358 return false;
359 }
360
361 //}
362
363 /* loadFromROS() method //{ */
364 template <typename T>
365 bool ParamProvider::loadFromROS(const resolved_name_t& resolved_name, T& value_out, const get_options_t<T>& opts) const
366 {
367 std::optional<T> loaded_value;
368 const bool was_declared = m_node->has_parameter(resolved_name.str);
369
370 // check if the current declaration is compatible with the desired declaration
371 if (was_declared)
372 {
373 const auto descriptor = m_node->describe_parameter(resolved_name.str);
374 if (descriptor.read_only && opts.declare_options.reconfigurable)
375 {
376 RCLCPP_ERROR_STREAM(m_node->get_logger(), "Parameter \"" << resolved_name << "\" already declared as read-only, cannot re-declare as reconfigurable!");
377 return false;
378 }
379
380 if (opts.declare_options.range.has_value() && !ranges_match(descriptor, opts.declare_options.range.value()))
381 {
382 RCLCPP_ERROR_STREAM(m_node->get_logger(),
383 "Parameter \"" << resolved_name << "\" already declared with a range, cannot re-declare with a different one!");
384 return false;
385 }
386 }
387
388 // if the parameter is not declared yet, check if it is available in ROS by declaring it as dynamically-typed and read-writable
389 if (!was_declared)
390 {
391 rcl_interfaces::msg::ParameterDescriptor descriptor;
392 descriptor.read_only = false;
393 descriptor.dynamic_typing = true;
394 try
395 {
396 m_node->declare_parameter(resolved_name.str, rclcpp::ParameterValue(), descriptor);
397 }
398 catch (const std::exception& e)
399 {
400 // if the declaration already fails, something is wrong
401 RCLCPP_ERROR_STREAM(m_node->get_logger(), "Failed to declare parameter \"" << resolved_name << "\": " << e.what());
402 return false;
403 }
404 }
405
406 // now try loading the parameter
407 try
408 {
409 /* RCLCPP_INFO_STREAM(m_node->get_logger(), "Getting param '" << resolved_name << "'"); */
410 rclcpp::Parameter param;
411 if (m_node->get_parameter(resolved_name.str, param))
412 loaded_value = param.get_value<T>();
413 }
414 // if the parameter has a wrong value, return failure
415 catch (const rclcpp::exceptions::InvalidParameterTypeException& e)
416 {
417 RCLCPP_ERROR_STREAM(m_node->get_logger(), "Could not get param '" << resolved_name << "' from ROS: " << e.what());
418 }
419
420 // if the parameter was not declared before, undecalre it:
421 // 1. either it was loaded successfully and has to be re-declared with the correct options
422 // 2. or it was not loaded and has to be un-declared
423 if (!was_declared)
424 m_node->undeclare_parameter(resolved_name.str);
425
426 // loading from ROS was not successful, just return false
427 if (!loaded_value.has_value())
428 return false;
429
430 // if the parameter was not declared before, redeclare it with the correct parameters
431 if (!was_declared)
432 {
433 auto declare_opts_local = opts.declare_options;
434 declare_opts_local.default_value = value_out;
435 if (!declareParam<T>(resolved_name, declare_opts_local))
436 {
437 RCLCPP_ERROR_STREAM(m_node->get_logger(), "Failed to declare parameter \"" << resolved_name << "\".");
438 return false;
439 }
440 }
441
442 // if all went good, set the value and return true
443 value_out = std::move(loaded_value.value());
444 return true;
445 }
446 //}
447
448} // namespace mrs_lib
449
450namespace std
451{
452 template <>
453 struct hash<mrs_lib::ParamProvider::resolved_name_t>
454 {
455 std::size_t operator()(const mrs_lib::ParamProvider::resolved_name_t& r) const noexcept
456 {
457 return std::hash<std::string>{}(r.str);
458 }
459 };
460} // namespace std
resolved_name_t resolveName(const std::string &param_name) const
Returns the parameter name with prefixes and subnode namespaces.
Definition param_provider.cpp:160
bool getParam(const std::string &param_name, T &value_out) const
Gets the value of a parameter.
Definition param_provider.hpp:83
get_result_t
Explicit result state for parameter loading.
Definition param_provider.h:133
@ DEFAULT
Parameter value resolved using provided default value.
@ LOADED
Parameter value loaded from YAML/ROS.
@ FAILED
Parameter value could not be resolved.
bool declareParam(const std::string &param_name) const
Defines a parameter.
Definition param_provider.hpp:188
get_result_t getParamResult(const std::string &param_name, T &value_out, const get_options_t< T > &opts={}) const
Gets the value of a parameter and returns an explicit result state.
Definition param_provider.hpp:89
bool setParam(const std::string &param_name, const T &value) const
Sets the value of a parameter to ROS.
Definition param_provider.hpp:146
Convenience concept of a numeric value (i.e. either integral or floating point, and not bool).
Definition param_provider.h:58
All mrs_lib functions, classes, variables and definitions are contained in this namespace.
Definition attitude_converter.h:24
std::ostream & operator<<(std::ostream &os, const Eigen::MatrixX< T > &var)
Helper overload for printing of Eigen matrices.
Definition param_loader.hpp:21
rclcpp::ParameterType to_param_type()
Convenince function to get the corresponding rclcpp::ParamType from a C++ type.
Definition param_provider.hpp:75
Defines ParamProvider - a convenience class for seamlessly loading parameters from YAML or ROS.
Definition cyclic_example.cpp:23
Struct of options when declaring a parameter to ROS.
Definition param_provider.h:102
bool reconfigurable
If true, the parameter will be dynamically reconfigurable, otherwise it will be declared as read-only...
Definition param_provider.h:104
std::optional< T > default_value
An optional default value to initialize the parameter with.
Definition param_provider.h:106
std::optional< range_t< T > > range
An optional range of valid values of the parameter (only for numerical types).
Definition param_provider.h:108
Struct of options when getting a parameter from ROS.
Definition param_provider.h:118
declare_options_t< T > declare_options
Options when declaring a parameter to ROS (see the declare_options_t<T> documentation).
Definition param_provider.h:128
std::optional< bool > use_rosparam
Specifies whether the parameter should be attempted to be loaded from ROS if it cannot be loaded from...
Definition param_provider.h:124
bool always_declare
Iff true, the parameter will be declared to ROS even if it's value was loaded from a YAML.
Definition param_provider.h:120
bool use_yaml
Iff false, loading from YAML will be skipped even if some YAML files were specified.
Definition param_provider.h:122
Helper struct for a numeric range with named members to make the code a bit more readable.
Definition param_provider.h:87
T maximum
Maximal value of a parameter.
Definition param_provider.h:91
T minimum
Minimal value of a parameter.
Definition param_provider.h:89
Definition param_provider.hpp:43
Struct of options when setting a parameter to ROS.
Definition param_provider.h:149
declare_options_t< T > declare_options
Options when declaring a parameter to ROS (see the declare_options_t<T> documentation).
Definition param_provider.h:153