首頁>技術>

其中,構建藍芽BLE廣播報文的函式——advertising_payload是在單獨的模組(.py檔案)中自己實現的。由此,也從另外一個角度反映出,MicroPython只提供了藍芽BLE的“低階”介面:“低階”到廣播報文都需要自己實現,而非系統模組整合。當然,考慮到MicoPython作為一個通用的平臺,又運行於資源有限的微控制器上,其只負責最核心的功能,而將業務場景有關的程式碼實現剝離出來由使用者自己實現,似乎也是無可厚非的了。

兩個藍芽裝置想要建立連線,首先需要外設裝置向外廣播,然後中心裝置才能搜尋到該裝置,再發起連線請求。外設裝置的廣播報文中包含裝置的相關資訊,比如裝置名稱,裝置具有的服務UUID等等,中心裝置可以根據這些資訊決定其是不是自己關心的裝置,以及要不要發起對該裝置的連線請求。

基本的廣播資料包格式如下:

每個廣播資料包都是31位元組,資料包中又分為有效(significant)資料部分和無效(non-significant)資料部分。其中,無效資料部分全為零,僅僅是為了湊夠31位元組而存在。而有效資料部分,又由若干個資料單元組成,每個資料單元的格式為:

1位元組長度+n位元組(通常也是1位元組)型別+n位元組型別特定資料

這裡的型別,用於表明這個資料單元代表什麼,比如標記(Flag),裝置名,或者UUID等等。我們來看例程中是怎麼實現的:

from micropython import constimport structimport bluetooth# Advertising payloads are repeated packets of the following form:#   1 byte data length (N + 1)#   1 byte type (see constants below)#   N bytes type-specific data#常量定義_ADV_TYPE_FLAGS = const(0x01)_ADV_TYPE_NAME = const(0x09)_ADV_TYPE_UUID16_COMPLETE = const(0x3)_ADV_TYPE_UUID32_COMPLETE = const(0x5)_ADV_TYPE_UUID128_COMPLETE = const(0x7)_ADV_TYPE_UUID16_MORE = const(0x2)_ADV_TYPE_UUID32_MORE = const(0x4)_ADV_TYPE_UUID128_MORE = const(0x6)_ADV_TYPE_APPEARANCE = const(0x19)#構建廣播資料包def advertising_payload(limited_disc=False, br_edr=False, name=None, services=None, appearance=0):    payload = bytearray()    #構建廣播資料單元    def _append(adv_type, value):        nonlocal payload        payload += struct.pack("BB", len(value) + 1, adv_type) + value    #標記型別資料單元    _append(        _ADV_TYPE_FLAGS,        struct.pack("B", (0x01 if limited_disc else 0x02) + (0x18 if br_edr else 0x04)),    )    #裝置名型別資料單元    if name:        _append(_ADV_TYPE_NAME, name)    #服務UUID型別資料單元    if services:        for uuid in services:            b = bytes(uuid)            if len(b) == 2:                _append(_ADV_TYPE_UUID16_COMPLETE, b)            elif len(b) == 4:                _append(_ADV_TYPE_UUID32_COMPLETE, b)            elif len(b) == 16:                _append(_ADV_TYPE_UUID128_COMPLETE, b)    #外觀型別資料單元    # See org.bluetooth.characteristic.gap.appearance.xml    if appearance:        _append(_ADV_TYPE_APPEARANCE, struct.pack("<h", appearance))    return payload

Python中struct模組對應C語言中的結構體,其可將結構體中的資料打包成位元組串。比如上述程式碼中,struct.pack函式第一個引數為格式字串,其表示該結構體是如何構建的:"B"表示其中包含一位元組資料,而"BB"則表示其中包含兩位元組資料。上述程式碼中,定義了_append子函式用於構建一個一個的廣播資料單元,其格式正好與前面提到的標準廣播資料單元的格式相符合。

上述廣播資料包中,包含的第一個資料單元為“標記(Flag)”型別資料單元。藍芽BLE可用該型別資料單元表明裝置是有限可發現(LE Limited Discoverable)還是普通可發現(General Discoverable),還可表明裝置是支援雙模(同時支援經典藍芽和藍芽BLE)還是不支援經典藍芽(BR/EDR模式),僅支援藍芽BLE。之後,依據引數傳遞情況決定是否加入裝置名資料單元,服務UUID資料單元,和裝置外觀資料單元。

(:所謂有限可發現是指該裝置傳送廣播報文時,只是在每個週期中的一段時間之內廣播,其餘時間不廣播。而常規可發現是指該裝置沒有時間限制,一直髮送廣播報文。)

裝置名比較容易理解,上述服務UUID的定義中都有COMPLETE字尾,那麼還有對應INCOMPLETE的型別嗎?確實如此。如果該裝置有兩個服務,只廣播了其中一個,那麼就是INCOMPLETE的,否則,就是COMPLETE的。那外觀(APPEARANCE)又是什麼呢,其實就是告訴接收廣播報文的裝置,本裝置應該用什麼樣的圖示外觀進行顯示,是用一個耳機的圖示,還是用一個HID鍵盤的圖示等等。

如此看來,藍芽規範確實詳實啊,基本所有細節都有考慮!這也是其保證各種裝置能夠廣泛相容的一個措施:自定義功能特性越少,越能保證規範範圍內各裝置之間的相容性。

該模組中還有其它函式如下:

#解析payload中指定adv_type的內容def decode_field(payload, adv_type):    i = 0    result = []    while i + 1 < len(payload):        if payload[i + 1] == adv_type:            result.append(payload[i + 2 : i + payload[i] + 1])        i += 1 + payload[i]    return result    #解析裝置名,以字串形式返回def decode_name(payload):    n = decode_field(payload, _ADV_TYPE_NAME)    return str(n[0], "utf-8") if n else ""#解析服務def decode_services(payload):    services = []    for u in decode_field(payload, _ADV_TYPE_UUID16_COMPLETE):        services.append(bluetooth.UUID(struct.unpack("<h", u)[0]))    for u in decode_field(payload, _ADV_TYPE_UUID32_COMPLETE):        services.append(bluetooth.UUID(struct.unpack("<d", u)[0]))    for u in decode_field(payload, _ADV_TYPE_UUID128_COMPLETE):        services.append(bluetooth.UUID(u))    return services#構造單元測試函式,以驗證本模組其它函式工作是否正常def demo():    payload = advertising_payload(        name="micropython",        services=[bluetooth.UUID(0x181A), bluetooth.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E")],    )    print(payload)    print(decode_name(payload))    print(decode_services(payload))#作為模組單獨執行時的主入口if __name__ == "__main__":    demo()

上述函式基本都是配合advertising_payload函式,用於驗證其是否工作正常。如果直接執行該模組,__name__系統變數為__main__,從最後面的程式碼來看,則直接執行的是demo函式,其使用advertising_payload函式構造了一個廣播報文,並進行各個域的解析,以驗證所構造的報文是否正確。從單元測試的角度來講,這也可以說是Python程式設計中一種比較常規的正規化了。

21
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • leetcode725_go_分隔連結串列