Skip to content

Add a timer for scan responses #947

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/NimBLEAdvertisedDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,9 @@ class NimBLEAdvertisedDevice {
int8_t m_rssi{};
uint8_t m_callbackSent{};
uint8_t m_advLength{};

# if CONFIG_NIMBLE_CPP_SCAN_RSP_TIMEOUT
ble_npl_time_t m_time{};
# endif
# if CONFIG_BT_NIMBLE_EXT_ADV
bool m_isLegacyAdv{};
uint8_t m_sid{};
Expand Down
104 changes: 101 additions & 3 deletions src/NimBLEScan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,85 @@
# include "NimBLEDevice.h"
# include "NimBLELog.h"

# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "nimble/nimble_port.h"
# else
# include "nimble/porting/nimble/include/nimble/nimble_port.h"
# endif

# include <string>
# include <climits>

# define SR_TIMEOUT CONFIG_NIMBLE_CPP_SCAN_RSP_TIMEOUT

static const char* LOG_TAG = "NimBLEScan";
static NimBLEScanCallbacks defaultScanCallbacks;

# if SR_TIMEOUT
static ble_npl_event dummySrTimerEvent;
static ble_gap_disc_desc dummyDesc{
.event_type = BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP, .length_data = 0, .addr{}, .rssi = 127, .data = nullptr, .direct_addr{}};

extern "C" void ble_gap_rx_adv_report(ble_gap_disc_desc* desc);

/**
* @brief Sends dummy (null) scan response data to the scan event handler in order to
* provide the scan result to the callbacks when a device hasn't responded to the
* scan request in time. This is called by the host task from the default event queue.
*/
static void sendDummyScanResponse(ble_npl_event* ev) {
(void)ev;
ble_gap_rx_adv_report(&dummyDesc);
}

/**
* @brief This will schedule an event to run in the host task that will call sendDummyScanResponse
* which will send a null data scan response to the scan event handler if the device
* hasn't responded to a scan response request within the timeout period.
*/
void NimBLEScan::srTimerCb(ble_npl_event* event) {
NimBLEScan* pScan = (NimBLEScan*)ble_npl_event_get_arg(event);
NimBLEAdvertisedDevice* curDev = nullptr;
NimBLEAdvertisedDevice* nextDev = nullptr;
ble_npl_time_t now = ble_npl_time_get();

for (auto& dev : pScan->m_scanResults.m_deviceVec) {
if (dev->m_callbackSent < 2 && dev->isScannable()) {
if (!curDev || (now - dev->m_time > now - curDev->m_time)) {
nextDev = curDev;
curDev = dev;
continue;
}

if (!nextDev || now - dev->m_time > now - nextDev->m_time) {
nextDev = dev;
}
}
}

// Add the event to the host queue
if (curDev) {
memcpy(&dummyDesc.addr, curDev->getAddress().getBase(), sizeof(dummyDesc.addr));
NIMBLE_LOGI(LOG_TAG, "Scan response timeout for: %s", curDev->getAddress().toString().c_str());
ble_npl_eventq_put(nimble_port_get_dflt_eventq(), &dummySrTimerEvent);
}

// Restart the timer for the next device that we are expecting a scan response from
if (nextDev) {
auto nextTime = now - nextDev->m_time;
if (nextTime >= SR_TIMEOUT) {
nextTime = 1;
} else {
nextTime = SR_TIMEOUT - nextTime;
}

ble_npl_time_t ticks;
ble_npl_time_ms_to_ticks(nextTime, &ticks);
ble_npl_callout_reset(&pScan->m_srTimer, ticks);
}
}
# endif

/**
* @brief Scan constructor.
*/
Expand All @@ -36,13 +109,22 @@ NimBLEScan::NimBLEScan()
// 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_pTaskData{nullptr},
m_maxResults{0xFF} {}
m_maxResults{0xFF} {
# if SR_TIMEOUT
ble_npl_callout_init(&m_srTimer, nimble_port_get_dflt_eventq(), NimBLEScan::srTimerCb, this);
ble_npl_event_init(&dummySrTimerEvent, sendDummyScanResponse, NULL);
# endif
}

/**
* @brief Scan destructor, release any allocated resources.
*/
NimBLEScan::~NimBLEScan() {
clearResults();
# if SR_TIMEOUT
ble_npl_callout_deinit(&m_srTimer);
ble_npl_event_deinit(&dummySrTimerEvent);
# endif
}

/**
Expand Down Expand Up @@ -114,6 +196,9 @@ int NimBLEScan::handleGapEvent(ble_gap_event* event, void* arg) {
advertisedDevice = new NimBLEAdvertisedDevice(event, event_type);
pScan->m_scanResults.m_deviceVec.push_back(advertisedDevice);
NIMBLE_LOGI(LOG_TAG, "New advertiser: %s", advertisedAddress.toString().c_str());
# if SR_TIMEOUT
advertisedDevice->m_time = ble_npl_time_get();
# endif
} else {
advertisedDevice->update(event, event_type);
if (isLegacyAdv && event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP) {
Expand All @@ -137,6 +222,13 @@ int NimBLEScan::handleGapEvent(ble_gap_event* event, void* arg) {
advertisedDevice->m_callbackSent++;
// got the scan response report the full data.
pScan->m_pScanCallbacks->onResult(advertisedDevice);
# if SR_TIMEOUT
} else if (isLegacyAdv && !ble_npl_callout_is_active(&pScan->m_srTimer)) {
// Start the timer to wait for the scan response.
ble_npl_time_t ticks;
ble_npl_time_ms_to_ticks(SR_TIMEOUT, &ticks);
ble_npl_callout_reset(&pScan->m_srTimer, ticks);
# endif
}

// If not storing results and we have invoked the callback, delete the device.
Expand All @@ -149,13 +241,15 @@ int NimBLEScan::handleGapEvent(ble_gap_event* event, void* arg) {

case BLE_GAP_EVENT_DISC_COMPLETE: {
NIMBLE_LOGD(LOG_TAG, "discovery complete; reason=%d", event->disc_complete.reason);
# if SR_TIMEOUT
ble_npl_callout_stop(&pScan->m_srTimer);
# endif
pScan->m_pScanCallbacks->onScanEnd(pScan->m_scanResults, event->disc_complete.reason);

if (pScan->m_maxResults == 0) {
pScan->clearResults();
}

pScan->m_pScanCallbacks->onScanEnd(pScan->m_scanResults, event->disc_complete.reason);

if (pScan->m_pTaskData != nullptr) {
NimBLEUtils::taskRelease(*pScan->m_pTaskData, event->disc_complete.reason);
}
Expand Down Expand Up @@ -384,6 +478,10 @@ bool NimBLEScan::stop() {
return false;
}

# if SR_TIMEOUT
ble_npl_callout_stop(&m_srTimer);
# endif

if (m_maxResults == 0) {
clearResults();
}
Expand Down
7 changes: 7 additions & 0 deletions src/NimBLEScan.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,18 @@ class NimBLEScan {
static int handleGapEvent(ble_gap_event* event, void* arg);
void onHostSync();

# if CONFIG_NIMBLE_CPP_SCAN_RSP_TIMEOUT
static void srTimerCb(ble_npl_event* event);
# endif

NimBLEScanCallbacks* m_pScanCallbacks;
ble_gap_disc_params m_scanParams;
NimBLEScanResults m_scanResults;
NimBLETaskData* m_pTaskData;
uint8_t m_maxResults;
# if CONFIG_NIMBLE_CPP_SCAN_RSP_TIMEOUT
ble_npl_callout m_srTimer{};
# endif

# if CONFIG_BT_NIMBLE_EXT_ADV
uint8_t m_phy{SCAN_ALL};
Expand Down
12 changes: 12 additions & 0 deletions src/nimconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,14 @@
*/
// #define CONFIG_BT_NIMBLE_CRYPTO_STACK_MBEDTLS 1

/**
* @brief Un-comment to change the timeout, in milliseconds.
* @details This is the time to wait for a scan response before calling the onResult scan callback,
* lower values increase callback rates but will lose data more often, higher values give full data more
* often. Setting this to 0 will disable the scan response timeout.
*/
// #define CONFIG_NIMBLE_CPP_SCAN_RSP_TIMEOUT (30)

/**********************************
End Arduino user-config
**********************************/
Expand Down Expand Up @@ -255,6 +263,10 @@
#define CONFIG_NIMBLE_STACK_USE_MEM_POOLS 0
#endif

#ifndef CONFIG_NIMBLE_CPP_SCAN_RSP_TIMEOUT
#define CONFIG_NIMBLE_CPP_SCAN_RSP_TIMEOUT (30)
#endif

/** @brief Set if CCCD's and bond data should be stored in NVS */
#define CONFIG_BT_NIMBLE_NVS_PERSIST 1

Expand Down