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)

```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:

- 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

- ROS message type

### 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())
```