@@ -175,14 +175,15 @@ bool fixedMqtt2PrimitiveRos(mqtt::const_message_ptr mqtt_msg,
175
175
*
176
176
* @param [in] serialized_msg generic serialized ROS message
177
177
* @param [in] msg_type ROS message type, e.g. `std_msgs/msg/String`
178
+ * @param [in] fixed_encoding Truthfully this is just an ugly hack to change the encoding of bools in some cases
178
179
* @param [out] primitive string representation of primitive message data
179
180
*
180
181
* @return true if primitive ROS message type was found
181
182
* @return false if ROS message type is not primitive
182
183
*/
183
184
bool primitiveRosMessageToString (
184
185
const std::shared_ptr<rclcpp::SerializedMessage>& serialized_msg,
185
- const std::string& msg_type, std::string& primitive) {
186
+ const std::string& msg_type, bool fixed_encoding, std::string& primitive) {
186
187
187
188
bool found_primitive = true ;
188
189
@@ -193,7 +194,10 @@ bool primitiveRosMessageToString(
193
194
} else if (msg_type == " std_msgs/msg/Bool" ) {
194
195
std_msgs::msg::Bool msg;
195
196
deserializeRosMessage (*serialized_msg, msg);
196
- primitive = msg.data ? " 1" : " 0" ;
197
+ if (fixed_encoding)
198
+ primitive = msg.data ? " 1" : " 0" ;
199
+ else
200
+ primitive = msg.data ? " true" : " false" ;
197
201
} else if (msg_type == " std_msgs/msg/Char" ) {
198
202
std_msgs::msg::Char msg;
199
203
deserializeRosMessage (*serialized_msg, msg);
@@ -311,6 +315,10 @@ void MqttClient::loadParameters() {
311
315
declare_parameter (fmt::format (" bridge.ros2mqtt.{}.inject_timestamp" , ros_topic), rclcpp::ParameterType::PARAMETER_BOOL, param_desc);
312
316
param_desc.description = " ROS subscriber queue size" ;
313
317
declare_parameter (fmt::format (" bridge.ros2mqtt.{}.advanced.ros.queue_size" , ros_topic), rclcpp::ParameterType::PARAMETER_INTEGER, param_desc);
318
+ param_desc.description = " ROS subscriber QoS reliability" ;
319
+ declare_parameter (fmt::format (" bridge.ros2mqtt.{}.advanced.ros.qos.reliability" , ros_topic), rclcpp::ParameterType::PARAMETER_STRING, param_desc);
320
+ param_desc.description = " ROS subscriber QoS durability" ;
321
+ declare_parameter (fmt::format (" bridge.ros2mqtt.{}.advanced.ros.qos.durability" , ros_topic), rclcpp::ParameterType::PARAMETER_STRING, param_desc);
314
322
param_desc.description = " MQTT QoS value" ;
315
323
declare_parameter (fmt::format (" bridge.ros2mqtt.{}.advanced.mqtt.qos" , ros_topic), rclcpp::ParameterType::PARAMETER_INTEGER, param_desc);
316
324
param_desc.description = " whether to retain MQTT message" ;
@@ -437,6 +445,36 @@ void MqttClient::loadParameters() {
437
445
queue_size_param))
438
446
ros2mqtt.ros .queue_size = queue_size_param.as_int ();
439
447
448
+ rclcpp::Parameter durability_param;
449
+ if (get_parameter (fmt::format (" bridge.ros2mqtt.{}.advanced.ros.qos.durability" , mqtt_topic), durability_param)) {
450
+ const auto p = durability_param.as_string ();
451
+ if (p == " volatile" ) {
452
+ ros2mqtt.ros .qos .durability = rclcpp::DurabilityPolicy::Volatile;
453
+ } else if (p == " transient_local" ) {
454
+ ros2mqtt.ros .qos .durability = rclcpp::DurabilityPolicy::TransientLocal;
455
+ } else if (p == " auto" ) {
456
+ ros2mqtt.ros .qos .durability = {};
457
+ } else {
458
+ RCLCPP_ERROR (get_logger (), " Unexpected durability %s" , p.c_str ());
459
+ exit (EXIT_FAILURE);
460
+ }
461
+ }
462
+
463
+ rclcpp::Parameter reliability_param;
464
+ if (get_parameter (fmt::format (" bridge.ros2mqtt.{}.advanced.ros.qos.reliability" , mqtt_topic), reliability_param)) {
465
+ const auto p = reliability_param.as_string ();
466
+ if (p == " best_effort" ) {
467
+ ros2mqtt.ros .qos .reliability = rclcpp::ReliabilityPolicy::BestEffort;
468
+ } else if (p == " reliable" ) {
469
+ ros2mqtt.ros .qos .reliability = rclcpp::ReliabilityPolicy::Reliable;
470
+ } else if (p == " auto" ) {
471
+ ros2mqtt.ros .qos .reliability = {};
472
+ } else {
473
+ RCLCPP_ERROR (get_logger (), " Unexpected reliability %s" , p.c_str ());
474
+ exit (EXIT_FAILURE);
475
+ }
476
+ }
477
+
440
478
// ros2mqtt[k]/advanced/mqtt/qos
441
479
rclcpp::Parameter qos_param;
442
480
if (get_parameter (fmt::format (" bridge.ros2mqtt.{}.advanced.mqtt.qos" , ros_topic), qos_param))
@@ -628,64 +666,152 @@ void MqttClient::setup() {
628
666
std::bind (&MqttClient::setupSubscriptions, this ));
629
667
}
630
668
669
+ std::optional<rclcpp::QoS> MqttClient::getCompatibleQoS (const std::string &ros_topic, const rclcpp::TopicEndpointInfo &tei,
670
+ const Ros2MqttInterface &ros2mqtt) const
671
+ {
672
+ // the basic premise here is that we take the QoS from the publisher, overwrite any parts that are explicitly set
673
+ // via configuration then check if the result is compatible with the publisher
674
+ auto qos = tei.qos_profile ();
675
+
676
+ if (auto r = ros2mqtt.ros .qos .reliability )
677
+ qos.reliability (*r);
678
+ if (auto d = ros2mqtt.ros .qos .durability )
679
+ qos.durability (*d);
680
+ qos.keep_last (ros2mqtt.ros .queue_size );
681
+
682
+ const auto qres = rclcpp::qos_check_compatible (tei.qos_profile (), qos);
683
+
684
+ switch (qres.compatibility )
685
+ {
686
+ case rclcpp::QoSCompatibility::Ok:
687
+ return qos;
688
+ case rclcpp::QoSCompatibility::Warning:
689
+ RCLCPP_DEBUG (get_logger (), " QoS compatibility warning on topic %s - %s" , ros_topic.c_str (), qres.reason .c_str ());
690
+ // presumably this is still compatible
691
+ return qos;
692
+ case rclcpp::QoSCompatibility::Error:
693
+ default :
694
+ return {};
695
+ }
696
+
697
+ }
698
+
699
+ std::vector<rclcpp::TopicEndpointInfo> MqttClient::getCandidatePublishers (
700
+ const std::string& ros_topic, const Ros2MqttInterface& ros2mqtt) const
701
+ {
702
+ const auto &pubs = get_publishers_info_by_topic (ros_topic);
703
+
704
+ if (pubs.empty ())
705
+ return {};
706
+
707
+ std::vector<rclcpp::TopicEndpointInfo> ret;
708
+
709
+ ret.reserve (pubs.size ());
710
+
711
+ for (const auto &p : pubs)
712
+ {
713
+ const std::string msg_type = p.topic_type ();
714
+
715
+ // if the type isn't set, match aginst all t ypes, otherwise only match against mtching types
716
+ if (ros2mqtt.ros .msg_type .empty () || msg_type == ros2mqtt.ros .msg_type )
717
+ ret.push_back (p);
718
+ }
719
+
720
+ // If we found any matching types, return those
721
+ if (! ret.empty ())
722
+ return ret;
723
+
724
+ // If we didn't and we aren't fix type, then just return the full set of publishers
725
+ if (! ros2mqtt.fixed_type )
726
+ return pubs;
727
+
728
+ // None of these publishers will work... :sad_panda:
729
+ return {};
730
+ }
631
731
632
732
void MqttClient::setupSubscriptions () {
633
733
634
- // get info of all topics
635
- const auto all_topics_and_types = get_topic_names_and_types ();
734
+ // For each ros2mqtt interface, check if we need to do a lazy subscription (eg, lazy subscription would be if we
735
+ // are determining either the type of QoS dynamically). If we don't, then just make the fixed subscriber if its not
736
+ // already made.
737
+ //
738
+ // If we do, we check each publisher for a potential match. The first step is to filter down the list of
739
+ // publishers based on the type. If we are fixed type then that list only includes publishers with matching types.
740
+ // If we aren't its a little trickier. For dynamic types, if we already have matched against a type previously, the
741
+ // candidate list will include all publishers which have matching types if any exist. If none of the publishers
742
+ // have matching types, then the list will include all publishers.
743
+ //
744
+ // Then for each candidate publisher, check if their QoS is compatible with the QoS specic in the configuration. This
745
+ // is really just needed because there is the potential for someone to set half of the QoS to auto and half to
746
+ // explicit.
747
+ // Condition for requiring "lazy" subscription where we need to walk the
748
+ const auto requires_lazy_subscription = [] (const Ros2MqttInterface &ros2mqtt)
749
+ {
750
+ if (! ros2mqtt.fixed_type )
751
+ return true ;
752
+ if (ros2mqtt.ros .qos .reliability == std::nullopt ||
753
+ ros2mqtt.ros .qos .durability == std::nullopt)
754
+ return true ;
755
+ return false ;
756
+ };
636
757
637
- // check for ros2mqtt topics
638
758
for (auto & [ros_topic, ros2mqtt] : ros2mqtt_) {
639
759
640
- std::function<void (const std::shared_ptr<rclcpp::SerializedMessage> msg)>
641
- bound_callback_func =
760
+ std::function<void (const std::shared_ptr<rclcpp::SerializedMessage> msg)> bound_callback_func =
642
761
std::bind (&MqttClient::ros2mqtt, this , std::placeholders::_1, ros_topic);
643
762
644
- if (ros2mqtt.fixed_type && ! ros2mqtt.ros .subscriber ) {
763
+ if (! requires_lazy_subscription (ros2mqtt))
764
+ {
645
765
try {
766
+ if (ros2mqtt.ros .subscriber )
767
+ continue ;
768
+
646
769
ros2mqtt.ros .subscriber = create_generic_subscription (
647
770
ros_topic, ros2mqtt.ros .msg_type , ros2mqtt.ros .queue_size , bound_callback_func);
648
771
ros2mqtt.ros .is_stale = false ;
649
-
772
+ RCLCPP_INFO (get_logger (), " Subscribed ROS topic '%s' of type '%s'" ,
773
+ ros_topic.c_str (), ros2mqtt.ros .msg_type .c_str ());
650
774
} catch (rclcpp::exceptions::RCLError& e) {
651
775
RCLCPP_ERROR (get_logger (), " Failed to create generic subscriber: %s" ,
652
776
e.what ());
653
777
continue ;
654
778
}
655
- RCLCPP_INFO (get_logger (), " Subscribed ROS topic '%s' of type '%s'" ,
656
- ros_topic.c_str (), ros2mqtt.ros .msg_type .c_str ());
657
-
658
779
} else {
659
- const auto &pubs = get_publishers_info_by_topic (ros_topic);
780
+ const auto &pubs = getCandidatePublishers (ros_topic, ros2mqtt );
660
781
661
782
if (pubs.empty ()) continue ;
662
783
663
- const auto endpointInfo = pubs.front ();
664
- // check if message type has changed or if mapping is stale
665
- const std::string msg_type = endpointInfo.topic_type ();
784
+ for (auto endpointInfo : pubs) {
785
+ try {
786
+ // check if message type has changed or if mapping is stale
787
+ const std::string msg_type = endpointInfo.topic_type ();
666
788
667
- if (msg_type == ros2mqtt.ros .msg_type && !ros2mqtt.ros .is_stale ) continue ;
668
- ros2mqtt.ros .is_stale = false ;
669
- ros2mqtt.ros .msg_type = msg_type;
789
+ if (msg_type == ros2mqtt.ros .msg_type && !ros2mqtt.ros .is_stale && ros2mqtt.ros .subscriber )
790
+ continue ;
670
791
671
- try {
672
- auto qos = endpointInfo.qos_profile ();
673
- ros2mqtt.ros .subscriber = create_generic_subscription (
674
- ros_topic, msg_type, qos.keep_last (ros2mqtt.ros .queue_size ),
675
- bound_callback_func);
792
+ auto const qos = getCompatibleQoS (ros_topic, endpointInfo, ros2mqtt);
676
793
677
- } catch (rclcpp::exceptions::RCLError& e) {
678
- RCLCPP_ERROR (get_logger (), " Failed to create generic subscriber: %s" ,
679
- e.what ());
680
- continue ;
794
+ if (! qos)
795
+ continue ;
796
+
797
+ ros2mqtt.ros .is_stale = false ;
798
+ ros2mqtt.ros .msg_type = msg_type;
799
+
800
+ ros2mqtt.ros .subscriber = create_generic_subscription (
801
+ ros_topic, msg_type, *qos, bound_callback_func);
802
+
803
+ RCLCPP_INFO (get_logger (), " Subscribed ROS topic '%s' of type '%s'" ,
804
+ ros_topic.c_str (), msg_type.c_str ());
805
+ } catch (rclcpp::exceptions::RCLError& e) {
806
+ RCLCPP_ERROR (get_logger (), " Failed to create generic subscriber: %s" ,
807
+ e.what ());
808
+ continue ;
809
+ }
681
810
}
682
- RCLCPP_INFO (get_logger (), " Subscribed ROS topic '%s' of type '%s'" ,
683
- ros_topic.c_str (), msg_type.c_str ());
684
811
}
685
812
}
686
813
}
687
814
688
-
689
815
void MqttClient::setupClient () {
690
816
691
817
// basic client connection options
@@ -787,7 +913,7 @@ void MqttClient::ros2mqtt(
787
913
// resolve ROS messages to primitive strings if possible
788
914
std::string payload;
789
915
bool found_primitive =
790
- primitiveRosMessageToString (serialized_msg, ros_msg_type.name , payload);
916
+ primitiveRosMessageToString (serialized_msg, ros_msg_type.name , ros2mqtt. fixed_type , payload);
791
917
if (found_primitive) {
792
918
payload_buffer = std::vector<uint8_t >(payload.begin (), payload.end ());
793
919
} else {
0 commit comments