Skip to content

Commit ac329d8

Browse files
committed
Refactor notify/indicate
This refactors the handling of sending notifications and indications for greater efficiency. * Adds client subscription state tracking to NimBLEServer rather than relying on the stack. * Notifications/indications are now sent directly, no longer calling the callback to read the values. This avoids delays and flash writes in the stack, allowing for greater throughput.
1 parent 66fbe50 commit ac329d8

File tree

3 files changed

+129
-79
lines changed

3 files changed

+129
-79
lines changed

src/NimBLECharacteristic.cpp

Lines changed: 31 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@
1515
* limitations under the License.
1616
*/
1717

18-
# include "NimBLECharacteristic.h"
18+
#include "NimBLECharacteristic.h"
1919
#if CONFIG_BT_NIMBLE_ENABLED && MYNEWT_VAL(BLE_ROLE_PERIPHERAL)
2020

21-
#if defined(CONFIG_NIMBLE_CPP_IDF)
22-
# if !defined(ESP_IDF_VERSION_MAJOR) || ESP_IDF_VERSION_MAJOR < 5
23-
# define ble_gatts_notify_custom ble_gattc_notify_custom
24-
# define ble_gatts_indicate_custom ble_gattc_indicate_custom
21+
# if defined(CONFIG_NIMBLE_CPP_IDF)
22+
# if !defined(ESP_IDF_VERSION_MAJOR) || ESP_IDF_VERSION_MAJOR < 5
23+
# define ble_gatts_notify_custom ble_gattc_notify_custom
24+
# define ble_gatts_indicate_custom ble_gattc_indicate_custom
25+
# endif
2526
# endif
26-
#endif
2727

2828
# include "NimBLE2904.h"
2929
# include "NimBLEDevice.h"
@@ -225,7 +225,8 @@ void NimBLECharacteristic::setService(NimBLEService* pService) {
225225
* @return True if the indication was sent successfully, false otherwise.
226226
*/
227227
bool NimBLECharacteristic::indicate(uint16_t connHandle) const {
228-
return sendValue(nullptr, 0, false, connHandle);
228+
auto value{m_value}; // make a copy to avoid issues if the value is changed while indicating
229+
return sendValue(value.data(), value.size(), false, connHandle);
229230
} // indicate
230231

231232
/**
@@ -247,7 +248,8 @@ bool NimBLECharacteristic::indicate(const uint8_t* value, size_t length, uint16_
247248
* @return True if the notification was sent successfully, false otherwise.
248249
*/
249250
bool NimBLECharacteristic::notify(uint16_t connHandle) const {
250-
return sendValue(nullptr, 0, true, connHandle);
251+
auto value{m_value}; // make a copy to avoid issues if the value is changed while notifying
252+
return sendValue(value.data(), value.size(), true, connHandle);
251253
} // notify
252254

253255
/**
@@ -271,55 +273,36 @@ bool NimBLECharacteristic::notify(const uint8_t* value, size_t length, uint16_t
271273
* @return True if the value was sent successfully, false otherwise.
272274
*/
273275
bool NimBLECharacteristic::sendValue(const uint8_t* value, size_t length, bool isNotification, uint16_t connHandle) const {
274-
int rc = 0;
275-
276-
if (value != nullptr && length > 0) { // custom notification value
277-
os_mbuf* om = nullptr;
278-
279-
if (connHandle != BLE_HS_CONN_HANDLE_NONE) { // only sending to specific peer
280-
om = ble_hs_mbuf_from_flat(value, length);
281-
if (!om) {
282-
rc = BLE_HS_ENOMEM;
283-
goto done;
284-
}
285-
286-
// Null buffer will read the value from the characteristic
287-
if (isNotification) {
288-
rc = ble_gatts_notify_custom(connHandle, m_handle, om);
289-
} else {
290-
rc = ble_gatts_indicate_custom(connHandle, m_handle, om);
291-
}
276+
const auto subs = NimBLEDevice::getServer()->getSubscribers(m_handle);
277+
if (subs == nullptr) {
278+
return true;
279+
}
292280

293-
goto done;
281+
// Notify all connected peers unless a specific handle is provided
282+
int rc = 0;
283+
for (auto ch : *subs) {
284+
if (ch == BLE_HS_CONN_HANDLE_NONE || (connHandle != BLE_HS_CONN_HANDLE_NONE && ch != connHandle)) {
285+
continue;
294286
}
295287

296-
// Notify all connected peers unless a specific handle is provided
297-
for (const auto& ch : NimBLEDevice::getServer()->getPeerDevices()) {
298-
// Must re-create the data buffer on each iteration because it is freed by the calls bellow.
299-
om = ble_hs_mbuf_from_flat(value, length);
300-
if (!om) {
301-
rc = BLE_HS_ENOMEM;
302-
goto done;
303-
}
304-
305-
if (isNotification) {
306-
rc = ble_gatts_notify_custom(ch, m_handle, om);
307-
} else {
308-
rc = ble_gatts_indicate_custom(ch, m_handle, om);
309-
}
288+
// Must re-create the data buffer on each iteration because it is freed by the calls bellow.
289+
os_mbuf* om = ble_hs_mbuf_from_flat(value, length);
290+
if (!om) {
291+
rc = BLE_HS_ENOMEM;
292+
break;
310293
}
311-
} else if (connHandle != BLE_HS_CONN_HANDLE_NONE) {
312-
// Null buffer will read the value from the characteristic
294+
313295
if (isNotification) {
314-
rc = ble_gatts_notify_custom(connHandle, m_handle, nullptr);
296+
rc = ble_gatts_notify_custom(ch, m_handle, om);
315297
} else {
316-
rc = ble_gatts_indicate_custom(connHandle, m_handle, nullptr);
298+
rc = ble_gatts_indicate_custom(ch, m_handle, om);
299+
}
300+
301+
if (rc != 0 || connHandle != BLE_HS_CONN_HANDLE_NONE) {
302+
break;
317303
}
318-
} else { // Notify or indicate to all connected peers the characteristic value
319-
ble_gatts_chr_updated(m_handle);
320304
}
321305

322-
done:
323306
if (rc != 0) {
324307
NIMBLE_LOGE(LOG_TAG, "failed to send value, rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
325308
return false;

src/NimBLEServer.cpp

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,20 @@ void NimBLEServer::start() {
205205
svc->isStarted() ? "missing" : "not started");
206206
continue; // Skip this service as it was not started
207207
}
208+
209+
for (const auto& chr : svc->m_vChars) {
210+
if (chr->getRemoved() != 0) {
211+
continue;
212+
}
213+
214+
if ((chr->getProperties() & BLE_GATT_CHR_F_INDICATE) || (chr->getProperties() & BLE_GATT_CHR_F_NOTIFY)) {
215+
NIMBLE_LOGD(LOG_TAG,
216+
"Adding Notify/Indicate characteristic: %s, handle=%d",
217+
chr->getUUID().toString().c_str(),
218+
chr->getHandle());
219+
m_subChars.emplace_back(chr->getHandle());
220+
}
221+
}
208222
}
209223

210224
// Set the descriptor handles now as the stack does not set these when the service is started
@@ -360,9 +374,7 @@ int NimBLEServer::handleGapEvent(ble_gap_event* event, void* arg) {
360374
}
361375

362376
if (rc != 0) {
363-
NIMBLE_LOGE(LOG_TAG, "Connection failed rc = %d %s",
364-
rc,
365-
NimBLEUtils::returnCodeToString(rc));
377+
NIMBLE_LOGE(LOG_TAG, "Connection failed rc = %d %s", rc, NimBLEUtils::returnCodeToString(rc));
366378
# if !MYNEWT_VAL(BLE_EXT_ADV) && MYNEWT_VAL(BLE_ROLE_BROADCASTER)
367379
NimBLEDevice::startAdvertising();
368380
# endif
@@ -429,33 +441,72 @@ int NimBLEServer::handleGapEvent(ble_gap_event* event, void* arg) {
429441
} // BLE_GAP_EVENT_DISCONNECT
430442

431443
case BLE_GAP_EVENT_SUBSCRIBE: {
432-
NIMBLE_LOGI(LOG_TAG,
433-
"subscribe event; attr_handle=%d, subscribed: %s",
434-
event->subscribe.attr_handle,
435-
((event->subscribe.cur_notify || event->subscribe.cur_indicate) ? "true" : "false"));
444+
rc = ble_gap_conn_find(event->subscribe.conn_handle, &peerInfo.m_desc);
445+
if (rc != 0) {
446+
break;
447+
}
448+
449+
uint8_t subVal = event->subscribe.cur_notify + (event->subscribe.cur_indicate << 1);
450+
NIMBLE_LOGI(LOG_TAG, "subscribe event; attr_handle=%d, subscribed: %d", event->subscribe.attr_handle, subVal);
436451

452+
NimBLECharacteristic* pChar = nullptr;
437453
for (const auto& svc : pServer->m_svcVec) {
438454
for (const auto& chr : svc->m_vChars) {
439455
if (chr->getHandle() == event->subscribe.attr_handle) {
440-
rc = ble_gap_conn_find(event->subscribe.conn_handle, &peerInfo.m_desc);
441-
if (rc != 0) {
442-
break;
443-
}
444-
445-
auto chrProps = chr->getProperties();
446-
if (!peerInfo.isEncrypted() &&
447-
(chrProps & BLE_GATT_CHR_F_READ_AUTHEN || chrProps & BLE_GATT_CHR_F_READ_AUTHOR ||
448-
chrProps & BLE_GATT_CHR_F_READ_ENC)) {
449-
NimBLEDevice::startSecurity(event->subscribe.conn_handle);
450-
}
451-
452-
chr->m_pCallbacks->onSubscribe(chr,
453-
peerInfo,
454-
event->subscribe.cur_notify + (event->subscribe.cur_indicate << 1));
456+
pChar = chr;
457+
break;
455458
}
456459
}
460+
461+
if (pChar) {
462+
break;
463+
}
464+
}
465+
466+
if (!pChar) {
467+
NIMBLE_LOGE(LOG_TAG, "subscribe event; attr_handle=%d, not found", event->subscribe.attr_handle);
468+
break;
469+
}
470+
471+
auto subs = pServer->getSubscribers(pChar->getHandle());
472+
if (subs == nullptr) {
473+
NIMBLE_LOGE(LOG_TAG, "No subscriber list for handle %d", pChar->getHandle());
474+
break;
475+
}
476+
477+
bool found = false;
478+
int firstFree = -1;
479+
for (auto i = 0; i < subs->size(); i++) {
480+
if ((*subs)[i] == event->subscribe.conn_handle) {
481+
found = true;
482+
if (subVal == 0) { // remove subscriber
483+
(*subs)[i] = BLE_HS_CONN_HANDLE_NONE;
484+
break;
485+
}
486+
}
487+
488+
if (firstFree < 0 && (*subs)[i] == BLE_HS_CONN_HANDLE_NONE) {
489+
firstFree = i;
490+
}
491+
}
492+
493+
if (!found) {
494+
if (firstFree >= 0) {
495+
(*subs)[firstFree] = event->subscribe.conn_handle;
496+
} else {
497+
// This should never happen
498+
NIMBLE_LOGE(LOG_TAG, "Unable to add subscriber for handle %d", pChar->getHandle());
499+
break;
500+
}
501+
}
502+
503+
auto chrProps = pChar->getProperties();
504+
if (!peerInfo.isEncrypted() && (chrProps & BLE_GATT_CHR_F_READ_AUTHEN ||
505+
chrProps & BLE_GATT_CHR_F_READ_AUTHOR || chrProps & BLE_GATT_CHR_F_READ_ENC)) {
506+
NimBLEDevice::startSecurity(event->subscribe.conn_handle);
457507
}
458508

509+
pChar->m_pCallbacks->onSubscribe(pChar, peerInfo, subVal);
459510
break;
460511
} // BLE_GAP_EVENT_SUBSCRIBE
461512

src/NimBLEServer.h

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -118,26 +118,42 @@ class NimBLEServer {
118118

119119
NimBLEServer();
120120
~NimBLEServer();
121+
static int handleGapEvent(struct ble_gap_event* event, void* arg);
122+
static int handleGattEvent(uint16_t connHandle, uint16_t attrHandle, ble_gatt_access_ctxt* ctxt, void* arg);
123+
void serviceChanged();
124+
void resetGATT();
125+
126+
using ConnHandleArray = std::array<uint16_t, MYNEWT_VAL(BLE_MAX_CONNECTIONS)>;
127+
struct AttSubChar {
128+
AttSubChar() = delete;
129+
AttSubChar(uint16_t attHandle) : m_attHandle(attHandle) { m_connHandles.fill(BLE_HS_CONN_HANDLE_NONE); }
130+
uint16_t m_attHandle;
131+
ConnHandleArray m_connHandles;
132+
};
133+
134+
ConnHandleArray* getSubscribers(uint16_t attHandle) {
135+
for (auto& s : m_subChars) {
136+
if (s.m_attHandle == attHandle) {
137+
return &s.m_connHandles;
138+
}
139+
}
140+
return nullptr;
141+
}
121142

122143
bool m_gattsStarted : 1;
123144
bool m_svcChanged : 1;
124145
bool m_deleteCallbacks : 1;
125146
# if !MYNEWT_VAL(BLE_EXT_ADV)
126147
bool m_advertiseOnDisconnect : 1;
127148
# endif
128-
NimBLEServerCallbacks* m_pServerCallbacks;
129-
std::vector<NimBLEService*> m_svcVec;
130-
std::array<uint16_t, MYNEWT_VAL(BLE_MAX_CONNECTIONS)> m_connectedPeers;
149+
NimBLEServerCallbacks* m_pServerCallbacks;
150+
std::vector<NimBLEService*> m_svcVec;
151+
std::vector<AttSubChar> m_subChars;
152+
ConnHandleArray m_connectedPeers;
131153

132154
# if MYNEWT_VAL(BLE_ROLE_CENTRAL)
133155
NimBLEClient* m_pClient{nullptr};
134156
# endif
135-
136-
static int handleGapEvent(struct ble_gap_event* event, void* arg);
137-
static int handleGattEvent(uint16_t connHandle, uint16_t attrHandle, ble_gatt_access_ctxt* ctxt, void* arg);
138-
void serviceChanged();
139-
void resetGATT();
140-
141157
}; // NimBLEServer
142158

143159
/**

0 commit comments

Comments
 (0)