MQTT應用開發(二) - MQTT客戶端開發

在上一篇博客中,我們完成了MQTT服務器的搭建以及服務器和客戶端SSL證書的簽發,併成功用MQTT.fx軟件作爲客戶端連接發送信息。在實際應用中,我們需要根據需求來開發自己的客戶端應用程序,在本博文中,我將使用Python來開發一個模擬車載終端發送MQTT消息的應用。

在V2X領域,有很多標準化組織(ETSI/ISO/DSRC)制定了車輛V2X消息規範,例如我國的V2X標準中定義了以下5種消息規範:

  1. BSM,即Basic Safety Message,基礎安全消息,包括速度,轉向,剎車,雙閃,位置等等,多被用在V2V場景即變道預警,盲區預警,交叉路口碰撞預警等等;
  2. RSI,即Road Side Information,路側信息,用於事件的下方,路側RSU集成,平臺下發,多被用於V2I場景即道路施工,限速標誌,超速預警,公交車道預警等等;
  3. RSM,即Road Safety Message,路側安全消息,也是V2I,主要對接路側的邊緣設備,用於事件的識別,比如,車輛發生事故,車輛異常,異物闖入等等;
  4. SPAT,即Signal phase timing message,交通燈相位與時序消息,也是V2I,路側RSU集成信號機,或者信號機通過UU方式傳入到平臺,用於車速引導,綠波推送場景等等;
  5. MAP,地圖消息,地圖消息和SPAT消息一起使用,MAP消息可以描述一個路口,和該路口的紅綠燈也存在對應關係;

歐洲的ETSI也定義了類似的消息規範:

  1. CAM,Cooperative Awareness Message,合作感知信息,這是時間觸發信息,提供車輛的速度、位置、方向燈以及交通信號系統如交通信號燈的狀態,天氣提醒等信息;
  2. DENM,Decentralized Environmental Notification Message,分散環境通知信息,主要用於道路危險預警應用,是時間觸發型信息,一旦通過車載設備檢測到了安全隱患事件(例如前方車輛緊急剎車、道路施工警告等),車載ITS的相關應用就立即發射DENM信息,接收車輛可對比車輛自身位置與行車路線,判斷事件對自車的關聯性並預測可能的碰撞風險,以及提前通知駕駛員採取有效的措施,根據事件地點和類型,可能要求收到DENM信息的車輛向外轉發;

本文將按照歐洲標準規範,採用CAM來上報車輛的位置信息(每秒2次的頻率),採用DENM來上報車輛的緊急狀態事件(以緊急剎車爲例)。這2個消息協議的規範可以在ETSI的網站中找到,我選擇的是CAM ETSI EN 302 637-2 V1.4.1 以及 DENM ETSI EN 302 637-3 V1.3.1這2個最新版本的規範,下載對應的規範的PDF文檔後,可以在文檔中找到規範的具體的ASN格式的定義,注意這2個規範中都引用ETSI TS 102 894-2 V1.3.1的定義,因此在生成CAM和DENM的ASN文件時,需要把TS 102894這份規範裏面的字段定義也拷進去才能構成一個完整的ASN文件的定義。

有了CAM和DENM的ASN定義文件之後,我們就可以構建消息,採用UPER進行編碼,再把二進制數據編碼爲BASE64字符串,然後發送MQTT消息。

以下是模擬兩輛車在一段道路上行駛,這2輛車以每秒2條的頻率發送CAM消息報告位置,其中一輛車會隨機上報DENM消息。道路數據我是先用Google Map來進行GPS點的抓取,先在map上找一條道路,然後在其中以大致均勻的間隔取幾個點,然後調用Google的roads API,在path參數裏傳入這幾個點的座標,設置interpolate爲true,這樣Google就會自動幫我進行點的插值。我選了一條大概1.6公里的道路,傳入8個點,最終返回了54個點的座標。把返回值進行解析後保存這些點的座標在一個CSV文件中,作爲車輛的模擬行駛路線。

代碼如下:

import paho.mqtt.client as mqtt
import ssl
import base64
import asn1tools
import json
import time
import threading
import random
import datetime

start_timestamp = int(datetime.datetime(2004,1,1).timestamp()*1000)
def on_connect(client, userdata, flags, rc):
    print("Connected with result code "+str(rc))

def on_message(client, userdata, msg):
    print(msg.topic+" "+str(msg.payload))

client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.tls_set(ca_certs="cert/root.crt", certfile="cert/vehicle1.crt", keyfile="cert/vehicle1_key.pem", 
    cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLSv1_2, ciphers=None)

client.connect("broker", 8883, 60)

# Define the message to publish
cam_asn = asn1tools.compile_files('CAM.asn', 'uper')
cam_json = {
    'header': {
        'protocolVersion': 1,
        'messageID': 2,
        'stationID': 123
    },
    'cam': {
        'generationDeltaTime': 123,
        'camParameters': {
            'basicContainer': {
                'stationType': 0,
                'referencePosition': {
                    'latitude': 12345678,
                    'longitude': 87654321,
                    'positionConfidenceEllipse': {
                        'semiMajorConfidence': 4095,
                        'semiMinorConfidence': 4095,
                        'semiMajorOrientation': 3601
                    },
                    'altitude': {
                        'altitudeValue': 800001,
                        'altitudeConfidence': 'unavailable'
                    }
                }
            },
            'highFrequencyContainer': (
                'basicVehicleContainerHighFrequency', {
                    'heading': {
                        'headingValue': 3601,
                        'headingConfidence': 127
                    },
                    'speed': {
                        'speedValue': 30,
                        'speedConfidence': 127
                    },
                    'driveDirection': 'unavailable',
                    'vehicleLength': {
                        'vehicleLengthValue': 1023,
                        'vehicleLengthConfidenceIndication': 'unavailable'
                    },
                    'vehicleWidth': 62,
                    'longitudinalAcceleration': {
                        'longitudinalAccelerationValue': 161,
                        'longitudinalAccelerationConfidence': 102
                    },
                    'curvature': {
                        'curvatureValue': 1023,
                        'curvatureConfidence': 'unavailable'
                    },
                    'curvatureCalculationMode': 'unavailable',
                    'yawRate': {
                        'yawRateValue': 32767,
                        'yawRateConfidence': 'unavailable'
                    }
                }
            )
        }
    }
}
denm_asn = asn1tools.compile_files('DENM.asn', 'uper')
denm_json = {
    'header': {
        'protocolVersion': 1,
        'messageID': 2,
        'stationID': 123
    },
    'denm': {
        'management': {
            'actionID': {
                'originatingStationID': 123,
                'sequenceNumber': 1
            },
            'detectionTime': 1,
            'referenceTime': 1,
            'eventPosition': {
                'latitude': 123,
                'longitude': 321,
                'positionConfidenceEllipse': {
                    'semiMajorConfidence': 4095,
                    'semiMinorConfidence': 4095,
                    'semiMajorOrientation': 3601
                },
                'altitude': {
                    'altitudeValue': 800001,
                    'altitudeConfidence': 'unavailable'
                }
            },
            'validityDuration': 20,
            'stationType': 0
        },
        'situation': {
            'informationQuality': 0,
            'eventType': {
                'causeCode': 99,
                'subCauseCode': 1 
            },
            'linkedCause': {
                'causeCode': 99,
                'subCauseCode': 1
            }
        }
    }
}

# Read the simulation location list
roads_csv = open('roads.csv', 'r')
lines = roads_csv.readlines()
locations = []
for i in range(1, len(lines)):
    record = lines[i].split(',')
    lat = int(float(record[1])*10000000)
    lon = int(float(record[2])*10000000)
    locations.append((lat, lon))

def publish_data(VIN, denm_flag=False):
    for i in range(1):
        rand_denm = random.randint(0, len(locations))
        for j in range(len(locations)):
            cam_json['cam']['camParameters']['basicContainer']['referencePosition']['latitude'] = locations[j][0]
            cam_json['cam']['camParameters']['basicContainer']['referencePosition']['longitude'] = locations[j][1]
            cam_encoded = cam_asn.encode('CAM', cam_json)
            cam_b64 = str(base64.b64encode(cam_encoded),'utf-8')
            client.publish("vehicle/cam/"+VIN,cam_b64)
            if denm_flag:
                if j == rand_denm:
                    denm_json['denm']['management']['actionID']['sequenceNumber'] = i
                    denm_json['denm']['management']['eventPosition']['latitude'] = locations[j][0]
                    denm_json['denm']['management']['eventPosition']['longitude'] = locations[j][1]
                    denm_json['denm']['situation']['eventType']['causeCode'] = 99
                    denm_json['denm']['situation']['eventType']['subCauseCode'] = 1
                    detect_timestamp = int(datetime.datetime.utcnow().timestamp()*1000) - start_timestamp
                    denm_json['denm']['management']['detectionTime'] = detect_timestamp
                    denm_json['denm']['management']['referenceTime'] = detect_timestamp
                    denm_encoded = denm_asn.encode('DENM', denm_json)
                    denm_b64 = str(base64.b64encode(denm_encoded),'utf-8')
                    client.publish("vehicle/denm/"+VIN,denm_b64)
            time.sleep(0.5)
        time.sleep(10)
    
class myThread (threading.Thread):
    def __init__(self, VIN, denm_flag):
        threading.Thread.__init__(self)
        self.VIN = VIN
        self.denm_flag = denm_flag
    def run(self):
        publish_data(self.VIN, self.denm_flag)

thread1 = myThread("Vehicle1", True)
thread2 = myThread("Vehicle2", False)

thread1.start()
time.sleep(1)
thread2.start()
thread1.join()
thread2.join()

client.disconnect()

服務器端訂閱CAM和DENM主題,並打印接收到的信息,代碼如下:

import paho.mqtt.client as mqtt
import ssl
import base64
import asn1tools
import json
import time
import threading
import random
import datetime

start_timestamp = int(datetime.datetime(2004,1,1).timestamp()*1000)
def on_connect(client, userdata, flags, rc):
    print("Connected with result code "+str(rc))

def on_message(client, userdata, msg):
    print(msg.topic+" "+str(msg.payload))

client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.tls_set(ca_certs="cert/root.crt", certfile="cert/v2xapp.crt", keyfile="cert/v2xapp_key.pem", 
    cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLSv1_2, ciphers=None)

client.connect("broker", 8883, 60)

# Define the message to publish
cam_asn = asn1tools.compile_files('CAM.asn', 'uper')
cam_json = {
    'header': {
        'protocolVersion': 1,
        'messageID': 2,
        'stationID': 123
    },
    'cam': {
        'generationDeltaTime': 123,
        'camParameters': {
            'basicContainer': {
                'stationType': 0,
                'referencePosition': {
                    'latitude': 12345678,
                    'longitude': 87654321,
                    'positionConfidenceEllipse': {
                        'semiMajorConfidence': 4095,
                        'semiMinorConfidence': 4095,
                        'semiMajorOrientation': 3601
                    },
                    'altitude': {
                        'altitudeValue': 800001,
                        'altitudeConfidence': 'unavailable'
                    }
                }
            },
            'highFrequencyContainer': (
                'basicVehicleContainerHighFrequency', {
                    'heading': {
                        'headingValue': 3601,
                        'headingConfidence': 127
                    },
                    'speed': {
                        'speedValue': 30,
                        'speedConfidence': 127
                    },
                    'driveDirection': 'unavailable',
                    'vehicleLength': {
                        'vehicleLengthValue': 1023,
                        'vehicleLengthConfidenceIndication': 'unavailable'
                    },
                    'vehicleWidth': 62,
                    'longitudinalAcceleration': {
                        'longitudinalAccelerationValue': 161,
                        'longitudinalAccelerationConfidence': 102
                    },
                    'curvature': {
                        'curvatureValue': 1023,
                        'curvatureConfidence': 'unavailable'
                    },
                    'curvatureCalculationMode': 'unavailable',
                    'yawRate': {
                        'yawRateValue': 32767,
                        'yawRateConfidence': 'unavailable'
                    }
                }
            )
        }
    }
}
denm_asn = asn1tools.compile_files('DENM.asn', 'uper')
denm_json = {
    'header': {
        'protocolVersion': 1,
        'messageID': 2,
        'stationID': 123
    },
    'denm': {
        'management': {
            'actionID': {
                'originatingStationID': 123,
                'sequenceNumber': 1
            },
            'detectionTime': 1,
            'referenceTime': 1,
            'eventPosition': {
                'latitude': 123,
                'longitude': 321,
                'positionConfidenceEllipse': {
                    'semiMajorConfidence': 4095,
                    'semiMinorConfidence': 4095,
                    'semiMajorOrientation': 3601
                },
                'altitude': {
                    'altitudeValue': 800001,
                    'altitudeConfidence': 'unavailable'
                }
            },
            'validityDuration': 20,
            'stationType': 0
        },
        'situation': {
            'informationQuality': 0,
            'eventType': {
                'causeCode': 99,
                'subCauseCode': 1 
            },
            'linkedCause': {
                'causeCode': 99,
                'subCauseCode': 1
            }
        }
    }
}

client.subscribe('vehicle/#', 0)
client.loop_start()

start = time.time()
while True:
    time.sleep(1)
    end = time.time()
    if int(end - start) > 30:
        client.loop_stop()
        break
client.disconnect()

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章