ros2_foxglove_bridge.cpp
Go to the documentation of this file.
1 #include <unordered_set>
2 
3 #include <resource_retriever/retriever.hpp>
4 
6 
7 namespace foxglove_bridge {
8 namespace {
9 inline bool isHiddenTopicOrService(const std::string& name) {
10  if (name.empty()) {
11  throw std::invalid_argument("Topic or service name can't be empty");
12  }
13  return name.front() == '_' || name.find("/_") != std::string::npos;
14 }
15 } // namespace
16 
17 using namespace std::chrono_literals;
18 using namespace std::placeholders;
20 
21 FoxgloveBridge::FoxgloveBridge(const rclcpp::NodeOptions& options)
22  : Node("foxglove_bridge", options) {
23  const char* rosDistro = std::getenv("ROS_DISTRO");
24  RCLCPP_INFO(this->get_logger(), "Starting foxglove_bridge (%s, %s@%s) with %s", rosDistro,
27 
28  declareParameters(this);
29 
30  const auto port = static_cast<uint16_t>(this->get_parameter(PARAM_PORT).as_int());
31  const auto address = this->get_parameter(PARAM_ADDRESS).as_string();
32  const auto send_buffer_limit =
33  static_cast<size_t>(this->get_parameter(PARAM_SEND_BUFFER_LIMIT).as_int());
34  const auto useTLS = this->get_parameter(PARAM_USETLS).as_bool();
35  const auto certfile = this->get_parameter(PARAM_CERTFILE).as_string();
36  const auto keyfile = this->get_parameter(PARAM_KEYFILE).as_string();
37  _minQosDepth = static_cast<size_t>(this->get_parameter(PARAM_MIN_QOS_DEPTH).as_int());
38  _maxQosDepth = static_cast<size_t>(this->get_parameter(PARAM_MAX_QOS_DEPTH).as_int());
39  const auto bestEffortQosTopicWhiteList =
40  this->get_parameter(PARAM_BEST_EFFORT_QOS_TOPIC_WHITELIST).as_string_array();
41  _bestEffortQosTopicWhiteListPatterns = parseRegexStrings(this, bestEffortQosTopicWhiteList);
42  const auto topicWhiteList = this->get_parameter(PARAM_TOPIC_WHITELIST).as_string_array();
43  _topicWhitelistPatterns = parseRegexStrings(this, topicWhiteList);
44  const auto serviceWhiteList = this->get_parameter(PARAM_SERVICE_WHITELIST).as_string_array();
45  _serviceWhitelistPatterns = parseRegexStrings(this, serviceWhiteList);
46  const auto paramWhiteList = this->get_parameter(PARAM_PARAMETER_WHITELIST).as_string_array();
47  const auto paramWhitelistPatterns = parseRegexStrings(this, paramWhiteList);
48  const auto useCompression = this->get_parameter(PARAM_USE_COMPRESSION).as_bool();
49  _useSimTime = this->get_parameter("use_sim_time").as_bool();
50  _capabilities = this->get_parameter(PARAM_CAPABILITIES).as_string_array();
51  const auto clientTopicWhiteList =
52  this->get_parameter(PARAM_CLIENT_TOPIC_WHITELIST).as_string_array();
53  const auto clientTopicWhiteListPatterns = parseRegexStrings(this, clientTopicWhiteList);
54  _includeHidden = this->get_parameter(PARAM_INCLUDE_HIDDEN).as_bool();
55  const auto assetUriAllowlist = this->get_parameter(PARAM_ASSET_URI_ALLOWLIST).as_string_array();
56  _assetUriAllowlistPatterns = parseRegexStrings(this, assetUriAllowlist);
57  _disableLoanMessage = this->get_parameter(PARAM_DISABLE_LOAN_MESSAGE).as_bool();
58 
59  const auto logHandler = std::bind(&FoxgloveBridge::logHandler, this, _1, _2);
60  // Fetching of assets may be blocking, hence we fetch them in a separate thread.
61  _fetchAssetQueue = std::make_unique<foxglove::CallbackQueue>(logHandler, 1 /* num_threads */);
62 
63  foxglove::ServerOptions serverOptions;
64  serverOptions.capabilities = _capabilities;
65  if (_useSimTime) {
66  serverOptions.capabilities.push_back(foxglove::CAPABILITY_TIME);
67  }
68  serverOptions.supportedEncodings = {"cdr", "json"};
69  serverOptions.metadata = {{"ROS_DISTRO", rosDistro}};
70  serverOptions.sendBufferLimitBytes = send_buffer_limit;
71  serverOptions.sessionId = std::to_string(std::time(nullptr));
72  serverOptions.useCompression = useCompression;
73  serverOptions.useTls = useTLS;
74  serverOptions.certfile = certfile;
75  serverOptions.keyfile = keyfile;
76  serverOptions.clientTopicWhitelistPatterns = clientTopicWhiteListPatterns;
77 
78  _server = foxglove::ServerFactory::createServer<ConnectionHandle>("foxglove_bridge", logHandler,
79  serverOptions);
80 
82  hdlrs.subscribeHandler = std::bind(&FoxgloveBridge::subscribe, this, _1, _2);
83  hdlrs.unsubscribeHandler = std::bind(&FoxgloveBridge::unsubscribe, this, _1, _2);
84  hdlrs.clientAdvertiseHandler = std::bind(&FoxgloveBridge::clientAdvertise, this, _1, _2);
85  hdlrs.clientUnadvertiseHandler = std::bind(&FoxgloveBridge::clientUnadvertise, this, _1, _2);
86  hdlrs.clientMessageHandler = std::bind(&FoxgloveBridge::clientMessage, this, _1, _2);
87  hdlrs.serviceRequestHandler = std::bind(&FoxgloveBridge::serviceRequest, this, _1, _2);
89  std::bind(&FoxgloveBridge::subscribeConnectionGraph, this, _1);
90 
93  hdlrs.parameterRequestHandler = std::bind(&FoxgloveBridge::getParameters, this, _1, _2, _3);
94  hdlrs.parameterChangeHandler = std::bind(&FoxgloveBridge::setParameters, this, _1, _2, _3);
96  std::bind(&FoxgloveBridge::subscribeParameters, this, _1, _2, _3);
97 
98  _paramInterface = std::make_shared<ParameterInterface>(this, paramWhitelistPatterns);
99  _paramInterface->setParamUpdateCallback(std::bind(&FoxgloveBridge::parameterUpdates, this, _1));
100  }
101 
103  hdlrs.fetchAssetHandler = [this](const std::string& uri, uint32_t requestId,
104  ConnectionHandle hdl) {
105  _fetchAssetQueue->addCallback(
106  std::bind(&FoxgloveBridge::fetchAsset, this, uri, requestId, hdl));
107  };
108  }
109 
110  _server->setHandlers(std::move(hdlrs));
111  _server->start(address, port);
112 
113  // Get the actual port we bound to
114  uint16_t listeningPort = _server->getPort();
115  if (port != listeningPort) {
116  RCLCPP_DEBUG(this->get_logger(), "Reassigning \"port\" parameter from %d to %d", port,
117  listeningPort);
118  this->set_parameter(rclcpp::Parameter{PARAM_PORT, listeningPort});
119  }
120 
121  // Start the thread polling for rosgraph changes
123  std::make_unique<std::thread>(std::bind(&FoxgloveBridge::rosgraphPollThread, this));
124 
125  _subscriptionCallbackGroup = this->create_callback_group(rclcpp::CallbackGroupType::Reentrant);
127  this->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive);
128  _servicesCallbackGroup = this->create_callback_group(rclcpp::CallbackGroupType::Reentrant);
129 
130  if (_useSimTime) {
131  _clockSubscription = this->create_subscription<rosgraph_msgs::msg::Clock>(
132  "/clock", rclcpp::QoS{rclcpp::KeepLast(1)}.best_effort(),
133  [&](std::shared_ptr<const rosgraph_msgs::msg::Clock> msg) {
134  const auto timestamp = rclcpp::Time{msg->clock}.nanoseconds();
135  assert(timestamp >= 0 && "Timestamp is negative");
136  _server->broadcastTime(static_cast<uint64_t>(timestamp));
137  });
138  }
139 }
140 
142  _shuttingDown = true;
143  RCLCPP_INFO(this->get_logger(), "Shutting down %s", this->get_name());
144  if (_rosgraphPollThread) {
145  _rosgraphPollThread->join();
146  }
147  _server->stop();
148  RCLCPP_INFO(this->get_logger(), "Shutdown complete");
149 }
150 
152  updateAdvertisedTopics(get_topic_names_and_types());
154 
155  auto graphEvent = this->get_graph_event();
156  while (!_shuttingDown && rclcpp::ok()) {
157  try {
158  this->wait_for_graph_change(graphEvent, 200ms);
159  bool triggered = graphEvent->check_and_clear();
160  if (triggered) {
161  RCLCPP_DEBUG(this->get_logger(), "rosgraph change detected");
162  const auto topicNamesAndTypes = get_topic_names_and_types();
163  updateAdvertisedTopics(topicNamesAndTypes);
166  updateConnectionGraph(topicNamesAndTypes);
167  }
168  // Graph changes tend to come in batches, so wait a bit before checking again
169  std::this_thread::sleep_for(500ms);
170  }
171  } catch (const std::exception& ex) {
172  RCLCPP_ERROR(this->get_logger(), "Exception thrown in rosgraphPollThread: %s", ex.what());
173  }
174  }
175 
176  RCLCPP_DEBUG(this->get_logger(), "rosgraph polling thread exiting");
177 }
178 
180  const std::map<std::string, std::vector<std::string>>& topicNamesAndTypes) {
181  if (!rclcpp::ok()) {
182  return;
183  }
184 
185  std::unordered_set<TopicAndDatatype, PairHash> latestTopics;
186  latestTopics.reserve(topicNamesAndTypes.size());
187  for (const auto& topicNamesAndType : topicNamesAndTypes) {
188  const auto& topicName = topicNamesAndType.first;
189  const auto& datatypes = topicNamesAndType.second;
190 
191  // Ignore hidden topics if not explicitly included
192  if (!_includeHidden && isHiddenTopicOrService(topicName)) {
193  continue;
194  }
195 
196  // Ignore the topic if it is not on the topic whitelist
197  if (isWhitelisted(topicName, _topicWhitelistPatterns)) {
198  for (const auto& datatype : datatypes) {
199  latestTopics.emplace(topicName, datatype);
200  }
201  }
202  }
203 
204  if (const auto numIgnoredTopics = topicNamesAndTypes.size() - latestTopics.size()) {
205  RCLCPP_DEBUG(
206  this->get_logger(),
207  "%zu topics have been ignored as they do not match any pattern on the topic whitelist",
208  numIgnoredTopics);
209  }
210 
211  std::lock_guard<std::mutex> lock(_subscriptionsMutex);
212 
213  // Remove channels for which the topic does not exist anymore
214  std::vector<foxglove::ChannelId> channelIdsToRemove;
215  for (auto channelIt = _advertisedTopics.begin(); channelIt != _advertisedTopics.end();) {
216  const TopicAndDatatype topicAndDatatype = {channelIt->second.topic,
217  channelIt->second.schemaName};
218  if (latestTopics.find(topicAndDatatype) == latestTopics.end()) {
219  const auto channelId = channelIt->first;
220  channelIdsToRemove.push_back(channelId);
221  _subscriptions.erase(channelId);
222  RCLCPP_INFO(this->get_logger(), "Removed channel %d for topic \"%s\" (%s)", channelId,
223  topicAndDatatype.first.c_str(), topicAndDatatype.second.c_str());
224  channelIt = _advertisedTopics.erase(channelIt);
225  } else {
226  channelIt++;
227  }
228  }
229  _server->removeChannels(channelIdsToRemove);
230 
231  // Add new channels for new topics
232  std::vector<foxglove::ChannelWithoutId> channelsToAdd;
233  for (const auto& topicAndDatatype : latestTopics) {
234  if (std::find_if(_advertisedTopics.begin(), _advertisedTopics.end(),
235  [topicAndDatatype](const auto& channelIdAndChannel) {
236  const auto& channel = channelIdAndChannel.second;
237  return channel.topic == topicAndDatatype.first &&
238  channel.schemaName == topicAndDatatype.second;
239  }) != _advertisedTopics.end()) {
240  continue; // Topic already advertised
241  }
242 
243  foxglove::ChannelWithoutId newChannel{};
244  newChannel.topic = topicAndDatatype.first;
245  newChannel.schemaName = topicAndDatatype.second;
246 
247  try {
248  auto [format, schema] = _messageDefinitionCache.get_full_text(topicAndDatatype.second);
249  switch (format) {
251  newChannel.encoding = "cdr";
252  newChannel.schema = schema;
253  newChannel.schemaEncoding = "ros2msg";
254  break;
256  newChannel.encoding = "cdr";
257  newChannel.schema = schema;
258  newChannel.schemaEncoding = "ros2idl";
259  break;
260  }
261 
262  } catch (const foxglove::DefinitionNotFoundError& err) {
263  RCLCPP_WARN(this->get_logger(), "Could not find definition for type %s: %s",
264  topicAndDatatype.second.c_str(), err.what());
265  // We still advertise the channel, but with an emtpy schema
266  newChannel.schema = "";
267  } catch (const std::exception& err) {
268  RCLCPP_WARN(this->get_logger(), "Failed to add channel for topic \"%s\" (%s): %s",
269  topicAndDatatype.first.c_str(), topicAndDatatype.second.c_str(), err.what());
270  continue;
271  }
272  channelsToAdd.push_back(newChannel);
273  }
274 
275  const auto channelIds = _server->addChannels(channelsToAdd);
276  for (size_t i = 0; i < channelsToAdd.size(); ++i) {
277  const auto channelId = channelIds[i];
278  const auto& channel = channelsToAdd[i];
279  _advertisedTopics.emplace(channelId, channel);
280  RCLCPP_DEBUG(this->get_logger(), "Advertising channel %d for topic \"%s\" (%s)", channelId,
281  channel.topic.c_str(), channel.schemaName.c_str());
282  }
283 }
284 
286  if (!rclcpp::ok()) {
287  return;
289  return;
290  }
291 
292  // Get the current list of visible services and datatypes from the ROS graph
293  const auto serviceNamesAndTypes = this->get_node_graph_interface()->get_service_names_and_types();
294 
295  std::lock_guard<std::mutex> lock(_servicesMutex);
296 
297  // Remove advertisements for services that have been removed
298  std::vector<foxglove::ServiceId> servicesToRemove;
299  for (const auto& service : _advertisedServices) {
300  const auto it = std::find_if(serviceNamesAndTypes.begin(), serviceNamesAndTypes.end(),
301  [service](const auto& serviceNameAndTypes) {
302  return serviceNameAndTypes.first == service.second.name;
303  });
304  if (it == serviceNamesAndTypes.end()) {
305  servicesToRemove.push_back(service.first);
306  }
307  }
308  for (auto serviceId : servicesToRemove) {
309  _advertisedServices.erase(serviceId);
310  }
311  _server->removeServices(servicesToRemove);
312 
313  // Advertise new services
314  std::vector<foxglove::ServiceWithoutId> newServices;
315  for (const auto& serviceNamesAndType : serviceNamesAndTypes) {
316  const auto& serviceName = serviceNamesAndType.first;
317  const auto& datatypes = serviceNamesAndType.second;
318 
319  // Ignore the service if it's already advertised
320  if (std::find_if(_advertisedServices.begin(), _advertisedServices.end(),
321  [serviceName](const auto& idWithService) {
322  return idWithService.second.name == serviceName;
323  }) != _advertisedServices.end()) {
324  continue;
325  }
326 
327  // Ignore hidden services if not explicitly included
328  if (!_includeHidden && isHiddenTopicOrService(serviceName)) {
329  continue;
330  }
331 
332  // Ignore the service if it is not on the service whitelist
333  if (!isWhitelisted(serviceName, _serviceWhitelistPatterns)) {
334  continue;
335  }
336 
338  service.name = serviceName;
339  service.type = datatypes.front();
340 
341  try {
342  const auto requestTypeName = service.type + foxglove::SERVICE_REQUEST_MESSAGE_SUFFIX;
343  const auto responseTypeName = service.type + foxglove::SERVICE_RESPONSE_MESSAGE_SUFFIX;
344  const auto [format, reqSchema] = _messageDefinitionCache.get_full_text(requestTypeName);
345  const auto resSchema = _messageDefinitionCache.get_full_text(responseTypeName).second;
346  switch (format) {
348  service.requestSchema = reqSchema;
349  service.responseSchema = resSchema;
350  break;
352  RCLCPP_WARN(this->get_logger(),
353  "IDL message definition format cannot be communicated over ws-protocol. "
354  "Service \"%s\" (%s) may not decode correctly in clients",
355  service.name.c_str(), service.type.c_str());
356  service.requestSchema = reqSchema;
357  service.responseSchema = resSchema;
358  break;
359  }
360  } catch (const foxglove::DefinitionNotFoundError& err) {
361  RCLCPP_WARN(this->get_logger(), "Could not find definition for type %s: %s",
362  service.type.c_str(), err.what());
363  // We still advertise the service, but with an emtpy schema
364  service.requestSchema = "";
365  service.responseSchema = "";
366  } catch (const std::exception& err) {
367  RCLCPP_WARN(this->get_logger(), "Failed to add service \"%s\" (%s): %s", service.name.c_str(),
368  service.type.c_str(), err.what());
369  continue;
370  }
371 
372  newServices.push_back(service);
373  }
374 
375  const auto serviceIds = _server->addServices(newServices);
376  for (size_t i = 0; i < serviceIds.size(); ++i) {
377  _advertisedServices.emplace(serviceIds[i], newServices[i]);
378  }
379 }
380 
382  const std::map<std::string, std::vector<std::string>>& topicNamesAndTypes) {
383  foxglove::MapOfSets publishers, subscribers;
384 
385  for (const auto& topicNameAndType : topicNamesAndTypes) {
386  const auto& topicName = topicNameAndType.first;
387  if (!isWhitelisted(topicName, _topicWhitelistPatterns)) {
388  continue;
389  }
390 
391  const auto publishersInfo = get_publishers_info_by_topic(topicName);
392  const auto subscribersInfo = get_subscriptions_info_by_topic(topicName);
393  std::unordered_set<std::string> publisherIds, subscriberIds;
394  for (const auto& publisher : publishersInfo) {
395  const auto& ns = publisher.node_namespace();
396  const auto sep = (!ns.empty() && ns.back() == '/') ? "" : "/";
397  publisherIds.insert(ns + sep + publisher.node_name());
398  }
399  for (const auto& subscriber : subscribersInfo) {
400  const auto& ns = subscriber.node_namespace();
401  const auto sep = (!ns.empty() && ns.back() == '/') ? "" : "/";
402  subscriberIds.insert(ns + sep + subscriber.node_name());
403  }
404  publishers.emplace(topicName, publisherIds);
405  subscribers.emplace(topicName, subscriberIds);
406  }
407 
408  foxglove::MapOfSets services;
409  for (const auto& fqnNodeName : get_node_names()) {
410  const auto [nodeNs, nodeName] = getNodeAndNodeNamespace(fqnNodeName);
411  const auto serviceNamesAndTypes = get_service_names_and_types_by_node(nodeName, nodeNs);
412 
413  for (const auto& [serviceName, serviceTypes] : serviceNamesAndTypes) {
414  (void)serviceTypes;
415  if (isWhitelisted(serviceName, _serviceWhitelistPatterns)) {
416  services[serviceName].insert(fqnNodeName);
417  }
418  }
419  }
420 
421  _server->updateConnectionGraph(publishers, subscribers, services);
422 }
423 
426  updateConnectionGraph(get_topic_names_and_types());
427  };
428 }
429 
430 void FoxgloveBridge::subscribe(foxglove::ChannelId channelId, ConnectionHandle clientHandle) {
431  std::lock_guard<std::mutex> lock(_subscriptionsMutex);
432  auto it = _advertisedTopics.find(channelId);
433  if (it == _advertisedTopics.end()) {
435  channelId, "Received subscribe request for unknown channel " + std::to_string(channelId));
436  }
437 
438  const auto& channel = it->second;
439  const auto& topic = channel.topic;
440  const auto& datatype = channel.schemaName;
441 
442  // Get client subscriptions for this channel or insert an empty map.
443  auto [subscriptionsIt, firstSubscription] =
444  _subscriptions.emplace(channelId, SubscriptionsByClient());
445  auto& subscriptionsByClient = subscriptionsIt->second;
446 
447  if (!firstSubscription &&
448  subscriptionsByClient.find(clientHandle) != subscriptionsByClient.end()) {
450  channelId, "Client is already subscribed to channel " + std::to_string(channelId));
451  }
452 
453  rclcpp::SubscriptionEventCallbacks eventCallbacks;
454  eventCallbacks.incompatible_qos_callback = [&](const rclcpp::QOSRequestedIncompatibleQoSInfo&) {
455  RCLCPP_ERROR(this->get_logger(), "Incompatible subscriber QoS settings for topic \"%s\" (%s)",
456  topic.c_str(), datatype.c_str());
457  };
458 
459  rclcpp::SubscriptionOptions subscriptionOptions;
460  subscriptionOptions.event_callbacks = eventCallbacks;
461  subscriptionOptions.callback_group = _subscriptionCallbackGroup;
462 
463  // Select an appropriate subscription QOS profile. This is similar to how ros2 topic echo
464  // does it:
465  // https://github.com/ros2/ros2cli/blob/619b3d1c9/ros2topic/ros2topic/verb/echo.py#L137-L194
466  size_t depth = 0;
467  size_t reliabilityReliableEndpointsCount = 0;
468  size_t durabilityTransientLocalEndpointsCount = 0;
469 
470  const auto publisherInfo = this->get_publishers_info_by_topic(topic);
471  for (const auto& publisher : publisherInfo) {
472  const auto& qos = publisher.qos_profile();
473  if (qos.reliability() == rclcpp::ReliabilityPolicy::Reliable) {
474  ++reliabilityReliableEndpointsCount;
475  }
476  if (qos.durability() == rclcpp::DurabilityPolicy::TransientLocal) {
477  ++durabilityTransientLocalEndpointsCount;
478  }
479  // Some RMWs do not retrieve history information of the publisher endpoint in which case the
480  // history depth is 0. We use a lower limit of 1 here, so that the history depth is at least
481  // equal to the number of publishers. This covers the case where there are multiple
482  // transient_local publishers with a depth of 1 (e.g. multiple tf_static transform
483  // broadcasters). See also
484  // https://github.com/foxglove/ros-foxglove-bridge/issues/238 and
485  // https://github.com/foxglove/ros-foxglove-bridge/issues/208
486  const size_t publisherHistoryDepth = std::max(static_cast<size_t>(1), qos.depth());
487  depth = depth + publisherHistoryDepth;
488  }
489 
490  depth = std::max(depth, _minQosDepth);
491  if (depth > _maxQosDepth) {
492  RCLCPP_WARN(this->get_logger(),
493  "Limiting history depth for topic '%s' to %zu (was %zu). You may want to increase "
494  "the max_qos_depth parameter value.",
495  topic.c_str(), _maxQosDepth, depth);
496  depth = _maxQosDepth;
497  }
498 
499  rclcpp::QoS qos{rclcpp::KeepLast(depth)};
500 
501  // Force the QoS to be "best_effort" if in the whitelist
503  qos.best_effort();
504  } else if (!publisherInfo.empty() && reliabilityReliableEndpointsCount == publisherInfo.size()) {
505  // If all endpoints are reliable, ask for reliable
506  qos.reliable();
507  } else {
508  if (reliabilityReliableEndpointsCount > 0) {
509  RCLCPP_WARN(
510  this->get_logger(),
511  "Some, but not all, publishers on topic '%s' are offering QoSReliabilityPolicy.RELIABLE. "
512  "Falling back to QoSReliabilityPolicy.BEST_EFFORT as it will connect to all publishers",
513  topic.c_str());
514  }
515  qos.best_effort();
516  }
517 
518  // If all endpoints are transient_local, ask for transient_local
519  if (!publisherInfo.empty() && durabilityTransientLocalEndpointsCount == publisherInfo.size()) {
520  qos.transient_local();
521  } else {
522  if (durabilityTransientLocalEndpointsCount > 0) {
523  RCLCPP_WARN(this->get_logger(),
524  "Some, but not all, publishers on topic '%s' are offering "
525  "QoSDurabilityPolicy.TRANSIENT_LOCAL. Falling back to "
526  "QoSDurabilityPolicy.VOLATILE as it will connect to all publishers",
527  topic.c_str());
528  }
529  qos.durability_volatile();
530  }
531 
532  if (firstSubscription) {
533  RCLCPP_INFO(
534  this->get_logger(), "Subscribing to topic \"%s\" (%s) on channel %d with reliablity \"%s\"",
535  topic.c_str(), datatype.c_str(), channelId,
536  qos.reliability() == rclcpp::ReliabilityPolicy::Reliable ? "reliable" : "best_effort");
537 
538  } else {
539  RCLCPP_INFO(this->get_logger(), "Adding subscriber #%zu to topic \"%s\" (%s) on channel %d",
540  subscriptionsByClient.size(), topic.c_str(), datatype.c_str(), channelId);
541  }
542 
543  try {
544  auto subscriber = this->create_generic_subscription(
545  topic, datatype, qos,
546  [this, channelId, clientHandle](std::shared_ptr<const rclcpp::SerializedMessage> msg) {
547  this->rosMessageHandler(channelId, clientHandle, msg);
548  },
549  subscriptionOptions);
550  subscriptionsByClient.emplace(clientHandle, std::move(subscriber));
551  } catch (const std::exception& ex) {
553  channelId, "Failed to subscribe to topic " + topic + " (" + datatype + "): " + ex.what());
554  }
555 }
556 
558  std::lock_guard<std::mutex> lock(_subscriptionsMutex);
559 
560  const auto channelIt = _advertisedTopics.find(channelId);
561  if (channelIt == _advertisedTopics.end()) {
563  channelId, "Received unsubscribe request for unknown channel " + std::to_string(channelId));
564  }
565  const auto& channel = channelIt->second;
566 
567  auto subscriptionsIt = _subscriptions.find(channelId);
568  if (subscriptionsIt == _subscriptions.end()) {
569  throw foxglove::ChannelError(channelId, "Received unsubscribe request for channel " +
570  std::to_string(channelId) +
571  " that was not subscribed to");
572  }
573 
574  auto& subscriptionsByClient = subscriptionsIt->second;
575  const auto clientSubscription = subscriptionsByClient.find(clientHandle);
576  if (clientSubscription == subscriptionsByClient.end()) {
578  channelId, "Received unsubscribe request for channel " + std::to_string(channelId) +
579  "from a client that was not subscribed to this channel");
580  }
581 
582  subscriptionsByClient.erase(clientSubscription);
583  if (subscriptionsByClient.empty()) {
584  RCLCPP_INFO(this->get_logger(), "Unsubscribing from topic \"%s\" (%s) on channel %d",
585  channel.topic.c_str(), channel.schemaName.c_str(), channelId);
586  _subscriptions.erase(subscriptionsIt);
587  } else {
588  RCLCPP_INFO(this->get_logger(),
589  "Removed one subscription from channel %d (%zu subscription(s) left)", channelId,
590  subscriptionsByClient.size());
591  }
592 }
593 
595  ConnectionHandle hdl) {
596  std::lock_guard<std::mutex> lock(_clientAdvertisementsMutex);
597 
598  // Get client publications or insert an empty map.
599  auto [clientPublicationsIt, isFirstPublication] =
601 
602  auto& clientPublications = clientPublicationsIt->second;
603 
604  if (!isFirstPublication &&
605  clientPublications.find(advertisement.channelId) != clientPublications.end()) {
607  advertisement.channelId,
608  "Received client advertisement from " + _server->remoteEndpointString(hdl) + " for channel " +
609  std::to_string(advertisement.channelId) + " it had already advertised");
610  }
611 
612  if (advertisement.schemaName.empty()) {
614  advertisement.channelId,
615  "Received client advertisement from " + _server->remoteEndpointString(hdl) + " for channel " +
616  std::to_string(advertisement.channelId) + " with empty schema name");
617  }
618 
619  if (advertisement.encoding == "json") {
620  // register the JSON parser for this schemaName
621  auto parserIt = _jsonParsers.find(advertisement.schemaName);
622  if (parserIt == _jsonParsers.end()) {
623  const auto& schemaName = advertisement.schemaName;
624  std::string schema = "";
625 
626  if (!advertisement.schema.empty()) {
627  // Schema is given by the advertisement
628  schema = std::string(reinterpret_cast<const char*>(advertisement.schema.data()),
629  advertisement.schema.size());
630  } else {
631  // Schema not given, look it up.
632  auto [format, msgDefinition] = _messageDefinitionCache.get_full_text(schemaName);
635  advertisement.channelId,
636  "Message definition (.msg) for schema " + schemaName + " not found.");
637  }
638 
639  schema = msgDefinition;
640  }
641 
642  auto parser = std::make_shared<RosMsgParser::Parser>(
643  advertisement.topic, RosMsgParser::ROSType(schemaName), schema);
644  _jsonParsers.insert({schemaName, parser});
645  }
646  }
647 
648  try {
649  // Create a new topic advertisement
650  const auto& topicName = advertisement.topic;
651  const auto& topicType = advertisement.schemaName;
652 
653  // Lookup if there are publishers from other nodes for that topic. If that's the case, we use
654  // a matching QoS profile.
655  const auto otherPublishers = get_publishers_info_by_topic(topicName);
656  const auto otherPublisherIt =
657  std::find_if(otherPublishers.begin(), otherPublishers.end(),
658  [this](const rclcpp::TopicEndpointInfo& endpoint) {
659  return endpoint.node_name() != this->get_name() ||
660  endpoint.node_namespace() != this->get_namespace();
661  });
662  rclcpp::QoS qos = otherPublisherIt == otherPublishers.end() ? rclcpp::SystemDefaultsQoS()
663  : otherPublisherIt->qos_profile();
664 
665  // When the QoS profile is copied from another existing publisher, it can happen that the
666  // history policy is Unknown, leading to an error when subsequently trying to create a publisher
667  // with that QoS profile. As a fix, we explicitly set the history policy to the system default.
668  if (qos.history() == rclcpp::HistoryPolicy::Unknown) {
669  qos.history(rclcpp::HistoryPolicy::SystemDefault);
670  }
671  rclcpp::PublisherOptions publisherOptions{};
672  publisherOptions.callback_group = _clientPublishCallbackGroup;
673  auto publisher = this->create_generic_publisher(topicName, topicType, qos, publisherOptions);
674 
675  RCLCPP_INFO(this->get_logger(),
676  "Client %s is advertising \"%s\" (%s) on channel %d with encoding \"%s\"",
677  _server->remoteEndpointString(hdl).c_str(), topicName.c_str(), topicType.c_str(),
678  advertisement.channelId, advertisement.encoding.c_str());
679 
680  // Store the new topic advertisement
681  clientPublications.emplace(advertisement.channelId, std::move(publisher));
682  } catch (const std::exception& ex) {
683  throw foxglove::ClientChannelError(advertisement.channelId,
684  std::string("Failed to create publisher: ") + ex.what());
685  }
686 }
687 
689  std::lock_guard<std::mutex> lock(_clientAdvertisementsMutex);
690 
691  auto it = _clientAdvertisedTopics.find(hdl);
692  if (it == _clientAdvertisedTopics.end()) {
694  channelId, "Ignoring client unadvertisement from " + _server->remoteEndpointString(hdl) +
695  " for unknown channel " + std::to_string(channelId) +
696  ", client has no advertised topics");
697  }
698 
699  auto& clientPublications = it->second;
700  auto it2 = clientPublications.find(channelId);
701  if (it2 == clientPublications.end()) {
703  channelId, "Ignoring client unadvertisement from " + _server->remoteEndpointString(hdl) +
704  " for unknown channel " + std::to_string(channelId) + ", client has " +
705  std::to_string(clientPublications.size()) + " advertised topic(s)");
706  }
707 
708  const auto& publisher = it2->second;
709  RCLCPP_INFO(this->get_logger(),
710  "Client %s is no longer advertising %s (%zu subscribers) on channel %d",
711  _server->remoteEndpointString(hdl).c_str(), publisher->get_topic_name(),
712  publisher->get_subscription_count(), channelId);
713 
714  clientPublications.erase(it2);
715  if (clientPublications.empty()) {
716  _clientAdvertisedTopics.erase(it);
717  }
718 
719  // Create a timer that immedeately goes out of scope (so it never fires) which will trigger
720  // the previously destroyed publisher to be cleaned up. This is a workaround for
721  // https://github.com/ros2/rclcpp/issues/2146
722  this->create_wall_timer(1s, []() {});
723 }
724 
726  // Get the publisher
727  rclcpp::GenericPublisher::SharedPtr publisher;
728  {
729  const auto channelId = message.advertisement.channelId;
730  std::lock_guard<std::mutex> lock(_clientAdvertisementsMutex);
731 
732  auto it = _clientAdvertisedTopics.find(hdl);
733  if (it == _clientAdvertisedTopics.end()) {
735  channelId, "Dropping client message from " + _server->remoteEndpointString(hdl) +
736  " for unknown channel " + std::to_string(channelId) +
737  ", client has no advertised topics");
738  }
739 
740  auto& clientPublications = it->second;
741  auto it2 = clientPublications.find(channelId);
742  if (it2 == clientPublications.end()) {
744  channelId, "Dropping client message from " + _server->remoteEndpointString(hdl) +
745  " for unknown channel " + std::to_string(channelId) + ", client has " +
746  std::to_string(clientPublications.size()) + " advertised topic(s)");
747  }
748  publisher = it2->second;
749  }
750 
751  auto publishMessage = [publisher, this](const void* data, size_t size) {
752  // Copy the message payload into a SerializedMessage object
753  rclcpp::SerializedMessage serializedMessage{size};
754  auto& rclSerializedMsg = serializedMessage.get_rcl_serialized_message();
755  std::memcpy(rclSerializedMsg.buffer, data, size);
756  rclSerializedMsg.buffer_length = size;
757  // Publish the message
758  if (_disableLoanMessage || !publisher->can_loan_messages()) {
759  publisher->publish(serializedMessage);
760  } else {
761  publisher->publish_as_loaned_msg(serializedMessage);
762  }
763  };
764 
765  if (message.advertisement.encoding == "cdr") {
766  publishMessage(message.getData(), message.getLength());
767  } else if (message.advertisement.encoding == "json") {
768  // get the specific parser for this schemaName
769  std::shared_ptr<RosMsgParser::Parser> parser;
770  {
771  std::lock_guard<std::mutex> lock(_clientAdvertisementsMutex);
772  auto parserIt = _jsonParsers.find(message.advertisement.schemaName);
773  if (parserIt != _jsonParsers.end()) {
774  parser = parserIt->second;
775  }
776  }
777  if (!parser) {
779  "Dropping client message from " +
780  _server->remoteEndpointString(hdl) +
781  " with encoding \"json\": no parser found");
782  } else {
783  thread_local RosMsgParser::ROS2_Serializer serializer;
784  serializer.reset();
785  const std::string jsonMessage(reinterpret_cast<const char*>(message.getData()),
786  message.getLength());
787  try {
788  parser->serializeFromJson(jsonMessage, &serializer);
789  publishMessage(serializer.getBufferData(), serializer.getBufferSize());
790  } catch (const std::exception& ex) {
792  "Dropping client message from " +
793  _server->remoteEndpointString(hdl) +
794  " with encoding \"json\": " + ex.what());
795  }
796  }
797  } else {
799  message.advertisement.channelId,
800  "Dropping client message from " + _server->remoteEndpointString(hdl) +
801  " with unknown encoding \"" + message.advertisement.encoding + "\"");
802  }
803 }
804 
805 void FoxgloveBridge::setParameters(const std::vector<foxglove::Parameter>& parameters,
806  const std::optional<std::string>& requestId,
807  ConnectionHandle hdl) {
808  _paramInterface->setParams(parameters, std::chrono::seconds(5));
809 
810  // If a request Id was given, send potentially updated parameters back to client
811  if (requestId) {
812  std::vector<std::string> parameterNames(parameters.size());
813  for (size_t i = 0; i < parameters.size(); ++i) {
814  parameterNames[i] = parameters[i].getName();
815  }
816  getParameters(parameterNames, requestId, hdl);
817  }
818 }
819 
820 void FoxgloveBridge::getParameters(const std::vector<std::string>& parameters,
821  const std::optional<std::string>& requestId,
822  ConnectionHandle hdl) {
823  const auto params = _paramInterface->getParams(parameters, std::chrono::seconds(5));
824  _server->publishParameterValues(hdl, params, requestId);
825 }
826 
827 void FoxgloveBridge::subscribeParameters(const std::vector<std::string>& parameters,
831  _paramInterface->subscribeParams(parameters);
832  } else {
833  _paramInterface->unsubscribeParams(parameters);
834  }
835 }
836 
837 void FoxgloveBridge::parameterUpdates(const std::vector<foxglove::Parameter>& parameters) {
838  _server->updateParameterValues(parameters);
839 }
840 
841 void FoxgloveBridge::logHandler(LogLevel level, char const* msg) {
842  switch (level) {
843  case LogLevel::Debug:
844  RCLCPP_DEBUG(this->get_logger(), "[WS] %s", msg);
845  break;
846  case LogLevel::Info:
847  RCLCPP_INFO(this->get_logger(), "[WS] %s", msg);
848  break;
849  case LogLevel::Warn:
850  RCLCPP_WARN(this->get_logger(), "[WS] %s", msg);
851  break;
852  case LogLevel::Error:
853  RCLCPP_ERROR(this->get_logger(), "[WS] %s", msg);
854  break;
855  case LogLevel::Critical:
856  RCLCPP_FATAL(this->get_logger(), "[WS] %s", msg);
857  break;
858  }
859 }
860 
862  ConnectionHandle clientHandle,
863  std::shared_ptr<const rclcpp::SerializedMessage> msg) {
864  // NOTE: Do not call any RCLCPP_* logging functions from this function. Otherwise, subscribing
865  // to `/rosout` will cause a feedback loop
866  const auto timestamp = this->now().nanoseconds();
867  assert(timestamp >= 0 && "Timestamp is negative");
868  const auto rclSerializedMsg = msg->get_rcl_serialized_message();
869  _server->sendMessage(clientHandle, channelId, static_cast<uint64_t>(timestamp),
870  rclSerializedMsg.buffer, rclSerializedMsg.buffer_length);
871 }
872 
874  ConnectionHandle clientHandle) {
875  RCLCPP_DEBUG(this->get_logger(), "Received a request for service %d", request.serviceId);
876 
877  std::lock_guard<std::mutex> lock(_servicesMutex);
878  const auto serviceIt = _advertisedServices.find(request.serviceId);
879  if (serviceIt == _advertisedServices.end()) {
881  request.serviceId,
882  "Service with id " + std::to_string(request.serviceId) + " does not exist");
883  }
884 
885  auto clientIt = _serviceClients.find(request.serviceId);
886  if (clientIt == _serviceClients.end()) {
887  try {
888  auto clientOptions = rcl_client_get_default_options();
889  auto genClient = GenericClient::make_shared(
890  this->get_node_base_interface().get(), this->get_node_graph_interface(),
891  serviceIt->second.name, serviceIt->second.type, clientOptions);
892  clientIt = _serviceClients.emplace(request.serviceId, std::move(genClient)).first;
893  this->get_node_services_interface()->add_client(clientIt->second, _servicesCallbackGroup);
894  } catch (const std::exception& ex) {
896  request.serviceId,
897  "Failed to create service client for service " + serviceIt->second.name + ": " + ex.what());
898  }
899  }
900 
901  auto client = clientIt->second;
902  if (!client->wait_for_service(1s)) {
903  throw foxglove::ServiceError(request.serviceId,
904  "Service " + serviceIt->second.name + " is not available");
905  }
906 
907  auto reqMessage = std::make_shared<rclcpp::SerializedMessage>(request.data.size());
908  auto& rclSerializedMsg = reqMessage->get_rcl_serialized_message();
909  std::memcpy(rclSerializedMsg.buffer, request.data.data(), request.data.size());
910  rclSerializedMsg.buffer_length = request.data.size();
911 
912  auto responseReceivedCallback = [this, request,
913  clientHandle](GenericClient::SharedFuture future) {
914  const auto serializedResponseMsg = future.get()->get_rcl_serialized_message();
915  foxglove::ServiceRequest response{request.serviceId, request.callId, request.encoding,
916  std::vector<uint8_t>(serializedResponseMsg.buffer_length)};
917  std::memcpy(response.data.data(), serializedResponseMsg.buffer,
918  serializedResponseMsg.buffer_length);
919  _server->sendServiceResponse(clientHandle, response);
920  };
921  client->async_send_request(reqMessage, responseReceivedCallback);
922 }
923 
924 void FoxgloveBridge::fetchAsset(const std::string& uri, uint32_t requestId,
925  ConnectionHandle clientHandle) {
927  response.requestId = requestId;
928 
929  try {
930  // We reject URIs that are not on the allowlist or that contain two consecutive dots. The latter
931  // can be utilized to construct URIs for retrieving confidential files that should not be
932  // accessible over the WebSocket connection. Example:
933  // `package://<pkg_name>/../../../secret.txt`. This is an extra security measure and should not
934  // be necessary if the allowlist is strict enough.
935  if (uri.find("..") != std::string::npos || !isWhitelisted(uri, _assetUriAllowlistPatterns)) {
936  throw std::runtime_error("Asset URI not allowed: " + uri);
937  }
938 
940  const resource_retriever::MemoryResource memoryResource = resource_retriever.get(uri);
942  response.errorMessage = "";
943  response.data.resize(memoryResource.size);
944  std::memcpy(response.data.data(), memoryResource.data.get(), memoryResource.size);
945  } catch (const std::exception& ex) {
946  RCLCPP_WARN(this->get_logger(), "Failed to retrieve asset '%s': %s", uri.c_str(), ex.what());
948  response.errorMessage = "Failed to retrieve asset " + uri;
949  }
950 
951  if (_server) {
952  _server->sendFetchAssetResponse(clientHandle, response);
953  }
954 }
955 
956 bool FoxgloveBridge::hasCapability(const std::string& capability) {
957  return std::find(_capabilities.begin(), _capabilities.end(), capability) != _capabilities.end();
958 }
959 
960 } // namespace foxglove_bridge
961 
962 #include <rclcpp_components/register_node_macro.hpp>
963 
964 // Register the component with class_loader.
965 // This acts as a sort of entry point, allowing the component to be discoverable when its library
966 // is being loaded into a running process.
967 RCLCPP_COMPONENTS_REGISTER_NODE(foxglove_bridge::FoxgloveBridge)
foxglove_bridge::PARAM_BEST_EFFORT_QOS_TOPIC_WHITELIST
constexpr char PARAM_BEST_EFFORT_QOS_TOPIC_WHITELIST[]
Definition: ros2_foxglove_bridge/include/foxglove_bridge/param_utils.hpp:19
response
const std::string response
foxglove_bridge::FoxgloveBridge::hasCapability
bool hasCapability(const std::string &capability)
Definition: ros1_foxglove_bridge_nodelet.cpp:928
foxglove_bridge::SubscriptionsByClient
std::map< ConnectionHandle, ros::Subscriber, std::owner_less<> > SubscriptionsByClient
Definition: ros1_foxglove_bridge_nodelet.cpp:55
foxglove::isWhitelisted
bool isWhitelisted(const std::string &name, const std::vector< std::regex > &regexPatterns)
Definition: regex_utils.hpp:10
foxglove_bridge::FoxgloveBridge::_advertisedTopics
std::unordered_map< foxglove::ChannelId, foxglove::ChannelWithoutId > _advertisedTopics
Definition: ros1_foxglove_bridge_nodelet.cpp:939
resource_retriever::MemoryResource::size
uint32_t size
foxglove_bridge::FoxgloveBridge::_rosgraphPollThread
std::unique_ptr< std::thread > _rosgraphPollThread
Definition: ros2_foxglove_bridge.hpp:78
foxglove::MessageDefinitionFormat::IDL
@ IDL
foxglove_bridge::PARAM_INCLUDE_HIDDEN
constexpr char PARAM_INCLUDE_HIDDEN[]
Definition: ros2_foxglove_bridge/include/foxglove_bridge/param_utils.hpp:26
foxglove_bridge::ConnectionHandle
websocketpp::connection_hdl ConnectionHandle
Definition: ros1_foxglove_bridge_nodelet.cpp:53
foxglove::FOXGLOVE_BRIDGE_GIT_HASH
const char FOXGLOVE_BRIDGE_GIT_HASH[]
foxglove_bridge::FoxgloveBridge::_assetUriAllowlistPatterns
std::vector< std::regex > _assetUriAllowlistPatterns
Definition: ros1_foxglove_bridge_nodelet.cpp:937
foxglove::ServerOptions::certfile
std::string certfile
Definition: server_interface.hpp:51
foxglove::ClientAdvertisement::topic
std::string topic
Definition: common.hpp:79
foxglove::ServerHandlers::subscribeConnectionGraphHandler
std::function< void(bool)> subscribeConnectionGraphHandler
Definition: server_interface.hpp:75
foxglove_bridge::FoxgloveBridge
Definition: ros1_foxglove_bridge_nodelet.cpp:60
foxglove_bridge::parseRegexStrings
std::vector< std::regex > parseRegexStrings(rclcpp::Node *node, const std::vector< std::string > &strings)
Definition: ros2_foxglove_bridge/src/param_utils.cpp:184
format
Definition: format.py:1
foxglove_bridge::FoxgloveBridge::clientAdvertise
void clientAdvertise(const foxglove::ClientAdvertisement &channel, ConnectionHandle clientHandle)
Definition: ros1_foxglove_bridge_nodelet.cpp:304
foxglove_bridge::FoxgloveBridge::_advertisedServices
std::unordered_map< foxglove::ServiceId, foxglove::ServiceWithoutId > _advertisedServices
Definition: ros1_foxglove_bridge_nodelet.cpp:941
foxglove_bridge::GenericClient::SharedFuture
std::shared_future< SharedResponse > SharedFuture
Definition: generic_client.hpp:19
foxglove::ServiceResponse::encoding
std::string encoding
Definition: common.hpp:133
foxglove_bridge::FoxgloveBridge::_serviceWhitelistPatterns
std::vector< std::regex > _serviceWhitelistPatterns
Definition: ros1_foxglove_bridge_nodelet.cpp:936
foxglove::ServerHandlers::clientMessageHandler
std::function< void(const ClientMessage &, ConnectionHandle)> clientMessageHandler
Definition: server_interface.hpp:64
foxglove_bridge::FoxgloveBridge::TopicAndDatatype
std::pair< std::string, std::string > TopicAndDatatype
Definition: ros2_foxglove_bridge.hpp:36
foxglove_bridge::FoxgloveBridge::_paramInterface
std::shared_ptr< ParameterInterface > _paramInterface
Definition: ros2_foxglove_bridge.hpp:66
foxglove_bridge::FoxgloveBridge::_fetchAssetQueue
std::unique_ptr< foxglove::CallbackQueue > _fetchAssetQueue
Definition: ros1_foxglove_bridge_nodelet.cpp:954
foxglove::ServiceResponse
Definition: common.hpp:130
foxglove_bridge::FoxgloveBridge::updateAdvertisedServices
void updateAdvertisedServices()
Definition: ros2_foxglove_bridge.cpp:285
foxglove::ServerHandlers::serviceRequestHandler
std::function< void(const ServiceRequest &, ConnectionHandle)> serviceRequestHandler
Definition: server_interface.hpp:74
resource_retriever
foxglove_bridge::FoxgloveBridge::FoxgloveBridge
FoxgloveBridge()=default
resource_retriever::MemoryResource
foxglove::ServerHandlers::parameterChangeHandler
std::function< void(const std::vector< Parameter > &, const std::optional< std::string > &, ConnectionHandle)> parameterChangeHandler
Definition: server_interface.hpp:70
foxglove::ServiceWithoutId::responseSchema
std::string responseSchema
Definition: common.hpp:118
foxglove::ServiceWithoutId
Definition: common.hpp:114
foxglove_bridge::PARAM_CLIENT_TOPIC_WHITELIST
constexpr char PARAM_CLIENT_TOPIC_WHITELIST[]
Definition: ros2_foxglove_bridge/include/foxglove_bridge/param_utils.hpp:25
foxglove::ServiceResponse::callId
uint32_t callId
Definition: common.hpp:132
foxglove_bridge::FoxgloveBridge::_includeHidden
bool _includeHidden
Definition: ros2_foxglove_bridge.hpp:85
foxglove::SERVICE_RESPONSE_MESSAGE_SUFFIX
constexpr char SERVICE_RESPONSE_MESSAGE_SUFFIX[]
Definition: message_definition_cache.hpp:14
foxglove::FetchAssetResponse
Definition: common.hpp:155
foxglove::CAPABILITY_TIME
constexpr char CAPABILITY_TIME[]
Definition: common.hpp:14
foxglove::ParameterSubscriptionOperation
ParameterSubscriptionOperation
Definition: parameter.hpp:11
foxglove::ServerOptions::supportedEncodings
std::vector< std::string > supportedEncodings
Definition: server_interface.hpp:47
foxglove_bridge::FoxgloveBridge::clientMessage
void clientMessage(const foxglove::ClientMessage &clientMsg, ConnectionHandle clientHandle)
Definition: ros1_foxglove_bridge_nodelet.cpp:392
foxglove_bridge::PARAM_PARAMETER_WHITELIST
constexpr char PARAM_PARAMETER_WHITELIST[]
Definition: ros2_foxglove_bridge/include/foxglove_bridge/param_utils.hpp:22
foxglove::ServerOptions::keyfile
std::string keyfile
Definition: server_interface.hpp:52
foxglove_bridge::FoxgloveBridge::_subscribeGraphUpdates
std::atomic< bool > _subscribeGraphUpdates
Definition: ros1_foxglove_bridge_nodelet.cpp:953
foxglove_bridge::FoxgloveBridge::_subscriptions
std::unordered_map< foxglove::ChannelId, SubscriptionsByClient > _subscriptions
Definition: ros1_foxglove_bridge_nodelet.cpp:940
foxglove_bridge::FoxgloveBridge::_clientAdvertisementsMutex
std::mutex _clientAdvertisementsMutex
Definition: ros2_foxglove_bridge.hpp:76
foxglove_bridge::FoxgloveBridge::subscribe
void subscribe(foxglove::ChannelId channelId, ConnectionHandle clientHandle)
Definition: ros1_foxglove_bridge_nodelet.cpp:216
foxglove_bridge::FoxgloveBridge::_capabilities
std::vector< std::string > _capabilities
Definition: ros1_foxglove_bridge_nodelet.cpp:951
foxglove::ChannelWithoutId::topic
std::string topic
Definition: common.hpp:52
foxglove::ServerHandlers::fetchAssetHandler
std::function< void(const std::string &, uint32_t, ConnectionHandle)> fetchAssetHandler
Definition: server_interface.hpp:76
foxglove::ClientChannelError
Definition: server_interface.hpp:38
foxglove_bridge::FoxgloveBridge::~FoxgloveBridge
virtual ~FoxgloveBridge()
Definition: ros1_foxglove_bridge_nodelet.cpp:201
get
def get(url)
foxglove::ServerOptions::useCompression
bool useCompression
Definition: server_interface.hpp:54
foxglove::ServiceWithoutId::name
std::string name
Definition: common.hpp:115
resource_retriever::MemoryResource::data
boost::shared_array< uint8_t > data
foxglove::ServiceResponse::data
std::vector< uint8_t > data
Definition: common.hpp:134
foxglove_bridge::FoxgloveBridge::_servicesMutex
std::shared_mutex _servicesMutex
Definition: ros1_foxglove_bridge_nodelet.cpp:945
foxglove_bridge::FoxgloveBridge::rosgraphPollThread
void rosgraphPollThread()
Definition: ros2_foxglove_bridge.cpp:151
foxglove::ServiceWithoutId::type
std::string type
Definition: common.hpp:116
foxglove::DefinitionNotFoundError
Definition: message_definition_cache.hpp:40
foxglove_bridge::FoxgloveBridge::_clientAdvertisedTopics
PublicationsByClient _clientAdvertisedTopics
Definition: ros1_foxglove_bridge_nodelet.cpp:942
foxglove_bridge::FoxgloveBridge::_disableLoanMessage
bool _disableLoanMessage
Definition: ros2_foxglove_bridge.hpp:86
foxglove_bridge::FoxgloveBridge::_shuttingDown
std::atomic< bool > _shuttingDown
Definition: ros2_foxglove_bridge.hpp:89
foxglove_bridge::FoxgloveBridge::_maxQosDepth
size_t _maxQosDepth
Definition: ros2_foxglove_bridge.hpp:80
foxglove::ServerHandlers::unsubscribeHandler
std::function< void(ChannelId, ConnectionHandle)> unsubscribeHandler
Definition: server_interface.hpp:61
foxglove::ServerOptions::useTls
bool useTls
Definition: server_interface.hpp:50
foxglove_bridge::FoxgloveBridge::_jsonParsers
std::unordered_map< std::string, std::shared_ptr< RosMsgParser::Parser > > _jsonParsers
Definition: ros2_foxglove_bridge.hpp:88
foxglove_bridge::FoxgloveBridge::fetchAsset
void fetchAsset(const std::string &uri, uint32_t requestId, ConnectionHandle clientHandle)
Definition: ros1_foxglove_bridge_nodelet.cpp:897
foxglove::CAPABILITY_PARAMETERS_SUBSCRIBE
constexpr char CAPABILITY_PARAMETERS_SUBSCRIBE[]
Definition: common.hpp:16
foxglove::SERVICE_REQUEST_MESSAGE_SUFFIX
constexpr char SERVICE_REQUEST_MESSAGE_SUFFIX[]
Definition: message_definition_cache.hpp:13
foxglove_bridge::FoxgloveBridge::_servicesCallbackGroup
rclcpp::CallbackGroup::SharedPtr _servicesCallbackGroup
Definition: ros2_foxglove_bridge.hpp:74
foxglove::ServerOptions
Definition: server_interface.hpp:45
foxglove_bridge::FoxgloveBridge::unsubscribe
void unsubscribe(foxglove::ChannelId channelId, ConnectionHandle clientHandle)
Definition: ros1_foxglove_bridge_nodelet.cpp:266
foxglove_bridge::declareParameters
void declareParameters(rclcpp::Node *node)
Definition: ros2_foxglove_bridge/src/param_utils.cpp:8
foxglove_bridge::PARAM_PORT
constexpr char PARAM_PORT[]
Definition: ros2_foxglove_bridge/include/foxglove_bridge/param_utils.hpp:11
foxglove::WebSocketUserAgent
const char * WebSocketUserAgent()
Definition: foxglove_bridge.cpp:7
foxglove::ClientAdvertisement::channelId
ClientChannelId channelId
Definition: common.hpp:78
foxglove::ChannelError
Definition: server_interface.hpp:35
foxglove_bridge::FoxgloveBridge::updateAdvertisedTopics
void updateAdvertisedTopics()
Definition: ros1_foxglove_bridge_nodelet.cpp:490
foxglove::ServerHandlers
Definition: server_interface.hpp:59
foxglove::ParameterSubscriptionOperation::SUBSCRIBE
@ SUBSCRIBE
foxglove_bridge::PARAM_SEND_BUFFER_LIMIT
constexpr char PARAM_SEND_BUFFER_LIMIT[]
Definition: ros2_foxglove_bridge/include/foxglove_bridge/param_utils.hpp:13
foxglove::WebSocketLogLevel
WebSocketLogLevel
Definition: common.hpp:43
foxglove::MessageDefinitionFormat::MSG
@ MSG
foxglove::ServerHandlers::subscribeHandler
std::function< void(ChannelId, ConnectionHandle)> subscribeHandler
Definition: server_interface.hpp:60
foxglove_bridge::FoxgloveBridge::getParameters
void getParameters(const std::vector< std::string > &parameters, const std::optional< std::string > &requestId, ConnectionHandle hdl)
Definition: ros1_foxglove_bridge_nodelet.cpp:651
foxglove_bridge::FoxgloveBridge::parameterUpdates
void parameterUpdates(XmlRpc::XmlRpcValue &params, XmlRpc::XmlRpcValue &result)
Definition: ros1_foxglove_bridge_nodelet.cpp:800
foxglove_bridge::FoxgloveBridge::subscribeParameters
void subscribeParameters(const std::vector< std::string > &parameters, foxglove::ParameterSubscriptionOperation op, ConnectionHandle)
Definition: ros1_foxglove_bridge_nodelet.cpp:770
foxglove_bridge::PARAM_DISABLE_LOAN_MESSAGE
constexpr char PARAM_DISABLE_LOAN_MESSAGE[]
Definition: ros2_foxglove_bridge/include/foxglove_bridge/param_utils.hpp:27
foxglove_bridge::FoxgloveBridge::setParameters
void setParameters(const std::vector< foxglove::Parameter > &parameters, const std::optional< std::string > &requestId, ConnectionHandle hdl)
Definition: ros1_foxglove_bridge_nodelet.cpp:722
foxglove::ClientMessage::advertisement
ClientAdvertisement advertisement
Definition: common.hpp:89
foxglove_bridge
Definition: generic_service.hpp:9
foxglove::ClientAdvertisement
Definition: common.hpp:77
foxglove::ClientMessage::getData
const uint8_t * getData() const
Definition: common.hpp:106
foxglove::ServerOptions::sessionId
std::string sessionId
Definition: server_interface.hpp:53
foxglove_bridge::FoxgloveBridge::_minQosDepth
size_t _minQosDepth
Definition: ros2_foxglove_bridge.hpp:79
foxglove::ClientMessage
Definition: common.hpp:85
foxglove_bridge::FoxgloveBridge::subscribeConnectionGraph
void subscribeConnectionGraph(bool subscribe)
Definition: ros2_foxglove_bridge.cpp:424
foxglove::ServiceResponse::serviceId
ServiceId serviceId
Definition: common.hpp:131
resource_retriever::Retriever
foxglove_bridge::FoxgloveBridge::_bestEffortQosTopicWhiteListPatterns
std::vector< std::regex > _bestEffortQosTopicWhiteListPatterns
Definition: ros2_foxglove_bridge.hpp:65
foxglove::ClientAdvertisement::schema
std::vector< uint8_t > schema
Definition: common.hpp:82
foxglove_bridge::FoxgloveBridge::serviceRequest
void serviceRequest(const foxglove::ServiceRequest &request, ConnectionHandle clientHandle)
Definition: ros1_foxglove_bridge_nodelet.cpp:852
foxglove::ChannelWithoutId
Definition: common.hpp:51
foxglove::ServerOptions::capabilities
std::vector< std::string > capabilities
Definition: server_interface.hpp:46
foxglove::DefinitionNotFoundError::what
const char * what() const noexcept override
Definition: message_definition_cache.hpp:48
foxglove_bridge::FoxgloveBridge::_clientPublishCallbackGroup
rclcpp::CallbackGroup::SharedPtr _clientPublishCallbackGroup
Definition: ros2_foxglove_bridge.hpp:73
foxglove_bridge::FoxgloveBridge::_subscriptionCallbackGroup
rclcpp::CallbackGroup::SharedPtr _subscriptionCallbackGroup
Definition: ros2_foxglove_bridge.hpp:72
foxglove_bridge::PARAM_SERVICE_WHITELIST
constexpr char PARAM_SERVICE_WHITELIST[]
Definition: ros2_foxglove_bridge/include/foxglove_bridge/param_utils.hpp:21
foxglove::ServiceWithoutId::requestSchema
std::string requestSchema
Definition: common.hpp:117
foxglove::ClientAdvertisement::encoding
std::string encoding
Definition: common.hpp:80
format.parser
parser
Definition: format.py:58
foxglove::CAPABILITY_PARAMETERS
constexpr char CAPABILITY_PARAMETERS[]
Definition: common.hpp:15
foxglove_bridge::FoxgloveBridge::logHandler
void logHandler(foxglove::WebSocketLogLevel level, char const *msg)
Definition: ros1_foxglove_bridge_nodelet.cpp:824
foxglove::MapOfSets
std::unordered_map< std::string, std::unordered_set< std::string > > MapOfSets
Definition: server_interface.hpp:18
foxglove_bridge::PARAM_TOPIC_WHITELIST
constexpr char PARAM_TOPIC_WHITELIST[]
Definition: ros2_foxglove_bridge/include/foxglove_bridge/param_utils.hpp:20
foxglove_bridge::FoxgloveBridge::updateConnectionGraph
void updateConnectionGraph(const std::map< std::string, std::vector< std::string >> &topicNamesAndTypes)
Definition: ros2_foxglove_bridge.cpp:381
foxglove::ClientAdvertisement::schemaName
std::string schemaName
Definition: common.hpp:81
foxglove_bridge::FoxgloveBridge::_subscriptionsMutex
std::mutex _subscriptionsMutex
Definition: ros1_foxglove_bridge_nodelet.cpp:943
foxglove::ServerHandlers::clientUnadvertiseHandler
std::function< void(ClientChannelId, ConnectionHandle)> clientUnadvertiseHandler
Definition: server_interface.hpp:63
foxglove_bridge::PARAM_USE_COMPRESSION
constexpr char PARAM_USE_COMPRESSION[]
Definition: ros2_foxglove_bridge/include/foxglove_bridge/param_utils.hpp:23
foxglove::ServerHandlers::parameterRequestHandler
std::function< void(const std::vector< std::string > &, const std::optional< std::string > &, ConnectionHandle)> parameterRequestHandler
Definition: server_interface.hpp:67
foxglove_bridge::PARAM_ADDRESS
constexpr char PARAM_ADDRESS[]
Definition: ros2_foxglove_bridge/include/foxglove_bridge/param_utils.hpp:12
datatype
const char * datatype()
foxglove::ServerOptions::sendBufferLimitBytes
size_t sendBufferLimitBytes
Definition: server_interface.hpp:49
foxglove_bridge::PARAM_KEYFILE
constexpr char PARAM_KEYFILE[]
Definition: ros2_foxglove_bridge/include/foxglove_bridge/param_utils.hpp:16
foxglove_bridge::PARAM_USETLS
constexpr char PARAM_USETLS[]
Definition: ros2_foxglove_bridge/include/foxglove_bridge/param_utils.hpp:14
foxglove_bridge::FoxgloveBridge::_topicWhitelistPatterns
std::vector< std::regex > _topicWhitelistPatterns
Definition: ros1_foxglove_bridge_nodelet.cpp:934
foxglove::ServiceError
Definition: server_interface.hpp:41
foxglove_bridge::FoxgloveBridge::rosMessageHandler
void rosMessageHandler(const foxglove::ChannelId channelId, ConnectionHandle clientHandle, const ros::MessageEvent< ros_babel_fish::BabelFishMessage const > &msgEvent)
Definition: ros1_foxglove_bridge_nodelet.cpp:844
foxglove::ChannelId
uint32_t ChannelId
Definition: common.hpp:26
foxglove::FetchAssetStatus::Error
@ Error
foxglove_bridge::ClientPublications
std::unordered_map< foxglove::ClientChannelId, ros::Publisher > ClientPublications
Definition: ros1_foxglove_bridge_nodelet.cpp:56
foxglove::CAPABILITY_SERVICES
constexpr char CAPABILITY_SERVICES[]
Definition: common.hpp:17
foxglove::ServerHandlers::clientAdvertiseHandler
std::function< void(const ClientAdvertisement &, ConnectionHandle)> clientAdvertiseHandler
Definition: server_interface.hpp:62
foxglove_bridge::getNodeAndNodeNamespace
std::pair< std::string, std::string > getNodeAndNodeNamespace(const std::string &fqnNodeName)
Definition: utils.hpp:12
foxglove_bridge::PARAM_ASSET_URI_ALLOWLIST
constexpr char PARAM_ASSET_URI_ALLOWLIST[]
Definition: ros2_foxglove_bridge/include/foxglove_bridge/param_utils.hpp:28
foxglove_bridge::FoxgloveBridge::clientUnadvertise
void clientUnadvertise(foxglove::ClientChannelId channelId, ConnectionHandle clientHandle)
Definition: ros1_foxglove_bridge_nodelet.cpp:359
foxglove::FOXGLOVE_BRIDGE_VERSION
const char FOXGLOVE_BRIDGE_VERSION[]
foxglove_bridge::PARAM_MAX_QOS_DEPTH
constexpr char PARAM_MAX_QOS_DEPTH[]
Definition: ros2_foxglove_bridge/include/foxglove_bridge/param_utils.hpp:18
foxglove::CAPABILITY_ASSETS
constexpr char CAPABILITY_ASSETS[]
Definition: common.hpp:19
foxglove::MessageDefinitionCache::get_full_text
std::pair< MessageDefinitionFormat, std::string > get_full_text(const std::string &package_resource_name)
Definition: message_definition_cache.cpp:342
foxglove_bridge::FoxgloveBridge::_useSimTime
bool _useSimTime
Definition: ros1_foxglove_bridge_nodelet.cpp:950
ros2_foxglove_bridge.hpp
foxglove::ServerOptions::clientTopicWhitelistPatterns
std::vector< std::regex > clientTopicWhitelistPatterns
Definition: server_interface.hpp:55
foxglove_bridge::PARAM_CERTFILE
constexpr char PARAM_CERTFILE[]
Definition: ros2_foxglove_bridge/include/foxglove_bridge/param_utils.hpp:15
foxglove_bridge::FoxgloveBridge::_server
std::unique_ptr< foxglove::ServerInterface< ConnectionHandle > > _server
Definition: ros1_foxglove_bridge_nodelet.cpp:932
foxglove::FetchAssetStatus::Success
@ Success
foxglove_bridge::PARAM_MIN_QOS_DEPTH
constexpr char PARAM_MIN_QOS_DEPTH[]
Definition: ros2_foxglove_bridge/include/foxglove_bridge/param_utils.hpp:17
foxglove_bridge::PARAM_CAPABILITIES
constexpr char PARAM_CAPABILITIES[]
Definition: ros2_foxglove_bridge/include/foxglove_bridge/param_utils.hpp:24
foxglove_bridge::FoxgloveBridge::_clockSubscription
ros::Subscriber _clockSubscription
Definition: ros1_foxglove_bridge_nodelet.cpp:949
foxglove_bridge::FoxgloveBridge::_serviceClients
std::unordered_map< foxglove::ServiceId, GenericClient::SharedPtr > _serviceClients
Definition: ros2_foxglove_bridge.hpp:71
foxglove_bridge::FoxgloveBridge::_messageDefinitionCache
foxglove::MessageDefinitionCache _messageDefinitionCache
Definition: ros2_foxglove_bridge.hpp:61
foxglove::ServerOptions::metadata
std::unordered_map< std::string, std::string > metadata
Definition: server_interface.hpp:48
foxglove::ServerHandlers::parameterSubscriptionHandler
std::function< void(const std::vector< std::string > &, ParameterSubscriptionOperation, ConnectionHandle)> parameterSubscriptionHandler
Definition: server_interface.hpp:73
foxglove::ClientMessage::getLength
std::size_t getLength() const
Definition: common.hpp:109


foxglove_bridge
Author(s): Foxglove
autogenerated on Wed Mar 5 2025 03:34:31