Skip to content

Commit 542fd41

Browse files
committed
Add a timer for scan responses
This ensures that the callback will be called within the configured time (in ms) when devices fail to respond to a scan response request within that time.
1 parent b5110a2 commit 542fd41

6 files changed

Lines changed: 155 additions & 3 deletions

File tree

src/NimBLEAdvertisedDevice.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ NimBLEAdvertisedDevice::NimBLEAdvertisedDevice(const ble_gap_event* event, uint8
6161
void NimBLEAdvertisedDevice::update(const ble_gap_event* event, uint8_t eventType) {
6262
# if MYNEWT_VAL(BLE_EXT_ADV)
6363
const auto& disc = event->ext_disc;
64+
if (!disc.length_data) { // dummy sr, just return, don't update anything
65+
return;
66+
}
67+
6468
if (m_dataStatus == BLE_GAP_EXT_ADV_DATA_STATUS_INCOMPLETE) {
6569
m_payload.reserve(m_advLength + disc.length_data);
6670
m_payload.insert(m_payload.end(), disc.data, disc.data + disc.length_data);
@@ -75,6 +79,10 @@ void NimBLEAdvertisedDevice::update(const ble_gap_event* event, uint8_t eventTyp
7579
const auto& disc = event->disc;
7680
# endif
7781

82+
if (!disc.length_data) { // dummy sr, just return, don't update anything
83+
return;
84+
}
85+
7886
m_rssi = disc.rssi;
7987
if (eventType == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP && isLegacyAdvertisement()) {
8088
m_payload.insert(m_payload.end(), disc.data, disc.data + disc.length_data);

src/NimBLEAdvertisedDevice.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ class NimBLEAdvertisedDevice {
163163
int8_t m_rssi{};
164164
uint8_t m_callbackSent{};
165165
uint16_t m_advLength{};
166+
# if MYNEWT_VAL(NIMBLE_CPP_SCAN_RSP_TIMEOUT)
167+
ble_npl_time_t m_time{};
168+
# endif
166169

167170
# if MYNEWT_VAL(BLE_EXT_ADV)
168171
bool m_isLegacyAdv{};

src/NimBLEScan.cpp

Lines changed: 125 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,106 @@
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

32+
# define SR_TIMEOUT MYNEWT_VAL(NIMBLE_CPP_SCAN_RSP_TIMEOUT)
33+
2734
static const char* LOG_TAG = "NimBLEScan";
2835
static NimBLEScanCallbacks defaultScanCallbacks;
2936

37+
# if SR_TIMEOUT
38+
static ble_npl_event dummySrTimerEvent;
39+
static ble_npl_time_t srTimeoutTicks;
40+
41+
# if MYNEWT_VAL(BLE_EXT_ADV)
42+
struct ble_gap_ext_disc_desc dummyDesc{.props = BLE_HCI_ADV_LEGACY_MASK,
43+
.data_status = BLE_GAP_EXT_ADV_DATA_STATUS_COMPLETE,
44+
.legacy_event_type = BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP,
45+
.addr{},
46+
.rssi = 127,
47+
.tx_power = 127,
48+
.sid = 0,
49+
.primary_phy = BLE_HCI_LE_PHY_1M,
50+
.secondary_phy = BLE_HCI_LE_PHY_1M,
51+
.periodic_adv_itvl = 0,
52+
.length_data = 0,
53+
.data = nullptr,
54+
.direct_addr{}};
55+
56+
extern "C" void ble_gap_rx_ext_adv_report(struct ble_gap_ext_disc_desc* desc);
57+
# else
58+
static ble_gap_disc_desc dummyDesc{
59+
.event_type = BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP, .length_data = 0, .addr{}, .rssi = 127, .data = nullptr, .direct_addr{}};
60+
extern "C" void ble_gap_rx_adv_report(ble_gap_disc_desc* desc);
61+
# endif
62+
63+
/**
64+
* @brief Sends dummy (null) scan response data to the scan event handler in order to
65+
* provide the scan result to the callbacks when a device hasn't responded to the
66+
* scan request in time. This is called by the host task from the default event queue.
67+
*/
68+
static void sendDummyScanResponse(ble_npl_event* ev) {
69+
(void)ev;
70+
# if MYNEWT_VAL(BLE_EXT_ADV)
71+
ble_gap_rx_ext_adv_report(&dummyDesc);
72+
# else
73+
ble_gap_rx_adv_report(&dummyDesc);
74+
# endif
75+
}
76+
77+
/**
78+
* @brief This will schedule an event to run in the host task that will call sendDummyScanResponse
79+
* which will send a null data scan response to the scan event handler if the device
80+
* hasn't responded to a scan response request within the timeout period.
81+
*/
82+
void NimBLEScan::srTimerCb(ble_npl_event* event) {
83+
NimBLEScan* pScan = static_cast<NimBLEScan*>(ble_npl_event_get_arg(event));
84+
NimBLEAdvertisedDevice* curDev = nullptr;
85+
NimBLEAdvertisedDevice* nextDev = nullptr;
86+
ble_npl_time_t now = ble_npl_time_get();
87+
88+
for (auto& dev : pScan->m_scanResults.m_deviceVec) {
89+
if (dev->m_callbackSent < 2 && dev->isScannable()) {
90+
if (!curDev || (now - dev->m_time > now - curDev->m_time)) {
91+
nextDev = curDev;
92+
curDev = dev;
93+
continue;
94+
}
95+
96+
if (!nextDev || now - dev->m_time > now - nextDev->m_time) {
97+
nextDev = dev;
98+
}
99+
}
100+
}
101+
102+
// Add the event to the host queue
103+
if (curDev) {
104+
memcpy(&dummyDesc.addr, curDev->getAddress().getBase(), sizeof(dummyDesc.addr));
105+
NIMBLE_LOGI(LOG_TAG, "Scan response timeout for: %s", curDev->getAddress().toString().c_str());
106+
ble_npl_eventq_put(nimble_port_get_dflt_eventq(), &dummySrTimerEvent);
107+
}
108+
109+
// Restart the timer for the next device that we are expecting a scan response from
110+
if (nextDev) {
111+
auto nextTime = now - nextDev->m_time;
112+
if (nextTime >= srTimeoutTicks) {
113+
nextTime = 1;
114+
} else {
115+
nextTime = srTimeoutTicks - nextTime;
116+
}
117+
118+
ble_npl_callout_reset(&pScan->m_srTimer, nextTime);
119+
}
120+
}
121+
# endif // SR_TIMEOUT
122+
30123
/**
31124
* @brief Scan constructor.
32125
*/
@@ -35,7 +128,13 @@ NimBLEScan::NimBLEScan()
35128
// default interval + window, no whitelist scan filter,not limited scan, no scan response, filter_duplicates
36129
m_scanParams{0, 0, BLE_HCI_SCAN_FILT_NO_WL, 0, 1, 1},
37130
m_pTaskData{nullptr},
38-
m_maxResults{0xFF} {}
131+
m_maxResults{0xFF} {
132+
# if SR_TIMEOUT
133+
ble_npl_callout_init(&m_srTimer, nimble_port_get_dflt_eventq(), NimBLEScan::srTimerCb, this);
134+
ble_npl_event_init(&dummySrTimerEvent, sendDummyScanResponse, NULL);
135+
ble_npl_time_ms_to_ticks(SR_TIMEOUT, &srTimeoutTicks);
136+
# endif
137+
} // NimBLEScan::NimBLEScan
39138

40139
/**
41140
* @brief Scan destructor, release any allocated resources.
@@ -44,6 +143,10 @@ NimBLEScan::~NimBLEScan() {
44143
for (const auto& dev : m_scanResults.m_deviceVec) {
45144
delete dev;
46145
}
146+
# if SR_TIMEOUT
147+
ble_npl_callout_deinit(&m_srTimer);
148+
ble_npl_event_deinit(&dummySrTimerEvent);
149+
# endif
47150
}
48151

49152
/**
@@ -114,6 +217,9 @@ int NimBLEScan::handleGapEvent(ble_gap_event* event, void* arg) {
114217

115218
advertisedDevice = new NimBLEAdvertisedDevice(event, event_type);
116219
pScan->m_scanResults.m_deviceVec.push_back(advertisedDevice);
220+
# if SR_TIMEOUT
221+
advertisedDevice->m_time = ble_npl_time_get();
222+
# endif
117223
NIMBLE_LOGI(LOG_TAG, "New advertiser: %s", advertisedAddress.toString().c_str());
118224
} else {
119225
advertisedDevice->update(event, event_type);
@@ -122,6 +228,11 @@ int NimBLEScan::handleGapEvent(ble_gap_event* event, void* arg) {
122228
NIMBLE_LOGI(LOG_TAG, "Scan response from: %s", advertisedAddress.toString().c_str());
123229
} else {
124230
NIMBLE_LOGI(LOG_TAG, "Duplicate; updated: %s", advertisedAddress.toString().c_str());
231+
# if SR_TIMEOUT
232+
// Restart scan-response timeout when we see a new non-scan-response
233+
// legacy advertisement during active scanning for a scannable device.
234+
advertisedDevice->m_time = ble_npl_time_get();
235+
# endif
125236
}
126237
}
127238
}
@@ -147,6 +258,11 @@ int NimBLEScan::handleGapEvent(ble_gap_event* event, void* arg) {
147258
advertisedDevice->m_callbackSent++;
148259
// got the scan response report the full data.
149260
pScan->m_pScanCallbacks->onResult(advertisedDevice);
261+
# if SR_TIMEOUT
262+
} else if (isLegacyAdv && advertisedDevice->isScannable() && !ble_npl_callout_is_active(&pScan->m_srTimer)) {
263+
// Start the timer to wait for the scan response.
264+
ble_npl_callout_reset(&pScan->m_srTimer, srTimeoutTicks);
265+
# endif
150266
}
151267

152268
// If not storing results and we have invoked the callback, delete the device.
@@ -158,14 +274,16 @@ int NimBLEScan::handleGapEvent(ble_gap_event* event, void* arg) {
158274
}
159275

160276
case BLE_GAP_EVENT_DISC_COMPLETE: {
277+
# if SR_TIMEOUT
278+
ble_npl_callout_stop(&pScan->m_srTimer);
279+
# endif
161280
NIMBLE_LOGD(LOG_TAG, "discovery complete; reason=%d", event->disc_complete.reason);
162281

282+
pScan->m_pScanCallbacks->onScanEnd(pScan->m_scanResults, event->disc_complete.reason);
163283
if (pScan->m_maxResults == 0) {
164284
pScan->clearResults();
165285
}
166286

167-
pScan->m_pScanCallbacks->onScanEnd(pScan->m_scanResults, event->disc_complete.reason);
168-
169287
if (pScan->m_pTaskData != nullptr) {
170288
NimBLEUtils::taskRelease(*pScan->m_pTaskData, event->disc_complete.reason);
171289
}
@@ -394,6 +512,10 @@ bool NimBLEScan::stop() {
394512
return false;
395513
}
396514

515+
# if SR_TIMEOUT
516+
ble_npl_callout_stop(&m_srTimer);
517+
# endif
518+
397519
if (m_maxResults == 0) {
398520
clearResults();
399521
}

src/NimBLEScan.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,20 @@ class NimBLEScan {
9696
~NimBLEScan();
9797
static int handleGapEvent(ble_gap_event* event, void* arg);
9898
void onHostSync();
99+
# if MYNEWT_VAL(NIMBLE_CPP_SCAN_RSP_TIMEOUT)
100+
static void srTimerCb(ble_npl_event* event);
101+
# endif
99102

100103
NimBLEScanCallbacks* m_pScanCallbacks;
101104
ble_gap_disc_params m_scanParams;
102105
NimBLEScanResults m_scanResults;
103106
NimBLETaskData* m_pTaskData;
104107
uint8_t m_maxResults;
105108

109+
# if MYNEWT_VAL(NIMBLE_CPP_SCAN_RSP_TIMEOUT)
110+
ble_npl_callout m_srTimer{};
111+
# endif
112+
106113
# if MYNEWT_VAL(BLE_EXT_ADV)
107114
uint8_t m_phy{SCAN_ALL};
108115
uint16_t m_period{0};

src/nimconfig.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@
6969
/** @brief Un-comment to change the stack size for the NimBLE host task */
7070
// #define MYNEWT_VAL_NIMBLE_HOST_TASK_STACK_SIZE 4096
7171

72+
/**
73+
* @brief Un-comment to change the timeout, in milliseconds.
74+
* @details This is the time to wait for a scan response before calling the onResult scan callback,
75+
* lower values increase callback rates but will lose data more often, higher values give full data more
76+
* often. Setting this to 0 will disable the scan response timeout.
77+
*/
78+
// #define MYNEWT_VAL_NIMBLE_CPP_SCAN_RSP_TIMEOUT (30)
79+
7280
/**
7381
* @brief Un-comment to change the bit used to block tasks during BLE operations
7482
* that call NimBLEUtils::taskWait. This should be different than any other

src/syscfg/syscfg.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2023,4 +2023,8 @@
20232023
#define MYNEWT_VAL_NIMBLE_CPP_ENABLE_ADVERTISEMENT_TYPE_TEXT (0)
20242024
#endif
20252025

2026+
#ifndef MYNEWT_VAL_NIMBLE_CPP_SCAN_RSP_TIMEOUT
2027+
#define MYNEWT_VAL_NIMBLE_CPP_SCAN_RSP_TIMEOUT (30)
2028+
#endif
2029+
20262030
#endif /* H_SYSCFG_ */

0 commit comments

Comments
 (0)