1
- from typing import Any
1
+ from typing import Any , TypeVar , Callable , Generic
2
2
from dataclasses import dataclass
3
3
4
4
import rclpy
5
5
from rclpy .node import Node
6
6
from rclpy .publisher import Publisher
7
7
from rclpy .subscription import Subscription
8
+ from rclpy ._rclpy_pybind11 import RMWError
8
9
9
10
from std_msgs .msg import String
10
11
11
12
import paho .mqtt .client as MQTT
12
13
14
+ from mqtt_ros_bridge .serializer import Serializer , ROSDefaultSerializer
15
+
16
+
17
+ T = TypeVar ('T' )
18
+
13
19
14
20
@dataclass
15
- class TopicInfo ():
21
+ class TopicInfo (Generic [ T ] ):
16
22
"""Metadata about a single topic."""
17
23
18
24
name : str
25
+ msg_type : T
26
+ serializer : type [Serializer ]
19
27
publish_on_ros : bool
20
28
21
29
22
- TOPICS : list [TopicInfo ] = [
23
- TopicInfo ('pub_topic' , True ),
24
- TopicInfo ('sub_topic' , False )
25
- ]
30
+ TOPICS : dict [str , TopicInfo ] = {
31
+ 'pub_topic' : TopicInfo ('pub_topic' , String , ROSDefaultSerializer , True ),
32
+ 'sub_topic' : TopicInfo ('sub_topic' , String , ROSDefaultSerializer , False )
33
+ }
34
+
35
+ MQTT_PORT = 1883
36
+ MQTT_KEEPALIVE = 60
26
37
27
38
28
39
class BridgeNode (Node ):
@@ -31,45 +42,47 @@ class BridgeNode(Node):
31
42
def __init__ (self ) -> None :
32
43
super ().__init__ ('mqtt_bridge_node' )
33
44
34
- print ('Creating MQTT ROS bridge node' )
45
+ self . get_logger (). info ('Creating MQTT ROS bridge node' )
35
46
36
47
self .mqtt_client = MQTT .Client ()
37
48
self .mqtt_client .enable_logger ()
38
- self .mqtt_client .connect ('localhost' , port = 1883 , keepalive = 60 )
49
+ self .mqtt_client .connect ('localhost' , port = MQTT_PORT , keepalive = MQTT_KEEPALIVE )
39
50
self .mqtt_client .loop_start ()
40
51
41
52
self .ros_publishers : dict [str , Publisher ] = {}
42
53
self .ros_subscriptions : list [Subscription ] = []
43
54
44
- for topic_info in TOPICS :
55
+ for topic_info in TOPICS . values () :
45
56
if topic_info .publish_on_ros :
46
- publisher = self .create_publisher (String , topic_info .name , 10 )
57
+ publisher = self .create_publisher (topic_info . msg_type , topic_info .name , 10 )
47
58
self .ros_publishers [topic_info .name ] = publisher
48
59
self .mqtt_client .subscribe (topic_info .name )
49
60
else :
61
+ callback = self .make_ros_callback (topic_info )
50
62
subscription = self .create_subscription (
51
- String , topic_info .name , self . make_ros_receiver ( topic_info .name ) , 10 )
63
+ topic_info .msg_type , topic_info .name , callback , 10 )
52
64
self .ros_subscriptions .append (subscription )
53
65
54
- self .mqtt_client .on_message = self .mqtt_msg_received
66
+ self .mqtt_client .on_message = self .mqtt_callback
55
67
56
- def make_ros_receiver (self , topic : str ) :
68
+ def make_ros_callback (self , topic_info : TopicInfo [ T ]) -> Callable [[ T ], None ] :
57
69
"""
58
70
Create a callback function which re-publishes messages on the same topic in MQTT.
59
71
60
72
Parameters
61
73
----------
62
- topic : str
63
- the topic that the callback will publish on
74
+ topic_info : TopicInfo
75
+ information about the topic that the callback will publish on
64
76
65
77
"""
66
- def callback (msg : String ) :
67
- self .get_logger ().info (f" ROS RECEIVED: Topic: ' { topic } ' Payload: ' { msg } '" )
68
- self .mqtt_client .publish (topic , msg . data )
78
+ def callback (msg : T ) -> None :
79
+ self .get_logger ().info (f' ROS RECEIVED: Topic: " { topic_info . name } " Payload: " { msg } "' )
80
+ self .mqtt_client .publish (topic_info . name , topic_info . serializer . serialize ( msg ) )
69
81
70
82
return callback
71
83
72
- def mqtt_msg_received (self , _client : MQTT .Client , _userdata : Any , mqtt_msg : MQTT .MQTTMessage ):
84
+ def mqtt_callback (self , _client : MQTT .Client ,
85
+ _userdata : Any , mqtt_msg : MQTT .MQTTMessage ) -> None :
73
86
"""
74
87
Re-publish messages from MQTT on the same topic in ROS.
75
88
@@ -83,12 +96,19 @@ def mqtt_msg_received(self, _client: MQTT.Client, _userdata: Any, mqtt_msg: MQTT
83
96
the message received over MQTT
84
97
85
98
"""
99
+ topic_info = TOPICS [mqtt_msg .topic ]
100
+
86
101
self .get_logger ().info (
87
- f"MQTT RECEIVED: Topic: '{ mqtt_msg .topic } ' Payload: '{ mqtt_msg .payload !r} '" )
102
+ f'MQTT RECEIVED: Topic: "{ topic_info .name } " Payload: "{ mqtt_msg .payload !r} "' )
103
+
104
+ try :
105
+ ros_msg = topic_info .serializer .deserialize (mqtt_msg .payload , topic_info .msg_type )
106
+ except RMWError :
107
+ self .get_logger ().info ('Dropping message with bad serialization received from' +
108
+ f'MQTT on topic "{ mqtt_msg .topic } ": "{ mqtt_msg .payload !r} "' )
109
+ return
88
110
89
- ros_msg = String ()
90
- ros_msg .data = mqtt_msg .payload .decode ('utf-8' )
91
- self .ros_publishers [mqtt_msg .topic ].publish (ros_msg )
111
+ self .ros_publishers [topic_info .name ].publish (ros_msg )
92
112
93
113
94
114
def main (args = None ):
0 commit comments