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