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