The UUID32 case was incorrect: first, the "<d" should have been "<I", and second, the UUID constructor treats integer arguments as UUID16. So this UUID32 case needs to pass in the actual data bytes. And then it's simpler to just make all cases pass in the data bytes. Signed-off-by: Damien George <damien@micropython.org>
99 lines
2.8 KiB
Python
99 lines
2.8 KiB
Python
# Helpers for generating BLE advertising payloads.
|
|
|
|
# A more fully-featured (and easier to use) version of this is implemented in
|
|
# aioble. This code is provided just as a basic example. See
|
|
# https://github.com/micropython/micropython-lib/tree/master/micropython/bluetooth/aioble
|
|
|
|
from micropython import const
|
|
import struct
|
|
import 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)
|
|
|
|
_ADV_MAX_PAYLOAD = const(31)
|
|
|
|
|
|
# Generate a payload to be passed to gap_advertise(adv_data=...).
|
|
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)
|
|
|
|
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))
|
|
|
|
if len(payload) > _ADV_MAX_PAYLOAD:
|
|
raise ValueError("advertising payload too large")
|
|
|
|
return payload
|
|
|
|
|
|
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 code in (_ADV_TYPE_UUID16_COMPLETE, _ADV_TYPE_UUID32_COMPLETE, _ADV_TYPE_UUID128_COMPLETE):
|
|
for u in decode_field(payload, code):
|
|
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()
|