1 #include <unordered_set> 7 inline bool isHiddenTopicOrService(
const std::string& name) {
9 throw std::invalid_argument(
"Topic or service name can't be empty");
11 return name.front() ==
'_' || name.find(
"/_") != std::string::npos;
20 : Node(
"foxglove_bridge", options) {
21 const char* rosDistro = std::getenv(
"ROS_DISTRO");
22 RCLCPP_INFO(this->get_logger(),
"Starting foxglove_bridge (%s, %s@%s) with %s", rosDistro,
28 const auto port =
static_cast<uint16_t
>(this->get_parameter(
PARAM_PORT).as_int());
29 const auto address = this->get_parameter(
PARAM_ADDRESS).as_string();
30 const auto send_buffer_limit =
32 const auto useTLS = this->get_parameter(
PARAM_USETLS).as_bool();
33 const auto certfile = this->get_parameter(
PARAM_CERTFILE).as_string();
34 const auto keyfile = this->get_parameter(
PARAM_KEYFILE).as_string();
44 _useSimTime = this->get_parameter(
"use_sim_time").as_bool();
46 const auto clientTopicWhiteList =
48 const auto clientTopicWhiteListPatterns =
parseRegexStrings(
this, clientTopicWhiteList);
58 serverOptions.
metadata = {{
"ROS_DISTRO", rosDistro}};
60 serverOptions.
sessionId = std::to_string(std::time(
nullptr));
62 serverOptions.
useTls = useTLS;
64 serverOptions.
keyfile = keyfile;
67 _server = foxglove::ServerFactory::createServer<ConnectionHandle>(
"foxglove_bridge",
logHandler,
77 hdlrs.subscribeConnectionGraphHandler =
84 hdlrs.parameterSubscriptionHandler =
87 _paramInterface = std::make_shared<ParameterInterface>(
this, paramWhitelistPatterns);
91 _server->setHandlers(std::move(hdlrs));
95 uint16_t listeningPort =
_server->getPort();
96 if (port != listeningPort) {
97 RCLCPP_DEBUG(this->get_logger(),
"Reassigning \"port\" parameter from %d to %d", port,
99 this->set_parameter(rclcpp::Parameter{
PARAM_PORT, listeningPort});
108 this->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive);
113 "/clock", rclcpp::QoS{rclcpp::KeepLast(1)}.best_effort(),
114 [&](std::shared_ptr<rosgraph_msgs::msg::Clock> msg) {
115 const auto timestamp = rclcpp::Time{msg->clock}.nanoseconds();
116 assert(timestamp >= 0 &&
"Timestamp is negative");
117 _server->broadcastTime(static_cast<uint64_t>(timestamp));
123 RCLCPP_INFO(this->get_logger(),
"Shutting down %s", this->get_name());
128 RCLCPP_INFO(this->get_logger(),
"Shutdown complete");
135 auto graphEvent = this->get_graph_event();
136 while (rclcpp::ok()) {
138 this->wait_for_graph_change(graphEvent, 200ms);
139 bool triggered = graphEvent->check_and_clear();
141 RCLCPP_DEBUG(this->get_logger(),
"rosgraph change detected");
142 const auto topicNamesAndTypes = get_topic_names_and_types();
149 std::this_thread::sleep_for(500ms);
151 }
catch (
const std::exception& ex) {
152 RCLCPP_ERROR(this->get_logger(),
"Exception thrown in rosgraphPollThread: %s", ex.what());
156 RCLCPP_DEBUG(this->get_logger(),
"rosgraph polling thread exiting");
160 const std::map<std::string, std::vector<std::string>>& topicNamesAndTypes) {
165 std::unordered_set<TopicAndDatatype, PairHash> latestTopics;
166 latestTopics.reserve(topicNamesAndTypes.size());
167 for (
const auto& topicNamesAndType : topicNamesAndTypes) {
168 const auto& topicName = topicNamesAndType.first;
169 const auto& datatypes = topicNamesAndType.second;
178 for (
const auto&
datatype : datatypes) {
179 latestTopics.emplace(topicName,
datatype);
184 if (
const auto numIgnoredTopics = topicNamesAndTypes.size() - latestTopics.size()) {
187 "%zu topics have been ignored as they do not match any pattern on the topic whitelist",
194 std::vector<foxglove::ChannelId> channelIdsToRemove;
197 channelIt->second.schemaName};
198 if (latestTopics.find(topicAndDatatype) == latestTopics.end()) {
199 const auto channelId = channelIt->first;
200 channelIdsToRemove.push_back(channelId);
202 RCLCPP_INFO(this->get_logger(),
"Removed channel %d for topic \"%s\" (%s)", channelId,
203 topicAndDatatype.first.c_str(), topicAndDatatype.second.c_str());
209 _server->removeChannels(channelIdsToRemove);
212 std::vector<foxglove::ChannelWithoutId> channelsToAdd;
213 for (
const auto& topicAndDatatype : latestTopics) {
215 [topicAndDatatype](
const auto& channelIdAndChannel) {
216 const auto& channel = channelIdAndChannel.second;
217 return channel.topic == topicAndDatatype.first &&
218 channel.schemaName == topicAndDatatype.second;
224 newChannel.
topic = topicAndDatatype.first;
225 newChannel.schemaName = topicAndDatatype.second;
231 newChannel.encoding =
"cdr";
232 newChannel.schema = schema;
233 newChannel.schemaEncoding =
"ros2msg";
236 newChannel.encoding =
"cdr";
237 newChannel.schema = schema;
238 newChannel.schemaEncoding =
"ros2idl";
243 RCLCPP_WARN(this->get_logger(),
"Could not find definition for type %s: %s",
244 topicAndDatatype.second.c_str(), err.
what());
246 newChannel.schema =
"";
247 }
catch (
const std::exception& err) {
248 RCLCPP_WARN(this->get_logger(),
"Failed to add channel for topic \"%s\" (%s): %s",
249 topicAndDatatype.first.c_str(), topicAndDatatype.second.c_str(), err.what());
253 channelsToAdd.push_back(newChannel);
256 const auto channelIds =
_server->addChannels(channelsToAdd);
257 for (
size_t i = 0; i < channelsToAdd.size(); ++i) {
258 const auto channelId = channelIds[i];
259 const auto& channel = channelsToAdd[i];
261 RCLCPP_DEBUG(this->get_logger(),
"Advertising channel %d for topic \"%s\" (%s)", channelId,
262 channel.topic.c_str(), channel.schemaName.c_str());
274 const auto serviceNamesAndTypes = this->get_node_graph_interface()->get_service_names_and_types();
279 std::vector<foxglove::ServiceId> servicesToRemove;
281 const auto it = std::find_if(serviceNamesAndTypes.begin(), serviceNamesAndTypes.end(),
282 [service](
const auto& serviceNameAndTypes) {
283 return serviceNameAndTypes.first == service.second.name;
285 if (it == serviceNamesAndTypes.end()) {
286 servicesToRemove.push_back(service.first);
289 for (
auto serviceId : servicesToRemove) {
290 _advertisedServices.erase(serviceId);
292 _server->removeServices(servicesToRemove);
295 std::vector<foxglove::ServiceWithoutId> newServices;
296 for (
const auto& serviceNamesAndType : serviceNamesAndTypes) {
297 const auto& serviceName = serviceNamesAndType.first;
298 const auto& datatypes = serviceNamesAndType.second;
301 if (std::find_if(_advertisedServices.begin(), _advertisedServices.end(),
302 [serviceName](
const auto& idWithService) {
303 return idWithService.second.name == serviceName;
304 }) != _advertisedServices.end()) {
319 service.
name = serviceName;
320 service.
type = datatypes.front();
333 RCLCPP_WARN(this->get_logger(),
334 "IDL message definition format cannot be communicated over ws-protocol. " 335 "Service \"%s\" (%s) may not decode correctly in clients",
336 service.
name.c_str(), service.
type.c_str());
342 RCLCPP_WARN(this->get_logger(),
"Could not find definition for type %s: %s",
347 }
catch (
const std::exception& err) {
348 RCLCPP_WARN(this->get_logger(),
"Failed to add service \"%s\" (%s): %s", service.
name.c_str(),
349 service.
type.c_str(), err.what());
353 newServices.push_back(service);
356 const auto serviceIds =
_server->addServices(newServices);
357 for (
size_t i = 0; i < serviceIds.size(); ++i) {
358 _advertisedServices.emplace(serviceIds[i], newServices[i]);
363 const std::map<std::string, std::vector<std::string>>& topicNamesAndTypes) {
366 for (
const auto& topicNameAndType : topicNamesAndTypes) {
367 const auto& topicName = topicNameAndType.first;
372 const auto publishersInfo = get_publishers_info_by_topic(topicName);
373 const auto subscribersInfo = get_subscriptions_info_by_topic(topicName);
374 std::unordered_set<std::string> publisherIds, subscriberIds;
375 for (
const auto& publisher : publishersInfo) {
376 const auto& ns = publisher.node_namespace();
377 const auto sep = (!ns.empty() && ns.back() ==
'/') ?
"" :
"/";
378 publisherIds.insert(ns + sep + publisher.node_name());
380 for (
const auto& subscriber : subscribersInfo) {
381 const auto& ns = subscriber.node_namespace();
382 const auto sep = (!ns.empty() && ns.back() ==
'/') ?
"" :
"/";
383 subscriberIds.insert(ns + sep + subscriber.node_name());
385 publishers.emplace(topicName, publisherIds);
386 subscribers.emplace(topicName, subscriberIds);
390 for (
const auto& fqnNodeName : get_node_names()) {
392 const auto serviceNamesAndTypes = get_service_names_and_types_by_node(nodeName, nodeNs);
394 for (
const auto& [serviceName, serviceTypes] : serviceNamesAndTypes) {
397 services[serviceName].insert(fqnNodeName);
402 _server->updateConnectionGraph(publishers, subscribers, services);
416 channelId,
"Received subscribe request for unknown channel " + std::to_string(channelId));
419 const auto& channel = it->second;
420 const auto& topic = channel.topic;
421 const auto&
datatype = channel.schemaName;
424 auto [subscriptionsIt, firstSubscription] =
426 auto& subscriptionsByClient = subscriptionsIt->second;
428 if (!firstSubscription &&
429 subscriptionsByClient.find(clientHandle) != subscriptionsByClient.end()) {
431 channelId,
"Client is already subscribed to channel " + std::to_string(channelId));
434 rclcpp::SubscriptionEventCallbacks eventCallbacks;
435 eventCallbacks.incompatible_qos_callback = [&](
const rclcpp::QOSRequestedIncompatibleQoSInfo&) {
436 RCLCPP_ERROR(this->get_logger(),
"Incompatible subscriber QoS settings for topic \"%s\" (%s)",
440 rclcpp::SubscriptionOptions subscriptionOptions;
441 subscriptionOptions.event_callbacks = eventCallbacks;
448 size_t reliabilityReliableEndpointsCount = 0;
449 size_t durabilityTransientLocalEndpointsCount = 0;
451 const auto publisherInfo = this->get_publishers_info_by_topic(topic);
452 for (
const auto& publisher : publisherInfo) {
453 const auto& qos = publisher.qos_profile();
454 if (qos.reliability() == rclcpp::ReliabilityPolicy::Reliable) {
455 ++reliabilityReliableEndpointsCount;
457 if (qos.durability() == rclcpp::DurabilityPolicy::TransientLocal) {
458 ++durabilityTransientLocalEndpointsCount;
463 rclcpp::QoS qos{rclcpp::KeepLast(std::max(depth,
_minQosDepth))};
466 if (reliabilityReliableEndpointsCount == publisherInfo.size()) {
469 if (reliabilityReliableEndpointsCount > 0) {
472 "Some, but not all, publishers on topic '%s' are offering QoSReliabilityPolicy.RELIABLE. " 473 "Falling back to QoSReliabilityPolicy.BEST_EFFORT as it will connect to all publishers",
480 if (durabilityTransientLocalEndpointsCount == publisherInfo.size()) {
481 qos.transient_local();
483 if (durabilityTransientLocalEndpointsCount > 0) {
484 RCLCPP_WARN(this->get_logger(),
485 "Some, but not all, publishers on topic '%s' are offering " 486 "QoSDurabilityPolicy.TRANSIENT_LOCAL. Falling back to " 487 "QoSDurabilityPolicy.VOLATILE as it will connect to all publishers",
490 qos.durability_volatile();
493 if (firstSubscription) {
494 RCLCPP_INFO(this->get_logger(),
"Subscribing to topic \"%s\" (%s) on channel %d", topic.c_str(),
498 RCLCPP_INFO(this->get_logger(),
"Adding subscriber #%zu to topic \"%s\" (%s) on channel %d",
499 subscriptionsByClient.size(), topic.c_str(),
datatype.c_str(), channelId);
503 auto subscriber = this->create_generic_subscription(
506 subscriptionOptions);
507 subscriptionsByClient.emplace(clientHandle, std::move(subscriber));
508 }
catch (
const std::exception& ex) {
510 channelId,
"Failed to subscribe to topic " + topic +
" (" +
datatype +
"): " + ex.what());
520 channelId,
"Received unsubscribe request for unknown channel " + std::to_string(channelId));
522 const auto& channel = channelIt->second;
527 std::to_string(channelId) +
528 " that was not subscribed to");
531 auto& subscriptionsByClient = subscriptionsIt->second;
532 const auto clientSubscription = subscriptionsByClient.find(clientHandle);
533 if (clientSubscription == subscriptionsByClient.end()) {
535 channelId,
"Received unsubscribe request for channel " + std::to_string(channelId) +
536 "from a client that was not subscribed to this channel");
539 subscriptionsByClient.erase(clientSubscription);
540 if (subscriptionsByClient.empty()) {
541 RCLCPP_INFO(this->get_logger(),
"Unsubscribing from topic \"%s\" (%s) on channel %d",
542 channel.topic.c_str(), channel.schemaName.c_str(), channelId);
545 RCLCPP_INFO(this->get_logger(),
546 "Removed one subscription from channel %d (%zu subscription(s) left)", channelId,
547 subscriptionsByClient.size());
556 auto [clientPublicationsIt, isFirstPublication] =
559 auto& clientPublications = clientPublicationsIt->second;
561 if (!isFirstPublication &&
562 clientPublications.find(advertisement.
channelId) != clientPublications.end()) {
565 "Received client advertisement from " +
_server->remoteEndpointString(hdl) +
" for channel " +
566 std::to_string(advertisement.
channelId) +
" it had already advertised");
571 const auto& topicName = advertisement.
topic;
572 const auto& topicType = advertisement.
schemaName;
576 const auto otherPublishers = get_publishers_info_by_topic(topicName);
577 const auto otherPublisherIt =
578 std::find_if(otherPublishers.begin(), otherPublishers.end(),
579 [
this](
const rclcpp::TopicEndpointInfo& endpoint) {
580 return endpoint.node_name() != this->get_name() ||
581 endpoint.node_namespace() != this->get_namespace();
583 rclcpp::QoS qos = otherPublisherIt == otherPublishers.end() ? rclcpp::SystemDefaultsQoS()
584 : otherPublisherIt->qos_profile();
589 if (qos.history() == rclcpp::HistoryPolicy::Unknown) {
590 qos.history(rclcpp::HistoryPolicy::SystemDefault);
592 rclcpp::PublisherOptions publisherOptions{};
594 auto publisher = this->create_generic_publisher(topicName, topicType, qos, publisherOptions);
596 RCLCPP_INFO(this->get_logger(),
"Client %s is advertising \"%s\" (%s) on channel %d",
597 _server->remoteEndpointString(hdl).c_str(), topicName.c_str(), topicType.c_str(),
601 clientPublications.emplace(advertisement.
channelId, std::move(publisher));
602 }
catch (
const std::exception& ex) {
604 std::string(
"Failed to create publisher: ") + ex.what());
614 channelId,
"Ignoring client unadvertisement from " +
_server->remoteEndpointString(hdl) +
615 " for unknown channel " + std::to_string(channelId) +
616 ", client has no advertised topics");
619 auto& clientPublications = it->second;
620 auto it2 = clientPublications.find(channelId);
621 if (it2 == clientPublications.end()) {
623 channelId,
"Ignoring client unadvertisement from " +
_server->remoteEndpointString(hdl) +
624 " for unknown channel " + std::to_string(channelId) +
", client has " +
625 std::to_string(clientPublications.size()) +
" advertised topic(s)");
628 const auto& publisher = it2->second;
629 RCLCPP_INFO(this->get_logger(),
630 "Client %s is no longer advertising %s (%zu subscribers) on channel %d",
631 _server->remoteEndpointString(hdl).c_str(), publisher->get_topic_name(),
632 publisher->get_subscription_count(), channelId);
634 clientPublications.erase(it2);
635 if (clientPublications.empty()) {
642 this->create_wall_timer(1
s, []() {});
647 rclcpp::GenericPublisher::SharedPtr publisher;
655 channelId,
"Dropping client message from " +
_server->remoteEndpointString(hdl) +
656 " for unknown channel " + std::to_string(channelId) +
657 ", client has no advertised topics");
660 auto& clientPublications = it->second;
661 auto it2 = clientPublications.find(channelId);
662 if (it2 == clientPublications.end()) {
664 channelId,
"Dropping client message from " +
_server->remoteEndpointString(hdl) +
665 " for unknown channel " + std::to_string(channelId) +
", client has " +
666 std::to_string(clientPublications.size()) +
" advertised topic(s)");
668 publisher = it2->second;
672 rclcpp::SerializedMessage serializedMessage{message.
getLength()};
673 auto& rclSerializedMsg = serializedMessage.get_rcl_serialized_message();
674 std::memcpy(rclSerializedMsg.buffer, message.
getData(), message.
getLength());
675 rclSerializedMsg.buffer_length = message.
getLength();
678 publisher->publish(serializedMessage);
682 const std::optional<std::string>& requestId,
688 std::vector<std::string> parameterNames(parameters.size());
689 for (
size_t i = 0; i < parameters.size(); ++i) {
690 parameterNames[i] = parameters[i].getName();
697 const std::optional<std::string>& requestId,
699 const auto params =
_paramInterface->getParams(parameters, std::chrono::seconds(5));
700 _server->publishParameterValues(hdl, params, requestId);
714 _server->updateParameterValues(parameters);
719 case LogLevel::Debug:
720 RCLCPP_DEBUG(this->get_logger(),
"[WS] %s", msg);
723 RCLCPP_INFO(this->get_logger(),
"[WS] %s", msg);
726 RCLCPP_WARN(this->get_logger(),
"[WS] %s", msg);
728 case LogLevel::Error:
729 RCLCPP_ERROR(this->get_logger(),
"[WS] %s", msg);
731 case LogLevel::Critical:
732 RCLCPP_FATAL(this->get_logger(),
"[WS] %s", msg);
739 std::shared_ptr<rclcpp::SerializedMessage> msg) {
742 const auto timestamp = this->now().nanoseconds();
743 assert(timestamp >= 0 &&
"Timestamp is negative");
744 const auto rclSerializedMsg = msg->get_rcl_serialized_message();
745 _server->sendMessage(clientHandle, channelId, static_cast<uint64_t>(timestamp),
746 rclSerializedMsg.buffer, rclSerializedMsg.buffer_length);
751 RCLCPP_DEBUG(this->get_logger(),
"Received a request for service %d", request.
serviceId);
758 "Service with id " + std::to_string(request.
serviceId) +
" does not exist");
764 auto clientOptions = rcl_client_get_default_options();
765 auto genClient = GenericClient::make_shared(
766 this->get_node_base_interface().
get(), this->get_node_graph_interface(),
767 serviceIt->second.name, serviceIt->second.type, clientOptions);
770 }
catch (
const std::exception& ex) {
773 "Failed to create service client for service " + serviceIt->second.name +
": " + ex.what());
777 auto client = clientIt->second;
778 if (!client->wait_for_service(1
s)) {
780 "Service " + serviceIt->second.name +
" is not available");
783 auto reqMessage = std::make_shared<rclcpp::SerializedMessage>(request.
data.size());
784 auto& rclSerializedMsg = reqMessage->get_rcl_serialized_message();
785 std::memcpy(rclSerializedMsg.buffer, request.
data.data(), request.
data.size());
786 rclSerializedMsg.buffer_length = request.
data.size();
788 auto responseReceivedCallback = [
this, request,
790 const auto serializedResponseMsg = future.get()->get_rcl_serialized_message();
792 std::vector<uint8_t>(serializedResponseMsg.buffer_length)};
793 std::memcpy(
response.data.data(), serializedResponseMsg.buffer,
794 serializedResponseMsg.buffer_length);
797 client->async_send_request(reqMessage, responseReceivedCallback);
806 #include <rclcpp_components/register_node_macro.hpp> const char FOXGLOVE_BRIDGE_GIT_HASH[]
ClientAdvertisement advertisement
std::unique_ptr< std::thread > _rosgraphPollThread
constexpr char SERVICE_RESPONSE_MESSAGE_SUFFIX[]
constexpr char PARAM_USE_COMPRESSION[]
constexpr char PARAM_CAPABILITIES[]
ros::Subscriber _clockSubscription
constexpr char PARAM_SEND_BUFFER_LIMIT[]
std::unordered_map< foxglove::ServiceId, foxglove::ServiceWithoutId > _advertisedServices
void updateAdvertisedServices()
std::pair< std::string, std::string > getNodeAndNodeNamespace(const std::string &fqnNodeName)
foxglove::MessageDefinitionCache _messageDefinitionCache
std::shared_future< SharedResponse > SharedFuture
constexpr char PARAM_KEYFILE[]
std::vector< std::regex > _serviceWhitelistPatterns
std::vector< std::string > _capabilities
std::vector< std::regex > clientTopicWhitelistPatterns
constexpr char PARAM_INCLUDE_HIDDEN[]
void getParameters(const std::vector< std::string > ¶meters, const std::optional< std::string > &requestId, ConnectionHandle hdl)
void rosgraphPollThread()
void unsubscribe(foxglove::ChannelId channelId, ConnectionHandle clientHandle)
constexpr char PARAM_CERTFILE[]
virtual ~FoxgloveBridge()
bool isWhitelisted(const std::string &name, const std::vector< std::regex > ®exPatterns)
void subscribe(foxglove::ChannelId channelId, ConnectionHandle clientHandle)
std::string responseSchema
bool hasCapability(const std::string &capability)
constexpr char PARAM_MIN_QOS_DEPTH[]
std::pair< std::string, std::string > TopicAndDatatype
void subscribeParameters(const std::vector< std::string > ¶meters, foxglove::ParameterSubscriptionOperation op, ConnectionHandle)
void updateAdvertisedTopics()
const char FOXGLOVE_BRIDGE_VERSION[]
std::vector< uint8_t > data
std::unordered_map< std::string, std::string > metadata
constexpr char PARAM_USETLS[]
rclcpp::CallbackGroup::SharedPtr _clientPublishCallbackGroup
std::function< void(ChannelId, ConnectionHandle)> subscribeHandler
rclcpp::CallbackGroup::SharedPtr _subscriptionCallbackGroup
void setParameters(const std::vector< foxglove::Parameter > ¶meters, const std::optional< std::string > &requestId, ConnectionHandle hdl)
std::unordered_map< foxglove::ChannelId, SubscriptionsByClient > _subscriptions
std::unordered_map< std::string, std::unordered_set< std::string > > MapOfSets
constexpr char CAPABILITY_PARAMETERS_SUBSCRIBE[]
constexpr char PARAM_ADDRESS[]
PublicationsByClient _clientAdvertisedTopics
std::size_t getLength() const
void subscribeConnectionGraph(bool subscribe)
void logHandler(foxglove::WebSocketLogLevel level, char const *msg)
std::shared_mutex _servicesMutex
void clientMessage(const foxglove::ClientMessage &clientMsg, ConnectionHandle clientHandle)
void updateConnectionGraph(const std::map< std::string, std::vector< std::string >> &topicNamesAndTypes)
constexpr char CAPABILITY_TIME[]
const char * what() const noexcept override
std::map< ConnectionHandle, ros::Subscriber, std::owner_less<> > SubscriptionsByClient
constexpr char PARAM_PORT[]
constexpr char SERVICE_REQUEST_MESSAGE_SUFFIX[]
void parameterUpdates(XmlRpc::XmlRpcValue ¶ms, XmlRpc::XmlRpcValue &result)
rclcpp::CallbackGroup::SharedPtr _servicesCallbackGroup
constexpr char PARAM_MAX_QOS_DEPTH[]
constexpr char PARAM_TOPIC_WHITELIST[]
constexpr char PARAM_PARAMETER_WHITELIST[]
constexpr char PARAM_CLIENT_TOPIC_WHITELIST[]
const char * WebSocketUserAgent()
std::pair< MessageDefinitionFormat, std::string > get_full_text(const std::string &package_resource_name)
constexpr char CAPABILITY_SERVICES[]
size_t sendBufferLimitBytes
std::vector< std::string > capabilities
std::mutex _subscriptionsMutex
ParameterSubscriptionOperation
void declareParameters(rclcpp::Node *node)
void serviceRequest(const foxglove::ServiceRequest &request, ConnectionHandle clientHandle)
websocketpp::connection_hdl ConnectionHandle
constexpr char PARAM_SERVICE_WHITELIST[]
std::vector< std::regex > parseRegexStrings(rclcpp::Node *node, const std::vector< std::string > &strings)
constexpr char CAPABILITY_PARAMETERS[]
std::string requestSchema
const uint8_t * getData() const
std::vector< std::regex > _topicWhitelistPatterns
std::unordered_map< foxglove::ChannelId, foxglove::ChannelWithoutId > _advertisedTopics
std::unordered_map< foxglove::ClientChannelId, ros::Publisher > ClientPublications
ClientChannelId channelId
std::unique_ptr< foxglove::ServerInterface< ConnectionHandle > > _server
void rosMessageHandler(const foxglove::ChannelId channelId, ConnectionHandle clientHandle, const ros::MessageEvent< ros_babel_fish::BabelFishMessage const > &msgEvent)
void clientUnadvertise(foxglove::ClientChannelId channelId, ConnectionHandle clientHandle)
std::shared_ptr< ParameterInterface > _paramInterface
void clientAdvertise(const foxglove::ClientAdvertisement &channel, ConnectionHandle clientHandle)
std::unordered_map< foxglove::ServiceId, GenericClient::SharedPtr > _serviceClients
const std::string response
std::atomic< bool > _subscribeGraphUpdates
std::vector< std::string > supportedEncodings
std::mutex _clientAdvertisementsMutex