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.
This commit is contained in:
h2zero 2024-11-28 09:17:42 -07:00 committed by h2zero
parent db2fe36131
commit 3cb9adb61a
6 changed files with 168 additions and 48 deletions

View file

@ -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)

View file

@ -0,0 +1,4 @@
set(COMPONENT_SRCS "main.cpp")
set(COMPONENT_ADD_INCLUDEDIRS ".")
register_component()

View file

@ -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 <NimBLEDevice.h>
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");
}

View file

@ -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

View file

@ -32,7 +32,6 @@ NimBLEScan::NimBLEScan()
: m_pScanCallbacks{&defaultScanCallbacks}, : m_pScanCallbacks{&defaultScanCallbacks},
// default interval + window, no whitelist scan filter,not limited scan, no scan response, filter_duplicates // 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_scanParams{0, 0, BLE_HCI_SCAN_FILT_NO_WL, 0, 1, 1},
m_duration{BLE_HS_FOREVER},
m_pTaskData{nullptr}, m_pTaskData{nullptr},
m_maxResults{0xFF} {} m_maxResults{0xFF} {}
@ -173,11 +172,14 @@ void NimBLEScan::setActiveScan(bool active) {
/** /**
* @brief Set whether or not the BLE controller should only report results * @brief Set whether or not the BLE controller should only report results
* from devices it has not already seen. * from devices it has not already seen.
* @param [in] enabled If true, scanned devices will only be reported once. * @param [in] enabled If set to 1 (true), scanned devices will only be reported once.
* @details The controller has a limited buffer and will start reporting * If set to 0 duplicates will be reported each time they are seen.
* duplicate devices once the limit is reached. * 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; m_scanParams.filter_duplicates = enabled;
} // setDuplicateFilter } // setDuplicateFilter
@ -219,7 +221,7 @@ void NimBLEScan::setFilterPolicy(uint8_t filter) {
*/ */
void NimBLEScan::setMaxResults(uint8_t maxResults) { void NimBLEScan::setMaxResults(uint8_t maxResults) {
m_maxResults = maxResults; m_maxResults = maxResults;
} } // setMaxResults
/** /**
* @brief Set the call backs to be invoked. * @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. * @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) { void NimBLEScan::setInterval(uint16_t intervalMs) {
m_scanParams.itvl = (intervalMSecs * 16) / 10; m_scanParams.itvl = (intervalMs * 16) / 10;
} // setInterval } // setInterval
/** /**
* @brief Set the window to actively scan. * @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) { void NimBLEScan::setWindow(uint16_t windowMs) {
m_scanParams.window = (windowMSecs * 16) / 10; m_scanParams.window = (windowMs * 16) / 10;
} // setWindow } // setWindow
/** /**
@ -259,6 +263,29 @@ bool NimBLEScan::isScanning() {
return ble_gap_disc_active(); 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. * @brief Start scanning.
* @param [in] duration The duration in milliseconds for which to scan. 0 == scan forever. * @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) { bool NimBLEScan::start(uint32_t duration, bool isContinue, bool restart) {
NIMBLE_LOGD(LOG_TAG, ">> start: duration=%" PRIu32, duration); 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 (isScanning()) {
if (restart) { if (restart) {
NIMBLE_LOGI(LOG_TAG, "Scan already in progress, restarting it"); NIMBLE_LOGI(LOG_TAG, "Scan already in progress, restarting it");
if (!stop()) { if (!stop()) {
return false; return false;
} }
} else {
NIMBLE_LOGI(LOG_TAG, "Scan already in progress");
return true;
}
}
if (!isContinue) { if (!isContinue) {
clearResults(); 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;
} }
} else { // Don't clear results while scanning is active
if (!isContinue) {
clearResults();
}
}
// If scanning is already active, call the functions anyway as the parameters can be changed.
# if CONFIG_BT_NIMBLE_EXT_ADV # if CONFIG_BT_NIMBLE_EXT_ADV
ble_gap_ext_disc_params scan_params; 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.itvl = m_scanParams.itvl;
scan_params.window = m_scanParams.window; scan_params.window = m_scanParams.window;
int rc = ble_gap_ext_disc(NimBLEDevice::m_ownAddrType, int rc = ble_gap_ext_disc(NimBLEDevice::m_ownAddrType,
duration / 10, duration / 10, // 10ms units
0, m_period,
m_scanParams.filter_duplicates, m_scanParams.filter_duplicates,
m_scanParams.filter_policy, m_scanParams.filter_policy,
m_scanParams.limited, m_scanParams.limited,
&scan_params, m_phy & SCAN_1M ? &scan_params : NULL,
&scan_params, m_phy & SCAN_CODED ? &scan_params : NULL,
NimBLEScan::handleGapEvent, NimBLEScan::handleGapEvent,
NULL); NULL);
# else # 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 # endif
switch (rc) { switch (rc) {
case 0: case 0:
@ -403,9 +423,7 @@ void NimBLEScan::erase(const NimBLEAdvertisedDevice* device) {
* If the application was scanning indefinitely with a callback, restart it. * If the application was scanning indefinitely with a callback, restart it.
*/ */
void NimBLEScan::onHostSync() { void NimBLEScan::onHostSync() {
if (m_duration == 0 && m_pScanCallbacks != &defaultScanCallbacks) { m_pScanCallbacks->onScanEnd(m_scanResults, BLE_HS_ENOTSYNCED);
start(0, false);
}
} }
/** /**

View file

@ -11,8 +11,8 @@
* Created on: Jul 1, 2017 * Created on: Jul 1, 2017
* Author: kolban * Author: kolban
*/ */
#ifndef COMPONENTS_NIMBLE_SCAN_H_ #ifndef NIMBLE_CPP_SCAN_H_
#define COMPONENTS_NIMBLE_SCAN_H_ #define NIMBLE_CPP_SCAN_H_
#include "nimconfig.h" #include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER) #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
@ -66,9 +66,9 @@ class NimBLEScan {
bool isScanning(); bool isScanning();
void setScanCallbacks(NimBLEScanCallbacks* pScanCallbacks, bool wantDuplicates = false); void setScanCallbacks(NimBLEScanCallbacks* pScanCallbacks, bool wantDuplicates = false);
void setActiveScan(bool active); void setActiveScan(bool active);
void setInterval(uint16_t intervalMSecs); void setInterval(uint16_t intervalMs);
void setWindow(uint16_t windowMSecs); void setWindow(uint16_t windowMs);
void setDuplicateFilter(bool enabled); void setDuplicateFilter(uint8_t enabled);
void setLimitedOnly(bool enabled); void setLimitedOnly(bool enabled);
void setFilterPolicy(uint8_t filter); void setFilterPolicy(uint8_t filter);
bool stop(); bool stop();
@ -79,6 +79,12 @@ class NimBLEScan {
void erase(const NimBLEAddress& address); void erase(const NimBLEAddress& address);
void erase(const NimBLEAdvertisedDevice* device); 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: private:
friend class NimBLEDevice; friend class NimBLEDevice;
@ -90,9 +96,13 @@ class NimBLEScan {
NimBLEScanCallbacks* m_pScanCallbacks; NimBLEScanCallbacks* m_pScanCallbacks;
ble_gap_disc_params m_scanParams; ble_gap_disc_params m_scanParams;
NimBLEScanResults m_scanResults; NimBLEScanResults m_scanResults;
uint32_t m_duration;
NimBLETaskData* m_pTaskData; NimBLETaskData* m_pTaskData;
uint8_t m_maxResults; 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); virtual void onScanEnd(const NimBLEScanResults& scanResults, int reason);
}; };
#endif /* CONFIG_BT_ENABLED CONFIG_BT_NIMBLE_ROLE_OBSERVER */ #endif // CONFIG_BT_ENABLED CONFIG_BT_NIMBLE_ROLE_OBSERVER
#endif /* COMPONENTS_NIMBLE_SCAN_H_ */ #endif // NIMBLE_CPP_SCAN_H_