diff --git a/hal/src/nRF52840/ble_hal.cpp b/hal/src/nRF52840/ble_hal.cpp index d34460bf5a..99372d502a 100644 --- a/hal/src/nRF52840/ble_hal.cpp +++ b/hal/src/nRF52840/ble_hal.cpp @@ -1418,8 +1418,11 @@ void BleObject::Observer::onScanGuardTimerExpired(os_timer_t timer) { } int BleObject::Observer::startScanning(hal_ble_on_scan_result_cb_t callback, void* context) { + BleLock lk; + CHECK_FALSE(isScanning_, SYSTEM_ERROR_INVALID_STATE); SCOPE_GUARD ({ + stopScanning(); clearPendingResult(); }); ble_gap_scan_params_t bleGapScanParams = toPlatformScanParams(); @@ -1439,6 +1442,7 @@ int BleObject::Observer::startScanning(hal_ble_on_scan_result_cb_t callback, voi // We don't return here, as scanning may still timeout by Softdevice as expected. } } + lk.unlock(); if (os_semaphore_take(scanSemaphore_, CONCURRENT_WAIT_FOREVER, false)) { SPARK_ASSERT(false); return SYSTEM_ERROR_TIMEOUT; @@ -1447,9 +1451,12 @@ int BleObject::Observer::startScanning(hal_ble_on_scan_result_cb_t callback, voi } int BleObject::Observer::stopScanning() { + BleLock lk; + if (!isScanning_) { return SYSTEM_ERROR_NONE; } + if (os_timer_is_active(scanGuardTimer_, nullptr)) { os_timer_change(scanGuardTimer_, OS_TIMER_CHANGE_STOP, hal_interrupt_is_isr() ? true : false, 0, 0, nullptr); } @@ -1458,16 +1465,10 @@ int BleObject::Observer::stopScanning() { if (sd_ble_gap_scan_stop() != NRF_SUCCESS) { // LOG(ERROR, "Device is not in scanning state."); } - bool give = false; - ATOMIC_BLOCK() { - if (isScanning_) { - isScanning_ = false; - give = true; - } - } - if (give) { - os_semaphore_give(scanSemaphore_, false); - } + isScanning_ = false; + + // Just in case + os_semaphore_give(scanSemaphore_, false); return SYSTEM_ERROR_NONE; } @@ -1783,8 +1784,6 @@ int BleObject::ConnectionsManager::connect(const hal_ble_conn_cfg_t* config, hal CHECK_TRUE(connections_.size() < BLE_MAX_LINK_COUNT, SYSTEM_ERROR_LIMIT_EXCEEDED); CHECK_TRUE(config, SYSTEM_ERROR_INVALID_ARGUMENT); CHECK_TRUE(connHandle, SYSTEM_ERROR_INVALID_ARGUMENT); - // Stop scanning first to give the scanning semaphore if possible. - CHECK(BleObject::getInstance().observer()->stopScanning()); SCOPE_GUARD ({ connectingAddr_ = {}; }); @@ -4133,7 +4132,6 @@ int hal_ble_gap_get_scan_parameters(hal_ble_scan_params_t* scan_params, void* re } int hal_ble_gap_start_scan(hal_ble_on_scan_result_cb_t callback, void* context, void* reserved) { - BleLock lk; LOG_DEBUG(TRACE, "hal_ble_gap_start_scan()."); CHECK_TRUE(BleObject::getInstance().initialized(), SYSTEM_ERROR_INVALID_STATE); return BleObject::getInstance().observer()->startScanning(callback, context); diff --git a/hal/src/rtl872x/ble_hal.cpp b/hal/src/rtl872x/ble_hal.cpp index bf8fb9be4d..bdfc2ca9db 100644 --- a/hal/src/rtl872x/ble_hal.cpp +++ b/hal/src/rtl872x/ble_hal.cpp @@ -1675,21 +1675,31 @@ int BleGap::startScanning(hal_ble_on_scan_result_cb_t callback, void* context) { resetStack = true; return SYSTEM_ERROR_TIMEOUT; } + // We unlock BLE lock here though so that other BLE APIs are accessible while we are performing a scan. + // Especially if it's a continuous scan. + lk.unlock(); // To be consistent with Gen3, the scan proceedure is blocked for now, // so we can simply wait for the semaphore to be given without introducing a dedicated timer. - if (scanParams_.timeout == BLE_SCAN_TIMEOUT_UNLIMITED) { - lk.unlock(); - } os_semaphore_take(scanSemaphore_, (scanParams_.timeout == BLE_SCAN_TIMEOUT_UNLIMITED) ? CONCURRENT_WAIT_FOREVER : (scanParams_.timeout * 10), false); return SYSTEM_ERROR_NONE; } int BleGap::stopScanning(bool resetStack) { + BleLock lk; + if (!isScanning_) { return SYSTEM_ERROR_NONE; } - BleLock lk; + if (BleEventDispatcher::getInstance().isThreadCurrent()) { + // Can't stop scanning properly from event processing thread + // Unblock the caller of scan() and perform stopScanning() + // in there. + // XXX: It's not safe to call le_scan_stop() especially + // from inside the ble scan result callback! + os_semaphore_give(scanSemaphore_, false); + return 0; + } const int LE_SCAN_STOP_RETRIES = 1; for (int i = 0; i < LE_SCAN_STOP_RETRIES; i++) { @@ -1728,7 +1738,9 @@ int BleGap::stopScanning(bool resetStack) { clearPendingResult(); } + // Just in case os_semaphore_give(scanSemaphore_, false); + return SYSTEM_ERROR_NONE; } @@ -1886,8 +1898,6 @@ int BleGap::connect(const hal_ble_conn_cfg_t* config, hal_ble_conn_handle_t* con // Make sure that event dispatching is started CHECK(start()); - // Stop scanning first to give the scanning semaphore if possible. - CHECK(stopScanning()); SCOPE_GUARD ({ connectingAddr_ = {}; connecting_ = false; diff --git a/user/tests/wiring/ble_central_peripheral/ble_central/central.cpp b/user/tests/wiring/ble_central_peripheral/ble_central/central.cpp index efd567efde..ce338774ce 100644 --- a/user/tests/wiring/ble_central_peripheral/ble_central/central.cpp +++ b/user/tests/wiring/ble_central_peripheral/ble_central/central.cpp @@ -60,6 +60,9 @@ using namespace particle::test; constexpr uint16_t LOCAL_DESIRED_ATT_MTU = 100; constexpr uint16_t PEER_DESIRED_ATT_MTU = 123; +Thread* scanThread = nullptr; +volatile unsigned scanResults = 0; + test(BLE_000_Central_Cloud_Connect) { subscribeEvents(BLE_ROLE_PERIPHERAL); Particle.connect(); @@ -579,5 +582,92 @@ test(BLE_32_Att_Mtu_Exchange) { } } +test(BLE_33_Central_Can_Connect_While_Scanning) { + BleScanParams params = {}; + params.size = sizeof(BleScanParams); + params.timeout = 0; + params.interval = 8000; // *0.625ms = 5s + params.window = 8000; // *0.625 = 5s + params.active = true; // Send scan request + params.filter_policy = BLE_SCAN_FP_ACCEPT_ALL; + assertEqual(0, BLE.setScanParameters(¶ms)); + + scanThread = new Thread("test", [](void* param) -> os_thread_return_t { + BLE.scanWithFilter(BleScanFilter().allowDuplicates(true), +[](const BleScanResult *result, void *param) -> void { + auto scanResults = (volatile unsigned int*)param; + (*scanResults)++; + }, param); + }, (void*)&scanResults); + + assertTrue(BLE.scanning()); + assertFalse(BLE.connected()); + peer = BLE.connect(peerAddr, false); + assertTrue(peer.connected()); + assertTrue(BLE.scanning()); + SCOPE_GUARD({ + SCOPE_GUARD ({ + delay(500); + assertEqual(BLE.disconnect(peer), (int)SYSTEM_ERROR_NONE); + assertFalse(BLE.connected()); + delay(500); + }); + }); + scanResults = 0; + delay(2000); +#if !HAL_PLATFORM_NRF52840 + assertMoreOrEqual((unsigned)scanResults, 1); +#endif + assertTrue(peer.connected()); +} + +test(BLE_34_Central_Can_Connect_While_Scanning_After_Disconnect) { + SCOPE_GUARD({ + if (BLE.scanning() && scanThread) { + assertEqual(0, BLE.stopScanning()); + scanThread->join(); + delete scanThread; + } + }); + assertTrue(BLE.scanning()); + assertFalse(BLE.connected()); + scanResults = 0; + delay(5000); + assertMoreOrEqual((unsigned)scanResults, 1); +} + +test(BLE_35_Central_Can_Connect_While_Peripheral_Is_Scanning_Prepare) { +} + +test(BLE_36_Central_Can_Connect_While_Peripheral_Is_Scanning) { + peer = BLE.connect(peerAddr, false); + assertTrue(peer.connected()); + delay(5000); + { + SCOPE_GUARD ({ + delay(500); + assertEqual(BLE.disconnect(peer), (int)SYSTEM_ERROR_NONE); + assertFalse(BLE.connected()); + delay(500); + }); + } +} + +test(BLE_37_Central_Can_Connect_While_Peripheral_Is_Scanning_And_Stops_Scanning_Prepare) { +} + +test(BLE_38_Central_Can_Connect_While_Peripheral_Is_Scanning_And_Stops_Scanning) { + peer = BLE.connect(peerAddr, false); + assertTrue(peer.connected()); + delay(5000); + { + SCOPE_GUARD ({ + delay(500); + assertEqual(BLE.disconnect(peer), (int)SYSTEM_ERROR_NONE); + assertFalse(BLE.connected()); + delay(500); + }); + } +} + #endif // #if Wiring_BLE == 1 diff --git a/user/tests/wiring/ble_central_peripheral/ble_peripheral/peripheral.cpp b/user/tests/wiring/ble_central_peripheral/ble_peripheral/peripheral.cpp index 023c1ac71b..8533605cf4 100644 --- a/user/tests/wiring/ble_central_peripheral/ble_peripheral/peripheral.cpp +++ b/user/tests/wiring/ble_central_peripheral/ble_peripheral/peripheral.cpp @@ -69,6 +69,9 @@ using namespace particle::test; constexpr uint16_t LOCAL_DESIRED_ATT_MTU = 123; constexpr uint16_t PEER_DESIRED_ATT_MTU = 100; +Thread* scanThread = nullptr; +volatile unsigned scanResults = 0; + test(BLE_000_Peripheral_Cloud_Connect) { subscribeEvents(BLE_ROLE_PERIPHERAL); Particle.connect(); @@ -487,5 +490,118 @@ test(BLE_32_Att_Mtu_Exchange) { } } +test(BLE_33_Central_Can_Connect_While_Scanning) { + assertTrue(waitFor(BLE.connected, 20000)); + SCOPE_GUARD ({ + assertTrue(waitFor([]{ return !BLE.connected(); }, 10000)); + assertFalse(BLE.connected()); + }); +} + +test(BLE_34_Central_Can_Connect_While_Scanning_After_Disconnect) { +} + +test(BLE_35_Central_Can_Connect_While_Peripheral_Is_Scanning_Prepare) { + BleScanParams params = {}; + params.size = sizeof(BleScanParams); + params.timeout = 0; + params.interval = 8000; // *0.625ms = 5s + params.window = 8000; // *0.625 = 5s + params.active = true; // Send scan request + params.filter_policy = BLE_SCAN_FP_ACCEPT_ALL; + assertEqual(0, BLE.setScanParameters(¶ms)); + + scanThread = new Thread("test", [](void* param) -> os_thread_return_t { + BLE.scanWithFilter(BleScanFilter().allowDuplicates(true), +[](const BleScanResult *result, void *context) -> void { + scanResults++; + }, nullptr); + }, nullptr); + assertTrue((bool)scanThread); +} + +test(BLE_36_Central_Can_Connect_While_Peripheral_Is_Scanning) { + SCOPE_GUARD({ + if (BLE.scanning() && scanThread) { + BLE.stopScanning(); + scanThread->join(); + delete scanThread; + scanThread = nullptr; + } + }); + assertTrue(BLE.scanning()); + assertTrue(waitFor(BLE.connected, 60000)); + scanResults = 0; + delay(2000); +#if !HAL_PLATFORM_NRF52840 + assertMoreOrEqual((unsigned)scanResults, 1); +#endif + SCOPE_GUARD ({ + assertTrue(waitFor([]{ return !BLE.connected(); }, 10000)); + assertFalse(BLE.connected()); + + scanResults = 0; + delay(2000); +#if !HAL_PLATFORM_NRF52840 + assertMoreOrEqual((unsigned)scanResults, 1); +#endif + + assertEqual(0, BLE.stopScanning()); + assertFalse(BLE.scanning()); + scanThread->join(); + delete scanThread; + scanThread = nullptr; + }); +} + +test(BLE_37_Central_Can_Connect_While_Peripheral_Is_Scanning_And_Stops_Scanning_Prepare) { + BleScanParams params = {}; + params.size = sizeof(BleScanParams); + params.timeout = 0; + params.interval = 8000; // *0.625ms = 5s + params.window = 8000; // *0.625 = 5s + params.active = true; // Send scan request + params.filter_policy = BLE_SCAN_FP_ACCEPT_ALL; + assertEqual(0, BLE.setScanParameters(¶ms)); + + scanThread = new Thread("test", [](void* param) -> os_thread_return_t { + BLE.scanWithFilter(BleScanFilter().allowDuplicates(true), +[](const BleScanResult *result, void *context) -> void { + scanResults++; + }, nullptr); + }, nullptr); + assertTrue((bool)scanThread); +} + +test(BLE_38_Central_Can_Connect_While_Peripheral_Is_Scanning_And_Stops_Scanning) { + SCOPE_GUARD({ + if (BLE.scanning() && scanThread) { + BLE.stopScanning(); + scanThread->join(); + delete scanThread; + scanThread = nullptr; + } + }); + assertTrue(BLE.scanning()); + assertTrue(waitFor(BLE.connected, 60000)); + scanResults = 0; + delay(2000); +#if !HAL_PLATFORM_NRF52840 + assertMoreOrEqual((unsigned)scanResults, 1); +#endif + + assertEqual(0, BLE.stopScanning()); + assertFalse(BLE.scanning()); + scanThread->join(); + delete scanThread; + scanThread = nullptr; + + assertTrue(BLE.connected()); + + SCOPE_GUARD ({ + assertTrue(waitFor([]{ return !BLE.connected(); }, 10000)); + assertFalse(BLE.connected()); + }); +} + + #endif // #if Wiring_BLE == 1 diff --git a/user/tests/wiring/no_fixture_ble/ble.cpp b/user/tests/wiring/no_fixture_ble/ble.cpp index 425d34d53e..564878191b 100644 --- a/user/tests/wiring/no_fixture_ble/ble.cpp +++ b/user/tests/wiring/no_fixture_ble/ble.cpp @@ -508,5 +508,481 @@ test(BLE_12_BLE_Address_Validation) { assertFalse(address.isValid()); } +test(BLE_13_timed_scan) { + BLE.on(); + + BleScanParams params = {}; + params.size = sizeof(BleScanParams); + params.timeout = 500; // *10ms = 5s overall duration + params.interval = 8000; // *0.625ms = 5s + params.window = 8000; // *0.625 = 5s + params.active = true; // Send scan request + params.filter_policy = BLE_SCAN_FP_ACCEPT_ALL; + assertEqual(0, BLE.setScanParameters(¶ms)); + + struct BleScanData { + size_t results; + int retval; + }; + BleScanData data = {}; + + // Scan with duplicates + auto start = millis(); + int r = BLE.scanWithFilter(BleScanFilter().allowDuplicates(true), +[](const BleScanResult *result, void *context) -> void { + auto data = (BleScanData*)context; + data->results++; + }, &data); + auto end = millis(); + + assertFalse(BLE.scanning()); + assertMore(r, 0); + assertMoreOrEqual(end - start, 5000); + assertLessOrEqual(end - start, 6000); + + // Regular scan + memset(&data, 0, sizeof(data)); + start = millis(); + r = BLE.scan(+[](const BleScanResult *result, void *context) -> void { + auto data = (BleScanData*)context; + data->results++; + }, &data); + end = millis(); + + assertFalse(BLE.scanning()); + assertMore(r, 0); + assertMoreOrEqual(end - start, 5000); + assertLessOrEqual(end - start, 6000); + + // Scan with duplicates in a separate thread + memset(&data, 0, sizeof(data)); + start = millis(); + Thread* t = new Thread("test", [](void* param) -> os_thread_return_t { + auto data = (BleScanData*)param; + data->retval = BLE.scanWithFilter(BleScanFilter().allowDuplicates(true), +[](const BleScanResult *result, void *context) -> void { + auto data = (BleScanData*)context; + data->results++; + }, param); + }, (void*)&data); + SCOPE_GUARD({ + if (t) { + t->join(); + delete t; + } + }); + assertTrue(t); + delay(500); + assertTrue(BLE.scanning()); + waitForNot(BLE.scanning, 10000); + end = millis(); + assertFalse(BLE.scanning()); + t->join(); + delete t; + t = nullptr; + assertMore(data.retval, 0); + assertMore(data.results, 0); + assertMoreOrEqual(end - start, 5000); + assertLessOrEqual(end - start, 6000); + + // Regular scan in a separate thread + memset(&data, 0, sizeof(data)); + start = millis(); + t = new Thread("test", [](void* param) -> os_thread_return_t { + auto data = (BleScanData*)param; + data->retval = BLE.scan(+[](const BleScanResult *result, void *context) -> void { + auto data = (BleScanData*)context; + data->results++; + }, param); + }, (void*)&data); + SCOPE_GUARD({ + if (t) { + t->join(); + delete t; + } + }); + assertTrue(t); + delay(500); + assertTrue(BLE.scanning()); + waitForNot(BLE.scanning, 10000); + end = millis(); + assertFalse(BLE.scanning()); + t->join(); + delete t; + t = nullptr; + assertMore(data.retval, 0); + assertMore(data.results, 0); + assertMoreOrEqual(end - start, 5000); + assertLessOrEqual(end - start, 6000); +} + +test(BLE_14_timed_scan_interrupted_externally) { + BLE.on(); + + BleScanParams params = {}; + params.size = sizeof(BleScanParams); + params.timeout = 500; // *10ms = 5s overall duration + params.interval = 8000; // *0.625ms = 5s + params.window = 8000; // *0.625 = 5s + params.active = true; // Send scan request + params.filter_policy = BLE_SCAN_FP_ACCEPT_ALL; + assertEqual(0, BLE.setScanParameters(¶ms)); + + struct BleScanData { + size_t results; + int retval; + }; + BleScanData data = {}; + + // Scan with duplicates in a separate thread + memset(&data, 0, sizeof(data)); + auto start = millis(); + Thread* t = new Thread("test", [](void* param) -> os_thread_return_t { + auto data = (BleScanData*)param; + data->retval = BLE.scanWithFilter(BleScanFilter().allowDuplicates(true), +[](const BleScanResult *result, void *context) -> void { + auto data = (BleScanData*)context; + data->results++; + }, param); + }, (void*)&data); + SCOPE_GUARD({ + if (t) { + t->join(); + delete t; + } + }); + assertTrue(t); + delay(1000); + assertTrue(BLE.scanning()); + assertEqual(0, BLE.stopScanning()); + waitForNot(BLE.scanning, 10000); + auto end = millis(); + assertFalse(BLE.scanning()); + t->join(); + delete t; + t = nullptr; + assertMore(data.retval, 0); + assertMoreOrEqual(end - start, 1000); + assertLessOrEqual(end - start, 2000); + + // Regular scan in a separate thread + memset(&data, 0, sizeof(data)); + start = millis(); + t = new Thread("test", [](void* param) -> os_thread_return_t { + auto data = (BleScanData*)param; + data->retval = BLE.scan(+[](const BleScanResult *result, void *context) -> void { + auto data = (BleScanData*)context; + data->results++; + }, param); + }, (void*)&data); + SCOPE_GUARD({ + if (t) { + t->join(); + delete t; + } + }); + assertTrue(t); + delay(1000); + assertTrue(BLE.scanning()); + assertEqual(0, BLE.stopScanning()); + waitForNot(BLE.scanning, 10000); + end = millis(); + assertFalse(BLE.scanning()); + t->join(); + delete t; + t = nullptr; + assertMore(data.retval, 0); + assertMoreOrEqual(end - start, 1000); + assertLessOrEqual(end - start, 2000); +} + +test(BLE_15_timed_scan_interrupted_inside_scan_callback) { + BLE.on(); + + BleScanParams params = {}; + params.size = sizeof(BleScanParams); + params.timeout = 500; // *10ms = 5s overall duration + params.interval = 8000; // *0.625ms = 5s + params.window = 8000; // *0.625 = 5s + params.active = true; // Send scan request + params.filter_policy = BLE_SCAN_FP_ACCEPT_ALL; + assertEqual(0, BLE.setScanParameters(¶ms)); + + struct BleScanData { + size_t results; + int retval; + }; + BleScanData data = {}; + + // Scan with duplicates + auto start = millis(); + int r = BLE.scanWithFilter(BleScanFilter().allowDuplicates(true), +[](const BleScanResult *result, void *context) -> void { + auto data = (BleScanData*)context; + data->results++; + BLE.stopScanning(); + }, &data); + auto end = millis(); + + assertFalse(BLE.scanning()); + assertMoreOrEqual(r, 1); + assertLess(end - start, 2500); + + // Regular scan + memset(&data, 0, sizeof(data)); + start = millis(); + r = BLE.scan(+[](const BleScanResult *result, void *context) -> void { + auto data = (BleScanData*)context; + data->results++; + BLE.stopScanning(); + }, &data); + end = millis(); + + assertFalse(BLE.scanning()); + assertMoreOrEqual(r, 1); + assertLess(end - start, 2500); +} + +test(BLE_16_indefinite_scan_interrupted_externally) { + BLE.on(); + + BleScanParams params = {}; + params.size = sizeof(BleScanParams); + params.timeout = 0; // indefinite + params.interval = 8000; // *0.625ms = 5s + params.window = 8000; // *0.625 = 5s + params.active = true; // Send scan request + params.filter_policy = BLE_SCAN_FP_ACCEPT_ALL; + assertEqual(0, BLE.setScanParameters(¶ms)); + + struct BleScanData { + size_t results; + int retval; + }; + BleScanData data = {}; + + // Scan with duplicates in a separate thread + memset(&data, 0, sizeof(data)); + auto start = millis(); + Thread* t = new Thread("test", [](void* param) -> os_thread_return_t { + auto data = (BleScanData*)param; + data->retval = BLE.scanWithFilter(BleScanFilter().allowDuplicates(true), +[](const BleScanResult *result, void *context) -> void { + auto data = (BleScanData*)context; + data->results++; + }, param); + }, (void*)&data); + SCOPE_GUARD({ + if (t) { + t->join(); + delete t; + } + }); + assertTrue(t); + delay(1000); + assertTrue(BLE.scanning()); + assertEqual(0, BLE.stopScanning()); + waitForNot(BLE.scanning, 10000); + auto end = millis(); + assertFalse(BLE.scanning()); + t->join(); + delete t; + t = nullptr; + assertMore(data.retval, 0); + assertMoreOrEqual(end - start, 1000); + assertLessOrEqual(end - start, 2000); + + // Regular scan in a separate thread + memset(&data, 0, sizeof(data)); + start = millis(); + t = new Thread("test", [](void* param) -> os_thread_return_t { + auto data = (BleScanData*)param; + data->retval = BLE.scan(+[](const BleScanResult *result, void *context) -> void { + auto data = (BleScanData*)context; + data->results++; + }, param); + }, (void*)&data); + SCOPE_GUARD({ + if (t) { + t->join(); + delete t; + } + }); + assertTrue(t); + delay(1000); + assertTrue(BLE.scanning()); + assertEqual(0, BLE.stopScanning()); + waitForNot(BLE.scanning, 10000); + end = millis(); + assertFalse(BLE.scanning()); + t->join(); + delete t; + t = nullptr; + assertMore(data.retval, 0); + assertMoreOrEqual(end - start, 1000); + assertLessOrEqual(end - start, 2000); +} + +test(BLE_17_indefinite_scan_interrupted_inside_scan_callback) { + BLE.on(); + + BleScanParams params = {}; + params.size = sizeof(BleScanParams); + params.timeout = 0; // indefinite + params.interval = 8000; // *0.625ms = 5s + params.window = 8000; // *0.625 = 5s + params.active = true; // Send scan request + params.filter_policy = BLE_SCAN_FP_ACCEPT_ALL; + assertEqual(0, BLE.setScanParameters(¶ms)); + + struct BleScanData { + size_t results; + int retval; + }; + BleScanData data = {}; + + // Scan with duplicates + auto start = millis(); + int r = BLE.scanWithFilter(BleScanFilter().allowDuplicates(true), +[](const BleScanResult *result, void *context) -> void { + auto data = (BleScanData*)context; + data->results++; + BLE.stopScanning(); + }, &data); + auto end = millis(); + + assertFalse(BLE.scanning()); + assertMoreOrEqual(r, 1); + assertLess(end - start, 2500); + + // Regular scan + memset(&data, 0, sizeof(data)); + start = millis(); + r = BLE.scan(+[](const BleScanResult *result, void *context) -> void { + auto data = (BleScanData*)context; + data->results++; + BLE.stopScanning(); + }, &data); + end = millis(); + + assertFalse(BLE.scanning()); + assertMoreOrEqual(r, 1); + assertLess(end - start, 2500); +} + +test(BLE_17_other_ble_operations_while_timed_scanning) { + BLE.on(); + + BleScanParams params = {}; + params.size = sizeof(BleScanParams); + params.timeout = 3000; // *10ms = 30s overall duration + params.interval = 8000; // *0.625ms = 5s + params.window = 8000; // *0.625 = 5s + params.active = true; // Send scan request + params.filter_policy = BLE_SCAN_FP_ACCEPT_ALL; + assertEqual(0, BLE.setScanParameters(¶ms)); + + struct BleScanData { + size_t results; + int retval; + }; + BleScanData data = {}; + + // Scan with duplicates in a separate thread + memset(&data, 0, sizeof(data)); + auto start = millis(); + Thread* t = new Thread("test", [](void* param) -> os_thread_return_t { + auto data = (BleScanData*)param; + data->retval = BLE.scanWithFilter(BleScanFilter().allowDuplicates(true), +[](const BleScanResult *result, void *context) -> void { + auto data = (BleScanData*)context; + data->results++; + }, param); + }, (void*)&data); + SCOPE_GUARD({ + if (t) { + t->join(); + delete t; + } + }); + assertTrue(t); + delay(1000); + assertTrue(BLE.scanning()); + + assertFalse(BLE.advertising()); + assertEqual(0, BLE.advertise()); + assertTrue(BLE.advertising()); + assertEqual(0, BLE.stopAdvertising()); + assertFalse(BLE.advertising()); + + assertTrue(BLE.scanning()); + + auto m = millis(); + + assertEqual(0, BLE.stopScanning()); + waitForNot(BLE.scanning, 10000); + auto end = millis(); + assertFalse(BLE.scanning()); + t->join(); + delete t; + t = nullptr; + assertMore(data.retval, 0); + assertMoreOrEqual(end - start, m - start); + assertLessOrEqual(end - start, (m - start) + 2000); +} + +test(BLE_18_other_ble_operations_while_indefinite_scanning) { + BLE.on(); + + BleScanParams params = {}; + params.size = sizeof(BleScanParams); + params.timeout = 0; // indefinite + params.interval = 8000; // *0.625ms = 5s + params.window = 8000; // *0.625 = 5s + params.active = true; // Send scan request + params.filter_policy = BLE_SCAN_FP_ACCEPT_ALL; + assertEqual(0, BLE.setScanParameters(¶ms)); + + struct BleScanData { + size_t results; + int retval; + }; + BleScanData data = {}; + + // Scan with duplicates in a separate thread + memset(&data, 0, sizeof(data)); + auto start = millis(); + Thread* t = new Thread("test", [](void* param) -> os_thread_return_t { + auto data = (BleScanData*)param; + data->retval = BLE.scanWithFilter(BleScanFilter().allowDuplicates(true), +[](const BleScanResult *result, void *context) -> void { + auto data = (BleScanData*)context; + data->results++; + }, param); + }, (void*)&data); + SCOPE_GUARD({ + if (t) { + t->join(); + delete t; + } + }); + assertTrue(t); + delay(1000); + assertTrue(BLE.scanning()); + + assertFalse(BLE.advertising()); + assertEqual(0, BLE.advertise()); + assertTrue(BLE.advertising()); + assertEqual(0, BLE.stopAdvertising()); + assertFalse(BLE.advertising()); + + assertTrue(BLE.scanning()); + + auto m = millis(); + + assertEqual(0, BLE.stopScanning()); + waitForNot(BLE.scanning, 10000); + auto end = millis(); + assertFalse(BLE.scanning()); + t->join(); + delete t; + t = nullptr; + assertMore(data.retval, 0); + assertMoreOrEqual(end - start, m - start); + assertLessOrEqual(end - start, (m - start) + 2000); +} + #endif // #if Wiring_BLE == 1 diff --git a/wiring/inc/spark_wiring_ble.h b/wiring/inc/spark_wiring_ble.h index 923711447a..06bfd6dfcd 100644 --- a/wiring/inc/spark_wiring_ble.h +++ b/wiring/inc/spark_wiring_ble.h @@ -1051,6 +1051,7 @@ class BleLocalDevice { } int stopScanning() const; + bool scanning() const; // Access local characteristics BleCharacteristic addCharacteristic(const BleCharacteristic& characteristic); diff --git a/wiring/src/spark_wiring_ble.cpp b/wiring/src/spark_wiring_ble.cpp index e1bd812ac4..0b96c79af5 100644 --- a/wiring/src/spark_wiring_ble.cpp +++ b/wiring/src/spark_wiring_ble.cpp @@ -2677,6 +2677,10 @@ int BleLocalDevice::stopScanning() const { return hal_ble_gap_stop_scan(nullptr); } +bool BleLocalDevice::scanning() const { + return hal_ble_gap_is_scanning(nullptr); +} + int BleLocalDevice::setPPCP(uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t timeout) const { hal_ble_conn_params_t ppcp = {}; ppcp.size = sizeof(hal_ble_conn_params_t);