2020
2121# include " NimBLEDevice.h"
2222# include " NimBLELog.h"
23+ # if defined(CONFIG_NIMBLE_CPP_IDF)
24+ # include " nimble/nimble_port.h"
25+ # else
26+ # include " nimble/porting/nimble/include/nimble/nimble_port.h"
27+ # endif
2328
2429# include < string>
2530# include < climits>
2631
2732static const char * LOG_TAG = " NimBLEScan" ;
2833static NimBLEScanCallbacks defaultScanCallbacks;
2934
35+ /* *
36+ * @brief Calls the onResult callback with the given device,
37+ * this is used to provide a scan result to the callbacks when a device hasn't responded to
38+ * the scan request in time. This is called by the host task from the default event queue.
39+ */
40+ void NimBLEScan::forceResultCallback (ble_npl_event* ev) {
41+ auto pScan = NimBLEDevice::getScan ();
42+ pScan->m_stats .incMissedSrCount ();
43+ NimBLEAdvertisedDevice* pDev = static_cast <NimBLEAdvertisedDevice*>(ble_npl_event_get_arg (ev));
44+ pDev->m_callbackSent = 2 ;
45+ pScan->m_pScanCallbacks ->onResult (pDev);
46+ }
47+
48+ /* *
49+ * @brief This will schedule an event to run in the host task that will call forceResultCallback
50+ * which will call the onResult callback with the current data.
51+ */
52+ void NimBLEScan::srTimerCb (ble_npl_event* event) {
53+ NimBLEScan* pScan = static_cast <NimBLEScan*>(ble_npl_event_get_arg (event));
54+ NimBLEAdvertisedDevice* curDev = nullptr ;
55+ NimBLEAdvertisedDevice* nextDev = nullptr ;
56+ ble_npl_time_t now = ble_npl_time_get ();
57+
58+ for (auto & dev : pScan->m_scanResults .m_deviceVec ) {
59+ if (dev->m_callbackSent < 2 && dev->isScannable ()) {
60+ if (!curDev || (now - dev->m_time > now - curDev->m_time )) {
61+ nextDev = curDev;
62+ curDev = dev;
63+ continue ;
64+ }
65+
66+ if (!nextDev || now - dev->m_time > now - nextDev->m_time ) {
67+ nextDev = dev;
68+ }
69+ }
70+ }
71+
72+ // Add the event to the host queue
73+ if (curDev && now - curDev->m_time >= pScan->m_srTimeoutTicks ) {
74+ NIMBLE_LOGI (LOG_TAG , " Scan response timeout for: %s" , curDev->getAddress ().toString ().c_str ());
75+ ble_npl_event_set_arg (&pScan->m_srTimeoutEvent , curDev);
76+ ble_npl_eventq_put (nimble_port_get_dflt_eventq (), &pScan->m_srTimeoutEvent );
77+ }
78+
79+ // Restart the timer for the next device that we are expecting a scan response from
80+ if (nextDev) {
81+ auto nextTime = now - nextDev->m_time ;
82+ if (nextTime >= pScan->m_srTimeoutTicks ) {
83+ nextTime = 1 ;
84+ } else {
85+ nextTime = pScan->m_srTimeoutTicks - nextTime;
86+ }
87+
88+ ble_npl_callout_reset (&pScan->m_srTimer , nextTime);
89+ }
90+ }
91+
3092/* *
3193 * @brief Scan constructor.
3294 */
@@ -35,7 +97,11 @@ NimBLEScan::NimBLEScan()
3597 // default interval + window, no whitelist scan filter,not limited scan, no scan response, filter_duplicates
3698 m_scanParams{0 , 0 , BLE_HCI_SCAN_FILT_NO_WL , 0 , 1 , 1 },
3799 m_pTaskData{nullptr },
38- m_maxResults{0xFF } {}
100+ m_maxResults{0xFF } {
101+ ble_npl_callout_init (&m_srTimer, nimble_port_get_dflt_eventq (), NimBLEScan::srTimerCb, this );
102+ ble_npl_event_init (&m_srTimeoutEvent, forceResultCallback, NULL );
103+ ble_npl_time_ms_to_ticks (BLE_GAP_SCAN_FAST_WINDOW , &m_srTimeoutTicks);
104+ } // NimBLEScan::NimBLEScan
39105
40106/* *
41107 * @brief Scan destructor, release any allocated resources.
@@ -44,6 +110,9 @@ NimBLEScan::~NimBLEScan() {
44110 for (const auto & dev : m_scanResults.m_deviceVec ) {
45111 delete dev;
46112 }
113+
114+ ble_npl_callout_deinit (&m_srTimer);
115+ ble_npl_event_deinit (&m_srTimeoutEvent);
47116}
48117
49118/* *
@@ -101,6 +170,8 @@ int NimBLEScan::handleGapEvent(ble_gap_event* event, void* arg) {
101170 // If we haven't seen this device before; create a new instance and insert it in the vector.
102171 // Otherwise just update the relevant parameters of the already known device.
103172 if (advertisedDevice == nullptr ) {
173+ pScan->m_stats .incDevCount ();
174+
104175 // Check if we have reach the scan results limit, ignore this one if so.
105176 // We still need to store each device when maxResults is 0 to be able to append the scan results
106177 if (pScan->m_maxResults > 0 && pScan->m_maxResults < 0xFF &&
@@ -109,19 +180,36 @@ int NimBLEScan::handleGapEvent(ble_gap_event* event, void* arg) {
109180 }
110181
111182 if (isLegacyAdv && event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP ) {
183+ pScan->m_stats .incOrphanedSrCount ();
112184 NIMBLE_LOGI (LOG_TAG , " Scan response without advertisement: %s" , advertisedAddress.toString ().c_str ());
113185 }
114186
115187 advertisedDevice = new NimBLEAdvertisedDevice (event, event_type);
116188 pScan->m_scanResults .m_deviceVec .push_back (advertisedDevice);
189+ advertisedDevice->m_time = ble_npl_time_get ();
117190 NIMBLE_LOGI (LOG_TAG , " New advertiser: %s" , advertisedAddress.toString ().c_str ());
118191 } else {
119192 advertisedDevice->update (event, event_type);
120193 if (isLegacyAdv) {
121194 if (event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP ) {
122195 NIMBLE_LOGI (LOG_TAG , " Scan response from: %s" , advertisedAddress.toString ().c_str ());
196+ if (!pScan->m_stats .recordSrTime (ble_npl_time_get () - advertisedDevice->m_time ,
197+ pScan->m_scanParams .window * 625 / 1000 )) {
198+ NIMBLE_LOGD (LOG_TAG ,
199+ " Abnormal scan response time, stats ignored for device: %s" ,
200+ advertisedAddress.toString ().c_str ());
201+ }
123202 } else {
203+ pScan->m_stats .incDupCount ();
124204 NIMBLE_LOGI (LOG_TAG , " Duplicate; updated: %s" , advertisedAddress.toString ().c_str ());
205+ // Restart scan-response timeout when we see a new non-scan-response
206+ // legacy advertisement during active scanning for a scannable device.
207+ advertisedDevice->m_time = ble_npl_time_get ();
208+ advertisedDevice->m_callbackSent = 0 ;
209+ if (pScan->m_srTimeoutTicks && advertisedDevice->isScannable () &&
210+ !ble_npl_callout_is_active (&pScan->m_srTimer )) {
211+ ble_npl_callout_reset (&pScan->m_srTimer , pScan->m_srTimeoutTicks );
212+ }
125213 }
126214 }
127215 }
@@ -147,6 +235,10 @@ int NimBLEScan::handleGapEvent(ble_gap_event* event, void* arg) {
147235 advertisedDevice->m_callbackSent ++;
148236 // got the scan response report the full data.
149237 pScan->m_pScanCallbacks ->onResult (advertisedDevice);
238+ } else if (pScan->m_srTimeoutTicks && isLegacyAdv && advertisedDevice->isScannable () &&
239+ !ble_npl_callout_is_active (&pScan->m_srTimer )) {
240+ // Start the timer to wait for the scan response.
241+ ble_npl_callout_reset (&pScan->m_srTimer , pScan->m_srTimeoutTicks );
150242 }
151243
152244 // If not storing results and we have invoked the callback, delete the device.
@@ -158,14 +250,22 @@ int NimBLEScan::handleGapEvent(ble_gap_event* event, void* arg) {
158250 }
159251
160252 case BLE_GAP_EVENT_DISC_COMPLETE : {
253+ ble_npl_callout_stop (&pScan->m_srTimer );
254+ for (const auto & dev : pScan->m_scanResults .m_deviceVec ) {
255+ if (dev->isScannable () && dev->m_callbackSent < 2 ) {
256+ pScan->m_stats .incMissedSrCount ();
257+ pScan->m_pScanCallbacks ->onResult (dev);
258+ }
259+ }
260+
161261 NIMBLE_LOGD (LOG_TAG , " discovery complete; reason=%d" , event->disc_complete .reason );
262+ NIMBLE_LOGD (LOG_TAG , " %s" , pScan->getStatsString ().c_str ());
162263
264+ pScan->m_pScanCallbacks ->onScanEnd (pScan->m_scanResults , event->disc_complete .reason );
163265 if (pScan->m_maxResults == 0 ) {
164266 pScan->clearResults ();
165267 }
166268
167- pScan->m_pScanCallbacks ->onScanEnd (pScan->m_scanResults , event->disc_complete .reason );
168-
169269 if (pScan->m_pTaskData != nullptr ) {
170270 NimBLEUtils::taskRelease (*pScan->m_pTaskData , event->disc_complete .reason );
171271 }
@@ -178,6 +278,25 @@ int NimBLEScan::handleGapEvent(ble_gap_event* event, void* arg) {
178278 }
179279} // handleGapEvent
180280
281+ /* *
282+ * @brief Set the scan response timeout.
283+ * @param [in] timeoutMs The timeout in milliseconds to wait for a scan response.
284+ * @details If a scan response is not received within the timeout period,
285+ * a dummy scan response with null data will be sent to the scan event handler
286+ * which will trigger the callback with whatever data was in the advertisement.
287+ * If set to 0, no dummy scan response will be sent and the callback will only
288+ * be triggered when a scan response is received from the advertiser or when the scan completes.
289+ */
290+ void NimBLEScan::setScanResponseTimeout (uint32_t timeoutMs) {
291+ if (timeoutMs == 0 ) {
292+ ble_npl_callout_stop (&m_srTimer);
293+ m_srTimeoutTicks = 0 ;
294+ return ;
295+ }
296+
297+ ble_npl_time_ms_to_ticks (timeoutMs, &m_srTimeoutTicks);
298+ } // setScanResponseTimeout
299+
181300/* *
182301 * @brief Should we perform an active or passive scan?
183302 * The default is a passive scan. An active scan means that we will request a scan response.
@@ -323,11 +442,13 @@ bool NimBLEScan::start(uint32_t duration, bool isContinue, bool restart) {
323442
324443 if (!isContinue) {
325444 clearResults ();
445+ m_stats.reset ();
326446 }
327447 }
328448 } else { // Don't clear results while scanning is active
329449 if (!isContinue) {
330450 clearResults ();
451+ m_stats.reset ();
331452 }
332453 }
333454
@@ -394,6 +515,8 @@ bool NimBLEScan::stop() {
394515 return false ;
395516 }
396517
518+ ble_npl_callout_stop (&m_srTimer);
519+
397520 if (m_maxResults == 0 ) {
398521 clearResults ();
399522 }
0 commit comments