Server: Add onSubscribe() callback for characteristics. (#21)

Server: Add onSubscribe() callback for characteristics.

Adds a new method to NimBLECharacteristicCallbacks that gets called when a client
changes it's subscription status.

* Remove NimBLE2902 class.

As the NimBLE2902 class usefulness was only related to callback functions that were replaced
by the NimBLECharacteristicCallbacks:onSubscribe() method this removes the NimBLE2902 class and
moves all subscription handling to NimBLECharacteristic.

* Update documents and examples to reflect this change.

* Add getSubscribedCount() to get the number of subscribed clients.
This commit is contained in:
h2zero 2020-07-27 21:11:38 -06:00 committed by GitHub
parent fe4586a3ca
commit 4723b1cc53
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 93 additions and 236 deletions

View file

@ -5,7 +5,6 @@ cmake_minimum_required(VERSION 3.5)
set(SUPPORTED_TARGETS esp32)
idf_component_register(SRCS "src/FreeRTOS.cpp"
"src/NimBLE2902.cpp"
"src/NimBLE2904.cpp"
"src/NimBLEAddress.cpp"
"src/NimBLEAdvertisedDevice.cpp"

View file

@ -33,15 +33,18 @@ When creating a characteristic the properties are now set with `NIMBLE_PROPERTY:
### Descriptors
Descriptors are now created using the `NimBLECharacteristic::createDescriptor()` method.
The previous method `BLECharacteristic::addDescriptor()` is now a private function in the library.
The previous method `BLECharacteristic::addDescriptor()` has been removed.
This was done because the NimBLE host automatically creates a 0x2902 descriptor if a characteristic has NOTIFY or INDICATE properties applied.
Due to this fact, the library also creates one automatically for your application.
The only reason to manually create this descriptor now is to assign callback functions.
If you do not require this functionality you can safely exclude the manual creation of the 0x2902 descriptor.
0x2902 Descriptor class: formerly known as BLE2902 or NimBLE2902 has been removed.
It was no longer useful as a new callback `NimBLECharacteristicCallbacks::onSubscribe` was added
to handle callback functionality and the client subscription status is handled internally.
For any other descriptor, (except 0x2904, see below) it should now be created just as characteristics are
by using the `NimBLECharacteristic::createDescriptor` method.
NimBLE automatically creates the 0x2902 descriptor if a characteristic has a notification or indication property assigned to it.
**Note** Attempting to create a 0x2902 descriptor will trigger an assert to notify the error,
allowing the creation of it would cause a fault in the NimBLE stack.
All other desctiptors are now created just as characteristics are by using the `NimBLECharacteristic::createDescriptor` methods (except 0x2904, see below).
Which are defined as:
```
NimBLEDescriptor* createDescriptor(const char* uuid,
@ -67,15 +70,14 @@ pDescriptor = pCharacteristic->createDescriptor("ABCD",
Would create a descriptor with the UUID 0xABCD, publicly readable but only writable if paired/bonded (encrypted) and has a max value length of 25 bytes.
<br/>
For the 0x2904 and 0x2902 descriptor, there is a special class that is created when you call `createDescriptor("2904")`or `createDescriptor("2902")`.
For the 0x2904, there is a special class that is created when you call `createDescriptor("2904").
The pointer returned is of the base class `NimBLEDescriptor` but the call will create the derived class of `NimBLE2904` or `NimBLE2902` so you must cast the returned pointer to
`NimBLE2904` or `NimBLE2902` to access the specific class methods.
The pointer returned is of the base class `NimBLEDescriptor` but the call will create the derived class of `NimBLE2904` so you must cast the returned pointer to
`NimBLE2904` to access the specific class methods.
##### Example
```
p2904 = (NimBLE2904*)pCharacteristic->createDescriptor("2904");
p2902 = (NimBLE2902*)pCharacteristic->createDescriptor("2902");
```
<br/>
@ -91,7 +93,6 @@ Security is set on the characteristic or descriptor properties by applying one o
When a peer wants to read or write a characteristic or descriptor with any of these properties applied
it will trigger the pairing process. By default the "just-works" pairing will be performed automatically.
This can be changed to use passkey authentication or numeric comparison. See [Security Differences](#security-differences) for details.
<br/>
# Client API Differences

View file

@ -1,79 +0,0 @@
/*
* NimBLE2902.cpp
*
* Created: on March 10, 2020
* Author H2zero
*
* Originally:
*
* BLE2902.cpp
*
* Created on: Jun 25, 2017
* Author: kolban
*/
/*
* See also:
* https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "nimconfig.h"
#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#include "NimBLE2902.h"
NimBLE2902::NimBLE2902(NimBLECharacteristic* pCharacterisitic)
: NimBLEDescriptor(NimBLEUUID((uint16_t) 0x2902),
BLE_GATT_CHR_F_READ |
BLE_GATT_CHR_F_WRITE,
2, pCharacterisitic)
{
uint8_t data[2] = { 0, 0 };
setValue(data, 2);
} // NimBLE2902
/**
* @brief Get the notifications value.
* @return The notifications value. True if notifications are enabled and false if not.
*/
bool NimBLE2902::getNotifications() {
return (getValue()[0] & (1 << 0)) != 0;
} // getNotifications
/**
* @brief Get the indications value.
* @return The indications value. True if indications are enabled and false if not.
*/
bool NimBLE2902::getIndications() {
return (getValue()[0] & (1 << 1)) != 0;
} // getIndications
/**
* @brief Set the indications flag.
* @param [in] flag The indications flag.
*/
void NimBLE2902::setIndications(bool flag) {
uint8_t *pValue = getValue();
if (flag) pValue[0] |= 1 << 1;
else pValue[0] &= ~(1 << 1);
} // setIndications
/**
* @brief Set the notifications flag.
* @param [in] flag The notifications flag.
*/
void NimBLE2902::setNotifications(bool flag) {
uint8_t *pValue = getValue();
if (flag) pValue[0] |= 1 << 0;
else pValue[0] &= ~(1 << 0);
} // setNotifications
#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#endif

View file

@ -1,59 +0,0 @@
/*
* NimBLE2902.h
*
* Created: on March 10, 2020
* Author H2zero
*
* Originally:
*
* BLE2902.h
*
* Created on: Jun 25, 2017
* Author: kolban
*/
#ifndef MAIN_NIMBLE2902_H_
#define MAIN_NIMBLE2902_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "nimconfig.h"
#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#include "NimBLEDescriptor.h"
#include <vector>
#define NIMBLE_DESC_FLAG_NOTIFY 0x0001
#define NIMBLE_DESC_FLAG_INDICATE 0x0002
typedef struct {
uint16_t conn_id;
uint16_t sub_val;
} chr_sub_status_t;
/**
* @brief Descriptor for Client Characteristic Configuration.
*
* This is a convenience descriptor for the Client Characteristic Configuration which has a UUID of 0x2902.
*
* See also:
* https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
*/
class NimBLE2902: public NimBLEDescriptor {
public:
bool getNotifications();
bool getIndications();
void setNotifications(bool flag);
void setIndications(bool flag);
private:
NimBLE2902(NimBLECharacteristic* pCharacterisitic);
friend class NimBLECharacteristic;
std::vector<chr_sub_status_t> m_subscribedVec;
}; // NimBLE2902
#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#endif /* CONFIG_BT_ENABLED */
#endif /* MAIN_NIMBLE2902_H_ */

View file

@ -16,16 +16,18 @@
#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#include "NimBLECharacteristic.h"
#include "NimBLE2902.h"
#include "NimBLE2904.h"
#include "NimBLEDevice.h"
#include "NimBLELog.h"
#define NULL_HANDLE (0xffff)
#define NIMBLE_SUB_NOTIFY 0x0001
#define NIMBLE_SUB_INDICATE 0x0002
static NimBLECharacteristicCallbacks defaultCallback;
static const char* LOG_TAG = "NimBLECharacteristic";
/**
* @brief Construct a characteristic
* @param [in] uuid - UUID (const char*) for the characteristic.
@ -86,20 +88,9 @@ NimBLEDescriptor* NimBLECharacteristic::createDescriptor(const char* uuid, uint3
NimBLEDescriptor* NimBLECharacteristic::createDescriptor(const NimBLEUUID &uuid, uint32_t properties, uint16_t max_len) {
NimBLEDescriptor* pDescriptor = nullptr;
if(uuid == NimBLEUUID(uint16_t(0x2902))) {
if(!(m_properties & BLE_GATT_CHR_F_NOTIFY) && !(m_properties & BLE_GATT_CHR_F_INDICATE)) {
assert(0 && "Cannot create 2902 descriptior without characteristic notification or indication property set");
}
// We cannot have more than one 2902 descriptor, if it's already been created just return a pointer to it.
pDescriptor = getDescriptorByUUID(uuid);
if(pDescriptor == nullptr) {
pDescriptor = new NimBLE2902(this);
} else {
return pDescriptor;
}
assert(0 && "0x2902 descriptors cannot be manually created");
} else if (uuid == NimBLEUUID(uint16_t(0x2904))) {
pDescriptor = new NimBLE2904(this);
} else {
pDescriptor = new NimBLEDescriptor(uuid, properties, max_len, this);
}
@ -269,21 +260,35 @@ int NimBLECharacteristic::handleGapEvent(uint16_t conn_handle, uint16_t attr_han
}
/**
* @brief Get the number of clients subscribed to the characteristic.
* @returns Number of clients subscribed to notifications / indications.
*/
size_t NimBLECharacteristic::getSubscribedCount() {
return m_subscribedVec.size();
}
/**
* @brief Set the subscribe status for this characteristic.\n
* This will maintain a vector of subscribed clients and their indicate/notify status.
*/
void NimBLECharacteristic::setSubscribe(struct ble_gap_event *event) {
uint16_t subVal = 0;
if(event->subscribe.cur_notify) {
subVal |= NIMBLE_DESC_FLAG_NOTIFY;
ble_gap_conn_desc desc;
if(ble_gap_conn_find(event->subscribe.conn_handle, &desc) != 0) {
return;
}
if(event->subscribe.cur_indicate) {
subVal |= NIMBLE_DESC_FLAG_INDICATE;
uint16_t subVal = 0;
if(event->subscribe.cur_notify > 0 && (m_properties & NIMBLE_PROPERTY::NOTIFY)) {
subVal |= NIMBLE_SUB_NOTIFY;
}
if(event->subscribe.cur_indicate && (m_properties & NIMBLE_PROPERTY::INDICATE)) {
subVal |= NIMBLE_SUB_INDICATE;
}
if(m_pTaskData != nullptr) {
m_pTaskData->rc = (subVal & NIMBLE_DESC_FLAG_INDICATE) ? 0 :
m_pTaskData->rc = (subVal & NIMBLE_SUB_INDICATE) ? 0 :
NimBLECharacteristicCallbacks::Status::ERROR_INDICATE_DISABLED;
xTaskNotifyGive(m_pTaskData->task);
}
@ -291,40 +296,28 @@ void NimBLECharacteristic::setSubscribe(struct ble_gap_event *event) {
NIMBLE_LOGI(LOG_TAG, "New subscribe value for conn: %d val: %d",
event->subscribe.conn_handle, subVal);
NimBLE2902* p2902 = (NimBLE2902*)getDescriptorByUUID(uint16_t(0x2902));
if(p2902 == nullptr){
ESP_LOGE(LOG_TAG, "No 2902 descriptor found for %s",
std::string(getUUID()).c_str());
return;
}
m_pCallbacks->onSubscribe(this, &desc, subVal);
p2902->setNotifications(subVal & NIMBLE_DESC_FLAG_NOTIFY);
p2902->setIndications(subVal & NIMBLE_DESC_FLAG_INDICATE);
p2902->m_pCallbacks->onWrite(p2902);
auto it = p2902->m_subscribedVec.begin();
for(;it != p2902->m_subscribedVec.end(); ++it) {
if((*it).conn_id == event->subscribe.conn_handle) {
auto it = m_subscribedVec.begin();
for(;it != m_subscribedVec.end(); ++it) {
if((*it).first == event->subscribe.conn_handle) {
break;
}
}
if(subVal > 0) {
if(it == p2902->m_subscribedVec.end()) {
chr_sub_status_t client_sub;
client_sub.conn_id = event->subscribe.conn_handle;
client_sub.sub_val = subVal;
p2902->m_subscribedVec.push_back(client_sub);
if(it == m_subscribedVec.end()) {
m_subscribedVec.push_back({event->subscribe.conn_handle, subVal});
return;
}
(*it).sub_val = subVal;
(*it).second = subVal;
} else if(it != p2902->m_subscribedVec.end()) {
p2902->m_subscribedVec.erase(it);
p2902->m_subscribedVec.shrink_to_fit();
} else if(it != m_subscribedVec.end()) {
m_subscribedVec.erase(it);
m_subscribedVec.shrink_to_fit();
}
}
@ -348,15 +341,16 @@ void NimBLECharacteristic::indicate() {
void NimBLECharacteristic::notify(bool is_notification) {
NIMBLE_LOGD(LOG_TAG, ">> notify: length: %d", getDataLength());
NimBLE2902* p2902 = (NimBLE2902*)getDescriptorByUUID(uint16_t(0x2902));
if(p2902 == nullptr) {
if(!(m_properties & NIMBLE_PROPERTY::NOTIFY) &&
!(m_properties & NIMBLE_PROPERTY::INDICATE))
{
NIMBLE_LOGE(LOG_TAG,
"<< notify-Error; Notify/indicate not enabled for characterisitc: %s",
std::string(getUUID()).c_str());
}
if (p2902->m_subscribedVec.size() == 0) {
if (m_subscribedVec.size() == 0) {
NIMBLE_LOGD(LOG_TAG, "<< notify: No clients subscribed.");
return;
}
@ -370,18 +364,18 @@ void NimBLECharacteristic::notify(bool is_notification) {
(m_properties & BLE_GATT_CHR_F_READ_ENC);
int rc = 0;
for (auto &it : p2902->m_subscribedVec) {
uint16_t _mtu = getService()->getServer()->getPeerMTU(it.conn_id);
for (auto &it : m_subscribedVec) {
uint16_t _mtu = getService()->getServer()->getPeerMTU(it.first);
// check if connected and subscribed
if(_mtu == 0 || it.sub_val == 0) {
if(_mtu == 0 || it.second == 0) {
continue;
}
// check if security requirements are satisfied
if(reqSec) {
struct ble_gap_conn_desc desc;
rc = ble_gap_conn_find(it.conn_id, &desc);
rc = ble_gap_conn_find(it.first, &desc);
if(rc != 0 || !desc.sec_state.encrypted) {
continue;
}
@ -391,13 +385,13 @@ void NimBLECharacteristic::notify(bool is_notification) {
NIMBLE_LOGW(LOG_TAG, "- Truncating to %d bytes (maximum notify size)", _mtu - 3);
}
if(is_notification && (!(it.sub_val & NIMBLE_DESC_FLAG_NOTIFY))) {
if(is_notification && (!(it.second & NIMBLE_SUB_NOTIFY))) {
NIMBLE_LOGW(LOG_TAG,
"Sending notification to client subscribed to indications, sending indication instead");
is_notification = false;
}
if(!is_notification && (!(it.sub_val & NIMBLE_DESC_FLAG_INDICATE))) {
if(!is_notification && (!(it.second & NIMBLE_SUB_INDICATE))) {
NIMBLE_LOGW(LOG_TAG,
"Sending indication to client subscribed to notification, sending notification instead");
is_notification = true;
@ -414,7 +408,7 @@ void NimBLECharacteristic::notify(bool is_notification) {
ble_task_data_t taskData = {nullptr, xTaskGetCurrentTaskHandle(),0, nullptr};
m_pTaskData = &taskData;
rc = ble_gattc_indicate_custom(it.conn_id, m_handle, om);
rc = ble_gattc_indicate_custom(it.first, m_handle, om);
if(rc != 0){
statusRC = NimBLECharacteristicCallbacks::Status::ERROR_GATT;
} else {
@ -433,7 +427,7 @@ void NimBLECharacteristic::notify(bool is_notification) {
statusRC = NimBLECharacteristicCallbacks::Status::ERROR_INDICATE_FAILURE;
}
} else {
rc = ble_gattc_notify_custom(it.conn_id, m_handle, om);
rc = ble_gattc_notify_custom(it.first, m_handle, om);
if(rc == 0) {
statusRC = NimBLECharacteristicCallbacks::Status::SUCCESS_NOTIFY;
} else {
@ -574,5 +568,24 @@ void NimBLECharacteristicCallbacks::onStatus(NimBLECharacteristic* pCharacterist
NIMBLE_LOGD("NimBLECharacteristicCallbacks", "onStatus: default");
} // onStatus
/**
* @brief Callback function called when a client changes subscription status.
* @param [in] pCharacteristic The characteristic that is the source of the event.
* @param [in] desc The connection description struct that is associated with the client.
* @param [in] subValue The subscription status:
* * 0 = Un-Subscribed
* * 1 = Notifications
* * 2 = Indications
* * 3 = Notifications and Indications
*/
void NimBLECharacteristicCallbacks::onSubscribe(NimBLECharacteristic* pCharacteristic,
ble_gap_conn_desc* desc,
uint16_t subValue)
{
NIMBLE_LOGD("NimBLECharacteristicCallbacks", "onSubscribe: default");
}
#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#endif /* CONFIG_BT_ENABLED */

View file

@ -110,6 +110,7 @@ public:
std::string toString();
uint16_t getHandle();
size_t getSubscribedCount();
private:
@ -145,6 +146,8 @@ private:
ble_task_data_t *m_pTaskData;
portMUX_TYPE m_valMux;
time_t m_timestamp;
std::vector<std::pair<uint16_t, uint16_t>> m_subscribedVec;
}; // NimBLECharacteristic
@ -181,6 +184,7 @@ public:
virtual void onWrite(NimBLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc);
virtual void onNotify(NimBLECharacteristic* pCharacteristic);
virtual void onStatus(NimBLECharacteristic* pCharacteristic, Status s, int code);
virtual void onSubscribe(NimBLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc, uint16_t subValue);
};
#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)

View file

@ -106,7 +106,6 @@ public:
};
#include "NimBLE2904.h"
#include "NimBLE2902.h"
#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#endif /* CONFIG_BT_ENABLED */

View file

@ -182,10 +182,6 @@ void NimBLEServer::start() {
// we do it now.
if((chr->m_properties & BLE_GATT_CHR_F_INDICATE) ||
(chr->m_properties & BLE_GATT_CHR_F_NOTIFY)) {
if(nullptr == chr->getDescriptorByUUID(uint16_t(0x2902))) {
chr->createDescriptor(uint16_t(0x2902));
}
m_notifyChrVec.push_back(chr);
}
}
@ -303,9 +299,9 @@ size_t NimBLEServer::getConnectedCount() {
} // BLE_GAP_EVENT_DISCONNECT
case BLE_GAP_EVENT_SUBSCRIBE: {
NIMBLE_LOGI(LOG_TAG, "subscribe event; cur_notify=%d\n value handle; "
"val_handle=%d\n",
event->subscribe.cur_notify, event->subscribe.attr_handle);
NIMBLE_LOGI(LOG_TAG, "subscribe event; attr_handle=%d, subscribed: %s",
event->subscribe.attr_handle,
(event->subscribe.cur_notify ? "true":"false"));
for(auto &it : server->m_notifyChrVec) {
if(it->getHandle() == event->subscribe.attr_handle) {

View file

@ -147,31 +147,14 @@ bool NimBLEService::start() {
for(uint8_t i=0; i < numChrs;) {
uint8_t numDscs = pCharacteristic->m_dscVec.size();
if(numDscs) {
// skip 2902 as it's automatically created by NimBLE
// if Indicate or Notify flags are set
if(((pCharacteristic->m_properties & BLE_GATT_CHR_F_INDICATE) ||
(pCharacteristic->m_properties & BLE_GATT_CHR_F_NOTIFY)) &&
pCharacteristic->getDescriptorByUUID("2902") != nullptr)
{
numDscs--;
}
}
if(!numDscs){
pChr_a[i].descriptors = NULL;
} else {
// Must have last descriptor uuid = 0 so we have to create 1 extra
//NIMBLE_LOGD(LOG_TAG, "Adding %d descriptors", numDscs);
pDsc_a = new ble_gatt_dsc_def[numDscs+1];
NimBLEDescriptor* pDescriptor = *pCharacteristic->m_dscVec.begin();
for(uint8_t d=0; d < numDscs;) {
// skip 2902
if(pDescriptor->m_uuid == NimBLEUUID(uint16_t(0x2902))) {
//NIMBLE_LOGD(LOG_TAG, "Skipped 0x2902");
pDescriptor = *(pCharacteristic->m_dscVec.begin()+d+1);
continue;
}
pDsc_a[d].uuid = &pDescriptor->m_uuid.getNative()->u;
pDsc_a[d].att_flags = pDescriptor->m_properties;
pDsc_a[d].min_key_size = 0;