From 3cb9adb61ade4fcdafa02fbbd778761067fa1020 Mon Sep 17 00:00:00 2001 From: h2zero Date: Thu, 28 Nov 2024 09:17:42 -0700 Subject: [PATCH] Add extended scan features. * Added new method `NimBLEScan::setScanPhy` to enable/disable the PHY's to scan on. * Added new method `NimBLEScan::setScanPeriod` which will allow for setting a scan restart timer in the controller. * Updated `NimBLEScan::start` to allow the command to be sent with updated parameters if already scanning. * Added extended scan example. * Removed storing and restarting of the scan on host reset as it is more appropriate to call the scanEnded callback instead. --- .../NimBLE_extended_scan/CMakeLists.txt | 7 ++ .../NimBLE_extended_scan/main/CMakeLists.txt | 4 + .../NimBLE_extended_scan/main/main.cpp | 68 +++++++++++++ .../NimBLE_extended_scan/sdkconfig.defaults | 13 +++ src/NimBLEScan.cpp | 98 +++++++++++-------- src/NimBLEScan.h | 26 +++-- 6 files changed, 168 insertions(+), 48 deletions(-) create mode 100644 examples/Bluetooth_5/NimBLE_extended_scan/CMakeLists.txt create mode 100644 examples/Bluetooth_5/NimBLE_extended_scan/main/CMakeLists.txt create mode 100644 examples/Bluetooth_5/NimBLE_extended_scan/main/main.cpp create mode 100644 examples/Bluetooth_5/NimBLE_extended_scan/sdkconfig.defaults diff --git a/examples/Bluetooth_5/NimBLE_extended_scan/CMakeLists.txt b/examples/Bluetooth_5/NimBLE_extended_scan/CMakeLists.txt new file mode 100644 index 0000000..9270fad --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_extended_scan/CMakeLists.txt @@ -0,0 +1,7 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(SUPPORTED_TARGETS esp32c3 esp32s3 esp32c6 esp32h2 esp32c2) +project(NimBLE_extended_scan) diff --git a/examples/Bluetooth_5/NimBLE_extended_scan/main/CMakeLists.txt b/examples/Bluetooth_5/NimBLE_extended_scan/main/CMakeLists.txt new file mode 100644 index 0000000..0a5a557 --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_extended_scan/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "main.cpp") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() diff --git a/examples/Bluetooth_5/NimBLE_extended_scan/main/main.cpp b/examples/Bluetooth_5/NimBLE_extended_scan/main/main.cpp new file mode 100644 index 0000000..f7dfe94 --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_extended_scan/main/main.cpp @@ -0,0 +1,68 @@ +/** + * NimBLE Extended Scanner Demo: + * + * Demonstrates the Bluetooth 5.x scanning capabilities of the NimBLE library. + * + * Created: on November 28, 2024 + * Author: H2zero + * +*/ + +#include + +static uint32_t scanTime = 10 * 1000; // In milliseconds, 0 = scan forever +static NimBLEScan::Phy scanPhy = NimBLEScan::Phy::SCAN_ALL; + +// Define a class to handle the callbacks when advertisements are received +class scanCallbacks: public NimBLEScanCallbacks { + void onResult(const NimBLEAdvertisedDevice* advertisedDevice) { + printf("Advertised Device found: %s\n PHY1: %d\n PHY2: %d\n", advertisedDevice->toString().c_str(), + advertisedDevice->getPrimaryPhy(), advertisedDevice->getSecondaryPhy()); + } + + // Callback to process the results of the completed scan or restart it + void onScanEnd(const NimBLEScanResults& scanResults, int reason) { + printf("Scan Ended, reason: %d; found %d devices\n", reason, scanResults.getCount()); + + // Try Different PHY's + switch (scanPhy) { + case NimBLEScan::Phy::SCAN_ALL: + printf("Scanning only 1M PHY\n"); + scanPhy = NimBLEScan::Phy::SCAN_1M; + break; + case NimBLEScan::Phy::SCAN_1M: + printf("Scanning only CODED PHY\n"); + scanPhy = NimBLEScan::Phy::SCAN_CODED; + break; + case NimBLEScan::Phy::SCAN_CODED: + printf("Scanning all PHY's\n"); + scanPhy = NimBLEScan::Phy::SCAN_ALL; + break; + } + + NimBLEScan* pScan = NimBLEDevice::getScan(); + pScan->setPhy(scanPhy); + pScan->start(scanTime); + } +} scanCb; + +extern "C" void app_main (void) { + printf("Starting Extended Scanner\n"); + + // Initialize NimBLE, no device name specified as we are not advertising + NimBLEDevice::init(""); + NimBLEScan* pScan = NimBLEDevice::getScan(); + + // Set the callbacks that the scanner will call on events. + pScan->setScanCallbacks(&scanCb); + + // Use active scanning to obtain scan response data from advertisers + pScan->setActiveScan(true); + + // Set the initial PHY's to scan on, default is SCAN_ALL + pScan->setPhy(scanPhy); + + // Start scanning for scanTime, 0 = forever + pScan->start(scanTime); + printf("Scanning for peripherals\n"); +} diff --git a/examples/Bluetooth_5/NimBLE_extended_scan/sdkconfig.defaults b/examples/Bluetooth_5/NimBLE_extended_scan/sdkconfig.defaults new file mode 100644 index 0000000..81960c1 --- /dev/null +++ b/examples/Bluetooth_5/NimBLE_extended_scan/sdkconfig.defaults @@ -0,0 +1,13 @@ +# Override some defaults so BT stack is enabled +# in this example + +# +# BT config +# +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n +CONFIG_BTDM_CTRL_MODE_BTDM=n +CONFIG_BT_BLUEDROID_ENABLED=n +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_BT_NIMBLE_EXT_ADV=y diff --git a/src/NimBLEScan.cpp b/src/NimBLEScan.cpp index cf2dd4b..31ea9c7 100644 --- a/src/NimBLEScan.cpp +++ b/src/NimBLEScan.cpp @@ -32,7 +32,6 @@ NimBLEScan::NimBLEScan() : m_pScanCallbacks{&defaultScanCallbacks}, // default interval + window, no whitelist scan filter,not limited scan, no scan response, filter_duplicates m_scanParams{0, 0, BLE_HCI_SCAN_FILT_NO_WL, 0, 1, 1}, - m_duration{BLE_HS_FOREVER}, m_pTaskData{nullptr}, m_maxResults{0xFF} {} @@ -173,11 +172,14 @@ void NimBLEScan::setActiveScan(bool active) { /** * @brief Set whether or not the BLE controller should only report results * from devices it has not already seen. - * @param [in] enabled If true, scanned devices will only be reported once. - * @details The controller has a limited buffer and will start reporting - * duplicate devices once the limit is reached. + * @param [in] enabled If set to 1 (true), scanned devices will only be reported once. + * If set to 0 duplicates will be reported each time they are seen. + * If using extended scanning this can be set to 2 which will reset the duplicate filter + * at the end of each scan period if the scan period is set. + * @note The controller has a limited buffer and will start reporting +duplicate devices once the limit is reached. */ -void NimBLEScan::setDuplicateFilter(bool enabled) { +void NimBLEScan::setDuplicateFilter(uint8_t enabled) { m_scanParams.filter_duplicates = enabled; } // setDuplicateFilter @@ -219,7 +221,7 @@ void NimBLEScan::setFilterPolicy(uint8_t filter) { */ void NimBLEScan::setMaxResults(uint8_t maxResults) { m_maxResults = maxResults; -} +} // setMaxResults /** * @brief Set the call backs to be invoked. @@ -237,18 +239,20 @@ void NimBLEScan::setScanCallbacks(NimBLEScanCallbacks* pScanCallbacks, bool want /** * @brief Set the interval to scan. - * @param [in] intervalMSecs The scan interval (how often) in milliseconds. + * @param [in] intervalMs The scan interval in milliseconds. + * @details The interval is the time between the start of two consecutive scan windows. + * When a new interval starts the controller changes the channel it's scanning on. */ -void NimBLEScan::setInterval(uint16_t intervalMSecs) { - m_scanParams.itvl = (intervalMSecs * 16) / 10; +void NimBLEScan::setInterval(uint16_t intervalMs) { + m_scanParams.itvl = (intervalMs * 16) / 10; } // setInterval /** * @brief Set the window to actively scan. - * @param [in] windowMSecs How long during the interval to actively scan. + * @param [in] windowMs How long during the interval to actively scan in milliseconds. */ -void NimBLEScan::setWindow(uint16_t windowMSecs) { - m_scanParams.window = (windowMSecs * 16) / 10; +void NimBLEScan::setWindow(uint16_t windowMs) { + m_scanParams.window = (windowMs * 16) / 10; } // setWindow /** @@ -259,6 +263,29 @@ bool NimBLEScan::isScanning() { return ble_gap_disc_active(); } +# if CONFIG_BT_NIMBLE_EXT_ADV +/** + * @brief Set the PHYs to scan. + * @param [in] phyMask The PHYs to scan, a bit mask of: + * * NIMBLE_CPP_SCAN_1M + * * NIMBLE_CPP_SCAN_CODED + */ +void NimBLEScan::setPhy(Phy phyMask) { + m_phy = phyMask; +} // setScanPhy + +/** + * @brief Set the extended scanning period. + * @param [in] periodMs The scan period in milliseconds + * @details The period is the time between the start of two consecutive scan periods. + * This works as a timer to restart scanning at the specified amount of time in periodMs. + * @note The duration used when this is set must be less than period. + */ +void NimBLEScan::setPeriod(uint32_t periodMs) { + m_period = (periodMs + 500) / 1280; // round up 1.28 second units +} // setScanPeriod +# endif + /** * @brief Start scanning. * @param [in] duration The duration in milliseconds for which to scan. 0 == scan forever. @@ -269,35 +296,24 @@ bool NimBLEScan::isScanning() { */ bool NimBLEScan::start(uint32_t duration, bool isContinue, bool restart) { NIMBLE_LOGD(LOG_TAG, ">> start: duration=%" PRIu32, duration); - - if (ble_gap_conn_active()) { - NIMBLE_LOGE(LOG_TAG, "Connection in progress, cannot start scan"); - return false; - } - if (isScanning()) { if (restart) { NIMBLE_LOGI(LOG_TAG, "Scan already in progress, restarting it"); if (!stop()) { return false; } - } else { - NIMBLE_LOGI(LOG_TAG, "Scan already in progress"); - return true; + + if (!isContinue) { + clearResults(); + } + } + } else { // Don't clear results while scanning is active + if (!isContinue) { + clearResults(); } } - if (!isContinue) { - clearResults(); - } - - // Save the duration in the case that the host is reset so we can reuse it. - m_duration = duration; - - // If 0 duration specified then we assume a continuous scan is desired. - if (duration == 0) { - duration = BLE_HS_FOREVER; - } + // If scanning is already active, call the functions anyway as the parameters can be changed. # if CONFIG_BT_NIMBLE_EXT_ADV ble_gap_ext_disc_params scan_params; @@ -305,17 +321,21 @@ bool NimBLEScan::start(uint32_t duration, bool isContinue, bool restart) { scan_params.itvl = m_scanParams.itvl; scan_params.window = m_scanParams.window; int rc = ble_gap_ext_disc(NimBLEDevice::m_ownAddrType, - duration / 10, - 0, + duration / 10, // 10ms units + m_period, m_scanParams.filter_duplicates, m_scanParams.filter_policy, m_scanParams.limited, - &scan_params, - &scan_params, + m_phy & SCAN_1M ? &scan_params : NULL, + m_phy & SCAN_CODED ? &scan_params : NULL, NimBLEScan::handleGapEvent, NULL); # else - int rc = ble_gap_disc(NimBLEDevice::m_ownAddrType, duration, &m_scanParams, NimBLEScan::handleGapEvent, NULL); + int rc = ble_gap_disc(NimBLEDevice::m_ownAddrType, + duration ? duration : BLE_HS_FOREVER, + &m_scanParams, + NimBLEScan::handleGapEvent, + NULL); # endif switch (rc) { case 0: @@ -403,9 +423,7 @@ void NimBLEScan::erase(const NimBLEAdvertisedDevice* device) { * If the application was scanning indefinitely with a callback, restart it. */ void NimBLEScan::onHostSync() { - if (m_duration == 0 && m_pScanCallbacks != &defaultScanCallbacks) { - start(0, false); - } + m_pScanCallbacks->onScanEnd(m_scanResults, BLE_HS_ENOTSYNCED); } /** diff --git a/src/NimBLEScan.h b/src/NimBLEScan.h index 426b9b0..62b5e2a 100644 --- a/src/NimBLEScan.h +++ b/src/NimBLEScan.h @@ -11,8 +11,8 @@ * Created on: Jul 1, 2017 * Author: kolban */ -#ifndef COMPONENTS_NIMBLE_SCAN_H_ -#define COMPONENTS_NIMBLE_SCAN_H_ +#ifndef NIMBLE_CPP_SCAN_H_ +#define NIMBLE_CPP_SCAN_H_ #include "nimconfig.h" #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER) @@ -66,9 +66,9 @@ class NimBLEScan { bool isScanning(); void setScanCallbacks(NimBLEScanCallbacks* pScanCallbacks, bool wantDuplicates = false); void setActiveScan(bool active); - void setInterval(uint16_t intervalMSecs); - void setWindow(uint16_t windowMSecs); - void setDuplicateFilter(bool enabled); + void setInterval(uint16_t intervalMs); + void setWindow(uint16_t windowMs); + void setDuplicateFilter(uint8_t enabled); void setLimitedOnly(bool enabled); void setFilterPolicy(uint8_t filter); bool stop(); @@ -79,6 +79,12 @@ class NimBLEScan { void erase(const NimBLEAddress& address); void erase(const NimBLEAdvertisedDevice* device); +# if CONFIG_BT_NIMBLE_EXT_ADV + enum Phy { SCAN_1M = 0x01, SCAN_CODED = 0x02, SCAN_ALL = 0x03 }; + void setPhy(Phy phyMask); + void setPeriod(uint32_t periodMs); +# endif + private: friend class NimBLEDevice; @@ -90,9 +96,13 @@ class NimBLEScan { NimBLEScanCallbacks* m_pScanCallbacks; ble_gap_disc_params m_scanParams; NimBLEScanResults m_scanResults; - uint32_t m_duration; NimBLETaskData* m_pTaskData; uint8_t m_maxResults; + +# if CONFIG_BT_NIMBLE_EXT_ADV + uint8_t m_phy{SCAN_ALL}; + uint16_t m_period{0}; +# endif }; /** @@ -122,5 +132,5 @@ class NimBLEScanCallbacks { virtual void onScanEnd(const NimBLEScanResults& scanResults, int reason); }; -#endif /* CONFIG_BT_ENABLED CONFIG_BT_NIMBLE_ROLE_OBSERVER */ -#endif /* COMPONENTS_NIMBLE_SCAN_H_ */ +#endif // CONFIG_BT_ENABLED CONFIG_BT_NIMBLE_ROLE_OBSERVER +#endif // NIMBLE_CPP_SCAN_H_