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


foxglove_bridge
Author(s): Foxglove
autogenerated on Mon Jul 3 2023 02:12:22