From 46e243c592ab12d905248a2138e4910e25a88ace Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 25 May 2024 17:37:41 +1000 Subject: [PATCH 1/4] aioble/central.py: Fix ScanResult.services when decoding UUIDs. Fixes are needed to support the cases of: - There may be more than one UUID per advertising field. - The UUID advertising field may be empty (no UUIDs). - Constructing 32-bit `bluetooth.UUID()` entities, which must be done by passing in a 4-byte bytes object, not an integer. Signed-off-by: Damien George --- micropython/bluetooth/aioble-central/manifest.py | 2 +- micropython/bluetooth/aioble/aioble/central.py | 14 ++++++++------ micropython/bluetooth/aioble/manifest.py | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/micropython/bluetooth/aioble-central/manifest.py b/micropython/bluetooth/aioble-central/manifest.py index 128c90642..9564ecf77 100644 --- a/micropython/bluetooth/aioble-central/manifest.py +++ b/micropython/bluetooth/aioble-central/manifest.py @@ -1,4 +1,4 @@ -metadata(version="0.2.1") +metadata(version="0.2.2") require("aioble-core") diff --git a/micropython/bluetooth/aioble/aioble/central.py b/micropython/bluetooth/aioble/aioble/central.py index adfc9729e..2f1492d08 100644 --- a/micropython/bluetooth/aioble/aioble/central.py +++ b/micropython/bluetooth/aioble/aioble/central.py @@ -195,12 +195,14 @@ def name(self): # Generator that enumerates the service UUIDs that are advertised. def services(self): - for u in self._decode_field(_ADV_TYPE_UUID16_INCOMPLETE, _ADV_TYPE_UUID16_COMPLETE): - yield bluetooth.UUID(struct.unpack(" Date: Sat, 25 May 2024 17:45:42 +1000 Subject: [PATCH 2/4] aioble/multitests: Add test for advertising and scanning services. This tests both encoding and decoding multiple 16-bit and 32-bit services within the one advertising field. Signed-off-by: Damien George --- .../multitests/ble_advertise_services.py | 71 +++++++++++++++++++ .../multitests/ble_advertise_services.py.exp | 8 +++ 2 files changed, 79 insertions(+) create mode 100644 micropython/bluetooth/aioble/multitests/ble_advertise_services.py create mode 100644 micropython/bluetooth/aioble/multitests/ble_advertise_services.py.exp diff --git a/micropython/bluetooth/aioble/multitests/ble_advertise_services.py b/micropython/bluetooth/aioble/multitests/ble_advertise_services.py new file mode 100644 index 000000000..d849c387f --- /dev/null +++ b/micropython/bluetooth/aioble/multitests/ble_advertise_services.py @@ -0,0 +1,71 @@ +# Test advertising multiple services, and scanning them. + +import sys + +# ruff: noqa: E402 +sys.path.append("") + +import asyncio +import aioble +import bluetooth + +TIMEOUT_MS = 5000 + +_SERVICE_16_A = bluetooth.UUID(0x180F) # Battery Service +_SERVICE_16_B = bluetooth.UUID(0x181A) # Environmental Sensing Service +_SERVICE_32_A = bluetooth.UUID("AB12") # random +_SERVICE_32_B = bluetooth.UUID("CD34") # random + + +# Acting in peripheral role (advertising). +async def instance0_task(): + multitest.globals(BDADDR=aioble.config("mac")) + multitest.next() + + # Advertise, and wait for central to connect to us. + print("advertise") + async with await aioble.advertise( + 20_000, + name="MPY", + services=[_SERVICE_16_A, _SERVICE_16_B, _SERVICE_32_A, _SERVICE_32_B], + timeout_ms=TIMEOUT_MS, + ) as connection: + print("connected") + await connection.disconnected() + print("disconnected") + + +def instance0(): + try: + asyncio.run(instance0_task()) + finally: + aioble.stop() + + +# Acting in central role (scanning). +async def instance1_task(): + multitest.next() + + wanted_device = aioble.Device(*BDADDR) + + # Scan for the wanted device/peripheral and print its advertised services. + async with aioble.scan(5000, interval_us=30000, window_us=30000, active=True) as scanner: + async for result in scanner: + if result.device == wanted_device: + services = list(result.services()) + if services: + print(services) + break + + # Connect to peripheral and then disconnect. + print("connect") + device = aioble.Device(*BDADDR) + async with await device.connect(timeout_ms=TIMEOUT_MS): + print("disconnect") + + +def instance1(): + try: + asyncio.run(instance1_task()) + finally: + aioble.stop() diff --git a/micropython/bluetooth/aioble/multitests/ble_advertise_services.py.exp b/micropython/bluetooth/aioble/multitests/ble_advertise_services.py.exp new file mode 100644 index 000000000..c0b2d974a --- /dev/null +++ b/micropython/bluetooth/aioble/multitests/ble_advertise_services.py.exp @@ -0,0 +1,8 @@ +--- instance0 --- +advertise +connected +disconnected +--- instance1 --- +[UUID(0x180f), UUID(0x181a), UUID(0x32314241), UUID(0x34334443)] +connect +disconnect From 1e792c39d3eb0f7bb2ddbf9b47eda479f32c9ae2 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 25 May 2024 18:24:19 +1000 Subject: [PATCH 3/4] aioble/multitests: Adjust expected output for write capture test. Testing shows that the first two writes always go through and the rest are dropped, so update the .exp file to match that. Signed-off-by: Damien George --- .../bluetooth/aioble/multitests/ble_write_capture.py.exp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/micropython/bluetooth/aioble/multitests/ble_write_capture.py.exp b/micropython/bluetooth/aioble/multitests/ble_write_capture.py.exp index dd0c6d688..366af008b 100644 --- a/micropython/bluetooth/aioble/multitests/ble_write_capture.py.exp +++ b/micropython/bluetooth/aioble/multitests/ble_write_capture.py.exp @@ -2,7 +2,7 @@ advertise connected written b'central0' -written b'central2' +written b'central1' written b'central0' True written b'central1' True written b'central2' True From 2c30a4e91bc5521a78efcd32ac04bc6ba928f4c7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 25 May 2024 18:24:38 +1000 Subject: [PATCH 4/4] aioble/multitests: Use multitest.output_metric for perf results. The perf multitests now "pass" when run. Signed-off-by: Damien George --- micropython/bluetooth/aioble/multitests/perf_gatt_notify.py | 6 +++++- .../bluetooth/aioble/multitests/perf_gatt_notify.py.exp | 4 ++++ micropython/bluetooth/aioble/multitests/perf_l2cap.py | 6 +++++- micropython/bluetooth/aioble/multitests/perf_l2cap.py.exp | 4 ++++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/micropython/bluetooth/aioble/multitests/perf_gatt_notify.py b/micropython/bluetooth/aioble/multitests/perf_gatt_notify.py index d601a0ee2..3d3159f59 100644 --- a/micropython/bluetooth/aioble/multitests/perf_gatt_notify.py +++ b/micropython/bluetooth/aioble/multitests/perf_gatt_notify.py @@ -47,6 +47,8 @@ async def instance0_task(): 20_000, adv_data=b"\x02\x01\x06\x04\xffMPY", timeout_ms=TIMEOUT_MS ) + print("connect") + client_characteristic = await discover_server(connection) # Give the central enough time to discover chars. @@ -61,7 +63,7 @@ async def instance0_task(): ticks_end = time.ticks_ms() ticks_total = time.ticks_diff(ticks_end, ticks_start) - print( + multitest.output_metric( "Acknowledged {} notifications in {} ms. {} ms/notification.".format( _NUM_NOTIFICATIONS, ticks_total, ticks_total // _NUM_NOTIFICATIONS ) @@ -87,6 +89,8 @@ async def instance1_task(): device = aioble.Device(*BDADDR) connection = await device.connect(timeout_ms=TIMEOUT_MS) + print("connect") + client_characteristic = await discover_server(connection) for i in range(_NUM_NOTIFICATIONS): diff --git a/micropython/bluetooth/aioble/multitests/perf_gatt_notify.py.exp b/micropython/bluetooth/aioble/multitests/perf_gatt_notify.py.exp index e69de29bb..4b7d220a0 100644 --- a/micropython/bluetooth/aioble/multitests/perf_gatt_notify.py.exp +++ b/micropython/bluetooth/aioble/multitests/perf_gatt_notify.py.exp @@ -0,0 +1,4 @@ +--- instance0 --- +connect +--- instance1 --- +connect diff --git a/micropython/bluetooth/aioble/multitests/perf_l2cap.py b/micropython/bluetooth/aioble/multitests/perf_l2cap.py index 9ccf54262..e21efd6fa 100644 --- a/micropython/bluetooth/aioble/multitests/perf_l2cap.py +++ b/micropython/bluetooth/aioble/multitests/perf_l2cap.py @@ -32,6 +32,8 @@ async def instance0_task(): 20_000, adv_data=b"\x02\x01\x06\x04\xffMPY", timeout_ms=TIMEOUT_MS ) + print("connect") + channel = await connection.l2cap_accept(_L2CAP_PSM, _L2CAP_MTU, timeout_ms=TIMEOUT_MS) random.seed(_RANDOM_SEED) @@ -66,6 +68,8 @@ async def instance1_task(): device = aioble.Device(*BDADDR) connection = await device.connect(timeout_ms=TIMEOUT_MS) + print("connect") + await asyncio.sleep_ms(500) channel = await connection.l2cap_connect(_L2CAP_PSM, _L2CAP_MTU, timeout_ms=TIMEOUT_MS) @@ -90,7 +94,7 @@ async def instance1_task(): ticks_end = time.ticks_ms() total_ticks = time.ticks_diff(ticks_end, ticks_first_byte) - print( + multitest.output_metric( "Received {}/{} bytes in {} ms. {} B/s".format( recv_bytes, recv_correct, total_ticks, recv_bytes * 1000 // total_ticks ) diff --git a/micropython/bluetooth/aioble/multitests/perf_l2cap.py.exp b/micropython/bluetooth/aioble/multitests/perf_l2cap.py.exp index e69de29bb..4b7d220a0 100644 --- a/micropython/bluetooth/aioble/multitests/perf_l2cap.py.exp +++ b/micropython/bluetooth/aioble/multitests/perf_l2cap.py.exp @@ -0,0 +1,4 @@ +--- instance0 --- +connect +--- instance1 --- +connect