[BREAKING] Refactor attributes

Refactor attributes to reduce code duplication and improve maintainability.

* Add attribute base classes to provide common code.
* Add const where possible to functions and parameters.
* `NimBLECharacteristic::notify` no longer takes a `bool is_notification` parameter, instead `indicate()` should be called to send indications.
* `NimBLECharacteristic::indicate` now takes the same parameters as `notify`.
* `NimBLECharacteristicCallbacks` and `NimBLEDescriptorCallbacks` methods now take `const NimBLEConnInfo&` instead of non-const.
* `NimBLECharacteristic::onNotify` callback removed as unnecessary, the library does not call notify without app input.
* `NimBLERemoteCharacteristic::getRemoteService` now returns a `const NimBLERemoteService*` instead of non-const.
* Add NimBLEUUID constructor that takes a reference to `ble_uuid_any_t`.
* `NimBLERemoteService::getCharacteristics` now returns a `const std::vector<NimBLERemoteCharacteristic*>&` instead of non-const `std::vector<NimBLERemoteCharacteristic*>*`
* `NimBLERemoteService::getValue` now returns `NimBLEAttValue` instead of `std::string`
* `NimBLEService::getCharacteristics` now returns a `const std::vector<NimBLECharacteristic*>&` instead of a copy of std::vector<NimBLECharacteristic *>.
* Remove const requirement for NimBLEConnInfo parameter in callbacks.
  Const is unnecessary as the data can't be changed by application code.
* Change NimBLERemoteCharacteristic::getRemoteService to return const pointer.
This commit is contained in:
h2zero 2024-07-26 14:47:36 -06:00 committed by h2zero
parent b4b3b0c455
commit 91210b8610
35 changed files with 1757 additions and 2536 deletions

View file

@ -52,6 +52,7 @@ idf_component_register(
"src/NimBLERemoteCharacteristic.cpp" "src/NimBLERemoteCharacteristic.cpp"
"src/NimBLERemoteDescriptor.cpp" "src/NimBLERemoteDescriptor.cpp"
"src/NimBLERemoteService.cpp" "src/NimBLERemoteService.cpp"
"src/NimBLERemoteValueAttribute.cpp"
"src/NimBLEScan.cpp" "src/NimBLEScan.cpp"
"src/NimBLEServer.cpp" "src/NimBLEServer.cpp"
"src/NimBLEService.cpp" "src/NimBLEService.cpp"

View file

@ -383,13 +383,13 @@ The security callback methods are now incorporated in the `NimBLEServerCallbacks
The callback methods are: The callback methods are:
> `bool onConfirmPIN(const NimBLEConnInfo& connInfo, uint32_t pin)` > `bool onConfirmPIN(NimBLEConnInfo& connInfo, uint32_t pin)`
Receives the pin when using numeric comparison authentication. Receives the pin when using numeric comparison authentication.
Call `NimBLEDevice::injectConfirmPIN(connInfo, true);` to accept or `NimBLEDevice::injectConfirmPIN(connInfo, false);` to reject. Call `NimBLEDevice::injectConfirmPIN(connInfo, true);` to accept or `NimBLEDevice::injectConfirmPIN(connInfo, false);` to reject.
<br/> <br/>
> `void onPassKeyEntry(const NimBLEConnInfo& connInfo)` > `void onPassKeyEntry(NimBLEConnInfo& connInfo)`
Client callback; client should respond with the passkey (pin) by calling `NimBLEDevice::injectPassKey(connInfo, 123456);` Client callback; client should respond with the passkey (pin) by calling `NimBLEDevice::injectPassKey(connInfo, 123456);`
<br/> <br/>
@ -399,7 +399,7 @@ Client callback; client should respond with the passkey (pin) by calling `NimBLE
Server callback; should return the passkey (pin) expected from the client. Server callback; should return the passkey (pin) expected from the client.
<br/> <br/>
> `void onAuthenticationComplete(const NimBLEConnInfo& connInfo)` > `void onAuthenticationComplete(NimBLEConnInfo& connInfo)`
Authentication complete, success or failed information is available from the `NimBLEConnInfo` methods. Authentication complete, success or failed information is available from the `NimBLEConnInfo` methods.
<br/> <br/>

View file

@ -39,7 +39,7 @@ class ClientCallbacks : public NimBLEClientCallbacks {
/********************* Security handled here ********************** /********************* Security handled here **********************
****** Note: these are the same return values as defaults ********/ ****** Note: these are the same return values as defaults ********/
void onPassKeyEntry(const NimBLEConnInfo& connInfo){ void onPassKeyEntry(NimBLEConnInfo& connInfo){
printf("Server Passkey Entry\n"); printf("Server Passkey Entry\n");
/** This should prompt the user to enter the passkey displayed /** This should prompt the user to enter the passkey displayed
* on the peer device. * on the peer device.
@ -47,14 +47,14 @@ class ClientCallbacks : public NimBLEClientCallbacks {
NimBLEDevice::injectPassKey(connInfo, 123456); NimBLEDevice::injectPassKey(connInfo, 123456);
}; };
void onConfirmPIN(const NimBLEConnInfo& connInfo, uint32_t pass_key){ void onConfirmPIN(NimBLEConnInfo& connInfo, uint32_t pass_key){
printf("The passkey YES/NO number: %" PRIu32 "\n", pass_key); printf("The passkey YES/NO number: %" PRIu32 "\n", pass_key);
/** Inject false if passkeys don't match. */ /** Inject false if passkeys don't match. */
NimBLEDevice::injectConfirmPIN(connInfo, true); NimBLEDevice::injectConfirmPIN(connInfo, true);
}; };
/** Pairing process complete, we can check the results in connInfo */ /** Pairing process complete, we can check the results in connInfo */
void onAuthenticationComplete(const NimBLEConnInfo& connInfo){ void onAuthenticationComplete(NimBLEConnInfo& connInfo){
if(!connInfo.isEncrypted()) { if(!connInfo.isEncrypted()) {
printf("Encrypt connection failed - disconnecting\n"); printf("Encrypt connection failed - disconnecting\n");
/** Find the client with the connection handle provided in desc */ /** Find the client with the connection handle provided in desc */
@ -92,7 +92,7 @@ class scanCallbacks: public NimBLEScanCallbacks {
void notifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify){ void notifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify){
std::string str = (isNotify == true) ? "Notification" : "Indication"; std::string str = (isNotify == true) ? "Notification" : "Indication";
str += " from "; str += " from ";
str += pRemoteCharacteristic->getRemoteService()->getClient()->getPeerAddress().toString(); str += pRemoteCharacteristic->getClient()->getPeerAddress().toString();
str += ": Service = " + pRemoteCharacteristic->getRemoteService()->getUUID().toString(); str += ": Service = " + pRemoteCharacteristic->getRemoteService()->getUUID().toString();
str += ", Characteristic = " + pRemoteCharacteristic->getUUID().toString(); str += ", Characteristic = " + pRemoteCharacteristic->getUUID().toString();
str += ", Value = " + std::string((char*)pData, length); str += ", Value = " + std::string((char*)pData, length);

View file

@ -52,13 +52,13 @@ class ServerCallbacks: public NimBLEServerCallbacks {
return 123456; return 123456;
}; };
void onConfirmPIN(const NimBLEConnInfo& connInfo, uint32_t pass_key){ void onConfirmPIN(NimBLEConnInfo& connInfo, uint32_t pass_key){
printf("The passkey YES/NO number: %" PRIu32 "\n", pass_key); printf("The passkey YES/NO number: %" PRIu32 "\n", pass_key);
/** Inject false if passkeys don't match. */ /** Inject false if passkeys don't match. */
NimBLEDevice::injectConfirmPIN(connInfo, true); NimBLEDevice::injectConfirmPIN(connInfo, true);
}; };
void onAuthenticationComplete(const NimBLEConnInfo& connInfo){ void onAuthenticationComplete(NimBLEConnInfo& connInfo){
/** Check that encryption was successful, if not we disconnect the client */ /** Check that encryption was successful, if not we disconnect the client */
if(!connInfo.isEncrypted()) { if(!connInfo.isEncrypted()) {
NimBLEDevice::getServer()->disconnect(connInfo.getConnHandle()); NimBLEDevice::getServer()->disconnect(connInfo.getConnHandle());
@ -142,7 +142,7 @@ void notifyTask(void * parameter){
if(pSvc) { if(pSvc) {
NimBLECharacteristic* pChr = pSvc->getCharacteristic("F00D"); NimBLECharacteristic* pChr = pSvc->getCharacteristic("F00D");
if(pChr) { if(pChr) {
pChr->notify(true); pChr->notify();
} }
} }
} }

View file

@ -25,7 +25,7 @@ class ServerCallbacks : public NimBLEServerCallbacks {
} }
// Same as before but now includes the name parameter // Same as before but now includes the name parameter
void onAuthenticationComplete(const NimBLEConnInfo& connInfo, const std::string& name) override { void onAuthenticationComplete(NimBLEConnInfo& connInfo, const std::string& name) override {
if (!connInfo.isEncrypted()) { if (!connInfo.isEncrypted()) {
NimBLEDevice::getServer()->disconnect(connInfo.getConnHandle()); NimBLEDevice::getServer()->disconnect(connInfo.getConnHandle());
printf("Encrypt connection failed - disconnecting client\n"); printf("Encrypt connection failed - disconnecting client\n");

View file

@ -51,7 +51,7 @@ class MyClientCallback : public BLEClientCallbacks {
} }
/***************** New - Security handled here ******************** /***************** New - Security handled here ********************
****** Note: these are the same return values as defaults ********/ ****** Note: these are the same return values as defaults ********/
void onPassKeyEntry(const NimBLEConnInfo& connInfo){ void onPassKeyEntry(NimBLEConnInfo& connInfo){
printf("Server Passkey Entry\n"); printf("Server Passkey Entry\n");
/** This should prompt the user to enter the passkey displayed /** This should prompt the user to enter the passkey displayed
* on the peer device. * on the peer device.
@ -59,14 +59,14 @@ class MyClientCallback : public BLEClientCallbacks {
NimBLEDevice::injectPassKey(connInfo, 123456); NimBLEDevice::injectPassKey(connInfo, 123456);
}; };
void onConfirmPIN(const NimBLEConnInfo& connInfo, uint32_t pass_key){ void onConfirmPIN(NimBLEConnInfo& connInfo, uint32_t pass_key){
printf("The passkey YES/NO number: %" PRIu32 "\n", pass_key); printf("The passkey YES/NO number: %" PRIu32 "\n", pass_key);
/** Inject false if passkeys don't match. */ /** Inject false if passkeys don't match. */
NimBLEDevice::injectConfirmPIN(connInfo, true); NimBLEDevice::injectConfirmPIN(connInfo, true);
}; };
/** Pairing process complete, we can check the results in connInfo */ /** Pairing process complete, we can check the results in connInfo */
void onAuthenticationComplete(const NimBLEConnInfo& connInfo){ void onAuthenticationComplete(NimBLEConnInfo& connInfo){
if(!connInfo.isEncrypted()) { if(!connInfo.isEncrypted()) {
printf("Encrypt connection failed - disconnecting\n"); printf("Encrypt connection failed - disconnecting\n");
/** Find the client with the connection handle provided in desc */ /** Find the client with the connection handle provided in desc */

View file

@ -65,13 +65,13 @@ class MyServerCallbacks: public BLEServerCallbacks {
return 123456; return 123456;
}; };
void onConfirmPIN(const NimBLEConnInfo& connInfo, uint32_t pass_key){ void onConfirmPIN(NimBLEConnInfo& connInfo, uint32_t pass_key){
printf("The passkey YES/NO number: %" PRIu32 "\n", pass_key); printf("The passkey YES/NO number: %" PRIu32 "\n", pass_key);
/** Inject false if passkeys don't match. */ /** Inject false if passkeys don't match. */
NimBLEDevice::injectConfirmPIN(connInfo, true); NimBLEDevice::injectConfirmPIN(connInfo, true);
}; };
void onAuthenticationComplete(const NimBLEConnInfo& connInfo){ void onAuthenticationComplete(NimBLEConnInfo& connInfo){
/** Check that encryption was successful, if not we disconnect the client */ /** Check that encryption was successful, if not we disconnect the client */
if(!connInfo.isEncrypted()) { if(!connInfo.isEncrypted()) {
NimBLEDevice::getServer()->disconnect(connInfo.getConnHandle()); NimBLEDevice::getServer()->disconnect(connInfo.getConnHandle());

View file

@ -67,13 +67,13 @@ class MyServerCallbacks: public BLEServerCallbacks {
return 123456; return 123456;
}; };
void onConfirmPIN(const NimBLEConnInfo& connInfo, uint32_t pass_key){ void onConfirmPIN(NimBLEConnInfo& connInfo, uint32_t pass_key){
printf("The passkey YES/NO number: %" PRIu32 "\n", pass_key); printf("The passkey YES/NO number: %" PRIu32 "\n", pass_key);
/** Inject false if passkeys don't match. */ /** Inject false if passkeys don't match. */
NimBLEDevice::injectConfirmPIN(connInfo, true); NimBLEDevice::injectConfirmPIN(connInfo, true);
}; };
void onAuthenticationComplete(const NimBLEConnInfo& connInfo){ void onAuthenticationComplete(NimBLEConnInfo& connInfo){
/** Check that encryption was successful, if not we disconnect the client */ /** Check that encryption was successful, if not we disconnect the client */
if(!connInfo.isEncrypted()) { if(!connInfo.isEncrypted()) {
NimBLEDevice::getServer()->disconnect(connInfo.getConnHandle()); NimBLEDevice::getServer()->disconnect(connInfo.getConnHandle());

View file

@ -192,12 +192,7 @@ class NimBLEAttValue {
return setValue(reinterpret_cast<const uint8_t*>(s), len); return setValue(reinterpret_cast<const uint8_t*>(s), len);
} }
/** const NimBLEAttValue& getValue(time_t* timestamp = nullptr) const {
* @brief Get a pointer to the value buffer with timestamp.
* @param[in] timestamp A pointer to a time_t variable to store the timestamp.
* @returns A pointer to the internal value buffer.
*/
const uint8_t* getValue(time_t* timestamp = nullptr) const {
if (timestamp != nullptr) { if (timestamp != nullptr) {
# if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED # if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED
*timestamp = m_timestamp; *timestamp = m_timestamp;
@ -205,7 +200,7 @@ class NimBLEAttValue {
*timestamp = 0; *timestamp = 0;
# endif # endif
} }
return m_attr_value; return *this;
} }
/** /**
@ -271,7 +266,15 @@ class NimBLEAttValue {
if (!skipSizeCheck && size() < sizeof(T)) { if (!skipSizeCheck && size() < sizeof(T)) {
return T(); return T();
} }
return *(reinterpret_cast<const T*>(getValue(timestamp))); if (timestamp != nullptr) {
# if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED
*timestamp = m_timestamp;
# else
*timestamp = 0;
# endif
}
return *(reinterpret_cast<const T*>(m_attr_value));
} }
/*********************** Operators ************************/ /*********************** Operators ************************/

50
src/NimBLEAttribute.h Normal file
View file

@ -0,0 +1,50 @@
/*
* NimBLEAttribute.h
*
* Created: on July 28 2024
* Author H2zero
*/
#ifndef NIMBLE_CPP_ATTRIBUTE_H_
#define NIMBLE_CPP_ATTRIBUTE_H_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && (defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) || defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL))
# include "NimBLEUUID.h"
/**
* @brief A base class for BLE attributes.
*/
class NimBLEAttribute {
public:
/**
* @brief Get the UUID of the attribute.
* @return The UUID.
*/
const NimBLEUUID& getUUID() const { return m_uuid; }
/**
* @brief Get the handle of the attribute.
*/
uint16_t getHandle() const { return m_handle; };
protected:
/**
* @brief Construct a new NimBLEAttribute object.
* @param [in] handle The handle of the attribute.
* @param [in] uuid The UUID of the attribute.
*/
NimBLEAttribute(const NimBLEUUID& uuid, uint16_t handle) : m_uuid{uuid}, m_handle{handle} {}
/**
* @brief Destroy the NimBLEAttribute object.
*/
~NimBLEAttribute() = default;
const NimBLEUUID m_uuid{};
uint16_t m_handle{0};
};
#endif // CONFIG_BT_ENABLED && (CONFIG_BT_NIMBLE_ROLE_PERIPHERAL || CONFIG_BT_NIMBLE_ROLE_CENTRAL)
#endif // NIMBLE_CPP_ATTRIBUTE_H_

View file

@ -18,14 +18,12 @@
# include "NimBLEDevice.h" # include "NimBLEDevice.h"
# include "NimBLELog.h" # include "NimBLELog.h"
#define NULL_HANDLE (0xffff)
# define NIMBLE_SUB_NOTIFY 0x0001 # define NIMBLE_SUB_NOTIFY 0x0001
# define NIMBLE_SUB_INDICATE 0x0002 # define NIMBLE_SUB_INDICATE 0x0002
static NimBLECharacteristicCallbacks defaultCallback; static NimBLECharacteristicCallbacks defaultCallback;
static const char* LOG_TAG = "NimBLECharacteristic"; static const char* LOG_TAG = "NimBLECharacteristic";
/** /**
* @brief Construct a characteristic * @brief Construct a characteristic
* @param [in] uuid - UUID (const char*) for the characteristic. * @param [in] uuid - UUID (const char*) for the characteristic.
@ -33,10 +31,8 @@ static const char* LOG_TAG = "NimBLECharacteristic";
* @param [in] max_len - The maximum length in bytes that the characteristic value can hold. (Default: 512 bytes for esp32, 20 for all others). * @param [in] max_len - The maximum length in bytes that the characteristic value can hold. (Default: 512 bytes for esp32, 20 for all others).
* @param [in] pService - pointer to the service instance this characteristic belongs to. * @param [in] pService - pointer to the service instance this characteristic belongs to.
*/ */
NimBLECharacteristic::NimBLECharacteristic(const char* uuid, uint16_t properties, NimBLECharacteristic::NimBLECharacteristic(const char* uuid, uint16_t properties, uint16_t max_len, NimBLEService* pService)
uint16_t max_len, NimBLEService* pService) : NimBLECharacteristic(NimBLEUUID(uuid), properties, max_len, pService) {}
: NimBLECharacteristic(NimBLEUUID(uuid), properties, max_len, pService) {
}
/** /**
* @brief Construct a characteristic * @brief Construct a characteristic
@ -45,27 +41,20 @@ NimBLECharacteristic::NimBLECharacteristic(const char* uuid, uint16_t properties
* @param [in] max_len - The maximum length in bytes that the characteristic value can hold. (Default: 512 bytes for esp32, 20 for all others). * @param [in] max_len - The maximum length in bytes that the characteristic value can hold. (Default: 512 bytes for esp32, 20 for all others).
* @param [in] pService - pointer to the service instance this characteristic belongs to. * @param [in] pService - pointer to the service instance this characteristic belongs to.
*/ */
NimBLECharacteristic::NimBLECharacteristic(const NimBLEUUID &uuid, uint16_t properties, NimBLECharacteristic::NimBLECharacteristic(const NimBLEUUID& uuid, uint16_t properties, uint16_t max_len, NimBLEService* pService)
uint16_t max_len, NimBLEService* pService) : NimBLELocalValueAttribute{uuid, 0, max_len}, m_pCallbacks{&defaultCallback}, m_pService{pService} {
: m_value(std::min(CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH , (int)max_len), max_len) { setProperties(properties);
m_uuid = uuid;
m_handle = NULL_HANDLE;
m_properties = properties;
m_pCallbacks = &defaultCallback;
m_pService = pService;
m_removed = 0;
} // NimBLECharacteristic } // NimBLECharacteristic
/** /**
* @brief Destructor. * @brief Destructor.
*/ */
NimBLECharacteristic::~NimBLECharacteristic() { NimBLECharacteristic::~NimBLECharacteristic() {
for(auto &it : m_dscVec) { for (const auto& dsc : m_vDescriptors) {
delete it; delete dsc;
} }
} // ~NimBLECharacteristic } // ~NimBLECharacteristic
/** /**
* @brief Create a new BLE Descriptor associated with this characteristic. * @brief Create a new BLE Descriptor associated with this characteristic.
* @param [in] uuid - The UUID of the descriptor. * @param [in] uuid - The UUID of the descriptor.
@ -77,7 +66,6 @@ NimBLEDescriptor* NimBLECharacteristic::createDescriptor(const char* uuid, uint3
return createDescriptor(NimBLEUUID(uuid), properties, max_len); return createDescriptor(NimBLEUUID(uuid), properties, max_len);
} }
/** /**
* @brief Create a new BLE Descriptor associated with this characteristic. * @brief Create a new BLE Descriptor associated with this characteristic.
* @param [in] uuid - The UUID of the descriptor. * @param [in] uuid - The UUID of the descriptor.
@ -97,32 +85,37 @@ NimBLEDescriptor* NimBLECharacteristic::createDescriptor(const NimBLEUUID &uuid,
return pDescriptor; return pDescriptor;
} // createDescriptor } // createDescriptor
/** /**
* @brief Add a descriptor to the characteristic. * @brief Add a descriptor to the characteristic.
* @param [in] pDescriptor A pointer to the descriptor to add. * @param [in] pDescriptor A pointer to the descriptor to add.
*/ */
void NimBLECharacteristic::addDescriptor(NimBLEDescriptor* pDescriptor) { void NimBLECharacteristic::addDescriptor(NimBLEDescriptor* pDescriptor) {
bool foundRemoved = false; bool foundRemoved = false;
if (pDescriptor->getRemoved() > 0) {
if(pDescriptor->m_removed > 0) { for (const auto& dsc : m_vDescriptors) {
for(auto& it : m_dscVec) { if (dsc == pDescriptor) {
if(it == pDescriptor) {
foundRemoved = true; foundRemoved = true;
pDescriptor->m_removed = 0; pDescriptor->setRemoved(0);
} }
} }
} }
// Check if the descriptor is already in the vector and if so, return.
for (const auto& dsc : m_vDescriptors) {
if (dsc == pDescriptor) {
pDescriptor->setCharacteristic(this); // Update the characteristic pointer in the descriptor.
return;
}
}
if (!foundRemoved) { if (!foundRemoved) {
m_dscVec.push_back(pDescriptor); m_vDescriptors.push_back(pDescriptor);
} }
pDescriptor->setCharacteristic(this); pDescriptor->setCharacteristic(this);
NimBLEDevice::getServer()->serviceChanged(); NimBLEDevice::getServer()->serviceChanged();
} }
/** /**
* @brief Remove a descriptor from the characteristic. * @brief Remove a descriptor from the characteristic.
* @param[in] pDescriptor A pointer to the descriptor instance to remove from the characteristic. * @param[in] pDescriptor A pointer to the descriptor instance to remove from the characteristic.
@ -132,12 +125,12 @@ void NimBLECharacteristic::removeDescriptor(NimBLEDescriptor *pDescriptor, bool
// Check if the descriptor was already removed and if so, check if this // Check if the descriptor was already removed and if so, check if this
// is being called to delete the object and do so if requested. // is being called to delete the object and do so if requested.
// Otherwise, ignore the call and return. // Otherwise, ignore the call and return.
if(pDescriptor->m_removed > 0) { if (pDescriptor->getRemoved() > 0) {
if (deleteDsc) { if (deleteDsc) {
for(auto it = m_dscVec.begin(); it != m_dscVec.end(); ++it) { for (auto it = m_vDescriptors.begin(); it != m_vDescriptors.end(); ++it) {
if ((*it) == pDescriptor) { if ((*it) == pDescriptor) {
delete *it; delete (*it);
m_dscVec.erase(it); m_vDescriptors.erase(it);
break; break;
} }
} }
@ -146,30 +139,28 @@ void NimBLECharacteristic::removeDescriptor(NimBLEDescriptor *pDescriptor, bool
return; return;
} }
pDescriptor->m_removed = deleteDsc ? NIMBLE_ATT_REMOVE_DELETE : NIMBLE_ATT_REMOVE_HIDE; pDescriptor->setRemoved(deleteDsc ? NIMBLE_ATT_REMOVE_DELETE : NIMBLE_ATT_REMOVE_HIDE);
NimBLEDevice::getServer()->serviceChanged(); NimBLEDevice::getServer()->serviceChanged();
} // removeDescriptor } // removeDescriptor
/** /**
* @brief Return the BLE Descriptor for the given UUID. * @brief Return the BLE Descriptor for the given UUID.
* @param [in] uuid The UUID of the descriptor. * @param [in] uuid The UUID of the descriptor.
* @return A pointer to the descriptor object or nullptr if not found. * @return A pointer to the descriptor object or nullptr if not found.
*/ */
NimBLEDescriptor* NimBLECharacteristic::getDescriptorByUUID(const char* uuid) { NimBLEDescriptor* NimBLECharacteristic::getDescriptorByUUID(const char* uuid) const {
return getDescriptorByUUID(NimBLEUUID(uuid)); return getDescriptorByUUID(NimBLEUUID(uuid));
} // getDescriptorByUUID } // getDescriptorByUUID
/** /**
* @brief Return the BLE Descriptor for the given UUID. * @brief Return the BLE Descriptor for the given UUID.
* @param [in] uuid The UUID of the descriptor. * @param [in] uuid The UUID of the descriptor.
* @return A pointer to the descriptor object or nullptr if not found. * @return A pointer to the descriptor object or nullptr if not found.
*/ */
NimBLEDescriptor* NimBLECharacteristic::getDescriptorByUUID(const NimBLEUUID &uuid) { NimBLEDescriptor* NimBLECharacteristic::getDescriptorByUUID(const NimBLEUUID& uuid) const {
for (auto &it : m_dscVec) { for (const auto& dsc : m_vDescriptors) {
if (it->getUUID() == uuid) { if (dsc->getUUID() == uuid) {
return it; return dsc;
} }
} }
return nullptr; return nullptr;
@ -180,174 +171,47 @@ NimBLEDescriptor* NimBLECharacteristic::getDescriptorByUUID(const NimBLEUUID &uu
* @param [in] handle The handle of the descriptor. * @param [in] handle The handle of the descriptor.
* @return A pointer to the descriptor object or nullptr if not found. * @return A pointer to the descriptor object or nullptr if not found.
*/ */
NimBLEDescriptor *NimBLECharacteristic::getDescriptorByHandle(uint16_t handle) { NimBLEDescriptor* NimBLECharacteristic::getDescriptorByHandle(uint16_t handle) const {
for (auto &it : m_dscVec) { for (const auto& dsc : m_vDescriptors) {
if (it->getHandle() == handle) { if (dsc->getHandle() == handle) {
return it; return dsc;
} }
} }
return nullptr; return nullptr;
} } // getDescriptorByHandle
/**
* @brief Get the handle of the characteristic.
* @return The handle of the characteristic.
*/
uint16_t NimBLECharacteristic::getHandle() {
return m_handle;
} // getHandle
/** /**
* @brief Get the properties of the characteristic. * @brief Get the properties of the characteristic.
* @return The properties of the characteristic. * @return The properties of the characteristic.
*/ */
uint16_t NimBLECharacteristic::getProperties() { uint16_t NimBLECharacteristic::getProperties() const {
return m_properties; return m_properties;
} // getProperties } // getProperties
/** /**
* @brief Get the service associated with this characteristic. * @brief Get the service that owns this characteristic.
*/ */
NimBLEService* NimBLECharacteristic::getService() { NimBLEService* NimBLECharacteristic::getService() const {
return m_pService; return m_pService;
} // getService } // getService
void NimBLECharacteristic::setService(NimBLEService* pService) { void NimBLECharacteristic::setService(NimBLEService* pService) {
m_pService = pService; m_pService = pService;
} } // setService
/**
* @brief Get the UUID of the characteristic.
* @return The UUID of the characteristic.
*/
NimBLEUUID NimBLECharacteristic::getUUID() {
return m_uuid;
} // getUUID
/**
* @brief Retrieve the current value of the characteristic.
* @return The NimBLEAttValue containing the current characteristic value.
*/
NimBLEAttValue NimBLECharacteristic::getValue(time_t *timestamp) {
if(timestamp != nullptr) {
m_value.getValue(timestamp);
}
return m_value;
} // getValue
/**
* @brief Retrieve the the current data length of the characteristic.
* @return The length of the current characteristic data.
*/
size_t NimBLECharacteristic::getDataLength() {
return m_value.size();
}
/**
* @brief STATIC callback to handle events from the NimBLE stack.
*/
int NimBLECharacteristic::handleGapEvent(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt,
void *arg)
{
if (conn_handle > BLE_HCI_LE_CONN_HANDLE_MAX)
{
NIMBLE_LOGW(LOG_TAG, "Conn_handle (%d) is above the maximum value (%d)", conn_handle, BLE_HCI_LE_CONN_HANDLE_MAX);
return BLE_ATT_ERR_INVALID_HANDLE;
}
const ble_uuid_t *uuid;
int rc;
NimBLEConnInfo peerInfo;
NimBLECharacteristic* pCharacteristic = (NimBLECharacteristic*)arg;
NIMBLE_LOGD(LOG_TAG, "Characteristic %s %s event", pCharacteristic->getUUID().toString().c_str(),
ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR ? "Read" : "Write");
uuid = ctxt->chr->uuid;
if(ble_uuid_cmp(uuid, pCharacteristic->getUUID().getBase()) == 0){
switch(ctxt->op) {
case BLE_GATT_ACCESS_OP_READ_CHR: {
ble_gap_conn_find(conn_handle, &peerInfo.m_desc);
// If the packet header is only 8 bytes this is a follow up of a long read
// so we don't want to call the onRead() callback again.
if(ctxt->om->om_pkthdr_len > 8 ||
conn_handle == BLE_HS_CONN_HANDLE_NONE ||
pCharacteristic->m_value.size() <= (ble_att_mtu(peerInfo.m_desc.conn_handle) - 3)) {
pCharacteristic->m_pCallbacks->onRead(pCharacteristic, peerInfo);
}
ble_npl_hw_enter_critical();
rc = os_mbuf_append(ctxt->om, pCharacteristic->m_value.data(), pCharacteristic->m_value.size());
ble_npl_hw_exit_critical(0);
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
case BLE_GATT_ACCESS_OP_WRITE_CHR: {
uint16_t att_max_len = pCharacteristic->m_value.max_size();
if (ctxt->om->om_len > att_max_len) {
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
uint8_t buf[att_max_len];
size_t len = ctxt->om->om_len;
memcpy(buf, ctxt->om->om_data,len);
os_mbuf *next;
next = SLIST_NEXT(ctxt->om, om_next);
while(next != NULL){
if((len + next->om_len) > att_max_len) {
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
memcpy(&buf[len], next->om_data, next->om_len);
len += next->om_len;
next = SLIST_NEXT(next, om_next);
}
ble_gap_conn_find(conn_handle, &peerInfo.m_desc);
pCharacteristic->setValue(buf, len);
pCharacteristic->m_pCallbacks->onWrite(pCharacteristic, peerInfo);
return 0;
}
default:
break;
}
}
return BLE_ATT_ERR_UNLIKELY;
}
/** /**
* @brief Get the number of clients subscribed to the characteristic. * @brief Get the number of clients subscribed to the characteristic.
* @returns Number of clients subscribed to notifications / indications. * @returns Number of clients subscribed to notifications / indications.
*/ */
size_t NimBLECharacteristic::getSubscribedCount() { size_t NimBLECharacteristic::getSubscribedCount() const {
return m_subscribedVec.size(); return m_subscribedVec.size();
} }
/** /**
* @brief Set the subscribe status for this characteristic.\n * @brief Set the subscribe status for this characteristic.\n
* This will maintain a vector of subscribed clients and their indicate/notify status. * This will maintain a vector of subscribed clients and their indicate/notify status.
*/ */
void NimBLECharacteristic::setSubscribe(struct ble_gap_event *event) { void NimBLECharacteristic::setSubscribe(const ble_gap_event* event, NimBLEConnInfo& connInfo) {
NimBLEConnInfo peerInfo;
if(ble_gap_conn_find(event->subscribe.conn_handle, &peerInfo.m_desc) != 0) {
return;
}
uint16_t subVal = 0; uint16_t subVal = 0;
if (event->subscribe.cur_notify > 0 && (m_properties & NIMBLE_PROPERTY::NOTIFY)) { if (event->subscribe.cur_notify > 0 && (m_properties & NIMBLE_PROPERTY::NOTIFY)) {
subVal |= NIMBLE_SUB_NOTIFY; subVal |= NIMBLE_SUB_NOTIFY;
@ -356,24 +220,22 @@ void NimBLECharacteristic::setSubscribe(struct ble_gap_event *event) {
subVal |= NIMBLE_SUB_INDICATE; subVal |= NIMBLE_SUB_INDICATE;
} }
NIMBLE_LOGI(LOG_TAG, "New subscribe value for conn: %d val: %d", NIMBLE_LOGI(LOG_TAG, "New subscribe value for conn: %d val: %d", connInfo.getConnHandle(), subVal);
event->subscribe.conn_handle, subVal);
if (!event->subscribe.cur_indicate && event->subscribe.prev_indicate) { if (!event->subscribe.cur_indicate && event->subscribe.prev_indicate) {
NimBLEDevice::getServer()->clearIndicateWait(event->subscribe.conn_handle); NimBLEDevice::getServer()->clearIndicateWait(connInfo.getConnHandle());
} }
auto it = m_subscribedVec.begin(); auto it = m_subscribedVec.begin();
for (; it != m_subscribedVec.end(); ++it) { for (; it != m_subscribedVec.end(); ++it) {
if((*it).first == event->subscribe.conn_handle) { if ((*it).first == connInfo.getConnHandle()) {
break; break;
} }
} }
if (subVal > 0) { if (subVal > 0) {
if (it == m_subscribedVec.end()) { if (it == m_subscribedVec.end()) {
m_subscribedVec.push_back({event->subscribe.conn_handle, subVal}); m_subscribedVec.push_back({connInfo.getConnHandle(), subVal});
} else { } else {
(*it).second = subVal; (*it).second = subVal;
} }
@ -381,150 +243,158 @@ void NimBLECharacteristic::setSubscribe(struct ble_gap_event *event) {
m_subscribedVec.erase(it); m_subscribedVec.erase(it);
} }
m_pCallbacks->onSubscribe(this, peerInfo, subVal); m_pCallbacks->onSubscribe(this, connInfo, subVal);
} }
/** /**
* @brief Send an indication. * @brief Send an indication.
* @param[in] conn_handle Connection handle to send an individual indication, or BLE_HS_CONN_HANDLE_NONE to send
* the indication to all subscribed clients.
*/ */
void NimBLECharacteristic::indicate() { void NimBLECharacteristic::indicate(uint16_t conn_handle) const {
notify(false); sendValue(m_value.data(), m_value.size(), false, conn_handle);
} // indicate } // indicate
/** /**
* @brief Send an indication. * @brief Send an indication.
* @param[in] value A pointer to the data to send. * @param[in] value A pointer to the data to send.
* @param[in] length The length of the data to send. * @param[in] length The length of the data to send.
* @param[in] conn_handle Connection handle to send an individual indication, or BLE_HS_CONN_HANDLE_NONE to send
* the indication to all subscribed clients.
*/ */
void NimBLECharacteristic::indicate(const uint8_t* value, size_t length) { void NimBLECharacteristic::indicate(const uint8_t* value, size_t length, uint16_t conn_handle) const {
notify(value, length, false); sendValue(value, length, false, conn_handle);
} // indicate } // indicate
/** /**
* @brief Send an indication. * @brief Send an indication.
* @param[in] value A std::vector<uint8_t> containing the value to send as the notification value. * @param[in] value A std::vector<uint8_t> containing the value to send as the notification value.
* @param[in] conn_handle Connection handle to send an individual indication, or BLE_HS_CONN_HANDLE_NONE to send
* the indication to all subscribed clients.
*/ */
void NimBLECharacteristic::indicate(const std::vector<uint8_t>& value) { void NimBLECharacteristic::indicate(const std::vector<uint8_t>& value, uint16_t conn_handle) const {
notify(value.data(), value.size(), false); sendValue(value.data(), value.size(), false, conn_handle);
} // indicate } // indicate
/** /**
* @brief Send a notification or indication. * @brief Send a notification.
* @param[in] is_notification if true sends a notification, false sends an indication. * @param[in] conn_handle Connection handle to send an individual notification, or BLE_HS_CONN_HANDLE_NONE to send
* @param[in] conn_handle Connection handle to send individual notification, or BLE_HCI_LE_CONN_HANDLE_MAX + 1 to send notification to all subscribed clients. * the notification to all subscribed clients.
*/ */
void NimBLECharacteristic::notify(bool is_notification, uint16_t conn_handle) { void NimBLECharacteristic::notify(uint16_t conn_handle) const {
notify(m_value.data(), m_value.length(), is_notification, conn_handle); sendValue(m_value.data(), m_value.size(), true, conn_handle);
} // notify } // notify
/**
* @brief Send a notification.
* @param[in] value A pointer to the data to send.
* @param[in] length The length of the data to send.
* @param[in] conn_handle Connection handle to send an individual notification, or BLE_HS_CONN_HANDLE_NONE to send
* the notification to all subscribed clients.
*/
void NimBLECharacteristic::notify(const uint8_t* value, size_t length, uint16_t conn_handle) const {
sendValue(value, length, true, conn_handle);
} // indicate
/** /**
* @brief Send a notification or indication. * @brief Send a notification.
* @param[in] value A std::vector<uint8_t> containing the value to send as the notification value. * @param[in] value A std::vector<uint8_t> containing the value to send as the notification value.
* @param[in] is_notification if true sends a notification, false sends an indication. * @param[in] conn_handle Connection handle to send an individual notification, or BLE_HS_CONN_HANDLE_NONE to send
* @param[in] conn_handle Connection handle to send individual notification, or BLE_HCI_LE_CONN_HANDLE_MAX + 1 to send notification to all subscribed clients. * the notification to all subscribed clients.
*/ */
void NimBLECharacteristic::notify(const std::vector<uint8_t>& value, bool is_notification, uint16_t conn_handle) { void NimBLECharacteristic::notify(const std::vector<uint8_t>& value, uint16_t conn_handle) const {
notify(value.data(), value.size(), is_notification, conn_handle); sendValue(value.data(), value.size(), true, conn_handle);
} // notify } // notify
/** /**
* @brief Send a notification or indication. * @brief Sends a notification or indication.
* @param[in] value A pointer to the data to send. * @param[in] value A pointer to the data to send.
* @param[in] length The length of the data to send. * @param[in] length The length of the data to send.
* @param[in] is_notification if true sends a notification, false sends an indication. * @param[in] is_notification if true sends a notification, false sends an indication.
* @param[in] conn_handle Connection handle to send individual notification, or BLE_HCI_LE_CONN_HANDLE_MAX + 1 to send notification to all subscribed clients. * @param[in] conn_handle Connection handle to send to a specific peer, or BLE_HS_CONN_HANDLE_NONE to send
* to all subscribed clients.
*/ */
void NimBLECharacteristic::notify(const uint8_t* value, size_t length, bool is_notification, uint16_t conn_handle) { void NimBLECharacteristic::sendValue(const uint8_t* value, size_t length, bool is_notification, uint16_t conn_handle) const {
NIMBLE_LOGD(LOG_TAG, ">> notify: length: %d", length); NIMBLE_LOGD(LOG_TAG, ">> sendValue");
if(!(m_properties & NIMBLE_PROPERTY::NOTIFY) && if (is_notification && !(getProperties() & NIMBLE_PROPERTY::NOTIFY)) {
!(m_properties & NIMBLE_PROPERTY::INDICATE)) NIMBLE_LOGE(LOG_TAG, "<< sendValue: notification not enabled for characteristic");
{
NIMBLE_LOGE(LOG_TAG,
"<< notify-Error; Notify/indicate not enabled for characteristic: %s",
std::string(getUUID()).c_str());
}
if (m_subscribedVec.size() == 0) {
NIMBLE_LOGD(LOG_TAG, "<< notify: No clients subscribed.");
return; return;
} }
m_pCallbacks->onNotify(this); if (!is_notification && !(getProperties() & NIMBLE_PROPERTY::INDICATE)) {
NIMBLE_LOGE(LOG_TAG, "<< sendValue: indication not enabled for characteristic");
return;
}
bool reqSec = (m_properties & BLE_GATT_CHR_F_READ_AUTHEN) || if (!m_subscribedVec.size()) {
(m_properties & BLE_GATT_CHR_F_READ_AUTHOR) || NIMBLE_LOGD(LOG_TAG, "<< sendValue: No clients subscribed.");
(m_properties & BLE_GATT_CHR_F_READ_ENC); return;
int rc = 0; }
for (auto &it : m_subscribedVec) { for (const auto& it : m_subscribedVec) {
// check if need a specific client // check if connected and subscribed
if (!it.second) {
continue;
}
// sending to a specific client?
if ((conn_handle <= BLE_HCI_LE_CONN_HANDLE_MAX) && (it.first != conn_handle)) { if ((conn_handle <= BLE_HCI_LE_CONN_HANDLE_MAX) && (it.first != conn_handle)) {
continue; continue;
} }
uint16_t _mtu = getService()->getServer()->getPeerMTU(it.first) - 3; if (is_notification && !(it.second & NIMBLE_SUB_NOTIFY)) {
continue;
}
// check if connected and subscribed if (!is_notification && !(it.second & NIMBLE_SUB_INDICATE)) {
if(_mtu == 0 || it.second == 0) {
continue; continue;
} }
// check if security requirements are satisfied // check if security requirements are satisfied
if(reqSec) { if ((getProperties() & BLE_GATT_CHR_F_READ_AUTHEN) || (getProperties() & BLE_GATT_CHR_F_READ_AUTHOR) ||
struct ble_gap_conn_desc desc; (getProperties() & BLE_GATT_CHR_F_READ_ENC)) {
rc = ble_gap_conn_find(it.first, &desc); ble_gap_conn_desc desc;
if(rc != 0 || !desc.sec_state.encrypted) { if (ble_gap_conn_find(it.first, &desc) != 0 || !desc.sec_state.encrypted) {
continue; continue;
} }
} }
if (length > _mtu) {
NIMBLE_LOGW(LOG_TAG, "- Truncating to %d bytes (maximum notify size)", _mtu);
}
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.second & NIMBLE_SUB_INDICATE))) {
NIMBLE_LOGW(LOG_TAG,
"Sending indication to client subscribed to notification, sending notification instead");
is_notification = true;
}
// don't create the m_buf until we are sure to send the data or else // don't create the m_buf until we are sure to send the data or else
// we could be allocating a buffer that doesn't get released. // we could be allocating a buffer that doesn't get released.
// We also must create it in each loop iteration because it is consumed with each host call. // We also must create it in each loop iteration because it is consumed with each host call.
os_mbuf* om = ble_hs_mbuf_from_flat(value, length); os_mbuf* om = ble_hs_mbuf_from_flat(value, length);
if (!om) {
NIMBLE_LOGE(LOG_TAG, "<< sendValue: failed to allocate mbuf");
return;
}
if(!is_notification && (m_properties & NIMBLE_PROPERTY::INDICATE)) { if (is_notification) {
ble_gattc_notify_custom(it.first, getHandle(), om);
} else {
if (!NimBLEDevice::getServer()->setIndicateWait(it.first)) { if (!NimBLEDevice::getServer()->setIndicateWait(it.first)) {
NIMBLE_LOGE(LOG_TAG, "prior Indication in progress"); NIMBLE_LOGE(LOG_TAG, "<< sendValue: waiting for previous indicate");
os_mbuf_free_chain(om); os_mbuf_free_chain(om);
return; return;
} }
rc = ble_gattc_indicate_custom(it.first, m_handle, om); if (ble_gattc_indicate_custom(it.first, getHandle(), om) != 0) {
if(rc != 0){
NimBLEDevice::getServer()->clearIndicateWait(it.first); NimBLEDevice::getServer()->clearIndicateWait(it.first);
} }
} else {
ble_gattc_notify_custom(it.first, m_handle, om);
} }
} }
NIMBLE_LOGD(LOG_TAG, "<< notify"); NIMBLE_LOGD(LOG_TAG, "<< sendValue");
} // Notify } // sendValue
void NimBLECharacteristic::readEvent(NimBLEConnInfo& connInfo) {
m_pCallbacks->onRead(this, connInfo);
}
void NimBLECharacteristic::writeEvent(const uint8_t* val, uint16_t len, NimBLEConnInfo& connInfo) {
setValue(val, len);
m_pCallbacks->onWrite(this, connInfo);
}
/** /**
* @brief Set the callback handlers for this characteristic. * @brief Set the callback handlers for this characteristic.
@ -539,50 +409,21 @@ void NimBLECharacteristic::setCallbacks(NimBLECharacteristicCallbacks* pCallback
} }
} // setCallbacks } // setCallbacks
/** /**
* @brief Get the callback handlers for this characteristic. * @brief Get the callback handlers for this characteristic.
*/ */
NimBLECharacteristicCallbacks* NimBLECharacteristic::getCallbacks() { NimBLECharacteristicCallbacks* NimBLECharacteristic::getCallbacks() const {
return m_pCallbacks; return m_pCallbacks;
} // getCallbacks } // getCallbacks
/**
* @brief Set the value of the characteristic from a data buffer .
* @param [in] data The data buffer to set for the characteristic.
* @param [in] length The number of bytes in the data buffer.
*/
void NimBLECharacteristic::setValue(const uint8_t* data, size_t length) {
#if CONFIG_NIMBLE_CPP_LOG_LEVEL >= 4
char* pHex = NimBLEUtils::buildHexData(nullptr, data, length);
NIMBLE_LOGD(LOG_TAG, ">> setValue: length=%d, data=%s, characteristic UUID=%s",
length, pHex, getUUID().toString().c_str());
free(pHex);
#endif
m_value.setValue(data, length);
NIMBLE_LOGD(LOG_TAG, "<< setValue");
} // setValue
/**
* @brief Set the value of the characteristic from a `std::vector<uint8_t>`.\n
* @param [in] vec The std::vector<uint8_t> reference to set the characteristic value from.
*/
void NimBLECharacteristic::setValue(const std::vector<uint8_t>& vec) {
return setValue((uint8_t*)&vec[0], vec.size());
}// setValue
/** /**
* @brief Return a string representation of the characteristic. * @brief Return a string representation of the characteristic.
* @return A string representation of the characteristic. * @return A string representation of the characteristic.
*/ */
std::string NimBLECharacteristic::toString() { std::string NimBLECharacteristic::toString() const {
std::string res = "UUID: " + m_uuid.toString() + ", handle : 0x"; std::string res = "UUID: " + m_uuid.toString() + ", handle : 0x";
char hex[5]; char hex[5];
snprintf(hex, sizeof(hex), "%04x", m_handle); snprintf(hex, sizeof(hex), "%04x", getHandle());
res += hex; res += hex;
res += " "; res += " ";
if (m_properties & BLE_GATT_CHR_PROP_READ) res += "Read "; if (m_properties & BLE_GATT_CHR_PROP_READ) res += "Read ";
@ -594,7 +435,6 @@ std::string NimBLECharacteristic::toString() {
return res; return res;
} // toString } // toString
/** /**
* @brief Callback function to support a read request. * @brief Callback function to support a read request.
* @param [in] pCharacteristic The characteristic that is the source of the event. * @param [in] pCharacteristic The characteristic that is the source of the event.
@ -604,7 +444,6 @@ void NimBLECharacteristicCallbacks::onRead(NimBLECharacteristic* pCharacteristic
NIMBLE_LOGD("NimBLECharacteristicCallbacks", "onRead: default"); NIMBLE_LOGD("NimBLECharacteristicCallbacks", "onRead: default");
} // onRead } // onRead
/** /**
* @brief Callback function to support a write request. * @brief Callback function to support a write request.
* @param [in] pCharacteristic The characteristic that is the source of the event. * @param [in] pCharacteristic The characteristic that is the source of the event.
@ -614,16 +453,6 @@ void NimBLECharacteristicCallbacks::onWrite(NimBLECharacteristic* pCharacteristi
NIMBLE_LOGD("NimBLECharacteristicCallbacks", "onWrite: default"); NIMBLE_LOGD("NimBLECharacteristicCallbacks", "onWrite: default");
} // onWrite } // onWrite
/**
* @brief Callback function to support a Notify request.
* @param [in] pCharacteristic The characteristic that is the source of the event.
*/
void NimBLECharacteristicCallbacks::onNotify(NimBLECharacteristic* pCharacteristic) {
NIMBLE_LOGD("NimBLECharacteristicCallbacks", "onNotify: default");
} // onNotify
/** /**
* @brief Callback function to support a Notify/Indicate Status report. * @brief Callback function to support a Notify/Indicate Status report.
* @param [in] pCharacteristic The characteristic that is the source of the event. * @param [in] pCharacteristic The characteristic that is the source of the event.
@ -635,7 +464,6 @@ void NimBLECharacteristicCallbacks::onStatus(NimBLECharacteristic* pCharacterist
NIMBLE_LOGD("NimBLECharacteristicCallbacks", "onStatus: default"); NIMBLE_LOGD("NimBLECharacteristicCallbacks", "onStatus: default");
} // onStatus } // onStatus
/** /**
* @brief Callback function called when a client changes subscription status. * @brief Callback function called when a client changes subscription status.
* @param [in] pCharacteristic The characteristic that is the source of the event. * @param [in] pCharacteristic The characteristic that is the source of the event.
@ -648,8 +476,7 @@ void NimBLECharacteristicCallbacks::onStatus(NimBLECharacteristic* pCharacterist
*/ */
void NimBLECharacteristicCallbacks::onSubscribe(NimBLECharacteristic* pCharacteristic, void NimBLECharacteristicCallbacks::onSubscribe(NimBLECharacteristic* pCharacteristic,
NimBLEConnInfo& connInfo, NimBLEConnInfo& connInfo,
uint16_t subValue) uint16_t subValue) {
{
NIMBLE_LOGD("NimBLECharacteristicCallbacks", "onSubscribe: default"); NIMBLE_LOGD("NimBLECharacteristicCallbacks", "onSubscribe: default");
} }

View file

@ -11,37 +11,16 @@
* Author: kolban * Author: kolban
*/ */
#ifndef MAIN_NIMBLECHARACTERISTIC_H_ #ifndef NIMBLE_CPP_CHARACTERISTIC_H_
#define MAIN_NIMBLECHARACTERISTIC_H_ #define NIMBLE_CPP_CHARACTERISTIC_H_
#include "nimconfig.h" #include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#if defined(CONFIG_NIMBLE_CPP_IDF) class NimBLECharacteristicCallbacks;
#include "host/ble_hs.h" class NimBLECharacteristic;
#else
#include "nimble/nimble/host/include/host/ble_hs.h"
#endif
/**** FIX COMPILATION ****/
#undef min
#undef max
/**************************/
typedef enum {
READ = BLE_GATT_CHR_F_READ,
READ_ENC = BLE_GATT_CHR_F_READ_ENC,
READ_AUTHEN = BLE_GATT_CHR_F_READ_AUTHEN,
READ_AUTHOR = BLE_GATT_CHR_F_READ_AUTHOR,
WRITE = BLE_GATT_CHR_F_WRITE,
WRITE_NR = BLE_GATT_CHR_F_WRITE_NO_RSP,
WRITE_ENC = BLE_GATT_CHR_F_WRITE_ENC,
WRITE_AUTHEN = BLE_GATT_CHR_F_WRITE_AUTHEN,
WRITE_AUTHOR = BLE_GATT_CHR_F_WRITE_AUTHOR,
BROADCAST = BLE_GATT_CHR_F_BROADCAST,
NOTIFY = BLE_GATT_CHR_F_NOTIFY,
INDICATE = BLE_GATT_CHR_F_INDICATE
} NIMBLE_PROPERTY;
# include "NimBLELocalValueAttribute.h"
# include "NimBLEServer.h"
# include "NimBLEService.h" # include "NimBLEService.h"
# include "NimBLEDescriptor.h" # include "NimBLEDescriptor.h"
# include "NimBLEAttValue.h" # include "NimBLEAttValue.h"
@ -50,92 +29,53 @@ typedef enum {
# include <string> # include <string>
# include <vector> # include <vector>
class NimBLEService;
class NimBLEDescriptor;
class NimBLECharacteristicCallbacks;
/** /**
* @brief The model of a %BLE Characteristic. * @brief The model of a BLE Characteristic.
* *
* A BLE Characteristic is an identified value container that manages a value. It is exposed by a BLE server and * A BLE Characteristic is an identified value container that manages a value. It is exposed by a BLE service and
* can be read and written to by a %BLE client. * can be read and written to by a BLE client.
*/ */
class NimBLECharacteristic { class NimBLECharacteristic : public NimBLELocalValueAttribute {
public: public:
NimBLECharacteristic(const char* uuid, NimBLECharacteristic(const char* uuid,
uint16_t properties = uint16_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE,
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN,
NimBLEService* pService = nullptr); NimBLEService* pService = nullptr);
NimBLECharacteristic(const NimBLEUUID& uuid, NimBLECharacteristic(const NimBLEUUID& uuid,
uint16_t properties = uint16_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE,
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN,
NimBLEService* pService = nullptr); NimBLEService* pService = nullptr);
~NimBLECharacteristic(); ~NimBLECharacteristic();
uint16_t getHandle(); std::string toString() const;
NimBLEUUID getUUID(); size_t getSubscribedCount() const;
std::string toString();
void indicate();
void indicate(const uint8_t* value, size_t length);
void indicate(const std::vector<uint8_t>& value);
void notify(bool is_notification = true, uint16_t conn_handle = BLE_HCI_LE_CONN_HANDLE_MAX + 1);
void notify(const uint8_t* value, size_t length, bool is_notification = true, uint16_t conn_handle = BLE_HCI_LE_CONN_HANDLE_MAX + 1);
void notify(const std::vector<uint8_t>& value, bool is_notification = true, uint16_t conn_handle = BLE_HCI_LE_CONN_HANDLE_MAX + 1);
size_t getSubscribedCount();
void addDescriptor(NimBLEDescriptor* pDescriptor); void addDescriptor(NimBLEDescriptor* pDescriptor);
NimBLEDescriptor* getDescriptorByUUID(const char* uuid);
NimBLEDescriptor* getDescriptorByUUID(const NimBLEUUID &uuid);
NimBLEDescriptor* getDescriptorByHandle(uint16_t handle);
void removeDescriptor(NimBLEDescriptor* pDescriptor, bool deleteDsc = false); void removeDescriptor(NimBLEDescriptor* pDescriptor, bool deleteDsc = false);
NimBLEService* getService(); uint16_t getProperties() const;
uint16_t getProperties();
NimBLEAttValue getValue(time_t *timestamp = nullptr);
size_t getDataLength();
void setValue(const uint8_t* data, size_t size);
void setValue(const std::vector<uint8_t>& vec);
void setCallbacks(NimBLECharacteristicCallbacks* pCallbacks); void setCallbacks(NimBLECharacteristicCallbacks* pCallbacks);
void indicate(uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const;
void indicate(const uint8_t* value, size_t length, uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const;
void indicate(const std::vector<uint8_t>& value, uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const;
void notify(uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const;
void notify(const uint8_t* value, size_t length, uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const;
void notify(const std::vector<uint8_t>& value, uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const;
NimBLEDescriptor* createDescriptor(const char* uuid, NimBLEDescriptor* createDescriptor(const char* uuid,
uint32_t properties = uint32_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE,
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN);;
NimBLEDescriptor* createDescriptor(const NimBLEUUID &uuid,
uint32_t properties =
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN); uint16_t max_len = BLE_ATT_ATTR_MAX_LEN);
NimBLEDescriptor* createDescriptor(const NimBLEUUID& uuid,
uint32_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN);
NimBLEDescriptor* getDescriptorByUUID(const char* uuid) const;
NimBLEDescriptor* getDescriptorByUUID(const NimBLEUUID& uuid) const;
NimBLEDescriptor* getDescriptorByHandle(uint16_t handle) const;
NimBLEService* getService() const;
NimBLECharacteristicCallbacks* getCallbacks(); NimBLECharacteristicCallbacks* getCallbacks() const;
/*********************** Template Functions ************************/ /*********************** Template Functions ************************/
/**
* @brief Template to set the characteristic value to <type\>val.
* @param [in] s The value to set.
*/
template<typename T>
void setValue(const T &s) { m_value.setValue<T>(s); }
/**
* @brief Template to convert the characteristic data to <type\>.
* @tparam T The type to convert the data to.
* @param [in] timestamp (Optional) A pointer to a time_t struct to store the time the value was read.
* @param [in] skipSizeCheck (Optional) If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is less than <tt>sizeof(<type\>)</tt>.
* @details <b>Use:</b> <tt>getValue<type>(&timestamp, skipSizeCheck);</tt>
*/
template<typename T>
T getValue(time_t *timestamp = nullptr, bool skipSizeCheck = false) {
return m_value.getValue<T>(timestamp, skipSizeCheck);
}
/** /**
* @brief Template to send a notification from a class type that has a c_str() and length() method. * @brief Template to send a notification from a class type that has a c_str() and length() method.
* @tparam T The a reference to a class containing the data to send. * @tparam T The a reference to a class containing the data to send.
@ -149,8 +89,8 @@ public:
# else # else
typename std::enable_if<Has_c_str_len<T>::value, void>::type typename std::enable_if<Has_c_str_len<T>::value, void>::type
# endif # endif
notify(const T& value, bool is_notification = true) { notify(const T& value, bool is_notification = true) const {
notify((uint8_t*)value.c_str(), value.length(), is_notification); notify(reinterpret_cast<const uint8_t*>(value.c_str()), value.length(), is_notification);
} }
/** /**
@ -165,33 +105,29 @@ public:
# else # else
typename std::enable_if<Has_c_str_len<T>::value, void>::type typename std::enable_if<Has_c_str_len<T>::value, void>::type
# endif # endif
indicate(const T& value) { indicate(const T& value) const {
indicate((uint8_t*)value.c_str(), value.length()); indicate(reinterpret_cast<const uint8_t*>(value.c_str()), value.length());
} }
private: private:
friend class NimBLEServer; friend class NimBLEServer;
friend class NimBLEService; friend class NimBLEService;
void setService(NimBLEService* pService); void setService(NimBLEService* pService);
void setSubscribe(struct ble_gap_event *event); void setSubscribe(const ble_gap_event* event, NimBLEConnInfo& connInfo);
static int handleGapEvent(uint16_t conn_handle, uint16_t attr_handle, void readEvent(NimBLEConnInfo& connInfo) override;
struct ble_gatt_access_ctxt *ctxt, void *arg); void writeEvent(const uint8_t* val, uint16_t len, NimBLEConnInfo& connInfo) override;
void sendValue(const uint8_t* value,
size_t length,
bool is_notification = true,
uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const;
NimBLEUUID m_uuid; NimBLECharacteristicCallbacks* m_pCallbacks{nullptr};
uint16_t m_handle; NimBLEService* m_pService{nullptr};
uint16_t m_properties; std::vector<NimBLEDescriptor*> m_vDescriptors{};
NimBLECharacteristicCallbacks* m_pCallbacks; std::vector<std::pair<uint16_t, uint16_t>> m_subscribedVec{};
NimBLEService* m_pService;
NimBLEAttValue m_value;
std::vector<NimBLEDescriptor*> m_dscVec;
uint8_t m_removed;
std::vector<std::pair<uint16_t, uint16_t>> m_subscribedVec;
}; // NimBLECharacteristic }; // NimBLECharacteristic
/** /**
* @brief Callbacks that can be associated with a %BLE characteristic to inform of events. * @brief Callbacks that can be associated with a %BLE characteristic to inform of events.
* *
@ -204,10 +140,9 @@ public:
virtual ~NimBLECharacteristicCallbacks() {} virtual ~NimBLECharacteristicCallbacks() {}
virtual void onRead(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo); virtual void onRead(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo);
virtual void onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo); virtual void onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo);
virtual void onNotify(NimBLECharacteristic* pCharacteristic);
virtual void onStatus(NimBLECharacteristic* pCharacteristic, int code); virtual void onStatus(NimBLECharacteristic* pCharacteristic, int code);
virtual void onSubscribe(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue); virtual void onSubscribe(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue);
}; };
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */ #endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */
#endif /*MAIN_NIMBLECHARACTERISTIC_H_*/ #endif /*NIMBLE_CPP_CHARACTERISTIC_H_*/

View file

@ -15,6 +15,8 @@
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
#include "NimBLEClient.h" #include "NimBLEClient.h"
#include "NimBLERemoteService.h"
#include "NimBLERemoteCharacteristic.h"
#include "NimBLEDevice.h" #include "NimBLEDevice.h"
#include "NimBLELog.h" #include "NimBLELog.h"
@ -333,10 +335,10 @@ bool NimBLEClient::connect(const NimBLEAddress &address, bool deleteAttributes)
* Called automatically when a characteristic or descriptor requires encryption or authentication to access it. * Called automatically when a characteristic or descriptor requires encryption or authentication to access it.
* @return True on success. * @return True on success.
*/ */
bool NimBLEClient::secureConnection() { bool NimBLEClient::secureConnection() const {
NIMBLE_LOGD(LOG_TAG, ">> secureConnection()"); NIMBLE_LOGD(LOG_TAG, ">> secureConnection()");
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
ble_task_data_t taskData = {this, cur_task, 0, nullptr}; ble_task_data_t taskData = {const_cast<NimBLEClient*>(this), cur_task, 0, nullptr};
int retryCount = 1; int retryCount = 1;
@ -542,7 +544,7 @@ void NimBLEClient::setConnectTimeout(uint32_t time) {
* @brief Get the connection id for this client. * @brief Get the connection id for this client.
* @return The connection id. * @return The connection id.
*/ */
uint16_t NimBLEClient::getConnId() { uint16_t NimBLEClient::getConnId() const {
return m_conn_id; return m_conn_id;
} // getConnId } // getConnId
@ -610,7 +612,7 @@ bool NimBLEClient::setConnection(uint16_t conn_id) {
/** /**
* @brief Retrieve the address of the peer. * @brief Retrieve the address of the peer.
*/ */
NimBLEAddress NimBLEClient::getPeerAddress() { NimBLEAddress NimBLEClient::getPeerAddress() const {
return m_peerAddress; return m_peerAddress;
} // getPeerAddress } // getPeerAddress
@ -776,7 +778,7 @@ bool NimBLEClient::discoverAttributes() {
return false; return false;
} }
for(auto chr: svc->m_characteristicVector) { for(auto chr: svc->m_vChars) {
if (!chr->retrieveDescriptors()) { if (!chr->retrieveDescriptors()) {
return false; return false;
} }
@ -792,7 +794,7 @@ bool NimBLEClient::discoverAttributes() {
* Here we ask the server for its set of services and wait until we have received them all. * Here we ask the server for its set of services and wait until we have received them all.
* @return true on success otherwise false if an error occurred * @return true on success otherwise false if an error occurred
*/ */
bool NimBLEClient::retrieveServices(const NimBLEUUID *uuid_filter) { bool NimBLEClient::retrieveServices(const NimBLEUUID *uuidFilter) {
/** /**
* Design * Design
* ------ * ------
@ -811,10 +813,10 @@ bool NimBLEClient::retrieveServices(const NimBLEUUID *uuid_filter) {
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
ble_task_data_t taskData = {this, cur_task, 0, nullptr}; ble_task_data_t taskData = {this, cur_task, 0, nullptr};
if(uuid_filter == nullptr) { if(uuidFilter == nullptr) {
rc = ble_gattc_disc_all_svcs(m_conn_id, NimBLEClient::serviceDiscoveredCB, &taskData); rc = ble_gattc_disc_all_svcs(m_conn_id, NimBLEClient::serviceDiscoveredCB, &taskData);
} else { } else {
rc = ble_gattc_disc_svc_by_uuid(m_conn_id, uuid_filter->getBase(), rc = ble_gattc_disc_svc_by_uuid(m_conn_id, uuidFilter->getBase(),
NimBLEClient::serviceDiscoveredCB, &taskData); NimBLEClient::serviceDiscoveredCB, &taskData);
} }
@ -972,7 +974,7 @@ NimBLERemoteCharacteristic* NimBLEClient::getCharacteristic(const uint16_t handl
* @brief Get the current mtu of this connection. * @brief Get the current mtu of this connection.
* @returns The MTU value. * @returns The MTU value.
*/ */
uint16_t NimBLEClient::getMTU() { uint16_t NimBLEClient::getMTU() const {
return ble_att_mtu(m_conn_id); return ble_att_mtu(m_conn_id);
} // getMTU } // getMTU
@ -1086,7 +1088,7 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event *event, void *arg) {
continue; continue;
} }
auto cVector = &it->m_characteristicVector; auto cVector = &it->m_vChars;
NIMBLE_LOGD(LOG_TAG, "checking service %s for handle: %d", NIMBLE_LOGD(LOG_TAG, "checking service %s for handle: %d",
it->getUUID().toString().c_str(), it->getUUID().toString().c_str(),
event->notify_rx.attr_handle); event->notify_rx.attr_handle);
@ -1325,20 +1327,20 @@ bool NimBLEClientCallbacks::onConnParamsUpdateRequest(NimBLEClient* pClient, con
return true; return true;
} }
void NimBLEClientCallbacks::onPassKeyEntry(const NimBLEConnInfo& connInfo){ void NimBLEClientCallbacks::onPassKeyEntry(NimBLEConnInfo& connInfo){
NIMBLE_LOGD("NimBLEClientCallbacks", "onPassKeyEntry: default: 123456"); NIMBLE_LOGD("NimBLEClientCallbacks", "onPassKeyEntry: default: 123456");
NimBLEDevice::injectPassKey(connInfo, 123456); NimBLEDevice::injectPassKey(connInfo, 123456);
} //onPassKeyEntry } //onPassKeyEntry
void NimBLEClientCallbacks::onAuthenticationComplete(const NimBLEConnInfo& connInfo){ void NimBLEClientCallbacks::onAuthenticationComplete(NimBLEConnInfo& connInfo){
NIMBLE_LOGD("NimBLEClientCallbacks", "onAuthenticationComplete: default"); NIMBLE_LOGD("NimBLEClientCallbacks", "onAuthenticationComplete: default");
} }
void NimBLEClientCallbacks::onIdentity(const NimBLEConnInfo& connInfo){ void NimBLEClientCallbacks::onIdentity(NimBLEConnInfo& connInfo){
NIMBLE_LOGD("NimBLEClientCallbacks", "onIdentity: default"); NIMBLE_LOGD("NimBLEClientCallbacks", "onIdentity: default");
} // onIdentity } // onIdentity
void NimBLEClientCallbacks::onConfirmPIN(const NimBLEConnInfo& connInfo, uint32_t pin){ void NimBLEClientCallbacks::onConfirmPIN(NimBLEConnInfo& connInfo, uint32_t pin){
NIMBLE_LOGD("NimBLEClientCallbacks", "onConfirmPIN: default: true"); NIMBLE_LOGD("NimBLEClientCallbacks", "onConfirmPIN: default: true");
NimBLEDevice::injectConfirmPIN(connInfo, true); NimBLEDevice::injectConfirmPIN(connInfo, true);
} }

View file

@ -23,15 +23,14 @@
#include "NimBLEConnInfo.h" #include "NimBLEConnInfo.h"
#include "NimBLEAttValue.h" #include "NimBLEAttValue.h"
#include "NimBLEAdvertisedDevice.h" #include "NimBLEAdvertisedDevice.h"
#include "NimBLERemoteService.h"
#include <vector> #include <vector>
#include <string> #include <string>
class NimBLERemoteService; class NimBLERemoteService;
class NimBLERemoteCharacteristic; class NimBLERemoteCharacteristic;
class NimBLEClientCallbacks;
class NimBLEAdvertisedDevice; class NimBLEAdvertisedDevice;
class NimBLEClientCallbacks;
/** /**
* @brief A model of a %BLE client. * @brief A model of a %BLE client.
@ -42,7 +41,7 @@ public:
bool connect(const NimBLEAddress &address, bool deleteAttributes = true); bool connect(const NimBLEAddress &address, bool deleteAttributes = true);
bool connect(bool deleteAttributes = true); bool connect(bool deleteAttributes = true);
int disconnect(uint8_t reason = BLE_ERR_REM_USER_CONN_TERM); int disconnect(uint8_t reason = BLE_ERR_REM_USER_CONN_TERM);
NimBLEAddress getPeerAddress(); NimBLEAddress getPeerAddress() const;
void setPeerAddress(const NimBLEAddress &address); void setPeerAddress(const NimBLEAddress &address);
int getRssi(); int getRssi();
std::vector<NimBLERemoteService*>* getServices(bool refresh = false); std::vector<NimBLERemoteService*>* getServices(bool refresh = false);
@ -60,12 +59,12 @@ public:
void setClientCallbacks(NimBLEClientCallbacks *pClientCallbacks, void setClientCallbacks(NimBLEClientCallbacks *pClientCallbacks,
bool deleteCallbacks = true); bool deleteCallbacks = true);
std::string toString(); std::string toString();
uint16_t getConnId(); uint16_t getConnId() const;
void clearConnection(); void clearConnection();
bool setConnection(NimBLEConnInfo &conn_info); bool setConnection(NimBLEConnInfo &conn_info);
bool setConnection(uint16_t conn_id); bool setConnection(uint16_t conn_id);
uint16_t getMTU(); uint16_t getMTU() const;
bool secureConnection(); bool secureConnection() const;
void setConnectTimeout(uint32_t timeout); void setConnectTimeout(uint32_t timeout);
void setConnectionParams(uint16_t minInterval, uint16_t maxInterval, void setConnectionParams(uint16_t minInterval, uint16_t maxInterval,
uint16_t latency, uint16_t timeout, uint16_t latency, uint16_t timeout,
@ -93,16 +92,16 @@ private:
const struct ble_gatt_svc *service, const struct ble_gatt_svc *service,
void *arg); void *arg);
static void dcTimerCb(ble_npl_event *event); static void dcTimerCb(ble_npl_event *event);
bool retrieveServices(const NimBLEUUID *uuid_filter = nullptr); bool retrieveServices(const NimBLEUUID *uuidFilter = nullptr);
NimBLEAddress m_peerAddress; NimBLEAddress m_peerAddress;
int m_lastErr; mutable int m_lastErr;
uint16_t m_conn_id; uint16_t m_conn_id;
bool m_connEstablished; bool m_connEstablished;
bool m_deleteCallbacks; bool m_deleteCallbacks;
int32_t m_connectTimeout; int32_t m_connectTimeout;
NimBLEClientCallbacks* m_pClientCallbacks; NimBLEClientCallbacks* m_pClientCallbacks;
ble_task_data_t* m_pTaskData; mutable ble_task_data_t* m_pTaskData;
ble_npl_callout m_dcTimer; ble_npl_callout m_dcTimer;
#if CONFIG_BT_NIMBLE_EXT_ADV #if CONFIG_BT_NIMBLE_EXT_ADV
uint8_t m_phyMask; uint8_t m_phyMask;
@ -149,27 +148,27 @@ public:
* @brief Called when server requests a passkey for pairing. * @brief Called when server requests a passkey for pairing.
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info. * @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.
*/ */
virtual void onPassKeyEntry(const NimBLEConnInfo& connInfo); virtual void onPassKeyEntry(NimBLEConnInfo& connInfo);
/** /**
* @brief Called when the pairing procedure is complete. * @brief Called when the pairing procedure is complete.
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.\n * @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.\n
* This can be used to check the status of the connection encryption/pairing. * This can be used to check the status of the connection encryption/pairing.
*/ */
virtual void onAuthenticationComplete(const NimBLEConnInfo& connInfo); virtual void onAuthenticationComplete(NimBLEConnInfo& connInfo);
/** /**
* @brief Called when using numeric comparision for pairing. * @brief Called when using numeric comparision for pairing.
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info. * @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.
* @param [in] pin The pin to compare with the server. * @param [in] pin The pin to compare with the server.
*/ */
virtual void onConfirmPIN(const NimBLEConnInfo& connInfo, uint32_t pin); virtual void onConfirmPIN(NimBLEConnInfo& connInfo, uint32_t pin);
/** /**
* @brief Called when the peer identity address is resolved. * @brief Called when the peer identity address is resolved.
* @param [in] connInfo A reference to a NimBLEConnInfo instance with information * @param [in] connInfo A reference to a NimBLEConnInfo instance with information
*/ */
virtual void onIdentity(const NimBLEConnInfo& connInfo); virtual void onIdentity(NimBLEConnInfo& connInfo);
}; };
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */ #endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */

View file

@ -1,20 +1,18 @@
#ifndef NIMBLECONNINFO_H_ #ifndef NIMBLECONNINFO_H_
#define NIMBLECONNINFO_H_ #define NIMBLECONNINFO_H_
#if defined(CONFIG_NIMBLE_CPP_IDF)
# include "host/ble_gap.h"
#else
# include "nimble/nimble/host/include/host/ble_gap.h"
#endif
#include "NimBLEAddress.h" #include "NimBLEAddress.h"
/** /**
* @brief Connection information. * @brief Connection information.
*/ */
class NimBLEConnInfo { class NimBLEConnInfo {
friend class NimBLEServer;
friend class NimBLEClient;
friend class NimBLECharacteristic;
friend class NimBLEDescriptor;
ble_gap_conn_desc m_desc{};
NimBLEConnInfo(){};
NimBLEConnInfo(ble_gap_conn_desc desc) { m_desc = desc; }
public: public:
/** @brief Gets the over-the-air address of the connected peer */ /** @brief Gets the over-the-air address of the connected peer */
NimBLEAddress getAddress() const { return NimBLEAddress(m_desc.peer_ota_addr); } NimBLEAddress getAddress() const { return NimBLEAddress(m_desc.peer_ota_addr); }
@ -54,5 +52,15 @@ public:
/** @brief Gets the key size used to encrypt the connection */ /** @brief Gets the key size used to encrypt the connection */
uint8_t getSecKeySize() const { return m_desc.sec_state.key_size; } uint8_t getSecKeySize() const { return m_desc.sec_state.key_size; }
private:
friend class NimBLEServer;
friend class NimBLEClient;
friend class NimBLECharacteristic;
friend class NimBLEDescriptor;
ble_gap_conn_desc m_desc{};
NimBLEConnInfo(){};
NimBLEConnInfo(ble_gap_conn_desc desc) { m_desc = desc; }
}; };
#endif #endif

View file

@ -21,11 +21,18 @@
# include <string> # include <string>
#define NULL_HANDLE (0xffff)
static const char* LOG_TAG = "NimBLEDescriptor"; static const char* LOG_TAG = "NimBLEDescriptor";
static NimBLEDescriptorCallbacks defaultCallbacks; static NimBLEDescriptorCallbacks defaultCallbacks;
/**
* @brief Construct a descriptor
* @param [in] uuid - UUID (const char*) for the descriptor.
* @param [in] properties - Properties for the descriptor.
* @param [in] max_len - The maximum length in bytes that the descriptor value can hold. (Default: 512 bytes for esp32, 20 for all others).
* @param [in] pCharacteristic - pointer to the characteristic instance this descriptor belongs to.
*/
NimBLEDescriptor::NimBLEDescriptor(const char* uuid, uint16_t properties, uint16_t max_len, NimBLECharacteristic* pCharacteristic)
: NimBLEDescriptor(NimBLEUUID(uuid), properties, max_len, pCharacteristic) {}
/** /**
* @brief Construct a descriptor * @brief Construct a descriptor
@ -34,194 +41,52 @@ static NimBLEDescriptorCallbacks defaultCallbacks;
* @param [in] max_len - The maximum length in bytes that the descriptor value can hold. (Default: 512 bytes for esp32, 20 for all others). * @param [in] max_len - The maximum length in bytes that the descriptor value can hold. (Default: 512 bytes for esp32, 20 for all others).
* @param [in] pCharacteristic - pointer to the characteristic instance this descriptor belongs to. * @param [in] pCharacteristic - pointer to the characteristic instance this descriptor belongs to.
*/ */
NimBLEDescriptor::NimBLEDescriptor(const char* uuid, uint16_t properties, uint16_t max_len, NimBLEDescriptor::NimBLEDescriptor(const NimBLEUUID& uuid, uint16_t properties, uint16_t max_len, NimBLECharacteristic* pCharacteristic)
NimBLECharacteristic* pCharacteristic) : NimBLELocalValueAttribute{uuid, 0, max_len}, m_pCallbacks{&defaultCallbacks}, m_pCharacteristic{pCharacteristic} {
: NimBLEDescriptor(NimBLEUUID(uuid), properties, max_len, pCharacteristic) {
}
/**
* @brief Construct a descriptor
* @param [in] uuid - UUID (const char*) for the descriptor.
* @param [in] properties - Properties for the descriptor.
* @param [in] max_len - The maximum length in bytes that the descriptor value can hold. (Default: 512 bytes for esp32, 20 for all others).
* @param [in] pCharacteristic - pointer to the characteristic instance this descriptor belongs to.
*/
NimBLEDescriptor::NimBLEDescriptor(NimBLEUUID uuid, uint16_t properties, uint16_t max_len,
NimBLECharacteristic* pCharacteristic)
: m_value(std::min(CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH , (int)max_len), max_len) {
m_uuid = uuid;
m_handle = NULL_HANDLE; // Handle is initially unknown.
m_pCharacteristic = pCharacteristic;
m_pCallbacks = &defaultCallbacks; // No initial callback.
m_properties = 0;
// Check if this is the client configuration descriptor and set to removed if true. // Check if this is the client configuration descriptor and set to removed if true.
if (uuid == NimBLEUUID((uint16_t)0x2902)) { if (uuid == NimBLEUUID((uint16_t)0x2902)) {
NIMBLE_LOGW(LOG_TAG, "Manually created 2902 descriptor has no functionality; please remove."); NIMBLE_LOGW(LOG_TAG, "Manually created 2902 descriptor has no functionality; please remove.");
m_removed = 1; setRemoved(NIMBLE_ATT_REMOVE_HIDE);
} else {
m_removed = 0;
} }
if (properties & BLE_GATT_CHR_F_READ) { // convert uint16_t properties to uint8_t // convert uint16_t properties to uint8_t for descriptor properties
m_properties |= BLE_ATT_F_READ; uint8_t descProperties = 0;
if (properties & NIMBLE_PROPERTY::READ) {
descProperties |= BLE_ATT_F_READ;
} }
if (properties & (BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_WRITE)) { if (properties & (NIMBLE_PROPERTY::WRITE_NR | NIMBLE_PROPERTY::WRITE)) {
m_properties |= BLE_ATT_F_WRITE; descProperties |= BLE_ATT_F_WRITE;
} }
if (properties & BLE_GATT_CHR_F_READ_ENC) { if (properties & NIMBLE_PROPERTY::READ_ENC) {
m_properties |= BLE_ATT_F_READ_ENC; descProperties |= BLE_ATT_F_READ_ENC;
} }
if (properties & BLE_GATT_CHR_F_READ_AUTHEN) { if (properties & NIMBLE_PROPERTY::READ_AUTHEN) {
m_properties |= BLE_ATT_F_READ_AUTHEN; descProperties |= BLE_ATT_F_READ_AUTHEN;
} }
if (properties & BLE_GATT_CHR_F_READ_AUTHOR) { if (properties & NIMBLE_PROPERTY::READ_AUTHOR) {
m_properties |= BLE_ATT_F_READ_AUTHOR; descProperties |= BLE_ATT_F_READ_AUTHOR;
} }
if (properties & BLE_GATT_CHR_F_WRITE_ENC) { if (properties & NIMBLE_PROPERTY::WRITE_ENC) {
m_properties |= BLE_ATT_F_WRITE_ENC; descProperties |= BLE_ATT_F_WRITE_ENC;
} }
if (properties & BLE_GATT_CHR_F_WRITE_AUTHEN) { if (properties & NIMBLE_PROPERTY::WRITE_AUTHEN) {
m_properties |= BLE_ATT_F_WRITE_AUTHEN; descProperties |= BLE_ATT_F_WRITE_AUTHEN;
} }
if (properties & BLE_GATT_CHR_F_WRITE_AUTHOR) { if (properties & NIMBLE_PROPERTY::WRITE_AUTHOR) {
m_properties |= BLE_ATT_F_WRITE_AUTHOR; descProperties |= BLE_ATT_F_WRITE_AUTHOR;
} }
setProperties(descProperties);
} // NimBLEDescriptor } // NimBLEDescriptor
/**
* @brief NimBLEDescriptor destructor.
*/
NimBLEDescriptor::~NimBLEDescriptor() {
} // ~NimBLEDescriptor
/**
* @brief Get the BLE handle for this descriptor.
* @return The handle for this descriptor.
*/
uint16_t NimBLEDescriptor::getHandle() {
return m_handle;
} // getHandle
/**
* @brief Get the length of the value of this descriptor.
* @return The length (in bytes) of the value of this descriptor.
*/
size_t NimBLEDescriptor::getLength() {
return m_value.size();
} // getLength
/**
* @brief Get the UUID of the descriptor.
*/
NimBLEUUID NimBLEDescriptor::getUUID() {
return m_uuid;
} // getUUID
/**
* @brief Get the value of this descriptor.
* @return The NimBLEAttValue of this descriptor.
*/
NimBLEAttValue NimBLEDescriptor::getValue(time_t *timestamp) {
if (timestamp != nullptr) {
m_value.getValue(timestamp);
}
return m_value;
} // getValue
/**
* @brief Get the value of this descriptor as a string.
* @return A std::string instance containing a copy of the descriptor's value.
*/
std::string NimBLEDescriptor::getStringValue() {
return std::string(m_value);
}
/** /**
* @brief Get the characteristic this descriptor belongs to. * @brief Get the characteristic this descriptor belongs to.
* @return A pointer to the characteristic this descriptor belongs to. * @return A pointer to the characteristic this descriptor belongs to.
*/ */
NimBLECharacteristic* NimBLEDescriptor::getCharacteristic() { NimBLECharacteristic* NimBLEDescriptor::getCharacteristic() const {
return m_pCharacteristic; return m_pCharacteristic;
} // getCharacteristic } // getCharacteristic
int NimBLEDescriptor::handleGapEvent(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg) {
(void)conn_handle;
(void)attr_handle;
const ble_uuid_t *uuid;
int rc;
NimBLEConnInfo peerInfo{};
NimBLEDescriptor* pDescriptor = (NimBLEDescriptor*)arg;
NIMBLE_LOGD(LOG_TAG, "Descriptor %s %s event", pDescriptor->getUUID().toString().c_str(),
ctxt->op == BLE_GATT_ACCESS_OP_READ_DSC ? "Read" : "Write");
uuid = ctxt->chr->uuid;
if(ble_uuid_cmp(uuid, pDescriptor->getUUID().getBase()) == 0){
switch(ctxt->op) {
case BLE_GATT_ACCESS_OP_READ_DSC: {
ble_gap_conn_find(conn_handle, &peerInfo.m_desc);
// If the packet header is only 8 bytes this is a follow up of a long read
// so we don't want to call the onRead() callback again.
if(ctxt->om->om_pkthdr_len > 8 ||
conn_handle == BLE_HS_CONN_HANDLE_NONE ||
pDescriptor->m_value.size() <= (ble_att_mtu(peerInfo.getConnHandle()) - 3)) {
pDescriptor->m_pCallbacks->onRead(pDescriptor, peerInfo);
}
ble_npl_hw_enter_critical();
rc = os_mbuf_append(ctxt->om, pDescriptor->m_value.data(), pDescriptor->m_value.size());
ble_npl_hw_exit_critical(0);
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
case BLE_GATT_ACCESS_OP_WRITE_DSC: {
ble_gap_conn_find(conn_handle, &peerInfo.m_desc);
uint16_t att_max_len = pDescriptor->m_value.max_size();
if (ctxt->om->om_len > att_max_len) {
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
uint8_t buf[att_max_len];
size_t len = ctxt->om->om_len;
memcpy(buf, ctxt->om->om_data,len);
os_mbuf *next;
next = SLIST_NEXT(ctxt->om, om_next);
while(next != NULL){
if((len + next->om_len) > att_max_len) {
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
memcpy(&buf[len], next->om_data, next->om_len);
len += next->om_len;
next = SLIST_NEXT(next, om_next);
}
pDescriptor->setValue(buf, len);
pDescriptor->m_pCallbacks->onWrite(pDescriptor, peerInfo);
return 0;
}
default:
break;
}
}
return BLE_ATT_ERR_UNLIKELY;
}
/** /**
* @brief Set the callback handlers for this descriptor. * @brief Set the callback handlers for this descriptor.
* @param [in] pCallbacks An instance of a callback structure used to define any callbacks for the descriptor. * @param [in] pCallbacks An instance of a callback structure used to define any callbacks for the descriptor.
@ -234,39 +99,6 @@ void NimBLEDescriptor::setCallbacks(NimBLEDescriptorCallbacks* pCallbacks) {
} }
} // setCallbacks } // setCallbacks
/**
* @brief Set the handle of this descriptor.
* Set the handle of this descriptor to be the supplied value.
* @param [in] handle The handle to be associated with this descriptor.
* @return N/A.
*/
void NimBLEDescriptor::setHandle(uint16_t handle) {
NIMBLE_LOGD(LOG_TAG, ">> setHandle(0x%.2x): Setting descriptor handle to be 0x%.2x", handle, handle);
m_handle = handle;
NIMBLE_LOGD(LOG_TAG, "<< setHandle()");
} // setHandle
/**
* @brief Set the value of the descriptor.
* @param [in] data The data to set for the descriptor.
* @param [in] length The length of the data in bytes.
*/
void NimBLEDescriptor::setValue(const uint8_t* data, size_t length) {
m_value.setValue(data, length);
} // setValue
/**
* @brief Set the value of the descriptor from a `std::vector<uint8_t>`.\n
* @param [in] vec The std::vector<uint8_t> reference to set the descriptor value from.
*/
void NimBLEDescriptor::setValue(const std::vector<uint8_t>& vec) {
return setValue((uint8_t*)&vec[0], vec.size());
} // setValue
/** /**
* @brief Set the characteristic this descriptor belongs to. * @brief Set the characteristic this descriptor belongs to.
* @param [in] pChar A pointer to the characteristic this descriptor belongs to. * @param [in] pChar A pointer to the characteristic this descriptor belongs to.
@ -275,18 +107,25 @@ void NimBLEDescriptor::setCharacteristic(NimBLECharacteristic* pChar) {
m_pCharacteristic = pChar; m_pCharacteristic = pChar;
} // setCharacteristic } // setCharacteristic
/** /**
* @brief Return a string representation of the descriptor. * @brief Return a string representation of the descriptor.
* @return A string representation of the descriptor. * @return A string representation of the descriptor.
*/ */
std::string NimBLEDescriptor::toString() { std::string NimBLEDescriptor::toString() const {
char hex[5]; char hex[5];
snprintf(hex, sizeof(hex), "%04x", m_handle); snprintf(hex, sizeof(hex), "%04x", getHandle());
std::string res = "UUID: " + m_uuid.toString() + ", handle: 0x" + hex; std::string res = "UUID: " + m_uuid.toString() + ", handle: 0x" + hex;
return res; return res;
} // toString } // toString
void NimBLEDescriptor::readEvent(NimBLEConnInfo& connInfo) {
m_pCallbacks->onRead(this, connInfo);
} // readEvent
void NimBLEDescriptor::writeEvent(const uint8_t* val, uint16_t len, NimBLEConnInfo& connInfo) {
setValue(val, len);
m_pCallbacks->onWrite(this, connInfo);
} // writeEvent
/** /**
* @brief Callback function to support a read request. * @brief Callback function to support a read request.
@ -294,18 +133,15 @@ std::string NimBLEDescriptor::toString() {
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info. * @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.
*/ */
void NimBLEDescriptorCallbacks::onRead(NimBLEDescriptor* pDescriptor, NimBLEConnInfo& connInfo) { void NimBLEDescriptorCallbacks::onRead(NimBLEDescriptor* pDescriptor, NimBLEConnInfo& connInfo) {
(void)pDescriptor;
NIMBLE_LOGD("NimBLEDescriptorCallbacks", "onRead: default"); NIMBLE_LOGD("NimBLEDescriptorCallbacks", "onRead: default");
} // onRead } // onRead
/** /**
* @brief Callback function to support a write request. * @brief Callback function to support a write request.
* @param [in] pDescriptor The descriptor that is the source of the event. * @param [in] pDescriptor The descriptor that is the source of the event.
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info. * @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.
*/ */
void NimBLEDescriptorCallbacks::onWrite(NimBLEDescriptor* pDescriptor, NimBLEConnInfo& connInfo) { void NimBLEDescriptorCallbacks::onWrite(NimBLEDescriptor* pDescriptor, NimBLEConnInfo& connInfo) {
(void)pDescriptor;
NIMBLE_LOGD("NimBLEDescriptorCallbacks", "onWrite: default"); NIMBLE_LOGD("NimBLEDescriptorCallbacks", "onWrite: default");
} // onWrite } // onWrite

View file

@ -12,12 +12,16 @@
* Author: kolban * Author: kolban
*/ */
#ifndef MAIN_NIMBLEDESCRIPTOR_H_ #ifndef NIMBLE_CPP_DESCRIPTOR_H_
#define MAIN_NIMBLEDESCRIPTOR_H_ #define NIMBLE_CPP_DESCRIPTOR_H_
#include "nimconfig.h" #include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
class NimBLEDescriptor;
class NimBLEDescriptorCallbacks;
# include "NimBLELocalValueAttribute.h"
# include "NimBLECharacteristic.h" # include "NimBLECharacteristic.h"
# include "NimBLEUUID.h" # include "NimBLEUUID.h"
# include "NimBLEAttValue.h" # include "NimBLEAttValue.h"
@ -25,81 +29,35 @@
# include <string> # include <string>
class NimBLEService;
class NimBLECharacteristic;
class NimBLEDescriptorCallbacks;
/** /**
* @brief A model of a %BLE descriptor. * @brief A model of a BLE descriptor.
*/ */
class NimBLEDescriptor { class NimBLEDescriptor : public NimBLELocalValueAttribute {
public: public:
NimBLEDescriptor(const char* uuid, uint16_t properties, NimBLEDescriptor(const char* uuid, uint16_t properties, uint16_t max_len, NimBLECharacteristic* pCharacteristic = nullptr);
NimBLEDescriptor(const NimBLEUUID& uuid,
uint16_t properties,
uint16_t max_len, uint16_t max_len,
NimBLECharacteristic* pCharacteristic = nullptr); NimBLECharacteristic* pCharacteristic = nullptr);
~NimBLEDescriptor() = default;
NimBLEDescriptor(NimBLEUUID uuid, uint16_t properties, std::string toString() const;
uint16_t max_len,
NimBLECharacteristic* pCharacteristic = nullptr);
~NimBLEDescriptor();
uint16_t getHandle();
NimBLEUUID getUUID();
std::string toString();
void setCallbacks(NimBLEDescriptorCallbacks* pCallbacks); void setCallbacks(NimBLEDescriptorCallbacks* pCallbacks);
NimBLECharacteristic* getCharacteristic(); NimBLECharacteristic* getCharacteristic() const;
size_t getLength();
NimBLEAttValue getValue(time_t *timestamp = nullptr);
std::string getStringValue();
void setValue(const uint8_t* data, size_t size);
void setValue(const std::vector<uint8_t>& vec);
/*********************** Template Functions ************************/
/**
* @brief Template to set the characteristic value to <type\>val.
* @param [in] s The value to set.
*/
template<typename T>
void setValue(const T &s) { m_value.setValue<T>(s); }
/**
* @brief Template to convert the descriptor data to <type\>.
* @tparam T The type to convert the data to.
* @param [in] timestamp (Optional) A pointer to a time_t struct to store the time the value was read.
* @param [in] skipSizeCheck (Optional) If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is less than <tt>sizeof(<type\>)</tt>.
* @details <b>Use:</b> <tt>getValue<type>(&timestamp, skipSizeCheck);</tt>
*/
template<typename T>
T getValue(time_t *timestamp = nullptr, bool skipSizeCheck = false) {
return m_value.getValue<T>(timestamp, skipSizeCheck);
}
private: private:
friend class NimBLECharacteristic; friend class NimBLECharacteristic;
friend class NimBLEService; friend class NimBLEService;
friend class NimBLE2904;
static int handleGapEvent(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg);
void setHandle(uint16_t handle);
void setCharacteristic(NimBLECharacteristic* pChar); void setCharacteristic(NimBLECharacteristic* pChar);
void readEvent(NimBLEConnInfo& connInfo) override;
void writeEvent(const uint8_t* val, uint16_t len, NimBLEConnInfo& connInfo) override;
NimBLEUUID m_uuid; NimBLEDescriptorCallbacks* m_pCallbacks{nullptr};
uint16_t m_handle; NimBLECharacteristic* m_pCharacteristic{nullptr};
NimBLEDescriptorCallbacks* m_pCallbacks;
NimBLECharacteristic* m_pCharacteristic;
uint8_t m_properties;
NimBLEAttValue m_value;
uint8_t m_removed;
}; // NimBLEDescriptor }; // NimBLEDescriptor
/** /**
* @brief Callbacks that can be associated with a %BLE descriptors to inform of events. * @brief Callbacks that can be associated with a %BLE descriptors to inform of events.
* *
@ -109,7 +67,7 @@ private:
*/ */
class NimBLEDescriptorCallbacks { class NimBLEDescriptorCallbacks {
public: public:
virtual ~NimBLEDescriptorCallbacks(){} virtual ~NimBLEDescriptorCallbacks() = default;
virtual void onRead(NimBLEDescriptor* pDescriptor, NimBLEConnInfo& connInfo); virtual void onRead(NimBLEDescriptor* pDescriptor, NimBLEConnInfo& connInfo);
virtual void onWrite(NimBLEDescriptor* pDescriptor, NimBLEConnInfo& connInfo); virtual void onWrite(NimBLEDescriptor* pDescriptor, NimBLEConnInfo& connInfo);
}; };
@ -117,4 +75,4 @@ public:
# include "NimBLE2904.h" # include "NimBLE2904.h"
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */ #endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */
#endif /* MAIN_NIMBLEDESCRIPTOR_H_ */ #endif /* NIMBLE_CPP_DESCRIPTOR_H_ */

View file

@ -54,8 +54,11 @@
# include "esp32-hal-bt.h" # include "esp32-hal-bt.h"
#endif #endif
#include "NimBLELog.h" #if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
#include "NimBLEClient.h"
#endif
#include "NimBLELog.h"
#include <algorithm> #include <algorithm>
static const char* LOG_TAG = "NimBLEDevice"; static const char* LOG_TAG = "NimBLEDevice";

View file

@ -31,7 +31,7 @@
#endif #endif
#if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) #if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
#include "NimBLEClient.h" class NimBLEClient;
#endif #endif
#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
@ -46,6 +46,7 @@
#endif #endif
#include <string> #include <string>
#include <array>
#define BLEDevice NimBLEDevice #define BLEDevice NimBLEDevice
#define BLEClient NimBLEClient #define BLEClient NimBLEClient
@ -236,6 +237,12 @@ private:
#endif #endif
}; };
#if defined( CONFIG_BT_NIMBLE_ROLE_CENTRAL)
#include "NimBLEClient.h"
#include "NimBLERemoteService.h"
#include "NimBLERemoteCharacteristic.h"
#include "NimBLERemoteDescriptor.h"
#endif
#endif // CONFIG_BT_ENABLED #endif // CONFIG_BT_ENABLED
#endif // MAIN_NIMBLEDEVICE_H_ #endif // MAIN_NIMBLEDEVICE_H_

View file

@ -0,0 +1,48 @@
/*
* NimBLELocalAttribute.cpp
*
* Created: on July 28 2024
* Author H2zero
*/
#ifndef NIMBLE_CPP_LOCAL_ATTRIBUTE_H_
#define NIMBLE_CPP_LOCAL_ATTRIBUTE_H_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
# include "NimBLEAttribute.h"
/**
* @brief A base class for local BLE attributes.
*/
class NimBLELocalAttribute : public NimBLEAttribute {
public:
/**
* @brief Get the removed flag.
* @return The removed flag.
*/
uint8_t getRemoved() const { return m_removed; }
protected:
/**
* @brief Construct a local attribute.
*/
NimBLELocalAttribute(const NimBLEUUID& uuid, uint16_t handle) : NimBLEAttribute{uuid, handle}, m_removed{0} {}
/**
* @brief Destroy the local attribute.
*/
~NimBLELocalAttribute() = default;
/**
* @brief Set the removed flag.
* @param [in] removed The removed flag.
*/
void setRemoved(uint8_t removed) { m_removed = removed; }
uint8_t m_removed{0};
};
#endif // CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL
#endif // NIMBLE_CPP_LOCAL_ATTRIBUTE_H_

View file

@ -0,0 +1,156 @@
/*
* NimBLELocalValueAttribute.cpp
*
* Created: on July 28 2024
* Author H2zero
*/
#ifndef NIMBLE_LOCAL_VALUE_ATTRIBUTE_H_
#define NIMBLE_LOCAL_VALUE_ATTRIBUTE_H_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include "host/ble_hs.h"
# else
# include "nimble/nimble/host/include/host/ble_hs.h"
# endif
typedef enum {
READ = BLE_GATT_CHR_F_READ,
READ_ENC = BLE_GATT_CHR_F_READ_ENC,
READ_AUTHEN = BLE_GATT_CHR_F_READ_AUTHEN,
READ_AUTHOR = BLE_GATT_CHR_F_READ_AUTHOR,
WRITE = BLE_GATT_CHR_F_WRITE,
WRITE_NR = BLE_GATT_CHR_F_WRITE_NO_RSP,
WRITE_ENC = BLE_GATT_CHR_F_WRITE_ENC,
WRITE_AUTHEN = BLE_GATT_CHR_F_WRITE_AUTHEN,
WRITE_AUTHOR = BLE_GATT_CHR_F_WRITE_AUTHOR,
BROADCAST = BLE_GATT_CHR_F_BROADCAST,
NOTIFY = BLE_GATT_CHR_F_NOTIFY,
INDICATE = BLE_GATT_CHR_F_INDICATE
} NIMBLE_PROPERTY;
# include "NimBLELocalAttribute.h"
# include "NimBLEAttValue.h"
# include <vector>
class NimBLEConnInfo;
class NimBLELocalValueAttribute : public NimBLELocalAttribute {
public:
/**
* @brief Get the properties of the attribute.
*/
uint16_t getProperties() const { return m_properties; }
/**
* @brief Get the length of the attribute value.
* @return The length of the attribute value.
*/
size_t getLength() const { return m_value.size(); }
/**
* @brief Get a copy of the value of the attribute value.
* @param [in] timestamp (Optional) A pointer to a time_t struct to get the time the value set.
* @return A copy of the attribute value.
*/
NimBLEAttValue getValue(time_t* timestamp = nullptr) const { return m_value; }
/**
* @brief Set the value of the attribute value.
* @param [in] data The data to set the value to.
* @param [in] size The size of the data.
*/
void setValue(const uint8_t* data, size_t size) { m_value.setValue(data, size); }
/**
* @brief Set the value of the attribute value.
* @param [in] str The string to set the value to.
*/
void setValue(const char* str) { m_value.setValue(str); }
/**
* @brief Set the value of the attribute value.
* @param [in] vec The vector to set the value to.
*/
void setValue(const std::vector<uint8_t>& vec) { m_value.setValue(vec); }
/**
* @brief Template to set the value to <type\>val.
* @param [in] val The value to set.
*/
template <typename T>
void setValue(const T& val) {
m_value.setValue<T>(val);
}
/**
* @brief Template to convert the data to <type\>.
* @tparam T The type to convert the data to.
* @param [in] timestamp (Optional) A pointer to a time_t struct to get the time the value set.
* @param [in] skipSizeCheck (Optional) If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is less than <tt>sizeof(<type\>)</tt>.
* @details <b>Use:</b> <tt>getValue<type>(&timestamp, skipSizeCheck);</tt>
*/
template <typename T>
T getValue(time_t* timestamp = nullptr, bool skipSizeCheck = false) const {
return m_value.getValue<T>(timestamp, skipSizeCheck);
}
protected:
friend class NimBLEServer;
/**
* @brief Construct a new NimBLELocalValueAttribute object.
* @param [in] uuid The UUID of the attribute.
* @param [in] handle The handle of the attribute.
* @param [in] maxLen The maximum length of the attribute value.
* @param [in] initLen The initial length of the attribute value.
*/
NimBLELocalValueAttribute(const NimBLEUUID& uuid,
uint16_t handle,
uint16_t maxLen,
uint16_t initLen = CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH)
: NimBLELocalAttribute(uuid, handle), m_value(initLen, maxLen) {}
/**
* @brief Destroy the NimBLELocalValueAttribute object.
*/
virtual ~NimBLELocalValueAttribute() = default;
/**
* @brief Callback function to support a read request.
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.
* @details This function is called by NimBLEServer when a read request is received.
*/
virtual void readEvent(NimBLEConnInfo& connInfo) = 0;
/**
* @brief Callback function to support a write request.
* @param [in] val The value to write.
* @param [in] len The length of the value.
* @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info.
* @details This function is called by NimBLEServer when a write request is received.
*/
virtual void writeEvent(const uint8_t* val, uint16_t len, NimBLEConnInfo& connInfo) = 0;
/**
* @brief Get a pointer to value of the attribute.
* @return A pointer to the value of the attribute.
* @details This function is used by NimBLEServer when handling read/write requests.
*/
const NimBLEAttValue& getAttVal() const { return m_value; }
/**
* @brief Set the properties of the attribute.
* @param [in] properties The properties of the attribute.
*/
void setProperties(uint16_t properties) { m_properties = properties; }
NimBLEAttValue m_value{};
uint16_t m_properties{0};
};
#endif // CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL
#endif // NIMBLE_LOCAL_VALUE_ATTRIBUTE_H_

View file

@ -16,16 +16,24 @@
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
# include "NimBLERemoteCharacteristic.h" # include "NimBLERemoteCharacteristic.h"
# include "NimBLERemoteDescriptor.h"
# include "NimBLERemoteService.h"
# include "NimBLEClient.h"
# include "NimBLEUtils.h" # include "NimBLEUtils.h"
# include "NimBLELog.h" # include "NimBLELog.h"
# include <climits> # include <climits>
typedef struct {
const NimBLEUUID* uuid;
void* task_data;
} desc_filter_t;
static const char* LOG_TAG = "NimBLERemoteCharacteristic"; static const char* LOG_TAG = "NimBLERemoteCharacteristic";
/** /**
* @brief Constructor. * @brief Constructor.
* @param [in] reference to the service this characteristic belongs to. * @param [in] svc A pointer to the service this characteristic belongs to.
* @param [in] ble_gatt_chr struct defined as: * @param [in] ble_gatt_chr struct defined as:
* struct ble_gatt_chr { * struct ble_gatt_chr {
* uint16_t def_handle; * uint16_t def_handle;
@ -34,34 +42,12 @@ static const char* LOG_TAG = "NimBLERemoteCharacteristic";
* ble_uuid_any_t uuid; * ble_uuid_any_t uuid;
* }; * };
*/ */
NimBLERemoteCharacteristic::NimBLERemoteCharacteristic(NimBLERemoteService *pRemoteService, NimBLERemoteCharacteristic::NimBLERemoteCharacteristic(const NimBLERemoteService* svc, const ble_gatt_chr* chr)
const struct ble_gatt_chr *chr) : NimBLERemoteValueAttribute{chr->uuid, chr->val_handle},
{ m_pRemoteService{svc},
NIMBLE_LOGD(LOG_TAG, ">> NimBLERemoteCharacteristic()"); m_properties{chr->properties},
switch (chr->uuid.u.type) { m_notifyCallback{},
case BLE_UUID_TYPE_16: m_vDescriptors{} {} // NimBLERemoteCharacteristic
m_uuid = NimBLEUUID(chr->uuid.u16.value);
break;
case BLE_UUID_TYPE_32:
m_uuid = NimBLEUUID(chr->uuid.u32.value);
break;
case BLE_UUID_TYPE_128:
m_uuid = NimBLEUUID(const_cast<ble_uuid128_t*>(&chr->uuid.u128));
break;
default:
break;
}
m_handle = chr->val_handle;
m_defHandle = chr->def_handle;
m_endHandle = 0;
m_charProp = chr->properties;
m_pRemoteService = pRemoteService;
m_notifyCallback = nullptr;
NIMBLE_LOGD(LOG_TAG, "<< NimBLERemoteCharacteristic(): %s", m_uuid.toString().c_str());
} // NimBLERemoteCharacteristic
/** /**
*@brief Destructor. *@brief Destructor.
@ -70,214 +56,60 @@ NimBLERemoteCharacteristic::~NimBLERemoteCharacteristic() {
deleteDescriptors(); deleteDescriptors();
} // ~NimBLERemoteCharacteristic } // ~NimBLERemoteCharacteristic
/*
#define BLE_GATT_CHR_PROP_BROADCAST 0x01
#define BLE_GATT_CHR_PROP_READ 0x02
#define BLE_GATT_CHR_PROP_WRITE_NO_RSP 0x04
#define BLE_GATT_CHR_PROP_WRITE 0x08
#define BLE_GATT_CHR_PROP_NOTIFY 0x10
#define BLE_GATT_CHR_PROP_INDICATE 0x20
#define BLE_GATT_CHR_PROP_AUTH_SIGN_WRITE 0x40
#define BLE_GATT_CHR_PROP_EXTENDED 0x80
*/
/**
* @brief Does the characteristic support broadcasting?
* @return True if the characteristic supports broadcasting.
*/
bool NimBLERemoteCharacteristic::canBroadcast() {
return (m_charProp & BLE_GATT_CHR_PROP_BROADCAST) != 0;
} // canBroadcast
/**
* @brief Does the characteristic support indications?
* @return True if the characteristic supports indications.
*/
bool NimBLERemoteCharacteristic::canIndicate() {
return (m_charProp & BLE_GATT_CHR_PROP_INDICATE) != 0;
} // canIndicate
/**
* @brief Does the characteristic support notifications?
* @return True if the characteristic supports notifications.
*/
bool NimBLERemoteCharacteristic::canNotify() {
return (m_charProp & BLE_GATT_CHR_PROP_NOTIFY) != 0;
} // canNotify
/**
* @brief Does the characteristic support reading?
* @return True if the characteristic supports reading.
*/
bool NimBLERemoteCharacteristic::canRead() {
return (m_charProp & BLE_GATT_CHR_PROP_READ) != 0;
} // canRead
/**
* @brief Does the characteristic support writing?
* @return True if the characteristic supports writing.
*/
bool NimBLERemoteCharacteristic::canWrite() {
return (m_charProp & BLE_GATT_CHR_PROP_WRITE) != 0;
} // canWrite
/**
* @brief Does the characteristic support writing with no response?
* @return True if the characteristic supports writing with no response.
*/
bool NimBLERemoteCharacteristic::canWriteNoResponse() {
return (m_charProp & BLE_GATT_CHR_PROP_WRITE_NO_RSP) != 0;
} // canWriteNoResponse
/** /**
* @brief Callback used by the API when a descriptor is discovered or search complete. * @brief Callback used by the API when a descriptor is discovered or search complete.
*/ */
int NimBLERemoteCharacteristic::descriptorDiscCB(uint16_t conn_handle, int NimBLERemoteCharacteristic::descriptorDiscCB(
const struct ble_gatt_error *error, uint16_t conn_handle, const ble_gatt_error* error, uint16_t chr_val_handle, const ble_gatt_dsc* dsc, void* arg) {
uint16_t chr_val_handle,
const struct ble_gatt_dsc *dsc,
void *arg)
{
int rc = error->status; int rc = error->status;
NIMBLE_LOGD(LOG_TAG, "Descriptor Discovered >> status: %d handle: %d", NIMBLE_LOGD(LOG_TAG, "Descriptor Discovery >> status: %d handle: %d", rc, (rc == 0) ? dsc->handle : -1);
rc, (rc == 0) ? dsc->handle : -1);
desc_filter_t *filter = (desc_filter_t*)arg; auto filter = (desc_filter_t*)arg;
const NimBLEUUID *uuid_filter = filter->uuid; auto pTaskData = (ble_task_data_t*)filter->task_data;
ble_task_data_t *pTaskData = (ble_task_data_t*)filter->task_data; const auto pChr = (NimBLERemoteCharacteristic*)pTaskData->pATT;
NimBLERemoteCharacteristic *characteristic = (NimBLERemoteCharacteristic*)pTaskData->pATT; const NimBLEUUID* uuidFilter = filter->uuid;
if (characteristic->getRemoteService()->getClient()->getConnId() != conn_handle){ if (pChr->getHandle() != chr_val_handle) {
return 0; rc = BLE_HS_EDONE; // descriptor not for this characteristic
}
switch (rc) {
case 0: {
if (uuid_filter != nullptr) {
if (ble_uuid_cmp(uuid_filter->getBase(), &dsc->uuid.u) != 0) {
return 0;
} else {
rc = BLE_HS_EDONE;
}
}
NimBLERemoteDescriptor* pNewRemoteDescriptor = new NimBLERemoteDescriptor(characteristic, dsc);
characteristic->m_descriptorVector.push_back(pNewRemoteDescriptor);
break;
}
default:
break;
}
/* If rc == BLE_HS_EDONE, resume the task with a success error code and stop the discovery process.
* Else if rc == 0, just return 0 to continue the discovery until we get BLE_HS_EDONE.
* If we get any other error code tell the application to abort by returning non-zero in the rc.
*/
if (rc == BLE_HS_EDONE) {
pTaskData->rc = 0;
xTaskNotifyGive(pTaskData->task);
} else if(rc != 0) {
// Error; abort discovery.
pTaskData->rc = rc;
xTaskNotifyGive(pTaskData->task);
}
NIMBLE_LOGD(LOG_TAG,"<< Descriptor Discovered. status: %d", pTaskData->rc);
return rc;
}
/**
* @brief callback from NimBLE when the next characteristic of the service is discovered.
*/
int NimBLERemoteCharacteristic::nextCharCB(uint16_t conn_handle,
const struct ble_gatt_error *error,
const struct ble_gatt_chr *chr, void *arg)
{
int rc = error->status;
NIMBLE_LOGD(LOG_TAG, "Next Characteristic >> status: %d handle: %d",
rc, (rc == 0) ? chr->val_handle : -1);
ble_task_data_t *pTaskData = (ble_task_data_t*)arg;
NimBLERemoteCharacteristic *pChar = (NimBLERemoteCharacteristic*)pTaskData->pATT;
if (pChar->getRemoteService()->getClient()->getConnId() != conn_handle) {
return 0;
} }
if (rc == 0) { if (rc == 0) {
pChar->m_endHandle = chr->def_handle - 1; if (uuidFilter != nullptr) {
rc = BLE_HS_EDONE; if (ble_uuid_cmp(uuidFilter->getBase(), &dsc->uuid.u) == 0) {
} else if (rc == BLE_HS_EDONE) { rc = BLE_HS_EDONE; // Found the descriptor, stop the search
pChar->m_endHandle = pChar->getRemoteService()->getEndHandle();
} else { } else {
pTaskData->rc = rc; return 0; // Not the descriptor we are looking for
}
} }
pChr->m_vDescriptors.push_back(new NimBLERemoteDescriptor(pChr, dsc));
}
if (rc != 0) {
pTaskData->rc = rc == BLE_HS_EDONE ? 0 : rc;
NIMBLE_LOGD(LOG_TAG, "<< Descriptor Discovery");
xTaskNotifyGive(pTaskData->task); xTaskNotifyGive(pTaskData->task);
}
return rc; return rc;
} }
/** /**
* @brief Populate the descriptors (if any) for this characteristic. * @brief Populate the descriptors (if any) for this characteristic.
* @param [in] the end handle of the characteristic, or the service, whichever comes first. * @param [in] the end handle of the characteristic, or the service, whichever comes first.
*/ */
bool NimBLERemoteCharacteristic::retrieveDescriptors(const NimBLEUUID *uuid_filter) { bool NimBLERemoteCharacteristic::retrieveDescriptors(const NimBLEUUID* uuidFilter) const {
NIMBLE_LOGD(LOG_TAG, ">> retrieveDescriptors() for characteristic: %s", getUUID().toString().c_str()); NIMBLE_LOGD(LOG_TAG, ">> retrieveDescriptors() for characteristic: %s", getUUID().toString().c_str());
// If this is the last handle then there are no descriptors
if (m_handle == getRemoteService()->getEndHandle()) {
return true;
}
int rc = 0;
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
ble_task_data_t taskData = {this, cur_task, 0, nullptr}; ble_task_data_t taskData = {const_cast<NimBLERemoteCharacteristic*>(this), cur_task, 0, nullptr};
desc_filter_t filter = {uuidFilter, &taskData};
// If we don't know the end handle of this characteristic retrieve the next one in the service int rc = ble_gattc_disc_all_dscs(getClient()->getConnId(),
// The end handle is the next characteristic definition handle -1. getHandle(),
if (m_endHandle == 0) {
rc = ble_gattc_disc_all_chrs(getRemoteService()->getClient()->getConnId(),
m_handle,
getRemoteService()->getEndHandle(), getRemoteService()->getEndHandle(),
NimBLERemoteCharacteristic::nextCharCB,
&taskData);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Error getting end handle rc=%d", rc);
return false;
}
#ifdef ulTaskNotifyValueClear
// Clear the task notification value to ensure we block
ulTaskNotifyValueClear(cur_task, ULONG_MAX);
#endif
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if (taskData.rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Could not retrieve end handle rc=%d", taskData.rc);
return false;
}
}
if (m_handle == m_endHandle) {
return true;
}
desc_filter_t filter = {uuid_filter, &taskData};
rc = ble_gattc_disc_all_dscs(getRemoteService()->getClient()->getConnId(),
m_handle,
m_endHandle,
NimBLERemoteCharacteristic::descriptorDiscCB, NimBLERemoteCharacteristic::descriptorDiscCB,
&filter); &filter);
if (rc != 0) { if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gattc_disc_all_dscs: rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc)); NIMBLE_LOGE(LOG_TAG, "ble_gattc_disc_all_dscs: rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
return false; return false;
@ -290,46 +122,48 @@ bool NimBLERemoteCharacteristic::retrieveDescriptors(const NimBLEUUID *uuid_filt
ulTaskNotifyTake(pdTRUE, portMAX_DELAY); ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if (taskData.rc != 0) { if (taskData.rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Failed to retrieve descriptors; startHandle:%d endHandle:%d taskData.rc=%d", NIMBLE_LOGE(LOG_TAG,
m_handle, m_endHandle, taskData.rc); "<< retrieveDescriptors(): failed: rc=%d %s",
taskData.rc,
NimBLEUtils::returnCodeToString(taskData.rc));
} else {
NIMBLE_LOGD(LOG_TAG, "<< retrieveDescriptors(): found %d descriptors.", m_vDescriptors.size());
} }
NIMBLE_LOGD(LOG_TAG, "<< retrieveDescriptors(): Found %d descriptors.", m_descriptorVector.size()); return taskData.rc == 0;
return (taskData.rc == 0);
} // retrieveDescriptors } // retrieveDescriptors
/** /**
* @brief Get the descriptor instance with the given UUID that belongs to this characteristic. * @brief Get the descriptor instance with the given UUID that belongs to this characteristic.
* @param [in] uuid The UUID of the descriptor to find. * @param [in] uuid The UUID of the descriptor to find.
* @return The Remote descriptor (if present) or null if not present. * @return The Remote descriptor (if present) or nullptr if not present.
*/ */
NimBLERemoteDescriptor* NimBLERemoteCharacteristic::getDescriptor(const NimBLEUUID &uuid) { NimBLERemoteDescriptor* NimBLERemoteCharacteristic::getDescriptor(const NimBLEUUID& uuid) const {
NIMBLE_LOGD(LOG_TAG, ">> getDescriptor: uuid: %s", uuid.toString().c_str()); NIMBLE_LOGD(LOG_TAG, ">> getDescriptor: uuid: %s", uuid.toString().c_str());
NimBLERemoteDescriptor* pDsc = nullptr;
size_t prev_size = m_vDescriptors.size();
for(auto &it: m_descriptorVector) { for (const auto& it : m_vDescriptors) {
if (it->getUUID() == uuid) { if (it->getUUID() == uuid) {
NIMBLE_LOGD(LOG_TAG, "<< getDescriptor: found the descriptor with uuid: %s", uuid.toString().c_str()); pDsc = it;
return it; goto Done;
} }
} }
size_t prev_size = m_descriptorVector.size();
if (retrieveDescriptors(&uuid)) { if (retrieveDescriptors(&uuid)) {
if(m_descriptorVector.size() > prev_size) { if (m_vDescriptors.size() > prev_size) {
return m_descriptorVector.back(); pDsc = m_vDescriptors.back();
goto Done;
} }
// If the request was successful but 16/32 bit uuid not found // If the request was successful but 16/32 bit uuid not found
// try again with the 128 bit uuid. // try again with the 128 bit uuid.
if(uuid.bitSize() == BLE_UUID_TYPE_16 || if (uuid.bitSize() == BLE_UUID_TYPE_16 || uuid.bitSize() == BLE_UUID_TYPE_32) {
uuid.bitSize() == BLE_UUID_TYPE_32)
{
NimBLEUUID uuid128(uuid); NimBLEUUID uuid128(uuid);
uuid128.to128(); uuid128.to128();
if (retrieveDescriptors(&uuid128)) { if (retrieveDescriptors(&uuid128)) {
if(m_descriptorVector.size() > prev_size) { if (m_vDescriptors.size() > prev_size) {
return m_descriptorVector.back(); pDsc = m_vDescriptors.back();
} }
} }
} else { } else {
@ -340,19 +174,19 @@ NimBLERemoteDescriptor* NimBLERemoteCharacteristic::getDescriptor(const NimBLEUU
// if the uuid was 128 bit but not of the BLE base type this check will fail // if the uuid was 128 bit but not of the BLE base type this check will fail
if (uuid16.bitSize() == BLE_UUID_TYPE_16) { if (uuid16.bitSize() == BLE_UUID_TYPE_16) {
if (retrieveDescriptors(&uuid16)) { if (retrieveDescriptors(&uuid16)) {
if(m_descriptorVector.size() > prev_size) { if (m_vDescriptors.size() > prev_size) {
return m_descriptorVector.back(); pDsc = m_vDescriptors.back();
} }
} }
} }
} }
} }
NIMBLE_LOGD(LOG_TAG, "<< getDescriptor: Not found"); Done:
return nullptr; NIMBLE_LOGD(LOG_TAG, "<< getDescriptor: %sfound", pDsc ? "" : "not ");
return pDsc;
} // getDescriptor } // getDescriptor
/** /**
* @brief Get a pointer to the vector of found descriptors. * @brief Get a pointer to the vector of found descriptors.
* @param [in] refresh If true the current descriptor vector will be cleared and\n * @param [in] refresh If true the current descriptor vector will be cleared and\n
@ -361,201 +195,39 @@ NimBLERemoteDescriptor* NimBLERemoteCharacteristic::getDescriptor(const NimBLEUU
* of this characteristic. * of this characteristic.
* @return A pointer to the vector of descriptors for this characteristic. * @return A pointer to the vector of descriptors for this characteristic.
*/ */
std::vector<NimBLERemoteDescriptor*>* NimBLERemoteCharacteristic::getDescriptors(bool refresh) { const std::vector<NimBLERemoteDescriptor*>& NimBLERemoteCharacteristic::getDescriptors(bool refresh) const {
if (refresh) { if (refresh) {
deleteDescriptors(); deleteDescriptors();
retrieveDescriptors();
}
if (!retrieveDescriptors()) { return m_vDescriptors;
NIMBLE_LOGE(LOG_TAG, "Error: Failed to get descriptors");
}
else{
NIMBLE_LOGI(LOG_TAG, "Found %d descriptor(s)", m_descriptorVector.size());
}
}
return &m_descriptorVector;
} // getDescriptors } // getDescriptors
/** /**
* @brief Get iterator to the beginning of the vector of remote descriptor pointers. * @brief Get iterator to the beginning of the vector of remote descriptor pointers.
* @return An iterator to the beginning of the vector of remote descriptor pointers. * @return An iterator to the beginning of the vector of remote descriptor pointers.
*/ */
std::vector<NimBLERemoteDescriptor*>::iterator NimBLERemoteCharacteristic::begin() { std::vector<NimBLERemoteDescriptor*>::iterator NimBLERemoteCharacteristic::begin() const {
return m_descriptorVector.begin(); return m_vDescriptors.begin();
} }
/** /**
* @brief Get iterator to the end of the vector of remote descriptor pointers. * @brief Get iterator to the end of the vector of remote descriptor pointers.
* @return An iterator to the end of the vector of remote descriptor pointers. * @return An iterator to the end of the vector of remote descriptor pointers.
*/ */
std::vector<NimBLERemoteDescriptor*>::iterator NimBLERemoteCharacteristic::end() { std::vector<NimBLERemoteDescriptor*>::iterator NimBLERemoteCharacteristic::end() const {
return m_descriptorVector.end(); return m_vDescriptors.end();
} }
/**
* @brief Get the handle for this characteristic.
* @return The handle for this characteristic.
*/
uint16_t NimBLERemoteCharacteristic::getHandle() {
return m_handle;
} // getHandle
/**
* @brief Get the handle for this characteristics definition.
* @return The handle for this characteristic definition.
*/
uint16_t NimBLERemoteCharacteristic::getDefHandle() {
return m_defHandle;
} // getDefHandle
/** /**
* @brief Get the remote service associated with this characteristic. * @brief Get the remote service associated with this characteristic.
* @return The remote service associated with this characteristic. * @return The remote service associated with this characteristic.
*/ */
NimBLERemoteService* NimBLERemoteCharacteristic::getRemoteService() { const NimBLERemoteService* NimBLERemoteCharacteristic::getRemoteService() const {
return m_pRemoteService; return m_pRemoteService;
} // getRemoteService } // getRemoteService
/**
* @brief Get the UUID for this characteristic.
* @return The UUID for this characteristic.
*/
NimBLEUUID NimBLERemoteCharacteristic::getUUID() {
return m_uuid;
} // getUUID
/**
* @brief Get the value of the remote characteristic.
* @param [in] timestamp A pointer to a time_t struct to store the time the value was read.
* @return The value of the remote characteristic.
*/
NimBLEAttValue NimBLERemoteCharacteristic::getValue(time_t *timestamp) {
if(timestamp != nullptr) {
*timestamp = m_value.getTimeStamp();
}
return m_value;
} // getValue
/**
* @brief Read the value of the remote characteristic.
* @param [in] timestamp A pointer to a time_t struct to store the time the value was read.
* @return The value of the remote characteristic.
*/
NimBLEAttValue NimBLERemoteCharacteristic::readValue(time_t *timestamp) {
NIMBLE_LOGD(LOG_TAG, ">> readValue(): uuid: %s, handle: %d 0x%.2x",
getUUID().toString().c_str(), getHandle(), getHandle());
NimBLEClient* pClient = getRemoteService()->getClient();
NimBLEAttValue value;
if (!pClient->isConnected()) {
NIMBLE_LOGE(LOG_TAG, "Disconnected");
return value;
}
int rc = 0;
int retryCount = 1;
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
ble_task_data_t taskData = {this, cur_task, 0, &value};
do {
rc = ble_gattc_read_long(pClient->getConnId(), m_handle, 0,
NimBLERemoteCharacteristic::onReadCB,
&taskData);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Error: Failed to read characteristic; rc=%d, %s",
rc, NimBLEUtils::returnCodeToString(rc));
return value;
}
#ifdef ulTaskNotifyValueClear
// Clear the task notification value to ensure we block
ulTaskNotifyValueClear(cur_task, ULONG_MAX);
#endif
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
rc = taskData.rc;
switch(rc){
case 0:
case BLE_HS_EDONE:
rc = 0;
break;
// Characteristic is not long-readable, return with what we have.
case BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_LONG):
NIMBLE_LOGI(LOG_TAG, "Attribute not long");
rc = 0;
break;
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHOR):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_ENC):
if (retryCount && pClient->secureConnection())
break;
/* Else falls through. */
default:
NIMBLE_LOGE(LOG_TAG, "<< readValue rc=%d", rc);
return value;
}
} while(rc != 0 && retryCount--);
value.setTimeStamp();
m_value = value;
if(timestamp != nullptr) {
*timestamp = value.getTimeStamp();
}
NIMBLE_LOGD(LOG_TAG, "<< readValue length: %d rc=%d", value.length(), rc);
return value;
} // readValue
/**
* @brief Callback for characteristic read operation.
* @return success == 0 or error code.
*/
int NimBLERemoteCharacteristic::onReadCB(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr, void *arg)
{
ble_task_data_t *pTaskData = (ble_task_data_t*)arg;
NimBLERemoteCharacteristic *characteristic = (NimBLERemoteCharacteristic*)pTaskData->pATT;
uint16_t conn_id = characteristic->getRemoteService()->getClient()->getConnId();
if(conn_id != conn_handle) {
return 0;
}
NIMBLE_LOGI(LOG_TAG, "Read complete; status=%d conn_handle=%d", error->status, conn_handle);
NimBLEAttValue *valBuf = (NimBLEAttValue*)pTaskData->buf;
int rc = error->status;
if(rc == 0) {
if(attr) {
uint16_t data_len = OS_MBUF_PKTLEN(attr->om);
if((valBuf->size() + data_len) > BLE_ATT_ATTR_MAX_LEN) {
rc = BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} else {
NIMBLE_LOGD(LOG_TAG, "Got %u bytes", data_len);
valBuf->append(attr->om->om_data, data_len);
return 0;
}
}
}
pTaskData->rc = rc;
xTaskNotifyGive(pTaskData->task);
return rc;
} // onReadCB
/** /**
* @brief Subscribe or unsubscribe for notifications or indications. * @brief Subscribe or unsubscribe for notifications or indications.
* @param [in] val 0x00 to unsubscribe, 0x01 for notifications, 0x02 for indications. * @param [in] val 0x00 to unsubscribe, 0x01 for notifications, 0x02 for indications.
@ -564,11 +236,10 @@ int NimBLERemoteCharacteristic::onReadCB(uint16_t conn_handle,
* If NULL is provided then no callback is performed. * If NULL is provided then no callback is performed.
* @return false if writing to the descriptor failed. * @return false if writing to the descriptor failed.
*/ */
bool NimBLERemoteCharacteristic::setNotify(uint16_t val, notify_callback notifyCallback, bool response) { bool NimBLERemoteCharacteristic::setNotify(uint16_t val, notify_callback notifyCallback, bool response) const {
NIMBLE_LOGD(LOG_TAG, ">> setNotify(): %s, %02x", toString().c_str(), val); NIMBLE_LOGD(LOG_TAG, ">> setNotify()");
m_notifyCallback = notifyCallback; m_notifyCallback = notifyCallback;
NimBLERemoteDescriptor* desc = getDescriptor(NimBLEUUID((uint16_t)0x2902)); NimBLERemoteDescriptor* desc = getDescriptor(NimBLEUUID((uint16_t)0x2902));
if (desc == nullptr) { if (desc == nullptr) {
NIMBLE_LOGW(LOG_TAG, "<< setNotify(): Callback set, CCCD not found"); NIMBLE_LOGW(LOG_TAG, "<< setNotify(): Callback set, CCCD not found");
@ -576,11 +247,9 @@ bool NimBLERemoteCharacteristic::setNotify(uint16_t val, notify_callback notifyC
} }
NIMBLE_LOGD(LOG_TAG, "<< setNotify()"); NIMBLE_LOGD(LOG_TAG, "<< setNotify()");
return desc->writeValue(reinterpret_cast<uint8_t*>(&val), 2, response);
return desc->writeValue((uint8_t *)&val, 2, response);
} // setNotify } // setNotify
/** /**
* @brief Subscribe for notifications or indications. * @brief Subscribe for notifications or indications.
* @param [in] notifications If true, subscribe for notifications, false subscribe for indications. * @param [in] notifications If true, subscribe for notifications, false subscribe for indications.
@ -589,69 +258,125 @@ bool NimBLERemoteCharacteristic::setNotify(uint16_t val, notify_callback notifyC
* If NULL is provided then no callback is performed. * If NULL is provided then no callback is performed.
* @return false if writing to the descriptor failed. * @return false if writing to the descriptor failed.
*/ */
bool NimBLERemoteCharacteristic::subscribe(bool notifications, notify_callback notifyCallback, bool response) { bool NimBLERemoteCharacteristic::subscribe(bool notifications, notify_callback notifyCallback, bool response) const {
if(notifications) { return setNotify(notifications ? 0x01 : 0x02, notifyCallback, response);
return setNotify(0x01, notifyCallback, response);
} else {
return setNotify(0x02, notifyCallback, response);
}
} // subscribe } // subscribe
/** /**
* @brief Unsubscribe for notifications or indications. * @brief Unsubscribe for notifications or indications.
* @param [in] response bool if true, require a write response from the descriptor write operation. * @param [in] response bool if true, require a write response from the descriptor write operation.
* @return false if writing to the descriptor failed. * @return false if writing to the descriptor failed.
*/ */
bool NimBLERemoteCharacteristic::unsubscribe(bool response) { bool NimBLERemoteCharacteristic::unsubscribe(bool response) const {
return setNotify(0x00, nullptr, response); return setNotify(0x00, nullptr, response);
} // unsubscribe } // unsubscribe
/** /**
* @brief Delete the descriptors in the descriptor vector. * @brief Delete the descriptors in the descriptor vector.
* @details We maintain a vector called m_descriptorVector that contains pointers to NimBLERemoteDescriptors * @details We maintain a vector called m_vDescriptors that contains pointers to NimBLERemoteDescriptors
* object references. Since we allocated these in this class, we are also responsible for deleting * object references. Since we allocated these in this class, we are also responsible for deleting
* them. This method does just that. * them. This method does just that.
*/ */
void NimBLERemoteCharacteristic::deleteDescriptors() { void NimBLERemoteCharacteristic::deleteDescriptors() const {
NIMBLE_LOGD(LOG_TAG, ">> deleteDescriptors"); NIMBLE_LOGD(LOG_TAG, ">> deleteDescriptors");
for(auto &it: m_descriptorVector) { for (const auto& it : m_vDescriptors) {
delete it; delete it;
} }
m_descriptorVector.clear(); std::vector<NimBLERemoteDescriptor*>().swap(m_vDescriptors);
NIMBLE_LOGD(LOG_TAG, "<< deleteDescriptors"); NIMBLE_LOGD(LOG_TAG, "<< deleteDescriptors");
} // deleteDescriptors } // deleteDescriptors
/** /**
* @brief Delete descriptor by UUID * @brief Delete descriptor by UUID
* @param [in] uuid The UUID of the descriptor to be deleted. * @param [in] uuid The UUID of the descriptor to be deleted.
* @return Number of descriptors left in the vector. * @return Number of descriptors left in the vector.
*/ */
size_t NimBLERemoteCharacteristic::deleteDescriptor(const NimBLEUUID &uuid) { size_t NimBLERemoteCharacteristic::deleteDescriptor(const NimBLEUUID& uuid) const {
NIMBLE_LOGD(LOG_TAG, ">> deleteDescriptor"); NIMBLE_LOGD(LOG_TAG, ">> deleteDescriptor");
for(auto it = m_descriptorVector.begin(); it != m_descriptorVector.end(); ++it) { for (auto it = m_vDescriptors.begin(); it != m_vDescriptors.end(); ++it) {
if ((*it)->getUUID() == uuid) { if ((*it)->getUUID() == uuid) {
delete *it; delete (*it);
m_descriptorVector.erase(it); m_vDescriptors.erase(it);
break; break;
} }
} }
NIMBLE_LOGD(LOG_TAG, "<< deleteDescriptor"); NIMBLE_LOGD(LOG_TAG, "<< deleteDescriptor");
return m_vDescriptors.size();
return m_descriptorVector.size();
} // deleteDescriptor } // deleteDescriptor
/**
* @brief Does the characteristic support value broadcasting?
* @return True if supported.
*/
bool NimBLERemoteCharacteristic::canBroadcast() const {
return (m_properties & BLE_GATT_CHR_PROP_BROADCAST) != 0;
};
/**
* @brief Does the characteristic support reading?
* @return True if supported.
*/
bool NimBLERemoteCharacteristic::canRead() const {
return (m_properties & BLE_GATT_CHR_PROP_READ);
};
/**
* @brief Does the characteristic support writing without a response?
* @return True if supported.
*/
bool NimBLERemoteCharacteristic::canWriteNoResponse() const {
return (m_properties & BLE_GATT_CHR_PROP_WRITE_NO_RSP);
};
/**
* @brief Does the characteristic support writing?
* @return True if supported.
*/
bool NimBLERemoteCharacteristic::canWrite() const {
return (m_properties & BLE_GATT_CHR_PROP_WRITE);
};
/**
* @brief Does the characteristic support reading with encryption?
* @return True if supported.
*/
bool NimBLERemoteCharacteristic::canNotify() const {
return (m_properties & BLE_GATT_CHR_PROP_NOTIFY);
};
/**
* @brief Does the characteristic support indication?
* @return True if supported.
*/
bool NimBLERemoteCharacteristic::canIndicate() const {
return (m_properties & BLE_GATT_CHR_PROP_INDICATE);
};
/**
* @brief Does the characteristic support signed writing?
* @return True if supported.
*/
bool NimBLERemoteCharacteristic::canWriteSigned() const {
return (m_properties & BLE_GATT_CHR_PROP_AUTH_SIGN_WRITE);
};
/**
* @brief Does the characteristic support extended properties?
* @return True if supported.
*/
bool NimBLERemoteCharacteristic::hasExtendedProps() const {
return (m_properties & BLE_GATT_CHR_PROP_EXTENDED);
};
/** /**
* @brief Convert a NimBLERemoteCharacteristic to a string representation; * @brief Convert a NimBLERemoteCharacteristic to a string representation;
* @return a String representation. * @return a String representation.
*/ */
std::string NimBLERemoteCharacteristic::toString() { std::string NimBLERemoteCharacteristic::toString() const {
std::string res = "Characteristic: uuid: " + m_uuid.toString(); std::string res = "Characteristic: uuid: " + m_uuid.toString();
char val[6]; char val[6];
res += ", handle: "; res += ", handle: ";
@ -662,145 +387,18 @@ std::string NimBLERemoteCharacteristic::toString() {
res += val; res += val;
res += ", props: "; res += ", props: ";
res += " 0x"; res += " 0x";
snprintf(val, sizeof(val), "%02x", m_charProp); snprintf(val, sizeof(val), "%02x", m_properties);
res += val; res += val;
for(auto &it: m_descriptorVector) { for (const auto& it : m_vDescriptors) {
res += "\n" + it->toString(); res += "\n" + it->toString();
} }
return res; return res;
} // toString } // toString
NimBLEClient* NimBLERemoteCharacteristic::getClient() const {
/** return getRemoteService()->getClient();
* @brief Write a new value to the remote characteristic from a std::vector<uint8_t>. } // getClient
* @param [in] vec A std::vector<uint8_t> value to write to the remote characteristic.
* @param [in] response Whether we require a response from the write.
* @return false if not connected or otherwise cannot perform write.
*/
bool NimBLERemoteCharacteristic::writeValue(const std::vector<uint8_t>& vec, bool response) {
return writeValue((uint8_t*)&vec[0], vec.size(), response);
} // writeValue
/**
* @brief Write a new value to the remote characteristic from a const char*.
* @param [in] char_s A character string to write to the remote characteristic.
* @param [in] response Whether we require a response from the write.
* @return false if not connected or otherwise cannot perform write.
*/
bool NimBLERemoteCharacteristic::writeValue(const char* char_s, bool response) {
return writeValue((uint8_t*)char_s, strlen(char_s), response);
} // writeValue
/**
* @brief Write a new value to the remote characteristic from a data buffer.
* @param [in] data A pointer to a data buffer.
* @param [in] length The length of the data in the data buffer.
* @param [in] response Whether we require a response from the write.
* @return false if not connected or otherwise cannot perform write.
*/
bool NimBLERemoteCharacteristic::writeValue(const uint8_t* data, size_t length, bool response) {
NIMBLE_LOGD(LOG_TAG, ">> writeValue(), length: %d", length);
NimBLEClient* pClient = getRemoteService()->getClient();
if (!pClient->isConnected()) {
NIMBLE_LOGE(LOG_TAG, "Disconnected");
return false;
}
int rc = 0;
int retryCount = 1;
uint16_t mtu = ble_att_mtu(pClient->getConnId()) - 3;
// Check if the data length is longer than we can write in one connection event.
// If so we must do a long write which requires a response.
if(length <= mtu && !response) {
rc = ble_gattc_write_no_rsp_flat(pClient->getConnId(), m_handle, data, length);
return (rc==0);
}
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
ble_task_data_t taskData = {this, cur_task, 0, nullptr};
do {
if(length > mtu) {
NIMBLE_LOGI(LOG_TAG,"long write %d bytes", length);
os_mbuf *om = ble_hs_mbuf_from_flat(data, length);
rc = ble_gattc_write_long(pClient->getConnId(), m_handle, 0, om,
NimBLERemoteCharacteristic::onWriteCB,
&taskData);
} else {
rc = ble_gattc_write_flat(pClient->getConnId(), m_handle,
data, length,
NimBLERemoteCharacteristic::onWriteCB,
&taskData);
}
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Error: Failed to write characteristic; rc=%d", rc);
return false;
}
#ifdef ulTaskNotifyValueClear
// Clear the task notification value to ensure we block
ulTaskNotifyValueClear(cur_task, ULONG_MAX);
#endif
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
rc = taskData.rc;
switch(rc){
case 0:
case BLE_HS_EDONE:
rc = 0;
break;
case BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_LONG):
NIMBLE_LOGE(LOG_TAG, "Long write not supported by peer; Truncating length to %d", mtu);
retryCount++;
length = mtu;
break;
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHOR):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_ENC):
if (retryCount && pClient->secureConnection())
break;
/* Else falls through. */
default:
NIMBLE_LOGE(LOG_TAG, "<< writeValue, rc: %d", rc);
return false;
}
} while(rc != 0 && retryCount--);
NIMBLE_LOGD(LOG_TAG, "<< writeValue, rc: %d", rc);
return (rc == 0);
} // writeValue
/**
* @brief Callback for characteristic write operation.
* @return success == 0 or error code.
*/
int NimBLERemoteCharacteristic::onWriteCB(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr, void *arg)
{
ble_task_data_t *pTaskData = (ble_task_data_t*)arg;
NimBLERemoteCharacteristic *characteristic = (NimBLERemoteCharacteristic*)pTaskData->pATT;
if(characteristic->getRemoteService()->getClient()->getConnId() != conn_handle){
return 0;
}
NIMBLE_LOGI(LOG_TAG, "Write complete; status=%d conn_handle=%d", error->status, conn_handle);
pTaskData->rc = error->status;
xTaskNotifyGive(pTaskData->task);
return 0;
}
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */ #endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */

View file

@ -12,169 +12,67 @@
* Author: kolban * Author: kolban
*/ */
#ifndef COMPONENTS_NIMBLEREMOTECHARACTERISTIC_H_ #ifndef NIMBLE_CPP_REMOTE_CHARACTERISTIC_H_
#define COMPONENTS_NIMBLEREMOTECHARACTERISTIC_H_ #define NIMBLE_CPP_REMOTE_CHARACTERISTIC_H_
#include "nimconfig.h" #include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
#include "NimBLERemoteService.h" # include "NimBLERemoteValueAttribute.h"
#include "NimBLERemoteDescriptor.h"
# include <vector> # include <vector>
# include <functional> # include <functional>
#include "NimBLELog.h"
class NimBLERemoteService; class NimBLERemoteService;
class NimBLERemoteDescriptor; class NimBLERemoteDescriptor;
typedef std::function<void (NimBLERemoteCharacteristic* pBLERemoteCharacteristic,
uint8_t* pData, size_t length, bool isNotify)> notify_callback;
typedef struct {
const NimBLEUUID *uuid;
void *task_data;
} desc_filter_t;
/** /**
* @brief A model of a remote %BLE characteristic. * @brief A model of a remote BLE characteristic.
*/ */
class NimBLERemoteCharacteristic { class NimBLERemoteCharacteristic : public NimBLERemoteValueAttribute {
public: public:
~NimBLERemoteCharacteristic(); std::string toString() const;
const NimBLERemoteService* getRemoteService() const;
void deleteDescriptors() const;
size_t deleteDescriptor(const NimBLEUUID& uuid) const;
bool canBroadcast() const;
bool canRead() const;
bool canWriteNoResponse() const;
bool canWrite() const;
bool canNotify() const;
bool canIndicate() const;
bool canWriteSigned() const;
bool hasExtendedProps() const;
NimBLEClient* getClient() const override;
// Public member functions typedef std::function<void(NimBLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify)> notify_callback;
bool canBroadcast();
bool canIndicate();
bool canNotify();
bool canRead();
bool canWrite();
bool canWriteNoResponse();
std::vector<NimBLERemoteDescriptor*>::iterator begin();
std::vector<NimBLERemoteDescriptor*>::iterator end();
NimBLERemoteDescriptor* getDescriptor(const NimBLEUUID &uuid);
std::vector<NimBLERemoteDescriptor*>* getDescriptors(bool refresh = false);
void deleteDescriptors();
size_t deleteDescriptor(const NimBLEUUID &uuid);
uint16_t getHandle();
uint16_t getDefHandle();
NimBLEUUID getUUID();
NimBLEAttValue readValue(time_t *timestamp = nullptr);
std::string toString();
NimBLERemoteService* getRemoteService();
NimBLEAttValue getValue(time_t *timestamp = nullptr);
bool subscribe(bool notifications = true,
notify_callback notifyCallback = nullptr,
bool response = true);
bool unsubscribe(bool response = true);
bool writeValue(const uint8_t* data,
size_t length,
bool response = false);
bool writeValue(const std::vector<uint8_t>& v, bool response = false);
bool writeValue(const char* s, bool response = false);
bool subscribe(bool notifications = true, const notify_callback notifyCallback = nullptr, bool response = true) const;
bool unsubscribe(bool response = true) const;
/*********************** Template Functions ************************/ std::vector<NimBLERemoteDescriptor*>::iterator begin() const;
std::vector<NimBLERemoteDescriptor*>::iterator end() const;
/** NimBLERemoteDescriptor* getDescriptor(const NimBLEUUID& uuid) const;
* @brief Template to set the remote characteristic value to <type\>val. const std::vector<NimBLERemoteDescriptor*>& getDescriptors(bool refresh = false) const;
* @param [in] s The value to write.
* @param [in] response True == request write response.
* @details Only used for non-arrays and types without a `c_str()` method.
*/
template<typename T>
#ifdef _DOXYGEN_
bool
#else
typename std::enable_if<!std::is_array<T>::value && !Has_c_str_len<T>::value, bool>::type
#endif
writeValue(const T& s, bool response = false) {
return writeValue((uint8_t*)&s, sizeof(T), response);
}
/**
* @brief Template to set the remote characteristic value to <type\>val.
* @param [in] s The value to write.
* @param [in] response True == request write response.
* @details Only used if the <type\> has a `c_str()` method.
*/
template<typename T>
#ifdef _DOXYGEN_
bool
#else
typename std::enable_if<Has_c_str_len<T>::value, bool>::type
#endif
writeValue(const T& s, bool response = false) {
return writeValue((uint8_t*)s.c_str(), s.length(), response);
}
/**
* @brief Template to convert the remote characteristic data to <type\>.
* @tparam T The type to convert the data to.
* @param [in] timestamp A pointer to a time_t struct to store the time the value was read.
* @param [in] skipSizeCheck If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is
* less than <tt>sizeof(<type\>)</tt>.
* @details <b>Use:</b> <tt>getValue<type>(&timestamp, skipSizeCheck);</tt>
*/
template<typename T>
T getValue(time_t *timestamp = nullptr, bool skipSizeCheck = false) {
if(!skipSizeCheck && m_value.size() < sizeof(T)) return T();
return *((T *)m_value.getValue(timestamp));
}
/**
* @brief Template to convert the remote characteristic data to <type\>.
* @tparam T The type to convert the data to.
* @param [in] timestamp A pointer to a time_t struct to store the time the value was read.
* @param [in] skipSizeCheck If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is
* less than <tt>sizeof(<type\>)</tt>.
* @details <b>Use:</b> <tt>readValue<type>(&timestamp, skipSizeCheck);</tt>
*/
template<typename T>
T readValue(time_t *timestamp = nullptr, bool skipSizeCheck = false) {
NimBLEAttValue value = readValue();
if(!skipSizeCheck && value.size() < sizeof(T)) return T();
return *((T *)value.getValue(timestamp));
}
private: private:
NimBLERemoteCharacteristic(NimBLERemoteService *pRemoteservice, const struct ble_gatt_chr *chr);
friend class NimBLEClient; friend class NimBLEClient;
friend class NimBLERemoteService; friend class NimBLERemoteService;
friend class NimBLERemoteDescriptor;
// Private member functions NimBLERemoteCharacteristic(const NimBLERemoteService* pRemoteService, const ble_gatt_chr* chr);
bool setNotify(uint16_t val, notify_callback notifyCallback = nullptr, bool response = true); ~NimBLERemoteCharacteristic();
bool retrieveDescriptors(const NimBLEUUID *uuid_filter = nullptr);
static int onReadCB(uint16_t conn_handle, const struct ble_gatt_error *error,
struct ble_gatt_attr *attr, void *arg);
static int onWriteCB(uint16_t conn_handle, const struct ble_gatt_error *error,
struct ble_gatt_attr *attr, void *arg);
static int descriptorDiscCB(uint16_t conn_handle, const struct ble_gatt_error *error,
uint16_t chr_val_handle, const struct ble_gatt_dsc *dsc,
void *arg);
static int nextCharCB(uint16_t conn_handle, const struct ble_gatt_error *error,
const struct ble_gatt_chr *chr, void *arg);
// Private properties bool setNotify(uint16_t val, notify_callback notifyCallback = nullptr, bool response = true) const;
NimBLEUUID m_uuid; bool retrieveDescriptors(const NimBLEUUID* uuidFilter = nullptr) const;
uint8_t m_charProp;
uint16_t m_handle; static int descriptorDiscCB(
uint16_t m_defHandle; uint16_t conn_handle, const ble_gatt_error* error, uint16_t chr_val_handle, const ble_gatt_dsc* dsc, void* arg);
uint16_t m_endHandle;
NimBLERemoteService* m_pRemoteService; const NimBLERemoteService* m_pRemoteService{nullptr};
NimBLEAttValue m_value; uint8_t m_properties{0};
notify_callback m_notifyCallback; mutable notify_callback m_notifyCallback{nullptr};
mutable std::vector<NimBLERemoteDescriptor*> m_vDescriptors{};
// We maintain a vector of descriptors owned by this characteristic.
std::vector<NimBLERemoteDescriptor*> m_descriptorVector;
}; // NimBLERemoteCharacteristic }; // NimBLERemoteCharacteristic
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */ #endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */
#endif /* COMPONENTS_NIMBLEREMOTECHARACTERISTIC_H_ */ #endif /* NIMBLE_CPP_REMOTE_CHARACTERISTIC_H_ */

View file

@ -16,180 +16,30 @@
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
# include "NimBLERemoteDescriptor.h" # include "NimBLERemoteDescriptor.h"
#include "NimBLEUtils.h" # include "NimBLERemoteCharacteristic.h"
#include "NimBLELog.h"
#include <climits>
static const char* LOG_TAG = "NimBLERemoteDescriptor";
/** /**
* @brief Remote descriptor constructor. * @brief Remote descriptor constructor.
* @param [in] pRemoteCharacteristic A pointer to the Characteristic that this belongs to. * @param [in] pRemoteCharacteristic A pointer to the Characteristic that this belongs to.
* @param [in] dsc A pointer to the struct that contains the descriptor information. * @param [in] dsc A pointer to the struct that contains the descriptor information.
*/ */
NimBLERemoteDescriptor::NimBLERemoteDescriptor(NimBLERemoteCharacteristic* pRemoteCharacteristic, NimBLERemoteDescriptor::NimBLERemoteDescriptor(const NimBLERemoteCharacteristic* pRemoteCharacteristic,
const struct ble_gatt_dsc *dsc) const ble_gatt_dsc* dsc)
{ : NimBLERemoteValueAttribute{dsc->uuid, dsc->handle}, m_pRemoteCharacteristic{pRemoteCharacteristic} {} // NimBLERemoteDescriptor
NIMBLE_LOGD(LOG_TAG, ">> NimBLERemoteDescriptor()");
switch (dsc->uuid.u.type) {
case BLE_UUID_TYPE_16:
m_uuid = NimBLEUUID(dsc->uuid.u16.value);
break;
case BLE_UUID_TYPE_32:
m_uuid = NimBLEUUID(dsc->uuid.u32.value);
break;
case BLE_UUID_TYPE_128:
m_uuid = NimBLEUUID(const_cast<ble_uuid128_t*>(&dsc->uuid.u128));
break;
default:
break;
}
m_handle = dsc->handle;
m_pRemoteCharacteristic = pRemoteCharacteristic;
NIMBLE_LOGD(LOG_TAG, "<< NimBLERemoteDescriptor(): %s", m_uuid.toString().c_str());
}
/**
* @brief Retrieve the handle associated with this remote descriptor.
* @return The handle associated with this remote descriptor.
*/
uint16_t NimBLERemoteDescriptor::getHandle() {
return m_handle;
} // getHandle
/** /**
* @brief Get the characteristic that owns this descriptor. * @brief Get the characteristic that owns this descriptor.
* @return The characteristic that owns this descriptor. * @return The characteristic that owns this descriptor.
*/ */
NimBLERemoteCharacteristic* NimBLERemoteDescriptor::getRemoteCharacteristic() { NimBLERemoteCharacteristic* NimBLERemoteDescriptor::getRemoteCharacteristic() const {
return m_pRemoteCharacteristic; return const_cast<NimBLERemoteCharacteristic*>(m_pRemoteCharacteristic);
} // getRemoteCharacteristic } // getRemoteCharacteristic
/**
* @brief Retrieve the UUID associated this remote descriptor.
* @return The UUID associated this remote descriptor.
*/
NimBLEUUID NimBLERemoteDescriptor::getUUID() {
return m_uuid;
} // getUUID
/**
* @brief Read the value of the remote descriptor.
* @return The value of the remote descriptor.
*/
NimBLEAttValue NimBLERemoteDescriptor::readValue() {
NIMBLE_LOGD(LOG_TAG, ">> Descriptor readValue: %s", toString().c_str());
NimBLEClient* pClient = getRemoteCharacteristic()->getRemoteService()->getClient();
NimBLEAttValue value;
if (!pClient->isConnected()) {
NIMBLE_LOGE(LOG_TAG, "Disconnected");
return value;
}
int rc = 0;
int retryCount = 1;
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
ble_task_data_t taskData = {this, cur_task, 0, &value};
do {
rc = ble_gattc_read_long(pClient->getConnId(), m_handle, 0,
NimBLERemoteDescriptor::onReadCB,
&taskData);
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Error: Failed to read descriptor; rc=%d, %s",
rc, NimBLEUtils::returnCodeToString(rc));
return value;
}
#ifdef ulTaskNotifyValueClear
// Clear the task notification value to ensure we block
ulTaskNotifyValueClear(cur_task, ULONG_MAX);
#endif
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
rc = taskData.rc;
switch(rc){
case 0:
case BLE_HS_EDONE:
rc = 0;
break;
// Descriptor is not long-readable, return with what we have.
case BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_LONG):
NIMBLE_LOGI(LOG_TAG, "Attribute not long");
rc = 0;
break;
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHOR):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_ENC):
if (retryCount && pClient->secureConnection())
break;
/* Else falls through. */
default:
return value;
}
} while(rc != 0 && retryCount--);
NIMBLE_LOGD(LOG_TAG, "<< Descriptor readValue(): length: %u rc=%d", value.length(), rc);
return value;
} // readValue
/**
* @brief Callback for Descriptor read operation.
* @return success == 0 or error code.
*/
int NimBLERemoteDescriptor::onReadCB(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr, void *arg)
{
(void)attr;
ble_task_data_t *pTaskData = (ble_task_data_t*)arg;
NimBLERemoteDescriptor* desc = (NimBLERemoteDescriptor*)pTaskData->pATT;
uint16_t conn_id = desc->getRemoteCharacteristic()->getRemoteService()->getClient()->getConnId();
if(conn_id != conn_handle){
return 0;
}
NIMBLE_LOGD(LOG_TAG, "Read complete; status=%d conn_handle=%d", error->status, conn_handle);
NimBLEAttValue *valBuf = (NimBLEAttValue*)pTaskData->buf;
int rc = error->status;
if(rc == 0) {
if(attr) {
uint16_t data_len = OS_MBUF_PKTLEN(attr->om);
if((valBuf->size() + data_len) > BLE_ATT_ATTR_MAX_LEN) {
rc = BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} else {
NIMBLE_LOGD(LOG_TAG, "Got %u bytes", data_len);
valBuf->append(attr->om->om_data, data_len);
return 0;
}
}
}
pTaskData->rc = rc;
xTaskNotifyGive(pTaskData->task);
return rc;
}
/** /**
* @brief Return a string representation of this Remote Descriptor. * @brief Return a string representation of this Remote Descriptor.
* @return A string representation of this Remote Descriptor. * @return A string representation of this Remote Descriptor.
*/ */
std::string NimBLERemoteDescriptor::toString() { std::string NimBLERemoteDescriptor::toString() const {
std::string res = "Descriptor: uuid: " + getUUID().toString(); std::string res = "Descriptor: uuid: " + getUUID().toString();
char val[6]; char val[6];
res += ", handle: "; res += ", handle: ";
@ -199,137 +49,8 @@ std::string NimBLERemoteDescriptor::toString() {
return res; return res;
} // toString } // toString
NimBLEClient* NimBLERemoteDescriptor::getClient() const {
/** return m_pRemoteCharacteristic->getClient();
* @brief Callback for descriptor write operation.
* @return success == 0 or error code.
*/
int NimBLERemoteDescriptor::onWriteCB(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr, void *arg)
{
ble_task_data_t *pTaskData = (ble_task_data_t*)arg;
NimBLERemoteDescriptor* descriptor = (NimBLERemoteDescriptor*)pTaskData->pATT;
if(descriptor->getRemoteCharacteristic()->getRemoteService()->getClient()->getConnId() != conn_handle){
return 0;
} }
NIMBLE_LOGI(LOG_TAG, "Write complete; status=%d conn_handle=%d", error->status, conn_handle);
pTaskData->rc = error->status;
xTaskNotifyGive(pTaskData->task);
return 0;
}
/**
* @brief Write a new value to a remote descriptor from a std::vector<uint8_t>.
* @param [in] vec A std::vector<uint8_t> value to write to the remote descriptor.
* @param [in] response Whether we require a response from the write.
* @return false if not connected or otherwise cannot perform write.
*/
bool NimBLERemoteDescriptor::writeValue(const std::vector<uint8_t>& vec, bool response) {
return writeValue((uint8_t*)&vec[0], vec.size(), response);
} // writeValue
/**
* @brief Write a new value to the remote descriptor from a const char*.
* @param [in] char_s A character string to write to the remote descriptor.
* @param [in] response Whether we require a response from the write.
* @return false if not connected or otherwise cannot perform write.
*/
bool NimBLERemoteDescriptor::writeValue(const char* char_s, bool response) {
return writeValue((uint8_t*)char_s, strlen(char_s), response);
} // writeValue
/**
* @brief Write a new value to a remote descriptor.
* @param [in] data The data to send to the remote descriptor.
* @param [in] length The length of the data to send.
* @param [in] response True if we expect a write response.
* @return false if not connected or otherwise cannot perform write.
*/
bool NimBLERemoteDescriptor::writeValue(const uint8_t* data, size_t length, bool response) {
NIMBLE_LOGD(LOG_TAG, ">> Descriptor writeValue: %s", toString().c_str());
NimBLEClient* pClient = getRemoteCharacteristic()->getRemoteService()->getClient();
// Check to see that we are connected.
if (!pClient->isConnected()) {
NIMBLE_LOGE(LOG_TAG, "Disconnected");
return false;
}
int rc = 0;
int retryCount = 1;
uint16_t mtu = ble_att_mtu(pClient->getConnId()) - 3;
// Check if the data length is longer than we can write in 1 connection event.
// If so we must do a long write which requires a response.
if(length <= mtu && !response) {
rc = ble_gattc_write_no_rsp_flat(pClient->getConnId(), m_handle, data, length);
return (rc == 0);
}
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
ble_task_data_t taskData = {this, cur_task, 0, nullptr};
do {
if(length > mtu) {
NIMBLE_LOGI(LOG_TAG,"long write %d bytes", length);
os_mbuf *om = ble_hs_mbuf_from_flat(data, length);
rc = ble_gattc_write_long(pClient->getConnId(), m_handle, 0, om,
NimBLERemoteDescriptor::onWriteCB,
&taskData);
} else {
rc = ble_gattc_write_flat(pClient->getConnId(), m_handle,
data, length,
NimBLERemoteDescriptor::onWriteCB,
&taskData);
}
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "Error: Failed to write descriptor; rc=%d", rc);
return false;
}
#ifdef ulTaskNotifyValueClear
// Clear the task notification value to ensure we block
ulTaskNotifyValueClear(cur_task, ULONG_MAX);
#endif
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
rc = taskData.rc;
switch(rc) {
case 0:
case BLE_HS_EDONE:
rc = 0;
break;
case BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_LONG):
NIMBLE_LOGE(LOG_TAG, "Long write not supported by peer; Truncating length to %d", mtu);
retryCount++;
length = mtu;
break;
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHOR):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_ENC):
if (retryCount && pClient->secureConnection())
break;
/* Else falls through. */
default:
return false;
}
} while(rc != 0 && retryCount--);
NIMBLE_LOGD(LOG_TAG, "<< Descriptor writeValue, rc: %d",rc);
return (rc == 0);
} // writeValue
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */ #endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */

View file

@ -12,93 +12,34 @@
* Author: kolban * Author: kolban
*/ */
#ifndef COMPONENTS_NIMBLEREMOTEDESCRIPTOR_H_ #ifndef NIMBLE_CPP_REMOTE_DESCRIPTOR_H_
#define COMPONENTS_NIMBLEREMOTEDESCRIPTOR_H_ #define NIMBLE_CPP_REMOTE_DESCRIPTOR_H_
#include "nimconfig.h" #include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
#include "NimBLERemoteCharacteristic.h" # include "NimBLERemoteValueAttribute.h"
class NimBLERemoteCharacteristic; class NimBLERemoteCharacteristic;
class NimBLEClient;
/** /**
* @brief A model of remote %BLE descriptor. * @brief A model of remote BLE descriptor.
*/ */
class NimBLERemoteDescriptor { class NimBLERemoteDescriptor : public NimBLERemoteValueAttribute {
public: public:
uint16_t getHandle(); NimBLERemoteCharacteristic* getRemoteCharacteristic() const;
NimBLERemoteCharacteristic* getRemoteCharacteristic(); std::string toString(void) const;
NimBLEUUID getUUID(); NimBLEClient* getClient() const override;
NimBLEAttValue readValue();
std::string toString(void);
bool writeValue(const uint8_t* data, size_t length, bool response = false);
bool writeValue(const std::vector<uint8_t>& v, bool response = false);
bool writeValue(const char* s, bool response = false);
/*********************** Template Functions ************************/
/**
* @brief Template to set the remote descriptor value to <type\>val.
* @param [in] s The value to write.
* @param [in] response True == request write response.
* @details Only used for non-arrays and types without a `c_str()` method.
*/
template<typename T>
#ifdef _DOXYGEN_
bool
#else
typename std::enable_if<!std::is_array<T>::value && !Has_c_str_len<T>::value, bool>::type
#endif
writeValue(const T& s, bool response = false) {
return writeValue((uint8_t*)&s, sizeof(T), response);
}
/**
* @brief Template to set the remote descriptor value to <type\>val.
* @param [in] s The value to write.
* @param [in] response True == request write response.
* @details Only used if the <type\> has a `c_str()` method.
*/
template<typename T>
#ifdef _DOXYGEN_
bool
#else
typename std::enable_if<Has_c_str_len<T>::value, bool>::type
#endif
writeValue(const T& s, bool response = false) {
return writeValue((uint8_t*)s.c_str(), s.length(), response);
}
/**
* @brief Template to convert the remote descriptor data to <type\>.
* @tparam T The type to convert the data to.
* @param [in] skipSizeCheck If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is
* less than <tt>sizeof(<type\>)</tt>.
* @details <b>Use:</b> <tt>readValue<type>(skipSizeCheck);</tt>
*/
template<typename T>
T readValue(bool skipSizeCheck = false) {
NimBLEAttValue value = readValue();
if(!skipSizeCheck && value.size() < sizeof(T)) return T();
return *((T *)value.data());
}
private: private:
friend class NimBLERemoteCharacteristic; friend class NimBLERemoteCharacteristic;
NimBLERemoteDescriptor (NimBLERemoteCharacteristic* pRemoteCharacteristic, NimBLERemoteDescriptor(const NimBLERemoteCharacteristic* pRemoteCharacteristic, const ble_gatt_dsc* dsc);
const struct ble_gatt_dsc *dsc); ~NimBLERemoteDescriptor() = default;
static int onWriteCB(uint16_t conn_handle, const struct ble_gatt_error *error,
struct ble_gatt_attr *attr, void *arg);
static int onReadCB(uint16_t conn_handle, const struct ble_gatt_error *error,
struct ble_gatt_attr *attr, void *arg);
uint16_t m_handle; const NimBLERemoteCharacteristic* m_pRemoteCharacteristic;
NimBLEUUID m_uuid;
NimBLERemoteCharacteristic* m_pRemoteCharacteristic;
}; };
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */ #endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */
#endif /* COMPONENTS_NIMBLEREMOTEDESCRIPTOR_H_ */ #endif /* NIMBLE_CPP_REMOTE_DESCRIPTOR_H_ */

View file

@ -16,8 +16,10 @@
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
# include "NimBLERemoteService.h" # include "NimBLERemoteService.h"
# include "NimBLERemoteCharacteristic.h"
# include "NimBLEClient.h"
# include "NimBLEAttValue.h"
# include "NimBLEUtils.h" # include "NimBLEUtils.h"
#include "NimBLEDevice.h"
# include "NimBLELog.h" # include "NimBLELog.h"
# include <climits> # include <climits>
@ -29,28 +31,8 @@ static const char* LOG_TAG = "NimBLERemoteService";
* @param [in] pClient A pointer to the client this belongs to. * @param [in] pClient A pointer to the client this belongs to.
* @param [in] service A pointer to the structure with the service information. * @param [in] service A pointer to the structure with the service information.
*/ */
NimBLERemoteService::NimBLERemoteService(NimBLEClient* pClient, const struct ble_gatt_svc* service) { NimBLERemoteService::NimBLERemoteService(NimBLEClient* pClient, const ble_gatt_svc* service)
: NimBLEAttribute{service->uuid, service->start_handle}, m_pClient{pClient}, m_endHandle{service->end_handle} {}
NIMBLE_LOGD(LOG_TAG, ">> NimBLERemoteService()");
m_pClient = pClient;
switch (service->uuid.u.type) {
case BLE_UUID_TYPE_16:
m_uuid = NimBLEUUID(service->uuid.u16.value);
break;
case BLE_UUID_TYPE_32:
m_uuid = NimBLEUUID(service->uuid.u32.value);
break;
case BLE_UUID_TYPE_128:
m_uuid = NimBLEUUID(const_cast<ble_uuid128_t*>(&service->uuid.u128));
break;
default:
break;
}
m_startHandle = service->start_handle;
m_endHandle = service->end_handle;
NIMBLE_LOGD(LOG_TAG, "<< NimBLERemoteService(): %s", m_uuid.toString().c_str());
}
/** /**
* @brief When deleting the service make sure we delete all characteristics and descriptors. * @brief When deleting the service make sure we delete all characteristics and descriptors.
@ -59,66 +41,62 @@ NimBLERemoteService::~NimBLERemoteService() {
deleteCharacteristics(); deleteCharacteristics();
} }
/** /**
* @brief Get iterator to the beginning of the vector of remote characteristic pointers. * @brief Get iterator to the beginning of the vector of remote characteristic pointers.
* @return An iterator to the beginning of the vector of remote characteristic pointers. * @return An iterator to the beginning of the vector of remote characteristic pointers.
*/ */
std::vector<NimBLERemoteCharacteristic*>::iterator NimBLERemoteService::begin() { std::vector<NimBLERemoteCharacteristic*>::iterator NimBLERemoteService::begin() const {
return m_characteristicVector.begin(); return m_vChars.begin();
} }
/** /**
* @brief Get iterator to the end of the vector of remote characteristic pointers. * @brief Get iterator to the end of the vector of remote characteristic pointers.
* @return An iterator to the end of the vector of remote characteristic pointers. * @return An iterator to the end of the vector of remote characteristic pointers.
*/ */
std::vector<NimBLERemoteCharacteristic*>::iterator NimBLERemoteService::end() { std::vector<NimBLERemoteCharacteristic*>::iterator NimBLERemoteService::end() const {
return m_characteristicVector.end(); return m_vChars.end();
} }
/** /**
* @brief Get the remote characteristic object for the characteristic UUID. * @brief Get the remote characteristic object for the characteristic UUID.
* @param [in] uuid Remote characteristic uuid. * @param [in] uuid Remote characteristic uuid.
* @return A pointer to the remote characteristic object. * @return A pointer to the remote characteristic object.
*/ */
NimBLERemoteCharacteristic* NimBLERemoteService::getCharacteristic(const char* uuid) { NimBLERemoteCharacteristic* NimBLERemoteService::getCharacteristic(const char* uuid) const {
return getCharacteristic(NimBLEUUID(uuid)); return getCharacteristic(NimBLEUUID(uuid));
} // getCharacteristic } // getCharacteristic
/** /**
* @brief Get the characteristic object for the UUID. * @brief Get the characteristic object for the UUID.
* @param [in] uuid Characteristic uuid. * @param [in] uuid Characteristic uuid.
* @return A pointer to the characteristic object, or nullptr if not found. * @return A pointer to the characteristic object, or nullptr if not found.
*/ */
NimBLERemoteCharacteristic* NimBLERemoteService::getCharacteristic(const NimBLEUUID &uuid) { NimBLERemoteCharacteristic* NimBLERemoteService::getCharacteristic(const NimBLEUUID& uuid) const {
NIMBLE_LOGD(LOG_TAG, ">> getCharacteristic: uuid: %s", uuid.toString().c_str()); NIMBLE_LOGD(LOG_TAG, ">> getCharacteristic: uuid: %s", uuid.toString().c_str());
NimBLERemoteCharacteristic* pChar = nullptr;
size_t prev_size = m_vChars.size();
for(auto &it: m_characteristicVector) { for (const auto& it : m_vChars) {
if (it->getUUID() == uuid) { if (it->getUUID() == uuid) {
NIMBLE_LOGD(LOG_TAG, "<< getCharacteristic: found the characteristic with uuid: %s", uuid.toString().c_str()); pChar = it;
return it; goto Done;
} }
} }
size_t prev_size = m_characteristicVector.size();
if (retrieveCharacteristics(&uuid)) { if (retrieveCharacteristics(&uuid)) {
if(m_characteristicVector.size() > prev_size) { if (m_vChars.size() > prev_size) {
return m_characteristicVector.back(); pChar = m_vChars.back();
goto Done;
} }
// If the request was successful but 16/32 bit uuid not found // If the request was successful but 16/32 bit uuid not found
// try again with the 128 bit uuid. // try again with the 128 bit uuid.
if(uuid.bitSize() == BLE_UUID_TYPE_16 || if (uuid.bitSize() == BLE_UUID_TYPE_16 || uuid.bitSize() == BLE_UUID_TYPE_32) {
uuid.bitSize() == BLE_UUID_TYPE_32)
{
NimBLEUUID uuid128(uuid); NimBLEUUID uuid128(uuid);
uuid128.to128(); uuid128.to128();
if (retrieveCharacteristics(&uuid128)) { if (retrieveCharacteristics(&uuid128)) {
if(m_characteristicVector.size() > prev_size) { if (m_vChars.size() > prev_size) {
return m_characteristicVector.back(); pChar = m_vChars.back();
} }
} }
} else { } else {
@ -129,282 +107,188 @@ NimBLERemoteCharacteristic* NimBLERemoteService::getCharacteristic(const NimBLEU
// if the uuid was 128 bit but not of the BLE base type this check will fail // if the uuid was 128 bit but not of the BLE base type this check will fail
if (uuid16.bitSize() == BLE_UUID_TYPE_16) { if (uuid16.bitSize() == BLE_UUID_TYPE_16) {
if (retrieveCharacteristics(&uuid16)) { if (retrieveCharacteristics(&uuid16)) {
if(m_characteristicVector.size() > prev_size) { if (m_vChars.size() > prev_size) {
return m_characteristicVector.back(); pChar = m_vChars.back();
} }
} }
} }
} }
} }
NIMBLE_LOGD(LOG_TAG, "<< getCharacteristic: not found"); Done:
return nullptr; NIMBLE_LOGD(LOG_TAG, "<< Characteristic %sfound", pChar ? "" : "not ");
return pChar;
} // getCharacteristic } // getCharacteristic
/** /**
* @brief Get a pointer to the vector of found characteristics. * @brief Get a pointer to the vector of found characteristics.
* @param [in] refresh If true the current characteristics vector will cleared and * @param [in] refresh If true the current characteristics vector will cleared and
* all characteristics for this service retrieved from the peripheral. * all characteristics for this service retrieved from the peripheral.
* If false the vector will be returned with the currently stored characteristics of this service. * If false the vector will be returned with the currently stored characteristics of this service.
* @return A pointer to the vector of descriptors for this characteristic. * @return A read-only reference to the vector of characteristics retrieved for this service.
*/ */
std::vector<NimBLERemoteCharacteristic*>* NimBLERemoteService::getCharacteristics(bool refresh) { const std::vector<NimBLERemoteCharacteristic*>& NimBLERemoteService::getCharacteristics(bool refresh) const {
if (refresh) { if (refresh) {
deleteCharacteristics(); deleteCharacteristics();
retrieveCharacteristics();
}
if (!retrieveCharacteristics()) { return m_vChars;
NIMBLE_LOGE(LOG_TAG, "Error: Failed to get characteristics");
}
else{
NIMBLE_LOGI(LOG_TAG, "Found %d characteristics", m_characteristicVector.size());
}
}
return &m_characteristicVector;
} // getCharacteristics } // getCharacteristics
/** /**
* @brief Callback for Characteristic discovery. * @brief Callback for Characteristic discovery.
* @return success == 0 or error code. * @return success == 0 or error code.
*/ */
int NimBLERemoteService::characteristicDiscCB(uint16_t conn_handle, int NimBLERemoteService::characteristicDiscCB(uint16_t conn_handle,
const struct ble_gatt_error *error, const ble_gatt_error* error,
const struct ble_gatt_chr *chr, void *arg) const ble_gatt_chr* chr,
{ void* arg) {
NIMBLE_LOGD(LOG_TAG,"Characteristic Discovered >> status: %d handle: %d", NIMBLE_LOGD(LOG_TAG, "Characteristic Discovery >>");
error->status, (error->status == 0) ? chr->val_handle : -1); auto pTaskData = (ble_task_data_t*)arg;
const auto pSvc = (NimBLERemoteService*)pTaskData->pATT;
ble_task_data_t *pTaskData = (ble_task_data_t*)arg;
NimBLERemoteService *service = (NimBLERemoteService*)pTaskData->pATT;
// Make sure the discovery is for this device // Make sure the discovery is for this device
if(service->getClient()->getConnId() != conn_handle){ if (pSvc->getClient()->getConnId() != conn_handle) {
return 0; return 0;
} }
if (error->status == 0) { if (error->status == 0) {
// Found a service - add it to the vector pSvc->m_vChars.push_back(new NimBLERemoteCharacteristic(pSvc, chr));
NimBLERemoteCharacteristic* pRemoteCharacteristic = new NimBLERemoteCharacteristic(service, chr);
service->m_characteristicVector.push_back(pRemoteCharacteristic);
return 0;
}
if(error->status == BLE_HS_EDONE) {
pTaskData->rc = 0;
} else { } else {
NIMBLE_LOGE(LOG_TAG, "characteristicDiscCB() rc=%d %s", pTaskData->rc = error->status == BLE_HS_EDONE ? 0 : error->status;
error->status, NIMBLE_LOGD(LOG_TAG, "<< Characteristic Discovery");
NimBLEUtils::returnCodeToString(error->status)); xTaskNotifyGive(pTaskData->task);
pTaskData->rc = error->status;
} }
xTaskNotifyGive(pTaskData->task);
NIMBLE_LOGD(LOG_TAG,"<< Characteristic Discovered");
return error->status; return error->status;
} }
/** /**
* @brief Retrieve all the characteristics for this service. * @brief Retrieve all the characteristics for this service.
* This function will not return until we have all the characteristics. * This function will not return until we have all the characteristics.
* @return True if successful. * @return True if successful.
*/ */
bool NimBLERemoteService::retrieveCharacteristics(const NimBLEUUID *uuid_filter) { bool NimBLERemoteService::retrieveCharacteristics(const NimBLEUUID* uuidFilter) const {
NIMBLE_LOGD(LOG_TAG, ">> retrieveCharacteristics() for service: %s", getUUID().toString().c_str()); NIMBLE_LOGD(LOG_TAG, ">> retrieveCharacteristics()");
int rc = 0; int rc = 0;
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
ble_task_data_t taskData = {this, cur_task, 0, nullptr}; ble_task_data_t taskData = {const_cast<NimBLERemoteService*>(this), cur_task, 0, nullptr};
if(uuid_filter == nullptr) { if (uuidFilter == nullptr) {
rc = ble_gattc_disc_all_chrs(m_pClient->getConnId(), rc = ble_gattc_disc_all_chrs(m_pClient->getConnId(),
m_startHandle, getHandle(),
m_endHandle, getEndHandle(),
NimBLERemoteService::characteristicDiscCB, NimBLERemoteService::characteristicDiscCB,
&taskData); &taskData);
} else { } else {
rc = ble_gattc_disc_chrs_by_uuid(m_pClient->getConnId(), rc = ble_gattc_disc_chrs_by_uuid(m_pClient->getConnId(),
m_startHandle, getHandle(),
m_endHandle, getEndHandle(),
uuid_filter->getBase(), uuidFilter->getBase(),
NimBLERemoteService::characteristicDiscCB, NimBLERemoteService::characteristicDiscCB,
&taskData); &taskData);
} }
if (rc != 0) { if (rc == 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gattc_disc_all_chrs: rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
return false;
}
# ifdef ulTaskNotifyValueClear # ifdef ulTaskNotifyValueClear
// Clear the task notification value to ensure we block // Clear the task notification value to ensure we block
ulTaskNotifyValueClear(cur_task, ULONG_MAX); ulTaskNotifyValueClear(cur_task, ULONG_MAX);
# endif # endif
ulTaskNotifyTake(pdTRUE, portMAX_DELAY); ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if(taskData.rc == 0){
if (uuid_filter == nullptr) {
if (m_characteristicVector.size() > 1) {
for (auto it = m_characteristicVector.begin(); it != m_characteristicVector.end(); ++it ) {
auto nx = std::next(it, 1);
if (nx == m_characteristicVector.end()) {
break;
}
(*it)->m_endHandle = (*nx)->m_defHandle - 1;
}
}
if (m_characteristicVector.size() > 0) {
m_characteristicVector.back()->m_endHandle = getEndHandle();
}
} }
if (taskData.rc != 0) {
NIMBLE_LOGE(LOG_TAG, "<< retrieveCharacteristics() rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
} else {
NIMBLE_LOGD(LOG_TAG, "<< retrieveCharacteristics()"); NIMBLE_LOGD(LOG_TAG, "<< retrieveCharacteristics()");
return true;
} }
NIMBLE_LOGE(LOG_TAG, "Could not retrieve characteristics"); return taskData.rc == 0;
return false;
} // retrieveCharacteristics } // retrieveCharacteristics
/** /**
* @brief Get the client associated with this service. * @brief Get the client associated with this service.
* @return A reference to the client associated with this service. * @return A reference to the client associated with this service.
*/ */
NimBLEClient* NimBLERemoteService::getClient() { NimBLEClient* NimBLERemoteService::getClient() const {
return m_pClient; return m_pClient;
} // getClient } // getClient
/**
* @brief Get the service end handle.
*/
uint16_t NimBLERemoteService::getEndHandle() {
return m_endHandle;
} // getEndHandle
/**
* @brief Get the service start handle.
*/
uint16_t NimBLERemoteService::getStartHandle() {
return m_startHandle;
} // getStartHandle
/**
* @brief Get the service UUID.
*/
NimBLEUUID NimBLERemoteService::getUUID() {
return m_uuid;
}
/** /**
* @brief Read the value of a characteristic associated with this service. * @brief Read the value of a characteristic associated with this service.
* @param [in] characteristicUuid The characteristic to read. * @param [in] uuid The characteristic to read.
* @returns a string containing the value or an empty string if not found or error. * @returns a string containing the value or an empty string if not found or error.
*/ */
std::string NimBLERemoteService::getValue(const NimBLEUUID &characteristicUuid) { NimBLEAttValue NimBLERemoteService::getValue(const NimBLEUUID& uuid) const {
NIMBLE_LOGD(LOG_TAG, ">> readValue: uuid: %s", characteristicUuid.toString().c_str()); const auto pChar = getCharacteristic(uuid);
if (pChar) {
std::string ret = ""; return pChar->readValue();
NimBLERemoteCharacteristic* pChar = getCharacteristic(characteristicUuid);
if(pChar != nullptr) {
ret = pChar->readValue();
} }
NIMBLE_LOGD(LOG_TAG, "<< readValue"); return NimBLEAttValue{};
return ret;
} // readValue } // readValue
/** /**
* @brief Set the value of a characteristic. * @brief Set the value of a characteristic.
* @param [in] characteristicUuid The characteristic to set. * @param [in] uuid The characteristic UUID to set.
* @param [in] value The value to set. * @param [in] value The value to set.
* @returns true on success, false if not found or error * @returns true on success, false if not found or error
*/ */
bool NimBLERemoteService::setValue(const NimBLEUUID &characteristicUuid, const std::string &value) { bool NimBLERemoteService::setValue(const NimBLEUUID& uuid, const NimBLEAttValue& value) const {
NIMBLE_LOGD(LOG_TAG, ">> setValue: uuid: %s", characteristicUuid.toString().c_str()); const auto pChar = getCharacteristic(uuid);
if (pChar) {
bool ret = false; return pChar->writeValue(value);
NimBLERemoteCharacteristic* pChar = getCharacteristic(characteristicUuid);
if(pChar != nullptr) {
ret = pChar->writeValue(value);
} }
NIMBLE_LOGD(LOG_TAG, "<< setValue"); return false;
return ret;
} // setValue } // setValue
/** /**
* @brief Delete the characteristics in the characteristics vector. * @brief Delete the characteristics in the characteristics vector.
* @details We maintain a vector called m_characteristicsVector that contains pointers to BLERemoteCharacteristic * @details We maintain a vector called m_characteristicsVector that contains pointers to BLERemoteCharacteristic
* object references. Since we allocated these in this class, we are also responsible for deleting * object references. Since we allocated these in this class, we are also responsible for deleting
* them. This method does just that. * them. This method does just that.
*/ */
void NimBLERemoteService::deleteCharacteristics() { void NimBLERemoteService::deleteCharacteristics() const {
NIMBLE_LOGD(LOG_TAG, ">> deleteCharacteristics"); for (const auto& it : m_vChars) {
for(auto &it: m_characteristicVector) {
delete it; delete it;
} }
m_characteristicVector.clear(); std::vector<NimBLERemoteCharacteristic*>{}.swap(m_vChars);
NIMBLE_LOGD(LOG_TAG, "<< deleteCharacteristics");
} // deleteCharacteristics } // deleteCharacteristics
/** /**
* @brief Delete characteristic by UUID * @brief Delete characteristic by UUID
* @param [in] uuid The UUID of the characteristic to be removed from the local database. * @param [in] uuid The UUID of the characteristic to be removed from the local database.
* @return Number of characteristics left. * @return Number of characteristics left.
*/ */
size_t NimBLERemoteService::deleteCharacteristic(const NimBLEUUID &uuid) { size_t NimBLERemoteService::deleteCharacteristic(const NimBLEUUID& uuid) const {
NIMBLE_LOGD(LOG_TAG, ">> deleteCharacteristic"); for (auto it = m_vChars.begin(); it != m_vChars.end(); ++it) {
for(auto it = m_characteristicVector.begin(); it != m_characteristicVector.end(); ++it) {
if ((*it)->getUUID() == uuid) { if ((*it)->getUUID() == uuid) {
delete *it; delete (*it);
m_characteristicVector.erase(it); m_vChars.erase(it);
break; break;
} }
} }
NIMBLE_LOGD(LOG_TAG, "<< deleteCharacteristic"); return m_vChars.size();
return m_characteristicVector.size();
} // deleteCharacteristic } // deleteCharacteristic
/** /**
* @brief Create a string representation of this remote service. * @brief Create a string representation of this remote service.
* @return A string representation of this remote service. * @return A string representation of this remote service.
*/ */
std::string NimBLERemoteService::toString() { std::string NimBLERemoteService::toString() const {
std::string res = "Service: uuid: " + m_uuid.toString(); std::string res = "Service: uuid: " + m_uuid.toString() + ", start_handle: 0x";
char val[6]; char val[5];
res += ", start_handle: "; snprintf(val, sizeof(val), "%04x", getHandle());
snprintf(val, sizeof(val), "%d", m_startHandle);
res += val; res += val;
snprintf(val, sizeof(val), "%04x", m_startHandle); res += ", end_handle: 0x";
res += " 0x"; snprintf(val, sizeof(val), "%04x", getEndHandle());
res += val;
res += ", end_handle: ";
snprintf(val, sizeof(val), "%d", m_endHandle);
res += val;
snprintf(val, sizeof(val), "%04x", m_endHandle);
res += " 0x";
res += val; res += val;
for (auto &it: m_characteristicVector) { for (const auto& chr : m_vChars) {
res += "\n" + it->toString(); res += "\n" + chr->toString();
} }
return res; return res;

View file

@ -12,74 +12,54 @@
* Author: kolban * Author: kolban
*/ */
#ifndef COMPONENTS_NIMBLEREMOTESERVICE_H_ #ifndef NIMBLE_CPP_REMOTE_SERVICE_H_
#define COMPONENTS_NIMBLEREMOTESERVICE_H_ #define NIMBLE_CPP_REMOTE_SERVICE_H_
#include "nimconfig.h" #include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
#include "NimBLEClient.h" # include "NimBLEAttribute.h"
#include "NimBLEUUID.h"
#include "NimBLERemoteCharacteristic.h"
# include <vector> # include <vector>
class NimBLEClient;
class NimBLERemoteCharacteristic; class NimBLERemoteCharacteristic;
class NimBLEClient;
class NimBLEAttValue;
/** /**
* @brief A model of a remote %BLE service. * @brief A model of a remote BLE service.
*/ */
class NimBLERemoteService { class NimBLERemoteService : public NimBLEAttribute {
public: public:
virtual ~NimBLERemoteService(); NimBLERemoteCharacteristic* getCharacteristic(const char* uuid) const;
NimBLERemoteCharacteristic* getCharacteristic(const NimBLEUUID& uuid) const;
void deleteCharacteristics() const;
size_t deleteCharacteristic(const NimBLEUUID& uuid) const;
NimBLEClient* getClient(void) const;
NimBLEAttValue getValue(const NimBLEUUID& characteristicUuid) const;
bool setValue(const NimBLEUUID& characteristicUuid, const NimBLEAttValue& value) const;
std::string toString(void) const;
uint16_t getStartHandle() const { return getHandle(); }
uint16_t getEndHandle() const { return m_endHandle; }
// Public methods const std::vector<NimBLERemoteCharacteristic*>& getCharacteristics(bool refresh = false) const;
std::vector<NimBLERemoteCharacteristic*>::iterator begin(); std::vector<NimBLERemoteCharacteristic*>::iterator begin() const;
std::vector<NimBLERemoteCharacteristic*>::iterator end(); std::vector<NimBLERemoteCharacteristic*>::iterator end() const;
NimBLERemoteCharacteristic* getCharacteristic(const char* uuid);
NimBLERemoteCharacteristic* getCharacteristic(const NimBLEUUID &uuid);
void deleteCharacteristics();
size_t deleteCharacteristic(const NimBLEUUID &uuid);
NimBLEClient* getClient(void);
//uint16_t getHandle();
NimBLEUUID getUUID(void);
std::string getValue(const NimBLEUUID &characteristicUuid);
bool setValue(const NimBLEUUID &characteristicUuid,
const std::string &value);
std::string toString(void);
std::vector<NimBLERemoteCharacteristic*>* getCharacteristics(bool refresh = false);
private: private:
// Private constructor ... never meant to be created by a user application.
NimBLERemoteService(NimBLEClient* pClient, const struct ble_gatt_svc *service);
// Friends
friend class NimBLEClient; friend class NimBLEClient;
friend class NimBLERemoteCharacteristic;
// Private methods NimBLERemoteService(NimBLEClient* pClient, const struct ble_gatt_svc* service);
bool retrieveCharacteristics(const NimBLEUUID *uuid_filter = nullptr); ~NimBLERemoteService();
bool retrieveCharacteristics(const NimBLEUUID* uuidFilter = nullptr) const;
static int characteristicDiscCB(uint16_t conn_handle, static int characteristicDiscCB(uint16_t conn_handle,
const struct ble_gatt_error* error, const struct ble_gatt_error* error,
const struct ble_gatt_chr* chr, const struct ble_gatt_chr* chr,
void* arg); void* arg);
uint16_t getStartHandle(); mutable std::vector<NimBLERemoteCharacteristic*> m_vChars{};
uint16_t getEndHandle(); NimBLEClient* m_pClient{nullptr};
void releaseSemaphores(); uint16_t m_endHandle{0};
// Properties
// We maintain a vector of characteristics owned by this service.
std::vector<NimBLERemoteCharacteristic*> m_characteristicVector;
NimBLEClient* m_pClient;
NimBLEUUID m_uuid;
uint16_t m_startHandle;
uint16_t m_endHandle;
}; // NimBLERemoteService }; // NimBLERemoteService
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */ #endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */
#endif /* COMPONENTS_NIMBLEREMOTESERVICE_H_ */ #endif /* NIMBLE_CPP_REMOTE_SERVICE_H_*/

View file

@ -0,0 +1,212 @@
/*
* NimBLERemoteValueAttribute.cpp
*
* Created: on July 28 2024
* Author H2zero
*/
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
# include "NimBLERemoteValueAttribute.h"
# include "NimBLEClient.h"
# include <climits>
const char* LOG_TAG = "NimBLERemoteValueAttribute";
bool NimBLERemoteValueAttribute::writeValue(const uint8_t* data, size_t length, bool response) const {
NIMBLE_LOGD(LOG_TAG, ">> writeValue()");
const NimBLEClient* pClient = getClient();
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
ble_task_data_t taskData = {const_cast<NimBLERemoteValueAttribute*>(this), cur_task, 0, nullptr};
int retryCount = 1;
int rc = 0;
uint16_t mtu = pClient->getMTU() - 3;
// Check if the data length is longer than we can write in one connection event.
// If so we must do a long write which requires a response.
if (length <= mtu && !response) {
rc = ble_gattc_write_no_rsp_flat(pClient->getConnId(), getHandle(), data, length);
goto Done;
}
do {
if (length > mtu) {
NIMBLE_LOGI(LOG_TAG, "writeValue: long write");
os_mbuf* om = ble_hs_mbuf_from_flat(data, length);
rc = ble_gattc_write_long(pClient->getConnId(), getHandle(), 0, om, NimBLERemoteValueAttribute::onWriteCB, &taskData);
} else {
rc = ble_gattc_write_flat(pClient->getConnId(),
getHandle(),
data,
length,
NimBLERemoteValueAttribute::onWriteCB,
&taskData);
}
if (rc != 0) {
goto Done;
}
# ifdef ulTaskNotifyValueClear
// Clear the task notification value to ensure we block
ulTaskNotifyValueClear(cur_task, ULONG_MAX);
# endif
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
rc = taskData.rc;
switch (rc) {
case 0:
case BLE_HS_EDONE:
rc = 0;
break;
case BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_LONG):
NIMBLE_LOGE(LOG_TAG, "Long write not supported by peer; Truncating length to %d", mtu);
retryCount++;
length = mtu;
break;
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHOR):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_ENC):
if (retryCount && pClient->secureConnection()) break;
/* Else falls through. */
default:
goto Done;
}
} while (rc != 0 && retryCount--);
Done:
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "<< writeValue failed, rc: %d %s", rc, NimBLEUtils::returnCodeToString(rc));
} else {
NIMBLE_LOGD(LOG_TAG, "<< writeValue, rc: %d", rc);
}
return (rc == 0);
} // writeValue
/**
* @brief Callback for characteristic write operation.
* @return success == 0 or error code.
*/
int NimBLERemoteValueAttribute::onWriteCB(uint16_t conn_handle, const ble_gatt_error* error, ble_gatt_attr* attr, void* arg) {
auto pTaskData = static_cast<ble_task_data_t*>(arg);
const auto pAtt = static_cast<NimBLERemoteValueAttribute*>(pTaskData->pATT);
if (pAtt->getClient()->getConnId() != conn_handle) {
return 0;
}
NIMBLE_LOGI(LOG_TAG, "Write complete; status=%d", error->status);
pTaskData->rc = error->status;
xTaskNotifyGive(pTaskData->task);
return 0;
}
/**
* @brief Read the value of the remote characteristic.
* @param [in] timestamp A pointer to a time_t struct to store the time the value was read.
* @return The value of the remote characteristic.
*/
NimBLEAttValue NimBLERemoteValueAttribute::readValue(time_t* timestamp) const {
NIMBLE_LOGD(LOG_TAG, ">> readValue()");
NimBLEAttValue value{};
const NimBLEClient* pClient = getClient();
int rc = 0;
int retryCount = 1;
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
ble_task_data_t taskData = {const_cast<NimBLERemoteValueAttribute*>(this), cur_task, 0, &value};
do {
rc = ble_gattc_read_long(pClient->getConnId(), getHandle(), 0, NimBLERemoteValueAttribute::onReadCB, &taskData);
if (rc != 0) {
goto Done;
}
# ifdef ulTaskNotifyValueClear
// Clear the task notification value to ensure we block
ulTaskNotifyValueClear(cur_task, ULONG_MAX);
# endif
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
rc = taskData.rc;
switch (rc) {
case 0:
case BLE_HS_EDONE:
rc = 0;
break;
// Characteristic is not long-readable, return with what we have.
case BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_LONG):
NIMBLE_LOGI(LOG_TAG, "Attribute not long");
rc = ble_gattc_read(pClient->getConnId(), getHandle(), NimBLERemoteValueAttribute::onReadCB, &taskData);
if (rc != 0) {
goto Done;
}
retryCount++;
break;
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHOR):
case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_ENC):
if (retryCount && pClient->secureConnection()) break;
/* Else falls through. */
default:
goto Done;
}
} while (rc != 0 && retryCount--);
value.setTimeStamp();
m_value = value;
if (timestamp != nullptr) {
*timestamp = value.getTimeStamp();
}
Done:
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "<< readValue failed rc=%d, %s", rc, NimBLEUtils::returnCodeToString(rc));
} else {
NIMBLE_LOGD(LOG_TAG, "<< readValue rc=%d", rc);
}
return value;
} // readValue
/**
* @brief Callback for characteristic read operation.
* @return success == 0 or error code.
*/
int NimBLERemoteValueAttribute::onReadCB(uint16_t conn_handle, const ble_gatt_error* error, ble_gatt_attr* attr, void* arg) {
auto pTaskData = static_cast<ble_task_data_t*>(arg);
const auto pAtt = static_cast<NimBLERemoteValueAttribute*>(pTaskData->pATT);
if (pAtt->getClient()->getConnId() != conn_handle) {
return 0;
}
int rc = error->status;
NIMBLE_LOGI(LOG_TAG, "Read complete; status=%d", rc);
if (rc == 0) {
if (attr) {
auto valBuf = static_cast<NimBLEAttValue*>(pTaskData->buf);
uint16_t data_len = OS_MBUF_PKTLEN(attr->om);
if ((valBuf->size() + data_len) > BLE_ATT_ATTR_MAX_LEN) {
rc = BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
} else {
NIMBLE_LOGD(LOG_TAG, "Got %u bytes", data_len);
valBuf->append(attr->om->om_data, data_len);
return 0;
}
}
}
pTaskData->rc = rc;
xTaskNotifyGive(pTaskData->task);
return rc;
} // onReadCB
#endif // CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL

View file

@ -0,0 +1,166 @@
/*
* NimBLERemoteValueAttribute.h
*
* Created: on July 28 2024
* Author H2zero
*/
#ifndef NIMBLE_CPP_REMOTE_VALUE_ATTRIBUTE_H_
#define NIMBLE_CPP_REMOTE_VALUE_ATTRIBUTE_H_
#include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
# if defined(CONFIG_NIMBLE_CPP_IDF)
# include <host/ble_gatt.h>
# else
# include <nimble/nimble/host/include/host/ble_gatt.h>
# endif
/**** FIX COMPILATION ****/
# undef min
# undef max
/**************************/
# include "NimBLEAttribute.h"
# include "NimBLEAttValue.h"
class NimBLEClient;
class NimBLERemoteValueAttribute : public NimBLEAttribute {
public:
/**
* @brief Read the value of the remote attribute.
* @param [in] timestamp A pointer to a time_t struct to store the time the value was read.
* @return The value of the remote attribute.
*/
NimBLEAttValue readValue(time_t* timestamp = nullptr) const;
/**
* @brief Get the length of the remote attribute value.
* @return The length of the remote attribute value.
*/
size_t getLength() const { return m_value.size(); }
/**
* @brief Get the value of the remote attribute.
* @return The value of the remote attribute.
* @details This returns a copy of the value to avoid potential race conditions.
*/
NimBLEAttValue getValue() const { return m_value; }
/**
* Get the client instance that owns this attribute.
*/
virtual NimBLEClient* getClient() const = 0;
/**
* @brief Write a new value to the remote characteristic from a data buffer.
* @param [in] data A pointer to a data buffer.
* @param [in] length The length of the data in the data buffer.
* @param [in] response Whether we require a response from the write.
* @return false if not connected or otherwise cannot perform write.
*/
bool writeValue(const uint8_t* data, size_t length, bool response = false) const;
/**
* @brief Write a new value to the remote characteristic from a std::vector<uint8_t>.
* @param [in] vec A std::vector<uint8_t> value to write to the remote characteristic.
* @param [in] response Whether we require a response from the write.
* @return false if not connected or otherwise cannot perform write.
*/
bool writeValue(const std::vector<uint8_t>& v, bool response = false) const {
return writeValue(&v[0], v.size(), response);
}
/**
* @brief Write a new value to the remote characteristic from a const char*.
* @param [in] str A character string to write to the remote characteristic.
* @param [in] length (optional) The length of the character string, uses strlen if omitted.
* @param [in] response Whether we require a response from the write.
* @return false if not connected or otherwise cannot perform write.
*/
bool writeValue(const char* str, size_t length = 0, bool response = false) const {
return writeValue(reinterpret_cast<const uint8_t*>(str), length ? length : strlen(str), response);
}
/**
* @brief Template to set the remote characteristic value to <type\>val.
* @param [in] s The value to write.
* @param [in] response True == request write response.
* @details Only used for non-arrays and types without a `c_str()` method.
*/
template <typename T>
# ifdef _DOXYGEN_
bool
# else
typename std::enable_if<!std::is_array<T>::value && !Has_c_str_len<T>::value, bool>::type
# endif
writeValue(const T& v, bool response = false) const {
return writeValue(reinterpret_cast<const uint8_t*>(&v), sizeof(T), response);
}
/**
* @brief Template to set the remote characteristic value to <type\>val.
* @param [in] s The value to write.
* @param [in] response True == request write response.
* @details Only used if the <type\> has a `c_str()` method.
*/
template <typename T>
# ifdef _DOXYGEN_
bool
# else
typename std::enable_if<Has_c_str_len<T>::value, bool>::type
# endif
writeValue(const T& s, bool response = false) const {
return writeValue(reinterpret_cast<const uint8_t*>(s.c_str()), s.length(), response);
}
/**
* @brief Template to convert the remote characteristic data to <type\>.
* @tparam T The type to convert the data to.
* @param [in] timestamp A pointer to a time_t struct to store the time the value was read.
* @param [in] skipSizeCheck If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is
* less than <tt>sizeof(<type\>)</tt>.
* @details <b>Use:</b> <tt>getValue<type>(&timestamp, skipSizeCheck);</tt>
*/
template <typename T>
T getValue(time_t* timestamp = nullptr, bool skipSizeCheck = false) const {
return m_value.getValue<T>(timestamp, skipSizeCheck);
}
/**
* @brief Template to convert the remote characteristic data to <type\>.
* @tparam T The type to convert the data to.
* @param [in] timestamp A pointer to a time_t struct to store the time the value was read.
* @param [in] skipSizeCheck If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is
* less than <tt>sizeof(<type\>)</tt>.
* @details <b>Use:</b> <tt>readValue<type>(&timestamp, skipSizeCheck);</tt>
*/
template <typename T>
T readValue(time_t* timestamp = nullptr, bool skipSizeCheck = false) const {
readValue();
return m_value.getValue<T>(timestamp, skipSizeCheck);
}
protected:
/**
* @brief Construct a new NimBLERemoteValueAttribute object.
*/
NimBLERemoteValueAttribute(const ble_uuid_any_t& uuid, uint16_t handle) : NimBLEAttribute(uuid, handle) {}
/**
* @brief Destroy the NimBLERemoteValueAttribute object.
*/
virtual ~NimBLERemoteValueAttribute() = default;
static int onReadCB(uint16_t conn_handle, const ble_gatt_error* error, ble_gatt_attr* attr, void* arg);
static int onWriteCB(uint16_t conn_handle, const ble_gatt_error* error, ble_gatt_attr* attr, void* arg);
mutable NimBLEAttValue m_value{};
};
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */
#endif // NIMBLE_CPP_REMOTE_VALUE_ATTRIBUTE_H_

View file

@ -61,7 +61,7 @@ NimBLEServer::NimBLEServer() {
* @brief Destructor: frees all resources / attributes created. * @brief Destructor: frees all resources / attributes created.
*/ */
NimBLEServer::~NimBLEServer() { NimBLEServer::~NimBLEServer() {
for(auto &it : m_svcVec) { for(const auto &it : m_svcVec) {
delete it; delete it;
} }
@ -218,7 +218,7 @@ void NimBLEServer::start() {
// Get the assigned service handles and build a vector of characteristics // Get the assigned service handles and build a vector of characteristics
// with Notify / Indicate capabilities for event handling // with Notify / Indicate capabilities for event handling
for(auto &svc : m_svcVec) { for(auto &svc : m_svcVec) {
if(svc->m_removed == 0) { if(svc->getRemoved() == 0) {
rc = ble_gatts_find_svc(svc->getUUID().getBase(), &svc->m_handle); rc = ble_gatts_find_svc(svc->getUUID().getBase(), &svc->m_handle);
if(rc != 0) { if(rc != 0) {
NIMBLE_LOGW(LOG_TAG, "GATT Server started without service: %s, Service %s", NIMBLE_LOGW(LOG_TAG, "GATT Server started without service: %s, Service %s",
@ -227,13 +227,20 @@ void NimBLEServer::start() {
} }
} }
for(auto &chr : svc->m_chrVec) { for(auto &chr : svc->m_vChars) {
// if Notify / Indicate is enabled but we didn't create the descriptor // if Notify / Indicate is enabled but we didn't create the descriptor
// we do it now. // we do it now.
if((chr->m_properties & BLE_GATT_CHR_F_INDICATE) || if((chr->m_properties & BLE_GATT_CHR_F_INDICATE) ||
(chr->m_properties & BLE_GATT_CHR_F_NOTIFY)) { (chr->m_properties & BLE_GATT_CHR_F_NOTIFY)) {
m_notifyChrVec.push_back(chr); m_notifyChrVec.push_back(chr);
} }
for (auto &desc : chr->m_vDescriptors) {
ble_gatts_find_dsc(svc->getUUID().getBase(),
chr->getUUID().getBase(),
desc->getUUID().getBase(),
&desc->m_handle);
}
} }
} }
@ -561,7 +568,7 @@ int NimBLEServer::handleGapEvent(struct ble_gap_event *event, void *arg) {
} }
} }
it->setSubscribe(event); it->setSubscribe(event, peerInfo);
break; break;
} }
} }
@ -720,6 +727,68 @@ int NimBLEServer::handleGapEvent(struct ble_gap_event *event, void *arg) {
} // handleGapEvent } // handleGapEvent
/**
* @brief STATIC callback to handle events from the NimBLE stack.
*/
int NimBLEServer::handleGattEvent(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg) {
NIMBLE_LOGD(LOG_TAG, "Gatt %s event", (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR ||
ctxt->op == BLE_GATT_ACCESS_OP_READ_DSC) ? "Read" : "Write");
auto pAtt = static_cast<NimBLELocalValueAttribute*>(arg);
const auto& val = pAtt->getAttVal();
NimBLEConnInfo peerInfo{};
ble_gap_conn_find(conn_handle, &peerInfo.m_desc);
switch(ctxt->op) {
case BLE_GATT_ACCESS_OP_READ_DSC:
case BLE_GATT_ACCESS_OP_READ_CHR: {
// If the packet header is only 8 bytes this is a follow up of a long read
// so we don't want to call the onRead() callback again.
if(ctxt->om->om_pkthdr_len > 8 ||
conn_handle == BLE_HS_CONN_HANDLE_NONE ||
val.size() <= (ble_att_mtu(conn_handle) - 3)) {
pAtt->readEvent(peerInfo);
}
ble_npl_hw_enter_critical();
int rc = os_mbuf_append(ctxt->om, val.data(), val.size());
ble_npl_hw_exit_critical(0);
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
case BLE_GATT_ACCESS_OP_WRITE_DSC:
case BLE_GATT_ACCESS_OP_WRITE_CHR: {
uint16_t att_max_len = val.max_size();
if (ctxt->om->om_len > att_max_len) {
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
uint8_t buf[att_max_len];
uint16_t len = ctxt->om->om_len;
memcpy(buf, ctxt->om->om_data,len);
os_mbuf *next;
next = SLIST_NEXT(ctxt->om, om_next);
while(next != NULL){
if((len + next->om_len) > att_max_len) {
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
memcpy(&buf[len], next->om_data, next->om_len);
len += next->om_len;
next = SLIST_NEXT(next, om_next);
}
pAtt->writeEvent(buf, len, peerInfo);
return 0;
}
default:
break;
}
return BLE_ATT_ERR_UNLIKELY;
} // handleGattEvent
/** /**
* @brief Set the server callbacks. * @brief Set the server callbacks.
* *
@ -762,7 +831,7 @@ void NimBLEServer::removeService(NimBLEService* service, bool deleteSvc) {
// Check if the service was already removed and if so check if this // Check if the service was already removed and if so check if this
// is being called to delete the object and do so if requested. // is being called to delete the object and do so if requested.
// Otherwise, ignore the call and return. // Otherwise, ignore the call and return.
if(service->m_removed > 0) { if(service->getRemoved() > 0) {
if(deleteSvc) { if(deleteSvc) {
for(auto it = m_svcVec.begin(); it != m_svcVec.end(); ++it) { for(auto it = m_svcVec.begin(); it != m_svcVec.end(); ++it) {
if ((*it) == service) { if ((*it) == service) {
@ -781,7 +850,7 @@ void NimBLEServer::removeService(NimBLEService* service, bool deleteSvc) {
return; return;
} }
service->m_removed = deleteSvc ? NIMBLE_ATT_REMOVE_DELETE : NIMBLE_ATT_REMOVE_HIDE; service->setRemoved(deleteSvc ? NIMBLE_ATT_REMOVE_DELETE : NIMBLE_ATT_REMOVE_HIDE);
serviceChanged(); serviceChanged();
#if !CONFIG_BT_NIMBLE_EXT_ADV #if !CONFIG_BT_NIMBLE_EXT_ADV
NimBLEDevice::getAdvertising()->removeServiceUUID(service->getUUID()); NimBLEDevice::getAdvertising()->removeServiceUUID(service->getUUID());
@ -805,12 +874,12 @@ void NimBLEServer::addService(NimBLEService* service) {
// If adding a service that was not removed add it and return. // If adding a service that was not removed add it and return.
// Else reset GATT and send service changed notification. // Else reset GATT and send service changed notification.
if(service->m_removed == 0) { if(service->getRemoved() == 0) {
m_svcVec.push_back(service); m_svcVec.push_back(service);
return; return;
} }
service->m_removed = 0; service->setRemoved(0);
serviceChanged(); serviceChanged();
} }
@ -829,8 +898,8 @@ void NimBLEServer::resetGATT() {
ble_svc_gatt_init(); ble_svc_gatt_init();
for(auto it = m_svcVec.begin(); it != m_svcVec.end(); ) { for(auto it = m_svcVec.begin(); it != m_svcVec.end(); ) {
if ((*it)->m_removed > 0) { if ((*it)->getRemoved() > 0) {
if ((*it)->m_removed == NIMBLE_ATT_REMOVE_DELETE) { if ((*it)->getRemoved() == NIMBLE_ATT_REMOVE_DELETE) {
delete *it; delete *it;
it = m_svcVec.erase(it); it = m_svcVec.erase(it);
} else { } else {
@ -1004,20 +1073,20 @@ uint32_t NimBLEServerCallbacks::onPassKeyDisplay(){
return 123456; return 123456;
} //onPassKeyDisplay } //onPassKeyDisplay
void NimBLEServerCallbacks::onConfirmPIN(const NimBLEConnInfo& connInfo, uint32_t pin){ void NimBLEServerCallbacks::onConfirmPIN(NimBLEConnInfo& connInfo, uint32_t pin){
NIMBLE_LOGD("NimBLEServerCallbacks", "onConfirmPIN: default: true"); NIMBLE_LOGD("NimBLEServerCallbacks", "onConfirmPIN: default: true");
NimBLEDevice::injectConfirmPIN(connInfo, true); NimBLEDevice::injectConfirmPIN(connInfo, true);
} // onConfirmPIN } // onConfirmPIN
void NimBLEServerCallbacks::onIdentity(const NimBLEConnInfo& connInfo){ void NimBLEServerCallbacks::onIdentity(NimBLEConnInfo& connInfo){
NIMBLE_LOGD("NimBLEServerCallbacks", "onIdentity: default"); NIMBLE_LOGD("NimBLEServerCallbacks", "onIdentity: default");
} // onIdentity } // onIdentity
void NimBLEServerCallbacks::onAuthenticationComplete(const NimBLEConnInfo& connInfo){ void NimBLEServerCallbacks::onAuthenticationComplete(NimBLEConnInfo& connInfo){
NIMBLE_LOGD("NimBLEServerCallbacks", "onAuthenticationComplete: default"); NIMBLE_LOGD("NimBLEServerCallbacks", "onAuthenticationComplete: default");
} // onAuthenticationComplete } // onAuthenticationComplete
void NimBLEServerCallbacks::onAuthenticationComplete(const NimBLEConnInfo& connInfo, const std::string& name){ void NimBLEServerCallbacks::onAuthenticationComplete(NimBLEConnInfo& connInfo, const std::string& name){
NIMBLE_LOGD("NimBLEServerCallbacks", "onAuthenticationComplete: default"); NIMBLE_LOGD("NimBLEServerCallbacks", "onAuthenticationComplete: default");
} // onAuthenticationComplete } // onAuthenticationComplete

View file

@ -18,10 +18,8 @@
#include "nimconfig.h" #include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
#define NIMBLE_ATT_REMOVE_HIDE 1 class NimBLEServer;
#define NIMBLE_ATT_REMOVE_DELETE 2 class NimBLEServerCallbacks;
#define onMtuChanged onMTUChange
#include "NimBLEUtils.h" #include "NimBLEUtils.h"
#include "NimBLEAddress.h" #include "NimBLEAddress.h"
@ -31,13 +29,13 @@
#include "NimBLEAdvertising.h" #include "NimBLEAdvertising.h"
#endif #endif
#include "NimBLEService.h" #include "NimBLEService.h"
#include "NimBLECharacteristic.h"
#include "NimBLEConnInfo.h" #include "NimBLEConnInfo.h"
#define NIMBLE_ATT_REMOVE_HIDE 1
#define NIMBLE_ATT_REMOVE_DELETE 2
class NimBLEService; #define onMtuChanged onMTUChange
class NimBLECharacteristic;
class NimBLEServerCallbacks;
/** /**
* @brief The model of a %BLE server. * @brief The model of a %BLE server.
@ -123,6 +121,9 @@ private:
bool setIndicateWait(uint16_t conn_handle); bool setIndicateWait(uint16_t conn_handle);
void clearIndicateWait(uint16_t conn_handle); void clearIndicateWait(uint16_t conn_handle);
static int handleGattEvent(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg);
}; // NimBLEServer }; // NimBLEServer
@ -182,14 +183,14 @@ public:
* Should be passed back to NimBLEDevice::injectConfirmPIN * Should be passed back to NimBLEDevice::injectConfirmPIN
* @param [in] pin The pin to compare with the client. * @param [in] pin The pin to compare with the client.
*/ */
virtual void onConfirmPIN(const NimBLEConnInfo& connInfo, uint32_t pin); virtual void onConfirmPIN(NimBLEConnInfo& connInfo, uint32_t pin);
/** /**
* @brief Called when the pairing procedure is complete. * @brief Called when the pairing procedure is complete.
* @param [in] connInfo A reference to a NimBLEConnInfo instance with information * @param [in] connInfo A reference to a NimBLEConnInfo instance with information
* about the peer connection parameters. * about the peer connection parameters.
*/ */
virtual void onAuthenticationComplete(const NimBLEConnInfo& connInfo); virtual void onAuthenticationComplete(NimBLEConnInfo& connInfo);
/** /**
* @brief Called when the pairing procedure is complete. * @brief Called when the pairing procedure is complete.
@ -197,13 +198,13 @@ public:
* @param [in] name The name of the connected peer device. * @param [in] name The name of the connected peer device.
* about the peer connection parameters. * about the peer connection parameters.
*/ */
virtual void onAuthenticationComplete(const NimBLEConnInfo& connInfo, const std::string& name); virtual void onAuthenticationComplete(NimBLEConnInfo& connInfo, const std::string& name);
/** /**
* @brief Called when the peer identity address is resolved. * @brief Called when the peer identity address is resolved.
* @param [in] connInfo A reference to a NimBLEConnInfo instance with information * @param [in] connInfo A reference to a NimBLEConnInfo instance with information
*/ */
virtual void onIdentity(const NimBLEConnInfo& connInfo); virtual void onIdentity(NimBLEConnInfo& connInfo);
}; // NimBLEServerCallbacks }; // NimBLEServerCallbacks
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */ #endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */

View file

@ -24,84 +24,60 @@
# include <string> # include <string>
static const char* LOG_TAG = "NimBLEService"; // Tag for logging. static const char* LOG_TAG = "NimBLEService";
#define NULL_HANDLE (0xffff)
/** /**
* @brief Construct an instance of the NimBLEService * @brief Construct an instance of the NimBLEService
* @param [in] uuid The UUID of the service. * @param [in] uuid The UUID of the service.
*/ */
NimBLEService::NimBLEService(const char* uuid) NimBLEService::NimBLEService(const char* uuid) : NimBLEService(NimBLEUUID(uuid)) {}
: NimBLEService(NimBLEUUID(uuid)) {
}
/** /**
* @brief Construct an instance of the BLEService * @brief Construct an instance of the BLEService
* @param [in] uuid The UUID of the service. * @param [in] uuid The UUID of the service.
*/ */
NimBLEService::NimBLEService(const NimBLEUUID &uuid) { NimBLEService::NimBLEService(const NimBLEUUID& uuid)
m_uuid = uuid; : NimBLELocalAttribute{uuid, 0}, m_pSvcDef{{0, getUUID().getBase(), nullptr, nullptr},{}} {}
m_handle = NULL_HANDLE;
m_pSvcDef = nullptr;
m_removed = 0;
} // NimBLEService
/**
* @brief Destructor, make sure we release the resources allocated for the service.
*/
NimBLEService::~NimBLEService() { NimBLEService::~NimBLEService() {
if(m_pSvcDef != nullptr) { if (m_pSvcDef->characteristics) {
if(m_pSvcDef->characteristics != nullptr) { if (m_pSvcDef->characteristics->descriptors) {
for(int i=0; m_pSvcDef->characteristics[i].uuid != NULL; ++i) { delete[] m_pSvcDef->characteristics->descriptors;
if(m_pSvcDef->characteristics[i].descriptors) {
delete(m_pSvcDef->characteristics[i].descriptors);
} }
} delete[] m_pSvcDef->characteristics;
delete(m_pSvcDef->characteristics);
} }
delete(m_pSvcDef); for (const auto& it : m_vChars) {
}
for(auto &it : m_chrVec) {
delete it; delete it;
} }
} } // ~NimBLEService
/** /**
* @brief Dump details of this BLE GATT service. * @brief Dump details of this BLE GATT service.
*/ */
void NimBLEService::dump() { void NimBLEService::dump() const {
NIMBLE_LOGD(LOG_TAG, "Service: uuid:%s, handle: 0x%2x", NIMBLE_LOGD(LOG_TAG, "Service: uuid:%s, handle: 0x%2x", getUUID().toString().c_str(), getHandle());
m_uuid.toString().c_str(),
m_handle);
std::string res; std::string res;
int count = 0; int count = 0;
char hex[5]; char hex[5];
for (auto &it: m_chrVec) { for (const auto& it : m_vChars) {
if (count > 0) {res += "\n";} if (count > 0) {
res += "\n";
}
snprintf(hex, sizeof(hex), "%04x", it->getHandle()); snprintf(hex, sizeof(hex), "%04x", it->getHandle());
count++; count++;
res += "handle: 0x"; res += "handle: 0x";
res += hex; res += hex;
res += ", uuid: " + std::string(it->getUUID()); res += ", uuid: " + std::string(it->getUUID());
} }
NIMBLE_LOGD(LOG_TAG, "Characteristics:\n%s", res.c_str()); NIMBLE_LOGD(LOG_TAG, "Characteristics:\n%s", res.c_str());
} // dump } // dump
/**
* @brief Get the UUID of the service.
* @return the UUID of the service.
*/
NimBLEUUID NimBLEService::getUUID() {
return m_uuid;
} // getUUID
/** /**
* @brief Builds the database of characteristics/descriptors for the service * @brief Builds the database of characteristics/descriptors for the service
* and registers it with the NimBLE stack. * and registers it with the NimBLE stack.
@ -111,149 +87,99 @@ bool NimBLEService::start() {
NIMBLE_LOGD(LOG_TAG, ">> start(): Starting service: %s", toString().c_str()); NIMBLE_LOGD(LOG_TAG, ">> start(): Starting service: %s", toString().c_str());
// Rebuild the service definition if the server attributes have changed. // Rebuild the service definition if the server attributes have changed.
if(getServer()->m_svcChanged && m_pSvcDef != nullptr) { if (getServer()->m_svcChanged) {
if(m_pSvcDef[0].characteristics) { if (m_pSvcDef->characteristics) {
if(m_pSvcDef[0].characteristics[0].descriptors) { if (m_pSvcDef->characteristics->descriptors) {
delete(m_pSvcDef[0].characteristics[0].descriptors); delete[] m_pSvcDef->characteristics->descriptors;
} }
delete(m_pSvcDef[0].characteristics); delete[] m_pSvcDef->characteristics;
} }
delete(m_pSvcDef); m_pSvcDef->type = 0;
m_pSvcDef = nullptr;
} }
if(m_pSvcDef == nullptr) { if (!m_pSvcDef->type) {
// Nimble requires an array of services to be sent to the api m_pSvcDef->type = BLE_GATT_SVC_TYPE_PRIMARY;
// Since we are adding 1 at a time we create an array of 2 and set the type size_t numChrs = 0;
// of the second service to 0 to indicate the end of the array. for (const auto& chr : m_vChars) {
ble_gatt_svc_def* svc = new ble_gatt_svc_def[2]; if (chr->getRemoved()) {
ble_gatt_chr_def* pChr_a = nullptr;
ble_gatt_dsc_def* pDsc_a = nullptr;
svc[0].type = BLE_GATT_SVC_TYPE_PRIMARY;
svc[0].uuid = m_uuid.getBase();
svc[0].includes = NULL;
int removedCount = 0;
for(auto it = m_chrVec.begin(); it != m_chrVec.end(); ) {
if ((*it)->m_removed > 0) {
if ((*it)->m_removed == NIMBLE_ATT_REMOVE_DELETE) {
delete *it;
it = m_chrVec.erase(it);
} else {
++removedCount;
++it;
}
continue; continue;
} }
++numChrs;
++it;
} }
size_t numChrs = m_chrVec.size() - removedCount;
NIMBLE_LOGD(LOG_TAG, "Adding %d characteristics for service %s", numChrs, toString().c_str()); NIMBLE_LOGD(LOG_TAG, "Adding %d characteristics for service %s", numChrs, toString().c_str());
if (numChrs) {
int i = 0;
if(!numChrs){
svc[0].characteristics = NULL;
}else{
// Nimble requires the last characteristic to have it's uuid = 0 to indicate the end // Nimble requires the last characteristic to have it's uuid = 0 to indicate the end
// of the characteristics for the service. We create 1 extra and set it to null // of the characteristics for the service. We create 1 extra and set it to null
// for this purpose. // for this purpose.
pChr_a = new ble_gatt_chr_def[numChrs + 1]{}; ble_gatt_chr_def* pChrs = new ble_gatt_chr_def[numChrs + 1]{};
int i = 0; for (const auto& chr : m_vChars) {
for(auto chr_it = m_chrVec.begin(); chr_it != m_chrVec.end(); ++chr_it) { if (chr->getRemoved()) {
if((*chr_it)->m_removed > 0) {
continue; continue;
} }
removedCount = 0; size_t numDscs = 0;
for(auto it = (*chr_it)->m_dscVec.begin(); it != (*chr_it)->m_dscVec.end(); ) { for (const auto& dsc : chr->m_vDescriptors) {
if ((*it)->m_removed > 0) { if (dsc->getRemoved()) {
if ((*it)->m_removed == NIMBLE_ATT_REMOVE_DELETE) {
delete *it;
it = (*chr_it)->m_dscVec.erase(it);
} else {
++removedCount;
++it;
}
continue; continue;
} }
++numDscs;
++it;
} }
size_t numDscs = (*chr_it)->m_dscVec.size() - removedCount; if (numDscs) {
int j = 0;
if(!numDscs){
pChr_a[i].descriptors = NULL;
} else {
// Must have last descriptor uuid = 0 so we have to create 1 extra // Must have last descriptor uuid = 0 so we have to create 1 extra
pDsc_a = new ble_gatt_dsc_def[numDscs+1]; ble_gatt_dsc_def* pDscs = new ble_gatt_dsc_def[numDscs + 1]{};
int d = 0; for (const auto& dsc : chr->m_vDescriptors) {
for(auto dsc_it = (*chr_it)->m_dscVec.begin(); dsc_it != (*chr_it)->m_dscVec.end(); ++dsc_it ) { if (dsc->getRemoved()) {
if((*dsc_it)->m_removed > 0) {
continue; continue;
} }
pDsc_a[d].uuid = (*dsc_it)->m_uuid.getBase();
pDsc_a[d].att_flags = (*dsc_it)->m_properties; printf("adding disc %s\n", dsc->toString().c_str());
pDsc_a[d].min_key_size = 0;
pDsc_a[d].access_cb = NimBLEDescriptor::handleGapEvent; pDscs[j].uuid = dsc->getUUID().getBase();
pDsc_a[d].arg = (*dsc_it); pDscs[j].att_flags = dsc->getProperties();
++d; pDscs[j].min_key_size = 0;
pDscs[j].access_cb = NimBLEServer::handleGattEvent;
pDscs[j].arg = dsc;
++j;
} }
pDsc_a[numDscs].uuid = NULL; pChrs[i].descriptors = pDscs;
pChr_a[i].descriptors = pDsc_a;
} }
pChr_a[i].uuid = (*chr_it)->m_uuid.getBase(); pChrs[i].uuid = chr->getUUID().getBase();
pChr_a[i].access_cb = NimBLECharacteristic::handleGapEvent; pChrs[i].access_cb = NimBLEServer::handleGattEvent;
pChr_a[i].arg = (*chr_it); pChrs[i].arg = chr;
pChr_a[i].flags = (*chr_it)->m_properties; pChrs[i].flags = chr->getProperties();
pChr_a[i].min_key_size = 0; pChrs[i].min_key_size = 0;
pChr_a[i].val_handle = &(*chr_it)->m_handle; pChrs[i].val_handle = &chr->m_handle;
++i; ++i;
} }
pChr_a[numChrs].uuid = NULL; m_pSvcDef->characteristics = pChrs;
svc[0].characteristics = pChr_a; }
} }
// end of services must indicate to api with type = 0 int rc = ble_gatts_count_cfg(m_pSvcDef);
svc[1].type = 0;
m_pSvcDef = svc;
}
int rc = ble_gatts_count_cfg((const ble_gatt_svc_def*)m_pSvcDef);
if (rc != 0) { if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gatts_count_cfg failed, rc= %d, %s", rc, NimBLEUtils::returnCodeToString(rc)); NIMBLE_LOGE(LOG_TAG, "ble_gatts_count_cfg failed, rc= %d, %s", rc, NimBLEUtils::returnCodeToString(rc));
return false; return false;
} }
rc = ble_gatts_add_svcs((const ble_gatt_svc_def*)m_pSvcDef); rc = ble_gatts_add_svcs(m_pSvcDef);
if (rc != 0) { if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gatts_add_svcs, rc= %d, %s", rc, NimBLEUtils::returnCodeToString(rc)); NIMBLE_LOGE(LOG_TAG, "ble_gatts_add_svcs, rc= %d, %s", rc, NimBLEUtils::returnCodeToString(rc));
return false; return false;
} }
NIMBLE_LOGD(LOG_TAG, "<< start()"); NIMBLE_LOGD(LOG_TAG, "<< start()");
return true; return true;
} // start } // start
/**
* @brief Get the handle associated with this service.
* @return The handle associated with this service.
*/
uint16_t NimBLEService::getHandle() {
if (m_handle == NULL_HANDLE) {
ble_gatts_find_svc(getUUID().getBase(), &m_handle);
}
return m_handle;
} // getHandle
/** /**
* @brief Create a new BLE Characteristic associated with this service. * @brief Create a new BLE Characteristic associated with this service.
* @param [in] uuid - The UUID of the characteristic. * @param [in] uuid - The UUID of the characteristic.
@ -263,8 +189,7 @@ uint16_t NimBLEService::getHandle() {
*/ */
NimBLECharacteristic* NimBLEService::createCharacteristic(const char* uuid, uint32_t properties, uint16_t max_len) { NimBLECharacteristic* NimBLEService::createCharacteristic(const char* uuid, uint32_t properties, uint16_t max_len) {
return createCharacteristic(NimBLEUUID(uuid), properties, max_len); return createCharacteristic(NimBLEUUID(uuid), properties, max_len);
} } // createCharacteristic
/** /**
* @brief Create a new BLE Characteristic associated with this service. * @brief Create a new BLE Characteristic associated with this service.
@ -274,58 +199,61 @@ NimBLECharacteristic* NimBLEService::createCharacteristic(const char* uuid, uint
* @return The new BLE characteristic. * @return The new BLE characteristic.
*/ */
NimBLECharacteristic* NimBLEService::createCharacteristic(const NimBLEUUID& uuid, uint32_t properties, uint16_t max_len) { NimBLECharacteristic* NimBLEService::createCharacteristic(const NimBLEUUID& uuid, uint32_t properties, uint16_t max_len) {
NimBLECharacteristic* pCharacteristic = new NimBLECharacteristic(uuid, properties, max_len, this); NimBLECharacteristic* pChar = new NimBLECharacteristic(uuid, properties, max_len, this);
if (getCharacteristic(uuid) != nullptr) { if (getCharacteristic(uuid) != nullptr) {
NIMBLE_LOGD(LOG_TAG, "<< Adding a duplicate characteristic with UUID: %s", NIMBLE_LOGD(LOG_TAG, "Adding a duplicate characteristic with UUID: %s", std::string(uuid).c_str());
std::string(uuid).c_str());
} }
addCharacteristic(pCharacteristic); addCharacteristic(pChar);
return pCharacteristic; return pChar;
} // createCharacteristic } // createCharacteristic
/** /**
* @brief Add a characteristic to the service. * @brief Add a characteristic to the service.
* @param[in] pCharacteristic A pointer to the characteristic instance to add to the service. * @param[in] pChar A pointer to the characteristic instance to add to the service.
*/ */
void NimBLEService::addCharacteristic(NimBLECharacteristic* pCharacteristic) { void NimBLEService::addCharacteristic(NimBLECharacteristic* pChar) {
bool foundRemoved = false; bool foundRemoved = false;
if (pChar->getRemoved() > 0) {
if(pCharacteristic->m_removed > 0) { for (const auto& chr : m_vChars) {
for(auto& it : m_chrVec) { if (chr == pChar) {
if(it == pCharacteristic) {
foundRemoved = true; foundRemoved = true;
pCharacteristic->m_removed = 0; pChar->setRemoved(0);
} }
} }
} }
// Check if the characteristic is already in the service and if so, return.
for (const auto& chr : m_vChars) {
if (chr == pChar) {
pChar->setService(this); // Update the service pointer in the characteristic.
return;
}
}
if (!foundRemoved) { if (!foundRemoved) {
m_chrVec.push_back(pCharacteristic); m_vChars.push_back(pChar);
} }
pCharacteristic->setService(this); pChar->setService(this);
getServer()->serviceChanged(); getServer()->serviceChanged();
} // addCharacteristic } // addCharacteristic
/** /**
* @brief Remove a characteristic from the service. * @brief Remove a characteristic from the service.
* @param[in] pCharacteristic A pointer to the characteristic instance to remove from the service. * @param[in] pChar A pointer to the characteristic instance to remove from the service.
* @param[in] deleteChr If true it will delete the characteristic instance and free it's resources. * @param[in] deleteChr If true it will delete the characteristic instance and free it's resources.
*/ */
void NimBLEService::removeCharacteristic(NimBLECharacteristic* pCharacteristic, bool deleteChr) { void NimBLEService::removeCharacteristic(NimBLECharacteristic* pChar, bool deleteChr) {
// Check if the characteristic was already removed and if so, check if this // Check if the characteristic was already removed and if so, check if this
// is being called to delete the object and do so if requested. // is being called to delete the object and do so if requested.
// Otherwise, ignore the call and return. // Otherwise, ignore the call and return.
if(pCharacteristic->m_removed > 0) { if (pChar->getRemoved() > 0) {
if (deleteChr) { if (deleteChr) {
for(auto it = m_chrVec.begin(); it != m_chrVec.end(); ++it) { for (auto it = m_vChars.begin(); it != m_vChars.end(); ++it) {
if ((*it) == pCharacteristic) { if ((*it) == pChar) {
m_chrVec.erase(it); delete (*it);
delete *it; m_vChars.erase(it);
break; break;
} }
} }
@ -334,80 +262,82 @@ void NimBLEService::removeCharacteristic(NimBLECharacteristic* pCharacteristic,
return; return;
} }
pCharacteristic->m_removed = deleteChr ? NIMBLE_ATT_REMOVE_DELETE : NIMBLE_ATT_REMOVE_HIDE; pChar->setRemoved(deleteChr ? NIMBLE_ATT_REMOVE_DELETE : NIMBLE_ATT_REMOVE_HIDE);
getServer()->serviceChanged(); getServer()->serviceChanged();
} // removeCharacteristic } // removeCharacteristic
/**
* @brief Get a pointer to the characteristic object with the specified UUID.
* @param [in] uuid The UUID of the characteristic.
* @param idx The index of the characteristic to return (used when multiple characteristics have the same UUID).
* @return A pointer to the characteristic object or nullptr if not found.
*/
NimBLECharacteristic* NimBLEService::getCharacteristic(const char* uuid, uint16_t idx) const {
return getCharacteristic(NimBLEUUID(uuid), idx);
} // getCharacteristic
/** /**
* @brief Get a pointer to the characteristic object with the specified UUID. * @brief Get a pointer to the characteristic object with the specified UUID.
* @param [in] uuid The UUID of the characteristic. * @param [in] uuid The UUID of the characteristic.
* @param instanceId The index of the characteristic to return (used when multiple characteristics have the same UUID). * @param idx The index of the characteristic to return (used when multiple characteristics have the same UUID).
* @return A pointer to the characteristic object or nullptr if not found. * @return A pointer to the characteristic object or nullptr if not found.
*/ */
NimBLECharacteristic* NimBLEService::getCharacteristic(const char* uuid, uint16_t instanceId) { NimBLECharacteristic* NimBLEService::getCharacteristic(const NimBLEUUID& uuid, uint16_t idx) const {
return getCharacteristic(NimBLEUUID(uuid), instanceId);
}
/**
* @brief Get a pointer to the characteristic object with the specified UUID.
* @param [in] uuid The UUID of the characteristic.
* @param instanceId The index of the characteristic to return (used when multiple characteristics have the same UUID).
* @return A pointer to the characteristic object or nullptr if not found.
*/
NimBLECharacteristic* NimBLEService::getCharacteristic(const NimBLEUUID &uuid, uint16_t instanceId) {
uint16_t position = 0; uint16_t position = 0;
for (auto &it : m_chrVec) { for (const auto& chr : m_vChars) {
if (it->getUUID() == uuid) { if (chr->getUUID() == uuid) {
if (position == instanceId) { if (position == idx) {
return it; return chr;
} }
position++; position++;
} }
} }
return nullptr; return nullptr;
} } // getCharacteristic
/** /**
* @brief Get a pointer to the characteristic object with the specified handle. * @brief Get a pointer to the characteristic object with the specified handle.
* @param handle The handle of the characteristic. * @param handle The handle of the characteristic.
* @return A pointer to the characteristic object or nullptr if not found. * @return A pointer to the characteristic object or nullptr if not found.
*/ */
NimBLECharacteristic *NimBLEService::getCharacteristicByHandle(uint16_t handle) { NimBLECharacteristic* NimBLEService::getCharacteristicByHandle(uint16_t handle) const {
for (auto &it : m_chrVec) { for (const auto& chr : m_vChars) {
if (it->getHandle() == handle) { if (chr->getHandle() == handle) {
return it; return chr;
} }
} }
return nullptr; return nullptr;
} } // getCharacteristicByHandle
/** /**
* @return A vector containing pointers to each characteristic associated with this service. * @return A vector containing pointers to each characteristic associated with this service.
*/ */
std::vector<NimBLECharacteristic *> NimBLEService::getCharacteristics() { const std::vector<NimBLECharacteristic*>& NimBLEService::getCharacteristics() const {
return m_chrVec; return m_vChars;
} } // getCharacteristics
/** /**
* @return A vector containing pointers to each characteristic with the provided UUID associated with this service. * @return A vector containing pointers to each characteristic with the provided UUID associated with this service.
*/ */
std::vector<NimBLECharacteristic *> NimBLEService::getCharacteristics(const char *uuid) { std::vector<NimBLECharacteristic*> NimBLEService::getCharacteristics(const char* uuid) const {
return getCharacteristics(NimBLEUUID(uuid)); return getCharacteristics(NimBLEUUID(uuid));
} } // getCharacteristics
/** /**
* @return A vector containing pointers to each characteristic with the provided UUID associated with this service. * @return A vector containing pointers to each characteristic with the provided UUID associated with this service.
*/ */
std::vector<NimBLECharacteristic *> NimBLEService::getCharacteristics(const NimBLEUUID &uuid) { std::vector<NimBLECharacteristic*> NimBLEService::getCharacteristics(const NimBLEUUID& uuid) const {
std::vector<NimBLECharacteristic*> result; std::vector<NimBLECharacteristic*> result;
for (auto &it : m_chrVec) { for (const auto& chr : m_vChars) {
if (it->getUUID() == uuid) { if (chr->getUUID() == uuid) {
result.push_back(it); result.push_back(chr);
} }
} }
return result; return result;
} } // getCharacteristics
/** /**
* @brief Return a string representation of this service. * @brief Return a string representation of this service.
@ -416,7 +346,7 @@ std::vector<NimBLECharacteristic *> NimBLEService::getCharacteristics(const NimB
* * Its handle * * Its handle
* @return A string representation of this service. * @return A string representation of this service.
*/ */
std::string NimBLEService::toString() { std::string NimBLEService::toString() const {
std::string res = "UUID: " + getUUID().toString(); std::string res = "UUID: " + getUUID().toString();
char hex[5]; char hex[5];
snprintf(hex, sizeof(hex), "%04x", getHandle()); snprintf(hex, sizeof(hex), "%04x", getHandle());
@ -425,22 +355,20 @@ std::string NimBLEService::toString() {
return res; return res;
} // toString } // toString
/** /**
* @brief Get the BLE server associated with this service. * @brief Get the BLE server associated with this service.
* @return The BLEServer associated with this service. * @return The BLEServer associated with this service.
*/ */
NimBLEServer* NimBLEService::getServer() { NimBLEServer* NimBLEService::getServer() const {
return NimBLEDevice::getServer(); return NimBLEDevice::getServer();
} // getServer } // getServer
/** /**
* @brief Checks if the service has been started. * @brief Checks if the service has been started.
* @return True if the service has been started. * @return True if the service has been started.
*/ */
bool NimBLEService::isStarted() { bool NimBLEService::isStarted() const {
return m_pSvcDef != nullptr; return m_pSvcDef->type > 0;
} } // isStarted
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */ #endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */

View file

@ -12,76 +12,59 @@
* Author: kolban * Author: kolban
*/ */
#ifndef MAIN_NIMBLESERVICE_H_ #ifndef NIMBLE_CPP_SERVICE_H_
#define MAIN_NIMBLESERVICE_H_ #define NIMBLE_CPP_SERVICE_H_
#include "nimconfig.h" #include "nimconfig.h"
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
class NimBLEService;
# include "NimBLEAttribute.h"
# include "NimBLEServer.h" # include "NimBLEServer.h"
# include "NimBLECharacteristic.h" # include "NimBLECharacteristic.h"
#include "NimBLEUUID.h"
class NimBLEServer;
class NimBLECharacteristic;
/** /**
* @brief The model of a %BLE service. * @brief The model of a BLE service.
* *
*/ */
class NimBLEService { class NimBLEService : public NimBLELocalAttribute {
public: public:
NimBLEService(const char* uuid); NimBLEService(const char* uuid);
NimBLEService(const NimBLEUUID& uuid); NimBLEService(const NimBLEUUID& uuid);
~NimBLEService(); ~NimBLEService();
NimBLEServer* getServer(); NimBLEServer* getServer() const;
std::string toString() const;
NimBLEUUID getUUID(); void dump() const;
uint16_t getHandle(); bool isStarted() const;
std::string toString();
void dump();
bool isStarted();
bool start(); bool start();
NimBLECharacteristic* createCharacteristic(const char* uuid, NimBLECharacteristic* createCharacteristic(const char* uuid,
uint32_t properties = uint32_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE,
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN); uint16_t max_len = BLE_ATT_ATTR_MAX_LEN);
NimBLECharacteristic* createCharacteristic(const NimBLEUUID& uuid, NimBLECharacteristic* createCharacteristic(const NimBLEUUID& uuid,
uint32_t properties = uint32_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE,
NIMBLE_PROPERTY::READ |
NIMBLE_PROPERTY::WRITE,
uint16_t max_len = BLE_ATT_ATTR_MAX_LEN); uint16_t max_len = BLE_ATT_ATTR_MAX_LEN);
void addCharacteristic(NimBLECharacteristic* pCharacteristic); void addCharacteristic(NimBLECharacteristic* pCharacteristic);
void removeCharacteristic(NimBLECharacteristic* pCharacteristic, bool deleteChr = false); void removeCharacteristic(NimBLECharacteristic* pCharacteristic, bool deleteChr = false);
NimBLECharacteristic* getCharacteristic(const char* uuid, uint16_t instanceId = 0); NimBLECharacteristic* getCharacteristic(const char* uuid, uint16_t instanceId = 0) const;
NimBLECharacteristic* getCharacteristic(const NimBLEUUID &uuid, uint16_t instanceId = 0); NimBLECharacteristic* getCharacteristic(const NimBLEUUID& uuid, uint16_t instanceId = 0) const;
NimBLECharacteristic* getCharacteristicByHandle(uint16_t handle); NimBLECharacteristic* getCharacteristicByHandle(uint16_t handle) const;
std::vector<NimBLECharacteristic*> getCharacteristics();
std::vector<NimBLECharacteristic*> getCharacteristics(const char* uuid);
std::vector<NimBLECharacteristic*> getCharacteristics(const NimBLEUUID &uuid);
const std::vector<NimBLECharacteristic*>& getCharacteristics() const;
std::vector<NimBLECharacteristic*> getCharacteristics(const char* uuid) const;
std::vector<NimBLECharacteristic*> getCharacteristics(const NimBLEUUID& uuid) const;
private: private:
friend class NimBLEServer; friend class NimBLEServer;
friend class NimBLEDevice;
uint16_t m_handle;
NimBLEUUID m_uuid;
ble_gatt_svc_def* m_pSvcDef;
uint8_t m_removed;
std::vector<NimBLECharacteristic*> m_chrVec;
std::vector<NimBLECharacteristic*> m_vChars{};
// Nimble requires an array of services to be sent to the api
// Since we are adding 1 at a time we create an array of 2 and set the type
// of the second service to 0 to indicate the end of the array.
ble_gatt_svc_def m_pSvcDef[2]{};
}; // NimBLEService }; // NimBLEService
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */ #endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */
#endif /* MAIN_NIMBLESERVICE_H_ */ #endif /* NIMBLE_CPP_SERVICE_H_ */

View file

@ -25,6 +25,12 @@ static const char* LOG_TAG = "NimBLEUUID";
static const uint8_t ble_base_uuid[] = { static const uint8_t ble_base_uuid[] = {
0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
/**
* @brief Create a UUID from the native UUID.
* @param [in] uuid The native UUID.
*/
NimBLEUUID::NimBLEUUID(const ble_uuid_any_t& uuid) : m_uuid{uuid} {}
/** /**
* @brief Create a UUID from a string. * @brief Create a UUID from a string.
* *

View file

@ -41,6 +41,7 @@ class NimBLEUUID {
* @brief Created a blank UUID. * @brief Created a blank UUID.
*/ */
NimBLEUUID() = default; NimBLEUUID() = default;
NimBLEUUID(const ble_uuid_any_t& uuid);
NimBLEUUID(const std::string& uuid); NimBLEUUID(const std::string& uuid);
NimBLEUUID(uint16_t uuid); NimBLEUUID(uint16_t uuid);
NimBLEUUID(uint32_t uuid); NimBLEUUID(uint32_t uuid);