---
# System prepended metadata

title: MQTT code Note
tags: [MQTT]

---


contributed by <`cwcoscar`> From NCKU MECLAB
Date : 2021/12/28

## Python class
### Python getattr()
- [Reference](https://www.runoob.com/python/python-func-getattr.html)

```python=0
>>> class A(object):
     bar = 1 
>>> a = A()
>>> getattr(a, 'bar')        # 获取属性 bar 值
1
>>> getattr(a, 'bar2')       # 属性 bar2 不存在，触发异常
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'bar2'
>>> getattr(a, 'bar2', 3)    # 属性 bar2 不存在，但设置了默认值
3
>>>
```
- `module_name`.`class_name`() [Reference](https://medium.com/%E5%AE%85%E7%94%B7%E9%9B%9C%E5%AD%B8%E7%AD%86%E8%A8%98/python%E7%9A%84%E5%8C%85%E8%A3%9D%E6%A9%9F%E5%88%B6-function-class-module-package-29bd8defb20e)
![](https://i.imgur.com/ndscEfq.png)


```python=0
#math_module_2.py
class Math2():
    
    
    def __init__(self, a, b):
        self.a = a 
        self.b = b
        self.answer = 0
        
    def times(self):
        self.answer = self.a * self.b
    def div(self):
        if self.b == 0:
            raise ValueError('cannot div 0')
        else:
            self.answer = self.a / self.b
        
    def __str__(self):
        return 'Answer: {0}'.format(self.answer)
```
```python=0
#main.py
import myMath.math_module as m1
import myMath.math_module_2 as m2
a1 = m1.Math(1, 2)
a2 = m2.Math2(3, 4)
a1.add()
a2.times()
print(a1)
print(a2)
'''
Answer: 3
Answer: 12
'''
```
- import_module('math')  [Reference](https://python3-cookbook.readthedocs.io/zh_CN/latest/c10/p10_import_modules_using_name_given_in_string.html)
```python=0
>>> import importlib
>>> math = importlib.import_module('math')
>>> math.sin(2)
0.9092974268256817
>>> mod = importlib.import_module('urllib.request')
>>> u = mod.urlopen('http://www.python.org')
>>>
```
- mqtt_bridge/util.py
```python=0
def lookup_object(object_path, package='mqtt_bridge'):
    """ lookup object from a some.module:object_name specification. """
    module_name, obj_name = object_path.split(":")
    module = import_module(module_name, package)
    obj = getattr(module, obj_name)
    return obj
```
- Return an Class `obj_name` obj

### Class Inherit
- [Reference](https://stackoverflow.com/questions/10043963/class-classname-versus-class-classnameobject)
- Class `inherit_class`(object)
- Class `class_name`(`inherit_class`)
```python=0
class Bridge(object):
    u""" Bridge base class

    :param mqtt.Client _mqtt_client: MQTT client
    :param _serialize: message serialize callable
    :param _deserialize: message deserialize callable
    """
    __metaclass__ = ABCMeta

    _mqtt_client = inject.attr(mqtt.Client)
    _serialize = inject.attr('serializer')
    _deserialize = inject.attr('deserializer')
    _extract_private_path = inject.attr('mqtt_private_path_extractor')


class RosToMqttBridge(Bridge):
    u""" Bridge from ROS topic to MQTT

    :param str topic_from: incoming ROS topic path
    :param str topic_to: outgoing MQTT topic path
    :param class msg_type: subclass of ROS Message
    :param (float|None) frequency: publish frequency
    """

    def __init__(self, topic_from, topic_to, msg_type, frequency=None):
        self._topic_from = topic_from
        self._topic_to = self._extract_private_path(topic_to)
        self._last_published = rospy.get_time()
        self._interval = 0 if frequency is None else 1.0 / frequency
        rospy.Subscriber(topic_from, msg_type, self._callback_ros)

    def _callback_ros(self, msg):
        rospy.logdebug("ROS received from {}".format(self._topic_from))
        now = rospy.get_time()
        if now - self._last_published > self._interval:
            self._publish(msg)
            self._last_published = now

    def _publish(self, msg):
        #payload = bytearray(self._serialize(extract_values(msg)))
        #payload = extract_values(msg)['data']
        #dict = {'a': 1, 'b': 2, 'b': '3'}
        data_out=js_converter.convert_ros_message_to_json(msg)  #Brain
        payload=data_out
        self._mqtt_client.publish(topic=self._topic_to , payload=payload, qos=0)

```

## ROS to MQTT

### Converting ROS to JSON 
- Requirement:
![](https://i.imgur.com/fZrPILe.png)

- Originally I make a msgtype like
> string vid
Lilee_gnss[] gnss 
- Later, I realized that data published on the topic has an unexpected [] outside objects of gnss
>{
  "gnss" : [ [ {
    "timestamp" : 1621998961259,
    "source_time" : 0,
    "speed" : 0.0036011110059916973,
    "heading" : 152.175582886,
    "coord" : [ 120.29276897833333, 46.88999938964844, 22.928032681666664 ]
  } ] ],
  "vid" : "072e92d501f6"
}
- To solve this problem, I found the section in the source code (message_converter.py)
```python=0
def convert_ros_message_to_dictionary(message):
    """
    Takes in a ROS message and returns a Python dictionary.

    Example:
        ros_message = std_msgs.msg.String(data="Hello, Robot")
        dict_message = convert_ros_message_to_dictionary(ros_message)
    """
    dictionary = {}
    message_fields = _get_message_fields(message)

    for field_name, field_type in message_fields:

        field_value = getattr(message, field_name)
        if field_name == 'imu':# add the specific case if the dict need to be included in list type //Brian
            dictionary[field_name] = [_convert_from_ros_type(field_type, field_value)]
        elif field_name == 'gnss':
            dictionary[field_name] = [_convert_from_ros_type(field_type, field_value)]
        elif field_name == 'ecu':
            dictionary[field_name] = [_convert_from_ros_type(field_type, field_value)]
        #elif field_name == 'DO':
        #    dictionary[field_name] = [_convert_from_ros_type(field_type, field_value)]
        elif field_name == 'radar':
            dictionary[field_name] = [_convert_from_ros_type(field_type, field_value)]
        elif field_name == 'lidar':
            dictionary[field_name] = [_convert_from_ros_type(field_type, field_value)]
        elif field_name == 'camera':
            dictionary[field_name] = [_convert_from_ros_type(field_type, field_value)]
        else:
            dictionary[field_name] = _convert_from_ros_type(field_type, field_value)
        
    return dictionary
```
- Realize that Brian add a comment "add the specific case if the dict need to be included in list type"
- That means you shouldn't make gnss a list of object in your message, but add 
>"elif field_name == '<your field name(here is gnss for example)>':
            dictionary[field_name] = [_convert_from_ros_type(field_type, field_value)]"
- so I change my message type to fix this problem
> string vid
Lilee_gnss gnss

### Building node to transfer data format

- value.coord = {data.latitude, data.longitude, data.altitude} will results in the **wrong sequence** of latitude, longitude and altitude

```python=0
output=Lilee_gps()

def callback_gps(data):
    global output
    output.vid = '072e92d501f6'
    value = Lilee_gnss()
    value.coord = {data.latitude, data.longitude, data.altitude}
    value.speed = data.speed
    value.heading = data.track
    value.timestamp = data.header.stamp.secs*1000 + data.header.stamp.nsecs / 1000000
    value.source_time = 0
    output.gnss.append(value)
    pub.publish(output)
```

- **Use a empty list** to append latitude, longitude and altitude one by one to secure the correct sequence when publishing in topic.

```python=0
def callback_gps(data):
    output=Lilee_gps()

    value = Lilee_gnss()

    List = []
    List.append(data.latitude)
    List.append(data.longitude)
    List.append(data.altitude)

    value.coord = List

    value.speed = data.speed
    value.heading = data.track
    value.timestamp = data.header.stamp.secs*1000 + data.header.stamp.nsecs / 1000000
    value.source_time = data.header.stamp.secs*1000 + data.header.stamp.nsecs / 1000000

    output.gnss = value
    output.vid = '072e92d501f6'
    pub.publish(output)
```

### Debug Section
#### Play rosbag problem
- It's usual to test the connection with the MQTT server and check if the server side can sucessfully received the message we publish to the server.
- A tip to test the connection by playing rosbag is to:
  1. Connect to MQTT
  2. Play the rosbag
- If you don't follow this activating sequence, the server side can not receive the message after the rosbag ending(assume you check the repeat box). That will be a little bothering.
- You will need to reconnect to the server then they can see the message again. 


## MQTT to ROS 

### Change config
- costumized messge type
    ![](https://i.imgur.com/UwKzPac.png)

- ROS message type
    ![](https://i.imgur.com/tQbwh8K.png)

### Converting JSON to ROS message
- Requirement
1. motc format in JSON
```json=0
{
  "version" : {
    "software" : "1.1.9",
    "format" : "1.2"
  },
  "RoadID" : "IPC_67",
  "Createtime" : "2021-12-16 17:33:27.152",
  "Datetime" : "2021-12-16 17:33:28.115",
  "MSG" : "5F032155040205000381818181",
  "TCStatus" : "Normal",
  "PhaseOrder" : "21",
  "SignalMap" : [ "N", "E", "S", "W" ],
  "ActiveDirection" : [ "N", "S" ],
  "DeactivateDirection" : [ "E", "W" ],
  "ActiveSignalGroup" : [ 2 ],
  "SubPhaseID" : 2,
  "StepID" : 5,
  "StepSec" : 3,
  "StepStatus" : "正常",
  "SignalCount" : 4,
  "ListSignalStatus" : [ {
    "Direction" : "N",
    "Status" : "0x81",
    "TrafficRedCount" : 0,
    "TrafficYellowCount" : 0,
    "TrafficGreenCount" : 0,
    "PedestrianCount" : 0,
    "SignalLight" : [ "Red", "PedestrianRed" ],
    "Red" : "1",
    "Yellow" : "0",
    "Green" : "0",
    "TurnLeftGreen" : "0",
    "StraightGreen" : "0",
    "TurnRightGreen" : "0",
    "PedestrianGreen" : "0",
    "PedestrianRed" : "1",
    "PedestrianFlash" : "0"
  }, {
    "Direction" : "E",
    "Status" : "0x81",
    "TrafficRedCount" : 2,
    "TrafficYellowCount" : 0,
    "TrafficGreenCount" : 0,
    "PedestrianCount" : 0,
    "SignalLight" : [ "Red", "PedestrianRed" ],
    "Red" : "1",
    "Yellow" : "0",
    "Green" : "0",
    "TurnLeftGreen" : "0",
    "StraightGreen" : "0",
    "TurnRightGreen" : "0",
    "PedestrianGreen" : "0",
    "PedestrianRed" : "1",
    "PedestrianFlash" : "0"
  } ]
}
```
2. spat format in JSON
```json=0
{
  "version" : {
    "software" : "1.1.9",
    "format" : "1.2"
  },
  "id" : {
    "region" : "0",
    "id" : "0"
  },
  "status" : "fixedTimeOperation",
  "moy" : 503133,
  "timestamp" : 28152,
  "states" : [ {
    "signalGroup" : 1,
    "stateTimeSpeed" : [ {
      "eventState" : "stop-And-Remain",
      "timing" : {
        "minEndTime" : 20502
      }
    } ]
  }, {
    "signalGroup" : 2,
    "stateTimeSpeed" : [ {
      "eventState" : "protected-Movement-Allowed",
      "timing" : {
        "startTime" : 20082,
        "minEndTime" : 20172
      }
    } ]
  } ]
}

```
- Originally, I thought the data type shouldn't be defined as "list" according to the experience when I was doing ROS to MQTT. Later, I found out I was wrong. Before I figured it out, I wrote the following code, which I mimic the coding in ROS to MQTT(The code with comment).
- At least, the experience of the mistake makes me know how the program works more clearly.
- Excuting the following code can learn what kind of data is needed after processing.
```python=0
    def _create_ros_message(self, mqtt_msg):
        u""" create ROS message from MQTT payload

        :param mqtt.Message mqtt_msg: MQTT Message
        :return rospy.Message: ROS Message
        """
        #msg_dict = self._deserialize(mqtt_msg.payload)
        #msg_dict = self._deserialize(self._serialize({'data':mqtt_msg.payload}))

        msg_dict=json.loads(mqtt_msg.payload)
        dict={}
        for key, value in msg_dict.items():
            if key == 'imu':# add the specific case if the dict need to be included in list type //Brian
                dict[key] = value[0]
            elif key == 'gnss':
                dict[key] = value[0]
            elif key == 'ecu':
                dict[key] = value[0]
            #elif key == 'DO':
                #dict[key] = value[0]
            elif key == 'radar':
                dict[key] = value[0]
            ################### CWC ####################
            # elif key == 'states':
            #     dict_2={}
            #     # dict[key] = value[0]
            #     # print(value)
            #     for i in value:
            #         for key_2, value_2 in i.items():
            #             # print(key_2)
            #             # print(value_2)
            #             if key_2 == 'stateTimeSpeed':
            #                 dict_2[key_2] = value_2[0]
            #             else:
            #                 dict_2[key_2] = value_2
            #     dict[key] = dict_2
            # elif key == 'ListSignalStatus':
            #     dict[key] = value[0]
            ################### CWC ####################
            else:
                dict[key] = value
                
        return populate_instance(dict, self._msg_type())
```