parameter_interface.cpp
Go to the documentation of this file.
2 
3 #include <nlohmann/json.hpp>
4 #include <rclcpp/qos.hpp>
5 #include <rclcpp/version.h>
6 
9 
10 namespace {
11 
12 constexpr char PARAM_SEP = '.';
13 
14 #if RCLCPP_VERSION_MAJOR > 16
15 const rclcpp::ParametersQoS parameterQoS;
16 #else
17 const rmw_qos_profile_t& parameterQoS = rmw_qos_profile_parameters;
18 #endif
19 
20 static std::pair<std::string, std::string> getNodeAndParamName(
21  const std::string& nodeNameAndParamName) {
22  return {nodeNameAndParamName.substr(0UL, nodeNameAndParamName.find(PARAM_SEP)),
23  nodeNameAndParamName.substr(nodeNameAndParamName.find(PARAM_SEP) + 1UL)};
24 }
25 
26 static std::string prependNodeNameToParamName(const std::string& paramName,
27  const std::string& nodeName) {
28  return nodeName + PARAM_SEP + paramName;
29 }
30 
31 static rclcpp::Parameter toRosParam(const foxglove::Parameter& p) {
32  using foxglove::Parameter;
34 
35  const auto paramType = p.getType();
36  const auto value = p.getValue();
37 
38  if (paramType == ParameterType::PARAMETER_BOOL) {
39  return rclcpp::Parameter(p.getName(), value.getValue<bool>());
40  } else if (paramType == ParameterType::PARAMETER_INTEGER) {
41  return rclcpp::Parameter(p.getName(), value.getValue<int64_t>());
42  } else if (paramType == ParameterType::PARAMETER_DOUBLE) {
43  return rclcpp::Parameter(p.getName(), value.getValue<double>());
44  } else if (paramType == ParameterType::PARAMETER_STRING) {
45  return rclcpp::Parameter(p.getName(), value.getValue<std::string>());
46  } else if (paramType == ParameterType::PARAMETER_BYTE_ARRAY) {
47  return rclcpp::Parameter(p.getName(), value.getValue<std::vector<unsigned char>>());
48  } else if (paramType == ParameterType::PARAMETER_ARRAY) {
49  const auto paramVec = value.getValue<std::vector<foxglove::ParameterValue>>();
50 
51  const auto elementType = paramVec.front().getType();
52  if (elementType == ParameterType::PARAMETER_BOOL) {
53  std::vector<bool> vec;
54  for (const auto& paramValue : paramVec) {
55  vec.push_back(paramValue.getValue<bool>());
56  }
57  return rclcpp::Parameter(p.getName(), vec);
58  } else if (elementType == ParameterType::PARAMETER_INTEGER) {
59  std::vector<int64_t> vec;
60  for (const auto& paramValue : paramVec) {
61  vec.push_back(paramValue.getValue<int64_t>());
62  }
63  return rclcpp::Parameter(p.getName(), vec);
64  } else if (elementType == ParameterType::PARAMETER_DOUBLE) {
65  std::vector<double> vec;
66  for (const auto& paramValue : paramVec) {
67  vec.push_back(paramValue.getValue<double>());
68  }
69  return rclcpp::Parameter(p.getName(), vec);
70  } else if (elementType == ParameterType::PARAMETER_STRING) {
71  std::vector<std::string> vec;
72  for (const auto& paramValue : paramVec) {
73  vec.push_back(paramValue.getValue<std::string>());
74  }
75  return rclcpp::Parameter(p.getName(), vec);
76  }
77  throw std::runtime_error("Unsupported parameter type");
78  } else if (paramType == ParameterType::PARAMETER_NOT_SET) {
79  return rclcpp::Parameter(p.getName());
80  } else {
81  throw std::runtime_error("Unsupported parameter type");
82  }
83 
84  return rclcpp::Parameter();
85 }
86 
87 static foxglove::Parameter fromRosParam(const rclcpp::Parameter& p) {
88  const auto type = p.get_type();
89 
90  if (type == rclcpp::ParameterType::PARAMETER_NOT_SET) {
91  return foxglove::Parameter(p.get_name(), foxglove::ParameterValue());
92  } else if (type == rclcpp::ParameterType::PARAMETER_BOOL) {
93  return foxglove::Parameter(p.get_name(), p.as_bool());
94  } else if (type == rclcpp::ParameterType::PARAMETER_INTEGER) {
95  return foxglove::Parameter(p.get_name(), p.as_int());
96  } else if (type == rclcpp::ParameterType::PARAMETER_DOUBLE) {
97  return foxglove::Parameter(p.get_name(), p.as_double());
98  } else if (type == rclcpp::ParameterType::PARAMETER_STRING) {
99  return foxglove::Parameter(p.get_name(), p.as_string());
100  } else if (type == rclcpp::ParameterType::PARAMETER_BYTE_ARRAY) {
101  return foxglove::Parameter(p.get_name(), p.as_byte_array());
102  } else if (type == rclcpp::ParameterType::PARAMETER_BOOL_ARRAY) {
103  std::vector<foxglove::ParameterValue> paramVec;
104  for (const auto value : p.as_bool_array()) {
105  paramVec.push_back(foxglove::ParameterValue(value));
106  }
107  return foxglove::Parameter(p.get_name(), paramVec);
108  } else if (type == rclcpp::ParameterType::PARAMETER_INTEGER_ARRAY) {
109  std::vector<foxglove::ParameterValue> paramVec;
110  for (const auto value : p.as_integer_array()) {
111  paramVec.push_back(value);
112  }
113  return foxglove::Parameter(p.get_name(), paramVec);
114  } else if (type == rclcpp::ParameterType::PARAMETER_DOUBLE_ARRAY) {
115  std::vector<foxglove::ParameterValue> paramVec;
116  for (const auto value : p.as_double_array()) {
117  paramVec.push_back(value);
118  }
119  return foxglove::Parameter(p.get_name(), paramVec);
120  } else if (type == rclcpp::ParameterType::PARAMETER_STRING_ARRAY) {
121  std::vector<foxglove::ParameterValue> paramVec;
122  for (const auto& value : p.as_string_array()) {
123  paramVec.push_back(value);
124  }
125  return foxglove::Parameter(p.get_name(), paramVec);
126  } else {
127  throw std::runtime_error("Unsupported parameter type");
128  }
129 }
130 
131 } // namespace
132 
133 namespace foxglove_bridge {
134 
136 
138  std::vector<std::regex> paramWhitelistPatterns,
139  UnresponsiveNodePolicy unresponsiveNodePolicy)
140  : _node(node)
141  , _paramWhitelistPatterns(paramWhitelistPatterns)
142  , _callbackGroup(node->create_callback_group(rclcpp::CallbackGroupType::Reentrant))
143  , _ignoredNodeNames({node->get_fully_qualified_name()})
144  , _unresponsiveNodePolicy(unresponsiveNodePolicy) {}
145 
146 ParameterList ParameterInterface::getParams(const std::vector<std::string>& paramNames,
147  const std::chrono::duration<double>& timeout) {
148  std::lock_guard<std::mutex> lock(_mutex);
149 
150  std::unordered_map<std::string, std::vector<std::string>> paramNamesByNodeName;
151  const auto thisNode = _node->get_fully_qualified_name();
152 
153  if (!paramNames.empty()) {
154  // Break apart fully qualified {node_name}.{param_name} strings and build a
155  // mape of node names to the list of parameters for each node
156  for (const auto& fullParamName : paramNames) {
157  const auto& [nodeName, paramName] = getNodeAndParamName(fullParamName);
158  paramNamesByNodeName[nodeName].push_back(paramName);
159  }
160 
161  RCLCPP_DEBUG(_node->get_logger(), "Getting %zu parameters from %zu nodes...", paramNames.size(),
162  paramNamesByNodeName.size());
163  } else {
164  // Make a map of node names to empty parameter lists
165  // Only consider nodes that offer services to list & get parameters.
166  for (const auto& fqnNodeName : _node->get_node_names()) {
167  if (_ignoredNodeNames.find(fqnNodeName) != _ignoredNodeNames.end()) {
168  continue;
169  }
170  const auto [nodeNs, nodeName] = getNodeAndNodeNamespace(fqnNodeName);
171  const auto serviceNamesAndTypes =
172  _node->get_service_names_and_types_by_node(nodeName, nodeNs);
173 
174  bool listParamsSrvFound = false, getParamsSrvFound = false;
175  for (const auto& [serviceName, serviceTypes] : serviceNamesAndTypes) {
176  constexpr char GET_PARAMS_SERVICE_TYPE[] = "rcl_interfaces/srv/GetParameters";
177  constexpr char LIST_PARAMS_SERVICE_TYPE[] = "rcl_interfaces/srv/ListParameters";
178 
179  if (!getParamsSrvFound) {
180  getParamsSrvFound = std::find(serviceTypes.begin(), serviceTypes.end(),
181  GET_PARAMS_SERVICE_TYPE) != serviceTypes.end();
182  }
183  if (!listParamsSrvFound) {
184  listParamsSrvFound = std::find(serviceTypes.begin(), serviceTypes.end(),
185  LIST_PARAMS_SERVICE_TYPE) != serviceTypes.end();
186  }
187  }
188 
189  if (listParamsSrvFound && getParamsSrvFound) {
190  paramNamesByNodeName.insert({fqnNodeName, {}});
191  }
192  }
193 
194  if (!paramNamesByNodeName.empty()) {
195  RCLCPP_DEBUG(_node->get_logger(), "Getting all parameters from %zu nodes...",
196  paramNamesByNodeName.size());
197  }
198  }
199 
200  std::unordered_map<std::string, std::future<ParameterList>> getParametersFuture;
201  for (const auto& [nodeName, nodeParamNames] : paramNamesByNodeName) {
202  if (nodeName == thisNode) {
203  continue;
204  }
205 
206  auto paramClientIt = _paramClientsByNode.find(nodeName);
207  if (paramClientIt == _paramClientsByNode.end()) {
208  const auto insertedPair = _paramClientsByNode.emplace(
209  nodeName,
210  rclcpp::AsyncParametersClient::make_shared(_node, nodeName, parameterQoS, _callbackGroup));
211  paramClientIt = insertedPair.first;
212  }
213 
214  getParametersFuture.emplace(
215  nodeName, std::async(std::launch::async, &ParameterInterface::getNodeParameters, this,
216  paramClientIt->second, nodeName, nodeParamNames, timeout));
217  }
218 
219  ParameterList result;
220  for (auto& [nodeName, future] : getParametersFuture) {
221  try {
222  const auto params = future.get();
223  result.insert(result.begin(), params.begin(), params.end());
224  } catch (const std::exception& e) {
225  RCLCPP_ERROR(_node->get_logger(), "Failed to retrieve parameters from node '%s': %s",
226  nodeName.c_str(), e.what());
227 
229  // Certain nodes may fail to handle incoming service requests — for example, if they're
230  // stuck in a busy loop or otherwise unresponsive. In such cases, attempting to retrieve
231  // parameter names or values can result in timeouts. To avoid repeated failures, these nodes
232  // are added to an ignore list, and future parameter-related service calls to them will be
233  // skipped.
234  _ignoredNodeNames.insert(nodeName);
235  RCLCPP_WARN(_node->get_logger(),
236  "Adding node %s to the ignore list to prevent repeated timeouts or failures in "
237  "future parameter requests.",
238  nodeName.c_str());
239  }
240  }
241  }
242 
243  return result;
244 }
245 
247  const std::chrono::duration<double>& timeout) {
248  std::lock_guard<std::mutex> lock(_mutex);
249 
250  rclcpp::ParameterMap paramsByNode;
251  for (const auto& param : parameters) {
252  if (!isWhitelisted(param.getName(), _paramWhitelistPatterns)) {
253  return;
254  }
255 
256  const auto rosParam = toRosParam(param);
257  const auto& [nodeName, paramName] = getNodeAndParamName(rosParam.get_name());
258  paramsByNode[nodeName].emplace_back(paramName, rosParam.get_parameter_value());
259  }
260 
261  std::vector<std::future<void>> setParametersFuture;
262  for (const auto& [nodeName, params] : paramsByNode) {
263  auto paramClientIt = _paramClientsByNode.find(nodeName);
264  if (paramClientIt == _paramClientsByNode.end()) {
265  const auto insertedPair = _paramClientsByNode.emplace(
266  nodeName,
267  rclcpp::AsyncParametersClient::make_shared(_node, nodeName, parameterQoS, _callbackGroup));
268  paramClientIt = insertedPair.first;
269  }
270 
271  setParametersFuture.emplace_back(std::async(std::launch::async,
273  paramClientIt->second, nodeName, params, timeout));
274  }
275 
276  for (auto& future : setParametersFuture) {
277  try {
278  future.get();
279  } catch (const std::exception& e) {
280  RCLCPP_ERROR(_node->get_logger(), "Exception when setting parameters: %s", e.what());
281  }
282  }
283 }
284 
285 void ParameterInterface::subscribeParams(const std::vector<std::string>& paramNames) {
286  std::lock_guard<std::mutex> lock(_mutex);
287 
288  std::unordered_set<std::string> nodesToSubscribe;
289  for (const auto& paramName : paramNames) {
290  if (!isWhitelisted(paramName, _paramWhitelistPatterns)) {
291  return;
292  }
293 
294  const auto& [nodeName, paramN] = getNodeAndParamName(paramName);
295  auto [subscribedParamsit, wasNewlyCreated] = _subscribedParamsByNode.try_emplace(nodeName);
296 
297  auto& subscribedNodeParams = subscribedParamsit->second;
298  subscribedNodeParams.insert(paramN);
299 
300  if (wasNewlyCreated) {
301  nodesToSubscribe.insert(nodeName);
302  }
303  }
304 
305  for (const auto& nodeName : nodesToSubscribe) {
306  auto paramClientIt = _paramClientsByNode.find(nodeName);
307  if (paramClientIt == _paramClientsByNode.end()) {
308  const auto insertedPair = _paramClientsByNode.emplace(
309  nodeName,
310  rclcpp::AsyncParametersClient::make_shared(_node, nodeName, parameterQoS, _callbackGroup));
311  paramClientIt = insertedPair.first;
312  }
313 
314  auto& paramClient = paramClientIt->second;
315 
316  _paramSubscriptionsByNode[nodeName] = paramClient->on_parameter_event(
317  [this, nodeName](rcl_interfaces::msg::ParameterEvent::ConstSharedPtr msg) {
318  RCLCPP_DEBUG(_node->get_logger(), "Retrieved param update for node %s: %zu params changed",
319  nodeName.c_str(), msg->changed_parameters.size());
320 
321  ParameterList result;
322  const auto& subscribedNodeParams = _subscribedParamsByNode[nodeName];
323  for (const auto& param : msg->changed_parameters) {
324  if (subscribedNodeParams.find(param.name) != subscribedNodeParams.end()) {
325  result.push_back(fromRosParam(
326  rclcpp::Parameter(prependNodeNameToParamName(param.name, nodeName), param.value)));
327  }
328  }
329 
330  if (!result.empty() && _paramUpdateFunc) {
331  _paramUpdateFunc(result);
332  }
333  });
334  }
335 }
336 
337 void ParameterInterface::unsubscribeParams(const std::vector<std::string>& paramNames) {
338  std::lock_guard<std::mutex> lock(_mutex);
339 
340  for (const auto& paramName : paramNames) {
341  const auto& [nodeName, paramN] = getNodeAndParamName(paramName);
342 
343  const auto subscribedNodeParamsIt = _subscribedParamsByNode.find(nodeName);
344  if (subscribedNodeParamsIt != _subscribedParamsByNode.end()) {
345  subscribedNodeParamsIt->second.erase(subscribedNodeParamsIt->second.find(paramN));
346 
347  if (subscribedNodeParamsIt->second.empty()) {
348  _subscribedParamsByNode.erase(subscribedNodeParamsIt);
350  }
351  }
352  }
353 }
354 
356  std::lock_guard<std::mutex> lock(_mutex);
357  _paramUpdateFunc = paramUpdateFunc;
358 }
359 
361  const rclcpp::AsyncParametersClient::SharedPtr paramClient, const std::string& nodeName,
362  const std::vector<std::string>& paramNames, const std::chrono::duration<double>& timeout) {
363  if (!paramClient->service_is_ready()) {
364  throw std::runtime_error("Parameter service for node '" + nodeName + "' is not ready");
365  }
366 
367  auto paramsToRequest = paramNames;
368  if (paramsToRequest.empty()) {
369  // `paramNames` is empty, list all parameter names for this node
370  auto future = paramClient->list_parameters({}, 0UL);
371  if (std::future_status::ready != future.wait_for(timeout)) {
372  throw std::runtime_error("Failed to retrieve parameter names for node '" + nodeName + "'");
373  }
374  paramsToRequest = future.get().names;
375  }
376 
377  // Start parameter fetches and wait for them to complete
378  auto getParamsFuture = paramClient->get_parameters(paramsToRequest);
379  if (std::future_status::ready != getParamsFuture.wait_for(timeout)) {
380  throw std::runtime_error("Timed out waiting for " + std::to_string(paramsToRequest.size()) +
381  " parameter(s) from node '" + nodeName + "'");
382  }
383  const auto params = getParamsFuture.get();
384 
385  ParameterList result;
386  for (const auto& param : params) {
387  const auto fullParamName = prependNodeNameToParamName(param.get_name(), nodeName);
388  if (isWhitelisted(fullParamName, _paramWhitelistPatterns)) {
389  result.push_back(fromRosParam(rclcpp::Parameter(fullParamName, param.get_parameter_value())));
390  }
391  }
392  return result;
393 }
394 
395 void ParameterInterface::setNodeParameters(rclcpp::AsyncParametersClient::SharedPtr paramClient,
396  const std::string& nodeName,
397  const std::vector<rclcpp::Parameter>& params,
398  const std::chrono::duration<double>& timeout) {
399  if (!paramClient->service_is_ready()) {
400  throw std::runtime_error("Parameter service for node '" + nodeName + "' is not ready");
401  }
402 
403  auto future = paramClient->set_parameters(params);
404 
405  std::vector<std::string> paramsToDelete;
406  for (const auto& p : params) {
407  if (p.get_type() == rclcpp::ParameterType::PARAMETER_NOT_SET) {
408  paramsToDelete.push_back(p.get_name());
409  }
410  }
411 
412  if (!paramsToDelete.empty()) {
413  auto deleteFuture = paramClient->delete_parameters(paramsToDelete);
414  if (std::future_status::ready != deleteFuture.wait_for(timeout)) {
415  RCLCPP_WARN(
416  _node->get_logger(),
417  "Param client failed to delete %zu parameter(s) for node '%s' within the given timeout",
418  paramsToDelete.size(), nodeName.c_str());
419  }
420  }
421 
422  if (std::future_status::ready != future.wait_for(timeout)) {
423  throw std::runtime_error("Param client failed to set " + std::to_string(params.size()) +
424  " parameter(s) for node '" + nodeName + "' within the given timeout");
425  }
426 
427  const auto setParamResults = future.get();
428  for (auto& result : setParamResults) {
429  if (!result.successful) {
430  RCLCPP_WARN(_node->get_logger(), "Failed to set one or more parameters for node '%s': %s",
431  nodeName.c_str(), result.reason.c_str());
432  }
433  }
434 }
435 
436 } // namespace foxglove_bridge
foxglove::isWhitelisted
bool isWhitelisted(const std::string &name, const std::vector< std::regex > &regexPatterns)
Definition: regex_utils.hpp:10
foxglove_bridge::ParameterInterface::_subscribedParamsByNode
std::unordered_map< std::string, std::unordered_set< std::string > > _subscribedParamsByNode
Definition: parameter_interface.hpp:43
utils.hpp
foxglove_bridge::ParameterInterface::setParams
void setParams(const ParameterList &params, const std::chrono::duration< double > &timeout)
Definition: parameter_interface.cpp:246
foxglove_bridge::ParameterInterface::setNodeParameters
void setNodeParameters(rclcpp::AsyncParametersClient::SharedPtr paramClient, const std::string &nodeName, const std::vector< rclcpp::Parameter > &params, const std::chrono::duration< double > &timeout)
Definition: parameter_interface.cpp:395
foxglove_bridge::ParameterInterface::getNodeParameters
ParameterList getNodeParameters(rclcpp::AsyncParametersClient::SharedPtr paramClient, const std::string &nodeName, const std::vector< std::string > &paramNames, const std::chrono::duration< double > &timeout)
Definition: parameter_interface.cpp:360
foxglove_bridge::ParameterInterface::_unresponsiveNodePolicy
UnresponsiveNodePolicy _unresponsiveNodePolicy
Definition: parameter_interface.hpp:46
foxglove_bridge::ParameterInterface::getParams
ParameterList getParams(const std::vector< std::string > &paramNames, const std::chrono::duration< double > &timeout)
Definition: parameter_interface.cpp:146
foxglove::ParameterType
ParameterType
Definition: parameter.hpp:16
regex_utils.hpp
foxglove_bridge::ParameterInterface::subscribeParams
void subscribeParams(const std::vector< std::string > &paramNames)
Definition: parameter_interface.cpp:285
foxglove_bridge::ParameterInterface::_paramClientsByNode
std::unordered_map< std::string, rclcpp::AsyncParametersClient::SharedPtr > _paramClientsByNode
Definition: parameter_interface.hpp:42
foxglove::Parameter::getType
ParameterType getType() const
Definition: parameter.hpp:64
foxglove_bridge::ParamUpdateFunc
std::function< void(const ParameterList &)> ParamUpdateFunc
Definition: parameter_interface.hpp:18
foxglove_bridge::ParameterInterface::setParamUpdateCallback
void setParamUpdateCallback(ParamUpdateFunc paramUpdateFunc)
Definition: parameter_interface.cpp:355
foxglove::ParameterValue
Definition: parameter.hpp:27
parameter_interface.hpp
foxglove::Parameter::getName
const std::string & getName() const
Definition: parameter.hpp:60
foxglove_bridge::ParameterInterface::_paramUpdateFunc
ParamUpdateFunc _paramUpdateFunc
Definition: parameter_interface.hpp:47
foxglove_bridge::ParameterInterface::unsubscribeParams
void unsubscribeParams(const std::vector< std::string > &paramNames)
Definition: parameter_interface.cpp:337
foxglove::Parameter
Definition: parameter.hpp:54
foxglove_bridge
Definition: generic_service.hpp:9
foxglove_bridge::toRosParam
XmlRpc::XmlRpcValue toRosParam(const foxglove::ParameterValue &param)
Definition: ros1_foxglove_bridge/src/param_utils.cpp:43
foxglove_bridge::ParameterInterface::_node
rclcpp::Node * _node
Definition: parameter_interface.hpp:38
foxglove_bridge::UnresponsiveNodePolicy
UnresponsiveNodePolicy
Definition: parameter_interface.hpp:20
foxglove_bridge::UnresponsiveNodePolicy::Ignore
@ Ignore
foxglove_bridge::ParameterInterface::_ignoredNodeNames
std::unordered_set< std::string > _ignoredNodeNames
Definition: parameter_interface.hpp:45
foxglove_bridge::ParameterList
std::vector< foxglove::Parameter > ParameterList
Definition: parameter_interface.hpp:17
foxglove_bridge::ParameterInterface::_paramWhitelistPatterns
std::vector< std::regex > _paramWhitelistPatterns
Definition: parameter_interface.hpp:39
foxglove_bridge::fromRosParam
foxglove::Parameter fromRosParam(const std::string &name, const XmlRpc::XmlRpcValue &value)
Definition: ros1_foxglove_bridge/src/param_utils.cpp:39
foxglove_bridge::ParameterInterface::_callbackGroup
rclcpp::CallbackGroup::SharedPtr _callbackGroup
Definition: parameter_interface.hpp:40
foxglove::Parameter::getValue
const ParameterValue & getValue() const
Definition: parameter.hpp:68
param
T param(const std::string &param_name, const T &default_val)
foxglove_bridge::ParameterInterface::_paramSubscriptionsByNode
std::unordered_map< std::string, rclcpp::SubscriptionBase::SharedPtr > _paramSubscriptionsByNode
Definition: parameter_interface.hpp:44
foxglove_bridge::ParameterInterface::_mutex
std::mutex _mutex
Definition: parameter_interface.hpp:41
foxglove_bridge::getNodeAndNodeNamespace
std::pair< std::string, std::string > getNodeAndNodeNamespace(const std::string &fqnNodeName)
Definition: utils.hpp:12
foxglove_bridge::ParameterInterface::ParameterInterface
ParameterInterface(rclcpp::Node *node, std::vector< std::regex > paramWhitelistPatterns, UnresponsiveNodePolicy unresponsiveNodePolicy)
Definition: parameter_interface.cpp:137


foxglove_bridge
Author(s): Foxglove
autogenerated on Tue May 20 2025 02:34:26