From e59206aa8716f30757969ce403cfbbda79e724db Mon Sep 17 00:00:00 2001 From: Florian Loitsch Date: Sun, 24 Nov 2024 19:12:46 +0100 Subject: [PATCH] Support active BLE scans. Active scans send a request to the device when they have received an advertisement. --- lib/ble/ble.toit | 2 +- lib/ble/remote.toit | 9 ++- src/primitive.h | 2 +- src/resources/ble_darwin.mm | 2 +- src/resources/ble_esp32.cc | 8 +-- tests/hw/esp32/ble6-advertise-shared.toit | 67 +++++++++++++++++++---- 6 files changed, 67 insertions(+), 23 deletions(-) diff --git a/lib/ble/ble.toit b/lib/ble/ble.toit index 29d007f88..bf05e3a27 100644 --- a/lib/ble/ble.toit +++ b/lib/ble/ble.toit @@ -1084,7 +1084,7 @@ ble-create-central-manager_ adapter-resource: ble-create-peripheral-manager_ adapter-resource bonding secure-connections: #primitive.ble.create-peripheral-manager -ble-scan-start_ central-manager duration-us: +ble-scan-start_ central-manager passive duration-us: #primitive.ble.scan-start ble-scan-next_ central-manager: diff --git a/lib/ble/remote.toit b/lib/ble/remote.toit index 3fad7c181..b5dac1605 100644 --- a/lib/ble/remote.toit +++ b/lib/ble/remote.toit @@ -50,14 +50,19 @@ class Central extends Resource_: Only one scan can run at a time. + If $active is true, then we request a scan response from discovered devices. + Users might need to merge the advertisement data from the scan response with the + advertisement data from the discovery event. Use + $RemoteScannedDevice.is-scan-response to distinguish between the two. + Connections cannot be established while a scan is ongoing. Stops the scan after the given $duration. */ - scan [block] --duration/Duration?=null: + scan [block] --duration/Duration?=null --active/bool=false: duration-us := duration ? (max 0 duration.in-us) : -1 resource-state_.clear-state COMPLETED-EVENT_ - ble-scan-start_ resource_ duration-us + ble-scan-start_ resource_ (not active) duration-us is-macos := system.platform == system.PLATFORM_MACOS try: while true: diff --git a/src/primitive.h b/src/primitive.h index 8a260be07..dec32f929 100644 --- a/src/primitive.h +++ b/src/primitive.h @@ -346,7 +346,7 @@ namespace toit { PRIMITIVE(create_central_manager, 1) \ PRIMITIVE(close, 1) \ PRIMITIVE(release_resource, 1) \ - PRIMITIVE(scan_start, 2) \ + PRIMITIVE(scan_start, 3) \ PRIMITIVE(scan_next, 1) \ PRIMITIVE(scan_stop, 1) \ PRIMITIVE(connect, 3) \ diff --git a/src/resources/ble_darwin.mm b/src/resources/ble_darwin.mm index be2ed47b8..3568e097a 100644 --- a/src/resources/ble_darwin.mm +++ b/src/resources/ble_darwin.mm @@ -826,7 +826,7 @@ - (id)initWithResource:(toit::BleResource*)resource { } PRIMITIVE(scan_start) { - ARGS(BleCentralManagerResource, central_manager, int64, duration_us); + ARGS(BleCentralManagerResource, central_manager, bool, passive, int64, duration_us); Locker locker(central_manager->scan_mutex()); bool active = [central_manager->central_manager() isScanning]; diff --git a/src/resources/ble_esp32.cc b/src/resources/ble_esp32.cc index 3e27761cd..806525d29 100644 --- a/src/resources/ble_esp32.cc +++ b/src/resources/ble_esp32.cc @@ -2417,7 +2417,7 @@ PRIMITIVE(close) { } PRIMITIVE(scan_start) { - ARGS(BleCentralManagerResource, central_manager, int64, duration_us) + ARGS(BleCentralManagerResource, central_manager, bool, passive, int64, duration_us) Locker locker(central_manager->group()->mutex()); @@ -2440,11 +2440,7 @@ PRIMITIVE(scan_start) { // repeated advertisements from the same device. // disc_params.filter_duplicates = 1; - /** - * Perform a passive scan. I.e., don't send follow-up scan requests to - * each advertiser. - */ - disc_params.passive = 1; + disc_params.passive = passive ? 1 : 0; /* Use defaults for the rest of the parameters. */ disc_params.itvl = 0; diff --git a/tests/hw/esp32/ble6-advertise-shared.toit b/tests/hw/esp32/ble6-advertise-shared.toit index af745bda3..3202a342a 100644 --- a/tests/hw/esp32/ble6-advertise-shared.toit +++ b/tests/hw/esp32/ble6-advertise-shared.toit @@ -62,24 +62,40 @@ main-peripheral: next-semaphore.down peripheral.stop-advertise - advertise := : | blocks | + advertise := : | blocks scan-response-blocks | advertisement = AdvertisementData blocks - peripheral.start-advertise --allow-connections advertisement + scan-response := scan-response-blocks + ? AdvertisementData scan-response-blocks + : null + peripheral.start-advertise --allow-connections advertisement --scan-response=scan-response next-semaphore.down peripheral.stop-advertise - advertise.call [] + advertise.call [] null advertise.call [ - DataBlock.flags --general-discovery --bredr-supported=false, - DataBlock.manufacturer-specific --company-id=COMPANY-ID MANUFACTURER-DATA, - ] + DataBlock.flags --general-discovery --bredr-supported=false, + DataBlock.manufacturer-specific --company-id=COMPANY-ID MANUFACTURER-DATA, + ] + null advertise.call [ - DataBlock.name "Test", - DataBlock.services-128 [TEST-SERVICE], - ] + DataBlock.name "Test", + DataBlock.services-128 [TEST-SERVICE], + ] + null advertise.call [ - DataBlock.manufacturer-specific (ByteArray 27: it) - ] + DataBlock.manufacturer-specific (ByteArray 27: it) + ] + null + advertise.call + [ + DataBlock.name "Test", + ] + [ // The scan response. + DataBlock.services-128 [TEST-SERVICE], + ] + + + done = true @@ -100,8 +116,10 @@ test-data address/any characteristic/RemoteCharacteristic --central/Central - --is-connectable/bool=true [block]: + --is-connectable/bool=true + [block]: remote-scanned := scan address --central=central + expect-not remote-scanned.is-scan-response expect-equals address remote-scanned.address expect-equals is-connectable remote-scanned.is-connectable block.call remote-scanned.data @@ -162,6 +180,31 @@ main-central: expect-equals (ByteArray 27: it) data id) + // Check that active/passive scanning works. + central.scan --duration=(Duration --s=2): | device/RemoteScannedDevice | + if device.address == address: + // Without doing an active scan we don't get a scan response. + expect-not device.is-scan-response + + advertisement/AdvertisementData? := null + scan-response/AdvertisementData? := null + while true: // Use loop to be able to break out of the block. + central.scan --duration=(Duration --s=3) --active: | device/RemoteScannedDevice | + if device.address == address: + if device.is-scan-response: + scan-response = device.data + else: + advertisement = device.data + if advertisement and scan-response: break + break + + expect-equals 1 advertisement.data-blocks.size + expect-equals "Test" advertisement.name + expect-equals 1 scan-response.data-blocks.size + expect-equals [TEST-SERVICE] scan-response.services + + characteristic.write #[central-test-counter++] + remote-device.close central.close adapter.close