Nav2 Navigation Stack - lyrical  lyrical
ROS 2 Navigation Stack
node_utils.hpp
1 // Copyright (c) 2019 Intel Corporation
2 // Copyright (c) 2023 Open Navigation LLC
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 
16 #ifndef NAV2_ROS_COMMON__NODE_UTILS_HPP_
17 #define NAV2_ROS_COMMON__NODE_UTILS_HPP_
18 
19 #include <vector>
20 #include <string>
21 #include <chrono>
22 #include <algorithm>
23 #include <cctype>
24 #include "rclcpp/version.h"
25 #include "rclcpp/rclcpp.hpp"
26 #include "rcl_interfaces/srv/list_parameters.hpp"
27 #include "pluginlib/exceptions.hpp"
28 #if RCLCPP_VERSION_GTE(30, 1, 4)
29 #include "ament_index_cpp/get_package_share_path.hpp"
30 #else
31 #include "ament_index_cpp/get_package_share_directory.hpp"
32 #endif
33 
34 #ifdef __APPLE__
35  #include <pthread.h>
36  #include <mach/mach.h>
37  #include <mach/thread_policy.h>
38 #else
39  #include <sched.h>
40  #include <errno.h>
41 #endif
42 
43 using std::chrono::high_resolution_clock;
44 using std::to_string;
45 using std::string;
46 using std::replace_if;
47 using std::isalnum;
48 
49 namespace nav2
50 {
51 
53 
62 inline std::string sanitize_node_name(const std::string & potential_node_name)
63 {
64  string node_name(potential_node_name);
65  // read this as `replace` characters in `node_name` `if` not alphanumeric.
66  // replace with '_'
67  replace_if(
68  begin(node_name), end(node_name),
69  [](auto c) {return !isalnum(c);},
70  '_');
71  return node_name;
72 }
73 
75 
80 inline std::string add_namespaces(const std::string & top_ns, const std::string & sub_ns = "")
81 {
82  if (!top_ns.empty() && top_ns.back() == '/') {
83  if (top_ns.front() == '/') {
84  return top_ns + sub_ns;
85  } else {
86  return "/" + top_ns + sub_ns;
87  }
88  }
89 
90  return top_ns + "/" + sub_ns;
91 }
92 
94 
102 inline std::string time_to_string(size_t len)
103 {
104  string output(len, '0'); // prefill the string with zeros
105  auto timepoint = high_resolution_clock::now();
106  auto timecount = timepoint.time_since_epoch().count();
107  auto timestring = to_string(timecount);
108  if (timestring.length() >= len) {
109  // if `timestring` is shorter, put it at the end of `output`
110  output.replace(
111  0, len,
112  timestring,
113  timestring.length() - len, len);
114  } else {
115  // if `output` is shorter, just copy in the end of `timestring`
116  output.replace(
117  len - timestring.length(), timestring.length(),
118  timestring,
119  0, timestring.length());
120  }
121  return output;
122 }
123 
125 
135 inline std::string generate_internal_node_name(const std::string & prefix = "")
136 {
137  return sanitize_node_name(prefix) + "_" + time_to_string(8);
138 }
139 
141 
150 inline rclcpp::Node::SharedPtr generate_internal_node(const std::string & prefix = "")
151 {
152  auto options =
153  rclcpp::NodeOptions()
154  .start_parameter_services(false)
155  .start_parameter_event_publisher(false)
156  .arguments({"--ros-args", "-r", "__node:=" + generate_internal_node_name(prefix), "--"});
157  return rclcpp::Node::make_shared("_", options);
158 }
159 
160 using ParameterDescriptor = rcl_interfaces::msg::ParameterDescriptor;
161 
163 /* Declares static ROS2 parameter and sets it to a given value
164  * if it was not already declared.
165  *
166  * \param[in] node A node in which given parameter to be declared
167  * \param[in] parameter_name The name of parameter
168  * \param[in] default_value Parameter value to initialize with
169  * \param[in] parameter_descriptor Parameter descriptor (optional)
170  */
171 template<typename NodeT>
172 inline void declare_parameter_if_not_declared(
173  NodeT node,
174  const std::string & parameter_name,
175  const rclcpp::ParameterValue & default_value,
176  const ParameterDescriptor & parameter_descriptor = ParameterDescriptor())
177 {
178  if (!node->has_parameter(parameter_name)) {
179  node->declare_parameter(parameter_name, default_value, parameter_descriptor);
180  }
181 }
182 
184 /* Declares static ROS2 parameter with given type if it was not already declared.
185  *
186  * \param[in] node A node in which given parameter to be declared
187  * \param[in] parameter_name Name of the parameter
188  * \param[in] param_type The type of parameter
189  * \param[in] parameter_descriptor Parameter descriptor (optional)
190  */
191 template<typename NodeT>
192 inline void declare_parameter_if_not_declared(
193  NodeT node,
194  const std::string & parameter_name,
195  const rclcpp::ParameterType & param_type,
196  const ParameterDescriptor & parameter_descriptor = ParameterDescriptor())
197 {
198  if (!node->has_parameter(parameter_name)) {
199  node->declare_parameter(parameter_name, param_type, parameter_descriptor);
200  }
201 }
202 
205 /* Declares a parameter with the specified type if it was not already declared.
206  * If the parameter was overridden, its value is returned, otherwise an
207  * rclcpp::exceptions::InvalidParameterValueException is thrown
208  *
209  * \param[in] node A node in which given parameter to be declared
210  * \param[in] parameter_name Name of the parameter
211  * \param[in] parameter_descriptor Parameter descriptor (optional)
212  * \return The value of the parameter or an exception
213  */
214 template<typename ParameterT, typename NodeT>
215 inline ParameterT declare_or_get_parameter(
216  NodeT node,
217  const std::string & parameter_name,
218  const ParameterDescriptor & parameter_descriptor = ParameterDescriptor())
219 {
220  if (node->has_parameter(parameter_name)) {
221  return node->get_parameter(parameter_name).template get_value<ParameterT>();
222  }
223  auto param_type = rclcpp::ParameterValue{ParameterT{}}.get_type();
224  auto parameter = node->declare_parameter(parameter_name, param_type, parameter_descriptor);
225  if (parameter.get_type() == rclcpp::ParameterType::PARAMETER_NOT_SET) {
226  std::string description = "Parameter " + parameter_name + " not in overrides";
227  throw rclcpp::exceptions::InvalidParameterValueException(description.c_str());
228  }
229  return parameter.template get<ParameterT>();
230 }
231 
232 using NodeParamInterfacePtr = rclcpp::node_interfaces::NodeParametersInterface::SharedPtr;
233 
236 
250 template<typename ParamType>
251 inline ParamType declare_or_get_parameter(
252  const rclcpp::Logger & logger, NodeParamInterfacePtr param_interface,
253  const std::string & parameter_name, const ParamType & default_value,
254  bool warn_if_no_override = false, bool strict_param_loading = false,
255  const ParameterDescriptor & parameter_descriptor = ParameterDescriptor())
256 {
257  if (param_interface->has_parameter(parameter_name)) {
258  rclcpp::Parameter param(parameter_name, default_value);
259  param_interface->get_parameter(parameter_name, param);
260  return param.get_value<ParamType>();
261  }
262 
263  auto return_value = param_interface
264  ->declare_parameter(
265  parameter_name, rclcpp::ParameterValue{default_value},
266  parameter_descriptor)
267  .get<ParamType>();
268 
269  const bool no_param_override = param_interface->get_parameter_overrides().find(parameter_name) ==
270  param_interface->get_parameter_overrides().end();
271  if (no_param_override) {
272  if (warn_if_no_override) {
273  RCLCPP_WARN_STREAM(
274  logger,
275  "Failed to get param " << parameter_name << " from overrides, using default value.");
276  }
277  if (strict_param_loading) {
278  std::string description = "Parameter " + parameter_name +
279  " not in overrides and strict_param_loading is True";
280  throw rclcpp::exceptions::InvalidParameterValueException(description.c_str());
281  }
282  }
283 
284  return return_value;
285 }
286 
289 
302 template<typename ParamType, typename NodeT>
303 inline ParamType declare_or_get_parameter(
304  NodeT node, const std::string & parameter_name,
305  const ParamType & default_value,
306  const ParameterDescriptor & parameter_descriptor = ParameterDescriptor())
307 {
308  declare_parameter_if_not_declared(node, "warn_on_missing_params", rclcpp::ParameterValue(false));
309  bool warn_if_no_override{false};
310  node->get_parameter("warn_on_missing_params", warn_if_no_override);
311  declare_parameter_if_not_declared(node, "strict_param_loading", rclcpp::ParameterValue(false));
312  bool strict_param_loading{false};
313  node->get_parameter("strict_param_loading", strict_param_loading);
314  return declare_or_get_parameter(
315  node->get_logger(), node->get_node_parameters_interface(),
316  parameter_name, default_value, warn_if_no_override, strict_param_loading, parameter_descriptor);
317 }
318 
320 
328 template<typename NodeT>
329 inline std::string get_plugin_type_param(
330  NodeT node,
331  const std::string & plugin_name)
332 {
333  declare_parameter_if_not_declared(node, plugin_name + ".plugin", rclcpp::PARAMETER_STRING);
334  std::string plugin_type;
335  try {
336  if (!node->get_parameter(plugin_name + ".plugin", plugin_type)) {
337  RCLCPP_FATAL(
338  node->get_logger(), "Can not get 'plugin' param value for %s", plugin_name.c_str());
339  throw pluginlib::PluginlibException("No 'plugin' param for param ns!");
340  }
341  } catch (rclcpp::exceptions::ParameterUninitializedException & ex) {
342  RCLCPP_FATAL(node->get_logger(), "'plugin' param not defined for %s", plugin_name.c_str());
343  throw pluginlib::PluginlibException("No 'plugin' param for param ns!");
344  }
345 
346  return plugin_type;
347 }
348 
354 inline void setSoftRealTimePriority()
355 {
356 #ifdef __APPLE__
357  // macOS: Use Mach thread API to approximate real-time scheduling
358  thread_port_t thread = pthread_mach_thread_np(pthread_self());
359 
360  thread_time_constraint_policy_data_t policy;
361  policy.period = 1000; // in microseconds (1 kHz loop)
362  policy.computation = 800; // expected compute time per period
363  policy.constraint = 1000; // max latency
364  policy.preemptible = 1; // allow preemption by higher-priority threads
365 
366  kern_return_t result = thread_policy_set(
367  thread,
368  THREAD_TIME_CONSTRAINT_POLICY,
369  (thread_policy_t)&policy,
370  THREAD_TIME_CONSTRAINT_POLICY_COUNT
371  );
372 
373  if (result != KERN_SUCCESS) {
374  std::string errmsg =
375  "Failed to set THREAD_TIME_CONSTRAINT_POLICY on macOS. "
376  "Thread remains at default priority. Mach Error Code: " +
377  std::to_string(result);
378  throw std::runtime_error(errmsg);
379  }
380 #else
381  // Linux: True real-time scheduling (requires privileges)
382  sched_param sch;
383  sch.sched_priority = 49;
384  if (sched_setscheduler(0, SCHED_FIFO, &sch) == -1) {
385  std::string errmsg(
386  "Cannot set as real-time thread. Users must set: <username> hard rtprio 99 and "
387  "<username> soft rtprio 99 in /etc/security/limits.conf to enable "
388  "realtime prioritization! Error: ");
389  throw std::runtime_error(errmsg + std::strerror(errno));
390  }
391 #endif
392 }
393 
394 template<typename InterfaceT>
395 inline void setIntrospectionMode(
396  InterfaceT & ros_interface,
397  rclcpp::node_interfaces::NodeParametersInterface::SharedPtr node_parameters_interface,
398  rclcpp::Clock::SharedPtr clock)
399 {
400  #if RCLCPP_VERSION_GTE(29, 0, 0)
401  rcl_service_introspection_state_t introspection_state = RCL_SERVICE_INTROSPECTION_OFF;
402  if (!node_parameters_interface->has_parameter("introspection_mode")) {
403  node_parameters_interface->declare_parameter(
404  "introspection_mode", rclcpp::ParameterValue("disabled"));
405  }
406  std::string introspection_mode =
407  node_parameters_interface->get_parameter("introspection_mode").as_string();
408  if (introspection_mode == "metadata") {
409  introspection_state = RCL_SERVICE_INTROSPECTION_METADATA;
410  } else if (introspection_mode == "contents") {
411  introspection_state = RCL_SERVICE_INTROSPECTION_CONTENTS;
412  }
413 
414  ros_interface->configure_introspection(clock, rclcpp::ServicesQoS(), introspection_state);
415  #else
416  (void)ros_interface;
417  (void)node_parameters_interface;
418  (void)clock;
419  #endif
420 }
421 
427 inline void replaceOrAddArgument(
428  std::vector<std::string> & arguments, const std::string & option,
429  const std::string & arg_name, const std::string & new_argument)
430 {
431  auto argument = std::find_if(
432  arguments.begin(), arguments.end(),
433  [arg_name](const std::string & value) {return value.find(arg_name) != std::string::npos;});
434  if (argument != arguments.end()) {
435  *argument = new_argument;
436  } else {
437  arguments.push_back("--ros-args");
438  arguments.push_back(option);
439  arguments.push_back(new_argument);
440  }
441 }
442 
443 inline std::string get_package_share_directory(const std::string & package_name)
444 {
445  #if RCLCPP_VERSION_GTE(30, 1, 4)
446  std::filesystem::path pkg_share_dir = ament_index_cpp::get_package_share_path(package_name);
447  return pkg_share_dir.string();
448  #else
449  return ament_index_cpp::get_package_share_directory(package_name);
450  #endif
451 }
452 
453 } // namespace nav2
454 
455 #endif // NAV2_ROS_COMMON__NODE_UTILS_HPP_