diff --git a/src/NimBLEAdvertisedDevice.h b/src/NimBLEAdvertisedDevice.h index dd9dcccd..a10585bc 100644 --- a/src/NimBLEAdvertisedDevice.h +++ b/src/NimBLEAdvertisedDevice.h @@ -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{}; diff --git a/src/NimBLEScan.cpp b/src/NimBLEScan.cpp index 20808ba6..545df058 100644 --- a/src/NimBLEScan.cpp +++ b/src/NimBLEScan.cpp @@ -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 # include +# 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. */ @@ -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 } /** @@ -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) { @@ -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. @@ -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); } @@ -384,6 +478,10 @@ bool NimBLEScan::stop() { return false; } +# if SR_TIMEOUT + ble_npl_callout_stop(&m_srTimer); +# endif + if (m_maxResults == 0) { clearResults(); } diff --git a/src/NimBLEScan.h b/src/NimBLEScan.h index 2546cdb1..dbff2296 100644 --- a/src/NimBLEScan.h +++ b/src/NimBLEScan.h @@ -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}; diff --git a/src/nimconfig.h b/src/nimconfig.h index 6d3ff3e7..7286f3b0 100644 --- a/src/nimconfig.h +++ b/src/nimconfig.h @@ -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 **********************************/ @@ -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