6 #include <shared_mutex> 8 #include <unordered_set> 15 #include <ros_babel_fish/babel_fish_message.h> 16 #include <ros_babel_fish/generation/providers/integrated_description_provider.h> 17 #include <rosgraph_msgs/Clock.h> 18 #include <websocketpp/common/connection_hdl.hpp> 22 #include <foxglove_bridge/param_utils.hpp> 31 std::unordered_set<std::string>
set;
32 for (
int i = 0; i < v.
size(); ++i) {
63 const auto address = nhp.param<std::string>(
"address",
DEFAULT_ADDRESS);
65 const auto send_buffer_limit =
static_cast<size_t>(
67 const auto useTLS = nhp.param<
bool>(
"tls",
false);
68 const auto certfile = nhp.param<std::string>(
"certfile",
"");
69 const auto keyfile = nhp.param<std::string>(
"keyfile",
"");
71 const auto useCompression = nhp.param<
bool>(
"use_compression",
false);
72 _useSimTime = nhp.param<
bool>(
"/use_sim_time",
false);
73 const auto sessionId = nhp.param<std::string>(
"/run_id", std::to_string(std::time(
nullptr)));
78 const auto topicWhitelistPatterns =
79 nhp.param<std::vector<std::string>>(
"topic_whitelist", {
".*"});
81 if (topicWhitelistPatterns.size() != _topicWhitelistPatterns.size()) {
82 ROS_ERROR(
"Failed to parse one or more topic whitelist patterns");
84 const auto paramWhitelist = nhp.param<std::vector<std::string>>(
"param_whitelist", {
".*"});
86 if (paramWhitelist.size() != _paramWhitelistPatterns.size()) {
87 ROS_ERROR(
"Failed to parse one or more param whitelist patterns");
90 const auto serviceWhitelist = nhp.param<std::vector<std::string>>(
"service_whitelist", {
".*"});
92 if (serviceWhitelist.size() != _serviceWhitelistPatterns.size()) {
93 ROS_ERROR(
"Failed to parse one or more service whitelist patterns");
96 const auto clientTopicWhitelist =
97 nhp.param<std::vector<std::string>>(
"client_topic_whitelist", {
".*"});
99 if (clientTopicWhitelist.size() != clientTopicWhitelistPatterns.size()) {
100 ROS_ERROR(
"Failed to parse one or more service whitelist patterns");
103 const char* rosDistro = std::getenv(
"ROS_DISTRO");
104 ROS_INFO(
"Starting foxglove_bridge (%s, %s@%s) with %s", rosDistro,
115 serverOptions.
metadata = {{
"ROS_DISTRO", rosDistro}};
118 serverOptions.
useTls = useTLS;
120 serverOptions.
keyfile = keyfile;
127 _server = foxglove::ServerFactory::createServer<ConnectionHandle>(
"foxglove_bridge",
132 hdlrs.unsubscribeHandler =
135 std::placeholders::_1, std::placeholders::_2);
137 std::placeholders::_1, std::placeholders::_2);
139 std::placeholders::_1, std::placeholders::_2);
140 hdlrs.parameterRequestHandler =
142 std::placeholders::_2, std::placeholders::_3);
143 hdlrs.parameterChangeHandler =
145 std::placeholders::_2, std::placeholders::_3);
146 hdlrs.parameterSubscriptionHandler =
148 std::placeholders::_2, std::placeholders::_3);
150 std::placeholders::_1, std::placeholders::_2);
151 hdlrs.subscribeConnectionGraphHandler = [
this](
bool subscribe) {
154 _server->setHandlers(std::move(hdlrs));
156 _server->start(address, static_cast<uint16_t>(port));
159 std::placeholders::_1, std::placeholders::_2));
166 "/clock", 10, [&](
const rosgraph_msgs::Clock::ConstPtr msg) {
167 _server->broadcastTime(msg->clock.toNSec());
170 }
catch (
const std::exception& err) {
171 ROS_ERROR(
"Failed to start websocket server: %s", err.what());
185 template <
class T1,
class T2>
186 std::size_t
operator()(
const std::pair<T1, T2>& pair)
const {
187 return std::hash<T1>()(pair.first) ^ std::hash<T2>()(pair.second);
196 const std::string errMsg =
197 "Received subscribe request for unknown channel " + std::to_string(channelId);
202 const auto& channel = it->second;
203 const auto& topic = channel.topic;
204 const auto&
datatype = channel.schemaName;
207 auto [subscriptionsIt, firstSubscription] =
209 auto& subscriptionsByClient = subscriptionsIt->second;
211 if (!firstSubscription &&
212 subscriptionsByClient.find(clientHandle) != subscriptionsByClient.end()) {
213 const std::string errMsg =
214 "Client is already subscribed to channel " + std::to_string(channelId);
220 subscriptionsByClient.emplace(
221 clientHandle,
getMTNodeHandle().subscribe<ros_babel_fish::BabelFishMessage>(
222 topic, SUBSCRIPTION_QUEUE_LENGTH,
224 std::placeholders::_1)));
225 if (firstSubscription) {
226 ROS_INFO(
"Subscribed to topic \"%s\" (%s) on channel %d", topic.c_str(),
datatype.c_str(),
230 ROS_INFO(
"Added subscriber #%zu to topic \"%s\" (%s) on channel %d",
231 subscriptionsByClient.size(), topic.c_str(),
datatype.c_str(), channelId);
233 }
catch (
const std::exception& ex) {
234 const std::string errMsg =
235 "Failed to subscribe to topic '" + topic +
"' (" +
datatype +
"): " + ex.what();
246 const std::string errMsg =
247 "Received unsubscribe request for unknown channel " + std::to_string(channelId);
251 const auto& channel = channelIt->second;
256 std::to_string(channelId) +
257 " that was not subscribed to ");
260 auto& subscriptionsByClient = subscriptionsIt->second;
261 const auto clientSubscription = subscriptionsByClient.find(clientHandle);
262 if (clientSubscription == subscriptionsByClient.end()) {
264 channelId,
"Received unsubscribe request for channel " + std::to_string(channelId) +
265 "from a client that was not subscribed to this channel");
268 subscriptionsByClient.erase(clientSubscription);
269 if (subscriptionsByClient.empty()) {
270 ROS_INFO(
"Unsubscribing from topic \"%s\" (%s) on channel %d", channel.topic.c_str(),
271 channel.schemaName.c_str(), channelId);
274 ROS_INFO(
"Removed one subscription from channel %d (%zu subscription(s) left)", channelId,
275 subscriptionsByClient.size());
281 if (channel.
encoding != ROS1_CHANNEL_ENCODING) {
283 channel.
channelId,
"Unsupported encoding. Only '" + std::string(ROS1_CHANNEL_ENCODING) +
284 "' encoding is supported at the moment.");
290 auto [clientPublicationsIt, isFirstPublication] =
293 auto& clientPublications = clientPublicationsIt->second;
294 if (!isFirstPublication &&
295 clientPublications.find(channel.
channelId) != clientPublications.end()) {
297 channel.
channelId,
"Received client advertisement from " +
298 _server->remoteEndpointString(clientHandle) +
" for channel " +
299 std::to_string(channel.
channelId) +
" it had already advertised");
303 if (!msgDescription) {
305 channel.
channelId,
"Failed to retrieve type information of data type '" +
312 advertiseOptions.
latch =
false;
313 advertiseOptions.
md5sum = msgDescription->md5;
320 clientPublications.insert({channel.
channelId, std::move(publisher)});
321 ROS_INFO(
"Client %s is advertising \"%s\" (%s) on channel %d",
322 _server->remoteEndpointString(clientHandle).c_str(), channel.
topic.c_str(),
326 "Failed to create publisher for topic " + channel.
topic +
"(" + channel.
schemaName +
")";
338 channelId,
"Ignoring client unadvertisement from " +
339 _server->remoteEndpointString(clientHandle) +
" for unknown channel " +
340 std::to_string(channelId) +
", client has no advertised topics");
343 auto& clientPublications = clientPublicationsIt->second;
345 auto channelPublicationIt = clientPublications.find(channelId);
346 if (channelPublicationIt == clientPublications.end()) {
348 channelId,
"Ignoring client unadvertisement from " +
349 _server->remoteEndpointString(clientHandle) +
" for unknown channel " +
350 std::to_string(channelId) +
", client has " +
351 std::to_string(clientPublications.size()) +
" advertised topic(s)");
354 const auto& publisher = channelPublicationIt->second;
355 ROS_INFO(
"Client %s is no longer advertising %s (%d subscribers) on channel %d",
356 _server->remoteEndpointString(clientHandle).c_str(), publisher.getTopic().c_str(),
357 publisher.getNumSubscribers(), channelId);
358 clientPublications.erase(channelPublicationIt);
360 if (clientPublications.empty()) {
366 ros_babel_fish::BabelFishMessage::Ptr msg(
new ros_babel_fish::BabelFishMessage);
367 msg->read(clientMsg);
375 channelId,
"Dropping client message from " +
_server->remoteEndpointString(clientHandle) +
376 " for unknown channel " + std::to_string(channelId) +
377 ", client has no advertised topics");
380 auto& clientPublications = clientPublicationsIt->second;
383 if (channelPublicationIt == clientPublications.end()) {
385 channelId,
"Dropping client message from " +
_server->remoteEndpointString(clientHandle) +
386 " for unknown channel " + std::to_string(channelId) +
", client has " +
387 std::to_string(clientPublications.size()) +
" advertised topic(s)");
391 channelPublicationIt->second.publish(msg);
392 }
catch (
const std::exception& ex) {
394 channelPublicationIt->second.getTopic() +
408 std::vector<std::string> serviceNames;
412 if (querySystemState) {
416 static_cast<int>(result[0]) == 1) {
417 const auto& systemState = result[2];
418 const auto& publishersXmlRpc = systemState[0];
419 const auto& subscribersXmlRpc = systemState[1];
420 const auto& servicesXmlRpc = systemState[2];
422 for (
int i = 0; i < servicesXmlRpc.size(); ++i) {
423 const std::string& name = servicesXmlRpc[i][0];
425 serviceNames.push_back(name);
426 services.emplace(name, rpcValueToStringSet(servicesXmlRpc[i][1]));
429 for (
int i = 0; i < publishersXmlRpc.size(); ++i) {
430 const std::string& name = publishersXmlRpc[i][0];
432 publishers.emplace(name, rpcValueToStringSet(publishersXmlRpc[i][1]));
435 for (
int i = 0; i < subscribersXmlRpc.size(); ++i) {
436 const std::string& name = subscribersXmlRpc[i][0];
438 subscribers.emplace(name, rpcValueToStringSet(subscribersXmlRpc[i][1]));
442 ROS_WARN(
"Failed to call getSystemState: %s", result.
toXml().c_str());
447 if (servicesEnabled) {
451 _server->updateConnectionGraph(publishers, subscribers, services);
457 const auto nextUpdateMs = std::max(
465 std::vector<ros::master::TopicInfo> topicNamesAndTypes;
467 ROS_WARN(
"Failed to retrieve published topics from ROS master.");
471 std::unordered_set<TopicAndDatatype, PairHash> latestTopics;
472 latestTopics.reserve(topicNamesAndTypes.size());
473 for (
const auto& topicNameAndType : topicNamesAndTypes) {
474 const auto& topicName = topicNameAndType.name;
475 const auto&
datatype = topicNameAndType.datatype;
479 latestTopics.emplace(topicName,
datatype);
483 if (
const auto numIgnoredTopics = topicNamesAndTypes.size() - latestTopics.size()) {
485 "%zu topics have been ignored as they do not match any pattern on the topic whitelist",
492 std::vector<foxglove::ChannelId> channelIdsToRemove;
495 channelIt->second.schemaName};
496 if (latestTopics.find(topicAndDatatype) == latestTopics.end()) {
497 const auto channelId = channelIt->first;
498 channelIdsToRemove.push_back(channelId);
500 ROS_DEBUG(
"Removed channel %d for topic \"%s\" (%s)", channelId,
501 topicAndDatatype.first.c_str(), topicAndDatatype.second.c_str());
507 _server->removeChannels(channelIdsToRemove);
510 std::vector<foxglove::ChannelWithoutId> channelsToAdd;
511 for (
const auto& topicAndDatatype : latestTopics) {
513 [topicAndDatatype](
const auto& channelIdAndChannel) {
514 const auto& channel = channelIdAndChannel.second;
515 return channel.topic == topicAndDatatype.first &&
516 channel.schemaName == topicAndDatatype.second;
522 newChannel.
topic = topicAndDatatype.first;
523 newChannel.schemaName = topicAndDatatype.second;
527 const auto msgDescription =
529 if (msgDescription) {
530 newChannel.schema = msgDescription->message_definition;
532 ROS_WARN(
"Could not find definition for type %s", topicAndDatatype.second.c_str());
535 newChannel.schema =
"";
537 }
catch (
const std::exception& err) {
538 ROS_WARN(
"Failed to add channel for topic \"%s\" (%s): %s", topicAndDatatype.first.c_str(),
539 topicAndDatatype.second.c_str(), err.what());
543 channelsToAdd.push_back(newChannel);
546 const auto channelIds =
_server->addChannels(channelsToAdd);
547 for (
size_t i = 0; i < channelsToAdd.size(); ++i) {
548 const auto channelId = channelIds[i];
549 const auto& channel = channelsToAdd[i];
551 ROS_DEBUG(
"Advertising channel %d for topic \"%s\" (%s)", channelId, channel.topic.c_str(),
552 channel.schemaName.c_str());
560 std::vector<foxglove::ServiceId> servicesToRemove;
563 std::find_if(serviceNames.begin(), serviceNames.end(), [service](
const auto& serviceName) {
564 return serviceName == service.second.name;
566 if (it == serviceNames.end()) {
567 servicesToRemove.push_back(service.first);
570 for (
auto serviceId : servicesToRemove) {
571 _advertisedServices.erase(serviceId);
573 _server->removeServices(servicesToRemove);
576 std::vector<foxglove::ServiceWithoutId> newServices;
577 for (
const auto& serviceName : serviceNames) {
578 if (std::find_if(_advertisedServices.begin(), _advertisedServices.end(),
579 [&serviceName](
const auto& idWithService) {
580 return idWithService.second.name == serviceName;
581 }) != _advertisedServices.end()) {
586 if (serviceTypeFuture.wait_for(std::chrono::milliseconds(
587 SERVICE_TYPE_RETRIEVAL_TIMEOUT_MS)) != std::future_status::ready) {
588 ROS_WARN(
"Failed to retrieve type of service %s", serviceName.c_str());
593 const auto serviceType = serviceTypeFuture.get();
597 service.
name = serviceName;
598 service.
type = serviceType;
600 if (srvDescription) {
601 service.
requestSchema = srvDescription->request->message_definition;
602 service.
responseSchema = srvDescription->response->message_definition;
604 ROS_ERROR(
"Failed to retrieve type information for service '%s' of type '%s'",
605 serviceName.c_str(), serviceType.c_str());
611 newServices.push_back(service);
612 }
catch (
const std::exception& e) {
613 ROS_ERROR(
"Failed to retrieve service type or service description of service %s: %s",
614 serviceName.c_str(), e.what());
619 const auto serviceIds =
_server->addServices(newServices);
620 for (
size_t i = 0; i < serviceIds.size(); ++i) {
621 _advertisedServices.emplace(serviceIds[i], newServices[i]);
627 const bool allParametersRequested = parameters.empty();
628 std::vector<std::string> parameterNames = parameters;
629 if (allParametersRequested) {
631 const auto errMsg =
"Failed to retrieve parameter names";
633 throw std::runtime_error(errMsg);
638 std::vector<foxglove::Parameter> params;
639 for (
const auto& paramName : parameterNames) {
641 if (allParametersRequested) {
644 ROS_ERROR(
"Parameter '%s' is not on the allowlist", paramName.c_str());
653 }
catch (
const std::exception& ex) {
654 ROS_ERROR(
"Invalid parameter '%s': %s", paramName.c_str(), ex.what());
660 ROS_ERROR(
"Invalid parameter '%s'", paramName.c_str());
665 _server->publishParameterValues(hdl, params, requestId);
668 throw std::runtime_error(
"Failed to retrieve one or multiple parameters");
678 for (
const auto&
param : parameters) {
679 const auto paramName =
param.getName();
681 ROS_ERROR(
"Parameter '%s' is not on the allowlist", paramName.c_str());
687 const auto paramType =
param.getType();
688 const auto paramValue =
param.getValue();
689 if (paramType == ParameterType::PARAMETER_NOT_SET) {
690 nh.deleteParam(paramName);
692 nh.setParam(paramName,
toRosParam(paramValue));
694 }
catch (
const std::exception& ex) {
695 ROS_ERROR(
"Failed to set parameter '%s': %s", paramName.c_str(), ex.what());
701 ROS_ERROR(
"Failed to set parameter '%s'", paramName.c_str());
708 std::vector<std::string> parameterNames(parameters.size());
709 for (
size_t i = 0; i < parameters.size(); ++i) {
710 parameterNames[i] = parameters[i].getName();
716 throw std::runtime_error(
"Failed to set one or multiple parameters");
725 for (
const auto& paramName : parameters) {
727 ROS_ERROR(
"Parameter '%s' is not allowlist", paramName.c_str());
736 const std::string opName = std::string(opVerb) +
"Param";
738 ROS_DEBUG(
"%s '%s'", opName.c_str(), paramName.c_str());
740 ROS_WARN(
"Failed to %s '%s': %s", opVerb, paramName.c_str(), result.
toXml().c_str());
746 throw std::runtime_error(
"Failed to " + std::string(opVerb) +
" one or multiple parameters.");
752 result[1] = std::string(
"");
755 if (params.
size() != 3) {
756 ROS_ERROR(
"Parameter update called with invalid parameter size: %d", params.
size());
765 }
catch (
const std::exception& ex) {
766 ROS_ERROR(
"Failed to update parameter: %s", ex.what());
799 _server->sendMessage(clientHandle, channelId, receiptTimeNs, msg->buffer(), msg->size());
807 "Service with id " + std::to_string(request.
serviceId) +
" does not exist";
811 const auto& serviceName = serviceIt->second.name;
812 const auto& serviceType = serviceIt->second.type;
813 ROS_DEBUG(
"Received a service request for service %s (%s)", serviceName.c_str(),
814 serviceType.c_str());
818 "Service '" + serviceName +
"' does not exist");
822 if (!srvDescription) {
824 "Failed to retrieve type information for service " + serviceName +
"(" + serviceType +
")";
830 genReq.
type = genRes.
type = serviceType;
840 _server->sendServiceResponse(clientHandle, res);
843 request.
serviceId,
"Failed to call service " + serviceName +
"(" + serviceType +
")");
851 std::unique_ptr<foxglove::ServerInterface<ConnectionHandle>>
_server;
const char FOXGLOVE_BRIDGE_GIT_HASH[]
const boost::shared_ptr< ConstMessage > & getConstMessage() const
ClientAdvertisement advertisement
void updateAdvertisedServices(const std::vector< std::string > &serviceNames)
ROSCPP_DECL bool getParamNames(std::vector< std::string > &keys)
bool param(const std::string ¶m_name, T ¶m_val, const T &default_val)
std::string message_definition
ros::Subscriber _clockSubscription
std::shared_mutex _publicationsMutex
std::vector< uint8_t > data
std::unordered_map< foxglove::ServiceId, foxglove::ServiceWithoutId > _advertisedServices
void updateAdvertisedServices()
constexpr size_t DEFAULT_SEND_BUFFER_LIMIT_BYTES
std::pair< std::string, std::string > TopicAndDatatype
Timer createTimer(Rate r, Handler h, Obj o, bool oneshot=false, bool autostart=true) const
Subscriber subscribe(const std::string &topic, uint32_t queue_size, void(T::*fp)(M), T *obj, const TransportHints &transport_hints=TransportHints())
std::vector< std::regex > _serviceWhitelistPatterns
std::vector< std::string > _capabilities
XmlRpc::XmlRpcValue toRosParam(const foxglove::ParameterValue ¶m)
bool call(const std::string &service_name, MReq &req, MRes &res)
std::vector< std::regex > clientTopicWhitelistPatterns
void getParameters(const std::vector< std::string > ¶meters, const std::optional< std::string > &requestId, ConnectionHandle hdl)
void unsubscribe(foxglove::ChannelId channelId, ConnectionHandle clientHandle)
virtual ~FoxgloveBridge()
std::size_t operator()(const std::pair< T1, T2 > &pair) const
ROSCPP_DECL bool getTopics(V_TopicInfo &topics)
const std::string & getMessage() const
bool isWhitelisted(const std::string &name, const std::vector< std::regex > ®exPatterns)
void subscribe(foxglove::ChannelId channelId, ConnectionHandle clientHandle)
std::string responseSchema
ROSCPP_DECL std::string resolve(const std::string &name, bool remap=true)
std::vector< std::regex > parseRegexPatterns(const std::vector< std::string > &strings)
ROSCPP_DECL std::string clean(const std::string &name)
bool hasCapability(const std::string &capability)
ros::NodeHandle & getPrivateNodeHandle() const
std::pair< std::string, std::string > TopicAndDatatype
void subscribeParameters(const std::vector< std::string > ¶meters, foxglove::ParameterSubscriptionOperation op, ConnectionHandle)
constexpr uint32_t PUBLICATION_QUEUE_LENGTH
void updateAdvertisedTopics()
const char FOXGLOVE_BRIDGE_VERSION[]
std::vector< uint8_t > data
std::future< std::string > retrieveServiceType(const std::string &serviceName)
std::unordered_map< std::string, std::string > metadata
constexpr int SERVICE_TYPE_RETRIEVAL_TIMEOUT_MS
constexpr uint32_t SUBSCRIPTION_QUEUE_LENGTH
const std::string & getName() const
std::function< void(ChannelId, ConnectionHandle)> subscribeHandler
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
PublicationsByClient _clientAdvertisedTopics
constexpr double MIN_UPDATE_PERIOD_MS
void logHandler(foxglove::WebSocketLogLevel level, char const *msg)
std::shared_mutex _servicesMutex
bool getParam(const std::string &key, std::string &s) const
void updateAdvertisedTopicsAndServices(const ros::TimerEvent &)
void clientMessage(const foxglove::ClientMessage &clientMsg, ConnectionHandle clientHandle)
constexpr char CAPABILITY_TIME[]
ROSCPP_DECL bool execute(const std::string &method, const XmlRpc::XmlRpcValue &request, XmlRpc::XmlRpcValue &response, XmlRpc::XmlRpcValue &payload, bool wait_for_master)
constexpr int DEFAULT_MAX_UPDATE_MS
std::map< ConnectionHandle, ClientPublications, std::owner_less<> > PublicationsByClient
std::map< ConnectionHandle, ros::Subscriber, std::owner_less<> > SubscriptionsByClient
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
void parameterUpdates(XmlRpc::XmlRpcValue ¶ms, XmlRpc::XmlRpcValue &result)
std::vector< std::regex > _paramWhitelistPatterns
#define ROS_WARN_STREAM(args)
ros::NodeHandle & getMTNodeHandle() const
ros::Time getReceiptTime() const
const char * WebSocketUserAgent()
constexpr char CAPABILITY_SERVICES[]
std::string toXml() const
bool bind(const std::string &function_name, const XMLRPCFunc &cb)
size_t sendBufferLimitBytes
std::vector< std::string > capabilities
std::mutex _subscriptionsMutex
ParameterSubscriptionOperation
void serviceRequest(const foxglove::ServiceRequest &request, ConnectionHandle clientHandle)
websocketpp::connection_hdl ConnectionHandle
ROSCPP_DECL bool exists(const std::string &service_name, bool print_failure_reason)
constexpr char ROS1_CHANNEL_ENCODING[]
constexpr char DEFAULT_ADDRESS[]
std::string requestSchema
std::vector< std::regex > _topicWhitelistPatterns
#define ROS_ERROR_STREAM(args)
std::unordered_map< foxglove::ChannelId, foxglove::ChannelWithoutId > _advertisedTopics
std::unordered_map< foxglove::ClientChannelId, ros::Publisher > ClientPublications
foxglove::Parameter fromRosParam(const std::string &name, const XmlRpc::XmlRpcValue &value)
ClientChannelId channelId
ros::XMLRPCManager xmlrpcServer
std::unique_ptr< foxglove::ServerInterface< ConnectionHandle > > _server
#define PLUGINLIB_EXPORT_CLASS(class_type, base_class_type)
constexpr int DEFAULT_PORT
void rosMessageHandler(const foxglove::ChannelId channelId, ConnectionHandle clientHandle, const ros::MessageEvent< ros_babel_fish::BabelFishMessage const > &msgEvent)
void clientUnadvertise(foxglove::ClientChannelId channelId, ConnectionHandle clientHandle)
void clientAdvertise(const foxglove::ClientAdvertisement &channel, ConnectionHandle clientHandle)
const std::string & getServerURI() const
std::atomic< bool > _subscribeGraphUpdates
std::vector< std::string > supportedEncodings
constexpr std::array< const char *, 5 > DEFAULT_CAPABILITIES
ros_babel_fish::IntegratedDescriptionProvider _rosTypeInfoProvider