diff --git a/CMakeLists.txt b/CMakeLists.txt index 769d80a..516e80f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,6 +52,7 @@ idf_component_register( "src/NimBLERemoteCharacteristic.cpp" "src/NimBLERemoteDescriptor.cpp" "src/NimBLERemoteService.cpp" + "src/NimBLERemoteValueAttribute.cpp" "src/NimBLEScan.cpp" "src/NimBLEServer.cpp" "src/NimBLEService.cpp" diff --git a/docs/Migration_guide.md b/docs/Migration_guide.md index d1fcee8..accf1bc 100644 --- a/docs/Migration_guide.md +++ b/docs/Migration_guide.md @@ -383,13 +383,13 @@ The security callback methods are now incorporated in the `NimBLEServerCallbacks 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. Call `NimBLEDevice::injectConfirmPIN(connInfo, true);` to accept or `NimBLEDevice::injectConfirmPIN(connInfo, false);` to reject.
-> `void onPassKeyEntry(const NimBLEConnInfo& connInfo)` +> `void onPassKeyEntry(NimBLEConnInfo& connInfo)` Client callback; client should respond with the passkey (pin) by calling `NimBLEDevice::injectPassKey(connInfo, 123456);`
@@ -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.
-> `void onAuthenticationComplete(const NimBLEConnInfo& connInfo)` +> `void onAuthenticationComplete(NimBLEConnInfo& connInfo)` Authentication complete, success or failed information is available from the `NimBLEConnInfo` methods.
diff --git a/examples/Advanced/NimBLE_Client/main/main.cpp b/examples/Advanced/NimBLE_Client/main/main.cpp index 90e2128..47f8419 100644 --- a/examples/Advanced/NimBLE_Client/main/main.cpp +++ b/examples/Advanced/NimBLE_Client/main/main.cpp @@ -39,7 +39,7 @@ class ClientCallbacks : public NimBLEClientCallbacks { /********************* Security handled here ********************** ****** Note: these are the same return values as defaults ********/ - void onPassKeyEntry(const NimBLEConnInfo& connInfo){ + void onPassKeyEntry(NimBLEConnInfo& connInfo){ printf("Server Passkey Entry\n"); /** This should prompt the user to enter the passkey displayed * on the peer device. @@ -47,14 +47,14 @@ class ClientCallbacks : public NimBLEClientCallbacks { 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); /** Inject false if passkeys don't match. */ NimBLEDevice::injectConfirmPIN(connInfo, true); }; /** Pairing process complete, we can check the results in connInfo */ - void onAuthenticationComplete(const NimBLEConnInfo& connInfo){ + void onAuthenticationComplete(NimBLEConnInfo& connInfo){ if(!connInfo.isEncrypted()) { printf("Encrypt connection failed - disconnecting\n"); /** 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){ std::string str = (isNotify == true) ? "Notification" : "Indication"; str += " from "; - str += pRemoteCharacteristic->getRemoteService()->getClient()->getPeerAddress().toString(); + str += pRemoteCharacteristic->getClient()->getPeerAddress().toString(); str += ": Service = " + pRemoteCharacteristic->getRemoteService()->getUUID().toString(); str += ", Characteristic = " + pRemoteCharacteristic->getUUID().toString(); str += ", Value = " + std::string((char*)pData, length); diff --git a/examples/Advanced/NimBLE_Server/main/main.cpp b/examples/Advanced/NimBLE_Server/main/main.cpp index 3effb54..472faa7 100644 --- a/examples/Advanced/NimBLE_Server/main/main.cpp +++ b/examples/Advanced/NimBLE_Server/main/main.cpp @@ -52,13 +52,13 @@ class ServerCallbacks: public NimBLEServerCallbacks { 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); /** Inject false if passkeys don't match. */ NimBLEDevice::injectConfirmPIN(connInfo, true); }; - void onAuthenticationComplete(const NimBLEConnInfo& connInfo){ + void onAuthenticationComplete(NimBLEConnInfo& connInfo){ /** Check that encryption was successful, if not we disconnect the client */ if(!connInfo.isEncrypted()) { NimBLEDevice::getServer()->disconnect(connInfo.getConnHandle()); @@ -142,7 +142,7 @@ void notifyTask(void * parameter){ if(pSvc) { NimBLECharacteristic* pChr = pSvc->getCharacteristic("F00D"); if(pChr) { - pChr->notify(true); + pChr->notify(); } } } diff --git a/examples/NimBLE_server_get_client_name/main/main.cpp b/examples/NimBLE_server_get_client_name/main/main.cpp index e255807..6f19db8 100644 --- a/examples/NimBLE_server_get_client_name/main/main.cpp +++ b/examples/NimBLE_server_get_client_name/main/main.cpp @@ -25,7 +25,7 @@ class ServerCallbacks : public NimBLEServerCallbacks { } // 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()) { NimBLEDevice::getServer()->disconnect(connInfo.getConnHandle()); printf("Encrypt connection failed - disconnecting client\n"); diff --git a/examples/basic/BLE_client/main/main.cpp b/examples/basic/BLE_client/main/main.cpp index cfb80e9..670c4ca 100644 --- a/examples/basic/BLE_client/main/main.cpp +++ b/examples/basic/BLE_client/main/main.cpp @@ -51,7 +51,7 @@ class MyClientCallback : public BLEClientCallbacks { } /***************** New - Security handled here ******************** ****** Note: these are the same return values as defaults ********/ - void onPassKeyEntry(const NimBLEConnInfo& connInfo){ + void onPassKeyEntry(NimBLEConnInfo& connInfo){ printf("Server Passkey Entry\n"); /** This should prompt the user to enter the passkey displayed * on the peer device. @@ -59,14 +59,14 @@ class MyClientCallback : public BLEClientCallbacks { 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); /** Inject false if passkeys don't match. */ NimBLEDevice::injectConfirmPIN(connInfo, true); }; /** Pairing process complete, we can check the results in connInfo */ - void onAuthenticationComplete(const NimBLEConnInfo& connInfo){ + void onAuthenticationComplete(NimBLEConnInfo& connInfo){ if(!connInfo.isEncrypted()) { printf("Encrypt connection failed - disconnecting\n"); /** Find the client with the connection handle provided in desc */ diff --git a/examples/basic/BLE_notify/main/main.cpp b/examples/basic/BLE_notify/main/main.cpp index b17f49a..807545a 100644 --- a/examples/basic/BLE_notify/main/main.cpp +++ b/examples/basic/BLE_notify/main/main.cpp @@ -65,13 +65,13 @@ class MyServerCallbacks: public BLEServerCallbacks { 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); /** Inject false if passkeys don't match. */ NimBLEDevice::injectConfirmPIN(connInfo, true); }; - void onAuthenticationComplete(const NimBLEConnInfo& connInfo){ + void onAuthenticationComplete(NimBLEConnInfo& connInfo){ /** Check that encryption was successful, if not we disconnect the client */ if(!connInfo.isEncrypted()) { NimBLEDevice::getServer()->disconnect(connInfo.getConnHandle()); diff --git a/examples/basic/BLE_uart/main/main.cpp b/examples/basic/BLE_uart/main/main.cpp index 18df6fa..3d0fd5c 100644 --- a/examples/basic/BLE_uart/main/main.cpp +++ b/examples/basic/BLE_uart/main/main.cpp @@ -67,13 +67,13 @@ class MyServerCallbacks: public BLEServerCallbacks { 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); /** Inject false if passkeys don't match. */ NimBLEDevice::injectConfirmPIN(connInfo, true); }; - void onAuthenticationComplete(const NimBLEConnInfo& connInfo){ + void onAuthenticationComplete(NimBLEConnInfo& connInfo){ /** Check that encryption was successful, if not we disconnect the client */ if(!connInfo.isEncrypted()) { NimBLEDevice::getServer()->disconnect(connInfo.getConnHandle()); diff --git a/src/NimBLEAttValue.h b/src/NimBLEAttValue.h index b95e38d..853ae06 100644 --- a/src/NimBLEAttValue.h +++ b/src/NimBLEAttValue.h @@ -192,12 +192,7 @@ class NimBLEAttValue { return setValue(reinterpret_cast(s), len); } - /** - * @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 { + const NimBLEAttValue& getValue(time_t* timestamp = nullptr) const { if (timestamp != nullptr) { # if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED *timestamp = m_timestamp; @@ -205,7 +200,7 @@ class NimBLEAttValue { *timestamp = 0; # endif } - return m_attr_value; + return *this; } /** @@ -271,7 +266,15 @@ class NimBLEAttValue { if (!skipSizeCheck && size() < sizeof(T)) { return T(); } - return *(reinterpret_cast(getValue(timestamp))); + if (timestamp != nullptr) { +# if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED + *timestamp = m_timestamp; +# else + *timestamp = 0; +# endif + } + + return *(reinterpret_cast(m_attr_value)); } /*********************** Operators ************************/ diff --git a/src/NimBLEAttribute.h b/src/NimBLEAttribute.h new file mode 100644 index 0000000..8fb3b7a --- /dev/null +++ b/src/NimBLEAttribute.h @@ -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_ diff --git a/src/NimBLECharacteristic.cpp b/src/NimBLECharacteristic.cpp index ba68a73..6a7b07c 100644 --- a/src/NimBLECharacteristic.cpp +++ b/src/NimBLECharacteristic.cpp @@ -13,18 +13,16 @@ #include "nimconfig.h" #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) -#include "NimBLECharacteristic.h" -#include "NimBLE2904.h" -#include "NimBLEDevice.h" -#include "NimBLELog.h" +# include "NimBLECharacteristic.h" +# include "NimBLE2904.h" +# include "NimBLEDevice.h" +# include "NimBLELog.h" -#define NULL_HANDLE (0xffff) -#define NIMBLE_SUB_NOTIFY 0x0001 -#define NIMBLE_SUB_INDICATE 0x0002 +# define NIMBLE_SUB_NOTIFY 0x0001 +# define NIMBLE_SUB_INDICATE 0x0002 static NimBLECharacteristicCallbacks defaultCallback; -static const char* LOG_TAG = "NimBLECharacteristic"; - +static const char* LOG_TAG = "NimBLECharacteristic"; /** * @brief Construct a 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] pService - pointer to the service instance this characteristic belongs to. */ -NimBLECharacteristic::NimBLECharacteristic(const char* uuid, uint16_t properties, - uint16_t max_len, NimBLEService* pService) -: NimBLECharacteristic(NimBLEUUID(uuid), properties, max_len, pService) { -} +NimBLECharacteristic::NimBLECharacteristic(const char* uuid, uint16_t properties, uint16_t max_len, NimBLEService* pService) + : NimBLECharacteristic(NimBLEUUID(uuid), properties, max_len, pService) {} /** * @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] pService - pointer to the service instance this characteristic belongs to. */ -NimBLECharacteristic::NimBLECharacteristic(const NimBLEUUID &uuid, uint16_t properties, - uint16_t max_len, NimBLEService* pService) -: m_value(std::min(CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH , (int)max_len), max_len) { - m_uuid = uuid; - m_handle = NULL_HANDLE; - m_properties = properties; - m_pCallbacks = &defaultCallback; - m_pService = pService; - m_removed = 0; +NimBLECharacteristic::NimBLECharacteristic(const NimBLEUUID& uuid, uint16_t properties, uint16_t max_len, NimBLEService* pService) + : NimBLELocalValueAttribute{uuid, 0, max_len}, m_pCallbacks{&defaultCallback}, m_pService{pService} { + setProperties(properties); } // NimBLECharacteristic /** * @brief Destructor. */ NimBLECharacteristic::~NimBLECharacteristic() { - for(auto &it : m_dscVec) { - delete it; + for (const auto& dsc : m_vDescriptors) { + delete dsc; } } // ~NimBLECharacteristic - /** * @brief Create a new BLE Descriptor associated with this characteristic. * @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); } - /** * @brief Create a new BLE Descriptor associated with this characteristic. * @param [in] uuid - The UUID of the descriptor. @@ -85,7 +73,7 @@ NimBLEDescriptor* NimBLECharacteristic::createDescriptor(const char* uuid, uint3 * @param [in] max_len - The max length in bytes of the descriptor value. * @return The new BLE descriptor. */ -NimBLEDescriptor* NimBLECharacteristic::createDescriptor(const NimBLEUUID &uuid, uint32_t properties, uint16_t max_len) { +NimBLEDescriptor* NimBLECharacteristic::createDescriptor(const NimBLEUUID& uuid, uint32_t properties, uint16_t max_len) { NimBLEDescriptor* pDescriptor = nullptr; if (uuid == NimBLEUUID(uint16_t(0x2904))) { pDescriptor = new NimBLE2904(this); @@ -97,47 +85,52 @@ NimBLEDescriptor* NimBLECharacteristic::createDescriptor(const NimBLEUUID &uuid, return pDescriptor; } // createDescriptor - /** * @brief Add a descriptor to the characteristic. * @param [in] pDescriptor A pointer to the descriptor to add. */ -void NimBLECharacteristic::addDescriptor(NimBLEDescriptor *pDescriptor) { +void NimBLECharacteristic::addDescriptor(NimBLEDescriptor* pDescriptor) { bool foundRemoved = false; - - if(pDescriptor->m_removed > 0) { - for(auto& it : m_dscVec) { - if(it == pDescriptor) { + if (pDescriptor->getRemoved() > 0) { + for (const auto& dsc : m_vDescriptors) { + if (dsc == pDescriptor) { foundRemoved = true; - pDescriptor->m_removed = 0; + pDescriptor->setRemoved(0); } } } - if(!foundRemoved) { - m_dscVec.push_back(pDescriptor); + // 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) { + m_vDescriptors.push_back(pDescriptor); } pDescriptor->setCharacteristic(this); NimBLEDevice::getServer()->serviceChanged(); } - /** * @brief Remove a descriptor from the characteristic. * @param[in] pDescriptor A pointer to the descriptor instance to remove from the characteristic. * @param[in] deleteDsc If true it will delete the descriptor instance and free it's resources. */ -void NimBLECharacteristic::removeDescriptor(NimBLEDescriptor *pDescriptor, bool deleteDsc) { +void NimBLECharacteristic::removeDescriptor(NimBLEDescriptor* pDescriptor, bool deleteDsc) { // 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. // Otherwise, ignore the call and return. - if(pDescriptor->m_removed > 0) { - if(deleteDsc) { - for(auto it = m_dscVec.begin(); it != m_dscVec.end(); ++it) { + if (pDescriptor->getRemoved() > 0) { + if (deleteDsc) { + for (auto it = m_vDescriptors.begin(); it != m_vDescriptors.end(); ++it) { if ((*it) == pDescriptor) { - delete *it; - m_dscVec.erase(it); + delete (*it); + m_vDescriptors.erase(it); break; } } @@ -146,30 +139,28 @@ void NimBLECharacteristic::removeDescriptor(NimBLEDescriptor *pDescriptor, bool 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(); } // removeDescriptor - /** * @brief Return the BLE Descriptor for the given UUID. * @param [in] uuid The UUID of the descriptor. * @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)); } // getDescriptorByUUID - /** * @brief Return the BLE Descriptor for the given UUID. * @param [in] uuid The UUID of the descriptor. * @return A pointer to the descriptor object or nullptr if not found. */ -NimBLEDescriptor* NimBLECharacteristic::getDescriptorByUUID(const NimBLEUUID &uuid) { - for (auto &it : m_dscVec) { - if (it->getUUID() == uuid) { - return it; +NimBLEDescriptor* NimBLECharacteristic::getDescriptorByUUID(const NimBLEUUID& uuid) const { + for (const auto& dsc : m_vDescriptors) { + if (dsc->getUUID() == uuid) { + return dsc; } } return nullptr; @@ -180,351 +171,230 @@ NimBLEDescriptor* NimBLECharacteristic::getDescriptorByUUID(const NimBLEUUID &uu * @param [in] handle The handle of the descriptor. * @return A pointer to the descriptor object or nullptr if not found. */ -NimBLEDescriptor *NimBLECharacteristic::getDescriptorByHandle(uint16_t handle) { - for (auto &it : m_dscVec) { - if (it->getHandle() == handle) { - return it; +NimBLEDescriptor* NimBLECharacteristic::getDescriptorByHandle(uint16_t handle) const { + for (const auto& dsc : m_vDescriptors) { + if (dsc->getHandle() == handle) { + return dsc; } } return nullptr; -} - - -/** - * @brief Get the handle of the characteristic. - * @return The handle of the characteristic. - */ -uint16_t NimBLECharacteristic::getHandle() { - return m_handle; -} // getHandle - +} // getDescriptorByHandle /** * @brief Get the properties of the characteristic. * @return The properties of the characteristic. */ -uint16_t NimBLECharacteristic::getProperties() { +uint16_t NimBLECharacteristic::getProperties() const { return m_properties; } // 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; } // getService - -void NimBLECharacteristic::setService(NimBLEService *pService) { +void NimBLECharacteristic::setService(NimBLEService* pService) { m_pService = pService; -} - - -/** - * @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; -} - +} // setService /** * @brief Get the number of clients subscribed to the characteristic. * @returns Number of clients subscribed to notifications / indications. */ -size_t NimBLECharacteristic::getSubscribedCount() { +size_t NimBLECharacteristic::getSubscribedCount() const { return m_subscribedVec.size(); } - /** * @brief Set the subscribe status for this characteristic.\n * This will maintain a vector of subscribed clients and their indicate/notify status. */ -void NimBLECharacteristic::setSubscribe(struct ble_gap_event *event) { - NimBLEConnInfo peerInfo; - if(ble_gap_conn_find(event->subscribe.conn_handle, &peerInfo.m_desc) != 0) { - return; - } - +void NimBLECharacteristic::setSubscribe(const ble_gap_event* event, NimBLEConnInfo& connInfo) { 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; } - if(event->subscribe.cur_indicate && (m_properties & NIMBLE_PROPERTY::INDICATE)) { + if (event->subscribe.cur_indicate && (m_properties & NIMBLE_PROPERTY::INDICATE)) { subVal |= NIMBLE_SUB_INDICATE; } - NIMBLE_LOGI(LOG_TAG, "New subscribe value for conn: %d val: %d", - event->subscribe.conn_handle, subVal); + NIMBLE_LOGI(LOG_TAG, "New subscribe value for conn: %d val: %d", connInfo.getConnHandle(), subVal); - if(!event->subscribe.cur_indicate && event->subscribe.prev_indicate) { - NimBLEDevice::getServer()->clearIndicateWait(event->subscribe.conn_handle); + if (!event->subscribe.cur_indicate && event->subscribe.prev_indicate) { + NimBLEDevice::getServer()->clearIndicateWait(connInfo.getConnHandle()); } - auto it = m_subscribedVec.begin(); - for(;it != m_subscribedVec.end(); ++it) { - if((*it).first == event->subscribe.conn_handle) { + for (; it != m_subscribedVec.end(); ++it) { + if ((*it).first == connInfo.getConnHandle()) { break; } } - if(subVal > 0) { - if(it == m_subscribedVec.end()) { - m_subscribedVec.push_back({event->subscribe.conn_handle, subVal}); + if (subVal > 0) { + if (it == m_subscribedVec.end()) { + m_subscribedVec.push_back({connInfo.getConnHandle(), subVal}); } else { (*it).second = subVal; } - } else if(it != m_subscribedVec.end()) { + } else if (it != m_subscribedVec.end()) { m_subscribedVec.erase(it); } - m_pCallbacks->onSubscribe(this, peerInfo, subVal); + m_pCallbacks->onSubscribe(this, connInfo, subVal); } - /** * @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() { - notify(false); +void NimBLECharacteristic::indicate(uint16_t conn_handle) const { + sendValue(m_value.data(), m_value.size(), false, conn_handle); } // indicate - /** * @brief Send an indication. * @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 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) { - notify(value, length, false); +void NimBLECharacteristic::indicate(const uint8_t* value, size_t length, uint16_t conn_handle) const { + sendValue(value, length, false, conn_handle); } // indicate - /** * @brief Send an indication. * @param[in] value A std::vector 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& value) { - notify(value.data(), value.size(), false); +void NimBLECharacteristic::indicate(const std::vector& value, uint16_t conn_handle) const { + sendValue(value.data(), value.size(), false, conn_handle); } // indicate - /** - * @brief Send a notification or 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. + * @brief Send a notification. + * @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(bool is_notification, uint16_t conn_handle) { - notify(m_value.data(), m_value.length(), is_notification, conn_handle); +void NimBLECharacteristic::notify(uint16_t conn_handle) const { + sendValue(m_value.data(), m_value.size(), true, conn_handle); } // 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 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 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 an individual notification, or BLE_HS_CONN_HANDLE_NONE to send + * the notification to all subscribed clients. */ -void NimBLECharacteristic::notify(const std::vector& value, bool is_notification, uint16_t conn_handle) { - notify(value.data(), value.size(), is_notification, conn_handle); +void NimBLECharacteristic::notify(const std::vector& value, uint16_t conn_handle) const { + sendValue(value.data(), value.size(), true, conn_handle); } // 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] length The length of the data to send. * @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) { - NIMBLE_LOGD(LOG_TAG, ">> notify: length: %d", length); +void NimBLECharacteristic::sendValue(const uint8_t* value, size_t length, bool is_notification, uint16_t conn_handle) const { + NIMBLE_LOGD(LOG_TAG, ">> sendValue"); - if(!(m_properties & NIMBLE_PROPERTY::NOTIFY) && - !(m_properties & NIMBLE_PROPERTY::INDICATE)) - { - 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."); + if (is_notification && !(getProperties() & NIMBLE_PROPERTY::NOTIFY)) { + NIMBLE_LOGE(LOG_TAG, "<< sendValue: notification not enabled for characteristic"); 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) || - (m_properties & BLE_GATT_CHR_F_READ_AUTHOR) || - (m_properties & BLE_GATT_CHR_F_READ_ENC); - int rc = 0; + if (!m_subscribedVec.size()) { + NIMBLE_LOGD(LOG_TAG, "<< sendValue: No clients subscribed."); + return; + } - for (auto &it : m_subscribedVec) { - // check if need a specific client + for (const auto& it : m_subscribedVec) { + // 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)) { 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(_mtu == 0 || it.second == 0) { + if (!is_notification && !(it.second & NIMBLE_SUB_INDICATE)) { continue; } // check if security requirements are satisfied - if(reqSec) { - struct ble_gap_conn_desc desc; - rc = ble_gap_conn_find(it.first, &desc); - if(rc != 0 || !desc.sec_state.encrypted) { + if ((getProperties() & BLE_GATT_CHR_F_READ_AUTHEN) || (getProperties() & BLE_GATT_CHR_F_READ_AUTHOR) || + (getProperties() & BLE_GATT_CHR_F_READ_ENC)) { + ble_gap_conn_desc desc; + if (ble_gap_conn_find(it.first, &desc) != 0 || !desc.sec_state.encrypted) { 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 // 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. - 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(!NimBLEDevice::getServer()->setIndicateWait(it.first)) { - NIMBLE_LOGE(LOG_TAG, "prior Indication in progress"); - os_mbuf_free_chain(om); - return; + if (is_notification) { + ble_gattc_notify_custom(it.first, getHandle(), om); + } else { + if (!NimBLEDevice::getServer()->setIndicateWait(it.first)) { + NIMBLE_LOGE(LOG_TAG, "<< sendValue: waiting for previous indicate"); + os_mbuf_free_chain(om); + return; } - rc = ble_gattc_indicate_custom(it.first, m_handle, om); - if(rc != 0){ + if (ble_gattc_indicate_custom(it.first, getHandle(), om) != 0) { NimBLEDevice::getServer()->clearIndicateWait(it.first); } - } else { - ble_gattc_notify_custom(it.first, m_handle, om); } } - NIMBLE_LOGD(LOG_TAG, "<< notify"); -} // Notify + NIMBLE_LOGD(LOG_TAG, "<< sendValue"); +} // 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. @@ -532,60 +402,31 @@ void NimBLECharacteristic::notify(const uint8_t* value, size_t length, bool is_n * used to define any callbacks for the characteristic. */ void NimBLECharacteristic::setCallbacks(NimBLECharacteristicCallbacks* pCallbacks) { - if (pCallbacks != nullptr){ + if (pCallbacks != nullptr) { m_pCallbacks = pCallbacks; } else { m_pCallbacks = &defaultCallback; } } // setCallbacks - /** * @brief Get the callback handlers for this characteristic. */ -NimBLECharacteristicCallbacks* NimBLECharacteristic::getCallbacks() { +NimBLECharacteristicCallbacks* NimBLECharacteristic::getCallbacks() const { return m_pCallbacks; -} //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`.\n - * @param [in] vec The std::vector reference to set the characteristic value from. - */ -void NimBLECharacteristic::setValue(const std::vector& vec) { - return setValue((uint8_t*)&vec[0], vec.size()); -}// setValue - +} // getCallbacks /** * @brief 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"; - char hex[5]; - snprintf(hex, sizeof(hex), "%04x", m_handle); + char hex[5]; + snprintf(hex, sizeof(hex), "%04x", getHandle()); res += hex; res += " "; - if (m_properties & BLE_GATT_CHR_PROP_READ ) res += "Read "; + if (m_properties & BLE_GATT_CHR_PROP_READ) res += "Read "; if (m_properties & BLE_GATT_CHR_PROP_WRITE) res += "Write "; if (m_properties & BLE_GATT_CHR_PROP_WRITE_NO_RSP) res += "WriteNoResponse "; if (m_properties & BLE_GATT_CHR_PROP_BROADCAST) res += "Broadcast "; @@ -594,7 +435,6 @@ std::string NimBLECharacteristic::toString() { return res; } // toString - /** * @brief Callback function to support a read request. * @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"); } // onRead - /** * @brief Callback function to support a write request. * @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"); } // 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. * @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"); } // onStatus - /** * @brief Callback function called when a client changes subscription status. * @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, NimBLEConnInfo& connInfo, - uint16_t subValue) -{ + uint16_t subValue) { NIMBLE_LOGD("NimBLECharacteristicCallbacks", "onSubscribe: default"); } diff --git a/src/NimBLECharacteristic.h b/src/NimBLECharacteristic.h index 494242c..c43054c 100644 --- a/src/NimBLECharacteristic.h +++ b/src/NimBLECharacteristic.h @@ -11,131 +11,71 @@ * Author: kolban */ -#ifndef MAIN_NIMBLECHARACTERISTIC_H_ -#define MAIN_NIMBLECHARACTERISTIC_H_ +#ifndef NIMBLE_CPP_CHARACTERISTIC_H_ +#define NIMBLE_CPP_CHARACTERISTIC_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 - -/**** 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 "NimBLEService.h" -#include "NimBLEDescriptor.h" -#include "NimBLEAttValue.h" -#include "NimBLEConnInfo.h" - -#include -#include - -class NimBLEService; -class NimBLEDescriptor; class NimBLECharacteristicCallbacks; +class NimBLECharacteristic; +# include "NimBLELocalValueAttribute.h" +# include "NimBLEServer.h" +# include "NimBLEService.h" +# include "NimBLEDescriptor.h" +# include "NimBLEAttValue.h" +# include "NimBLEConnInfo.h" + +# include +# include /** - * @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 - * can be read and written to by a %BLE client. + * 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. */ -class NimBLECharacteristic { -public: - NimBLECharacteristic(const char* uuid, - uint16_t properties = - NIMBLE_PROPERTY::READ | - NIMBLE_PROPERTY::WRITE, - uint16_t max_len = BLE_ATT_ATTR_MAX_LEN, - NimBLEService* pService = nullptr); - NimBLECharacteristic(const NimBLEUUID &uuid, - uint16_t properties = - NIMBLE_PROPERTY::READ | - NIMBLE_PROPERTY::WRITE, - uint16_t max_len = BLE_ATT_ATTR_MAX_LEN, - NimBLEService* pService = nullptr); +class NimBLECharacteristic : public NimBLELocalValueAttribute { + public: + NimBLECharacteristic(const char* uuid, + uint16_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE, + uint16_t max_len = BLE_ATT_ATTR_MAX_LEN, + NimBLEService* pService = nullptr); + NimBLECharacteristic(const NimBLEUUID& uuid, + uint16_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE, + uint16_t max_len = BLE_ATT_ATTR_MAX_LEN, + NimBLEService* pService = nullptr); ~NimBLECharacteristic(); - uint16_t getHandle(); - NimBLEUUID getUUID(); - std::string toString(); - void indicate(); - void indicate(const uint8_t* value, size_t length); - void indicate(const std::vector& 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& value, bool is_notification = true, uint16_t conn_handle = BLE_HCI_LE_CONN_HANDLE_MAX + 1); - size_t getSubscribedCount(); - 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); - NimBLEService* getService(); - 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& vec); - void setCallbacks(NimBLECharacteristicCallbacks* pCallbacks); + std::string toString() const; + size_t getSubscribedCount() const; + void addDescriptor(NimBLEDescriptor* pDescriptor); + void removeDescriptor(NimBLEDescriptor* pDescriptor, bool deleteDsc = false); + uint16_t getProperties() const; + 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& 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& value, uint16_t conn_handle = BLE_HS_CONN_HANDLE_NONE) const; + NimBLEDescriptor* createDescriptor(const char* uuid, - uint32_t properties = - 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); - - NimBLECharacteristicCallbacks* getCallbacks(); + uint32_t properties = 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); + NimBLEDescriptor* getDescriptorByUUID(const char* uuid) const; + NimBLEDescriptor* getDescriptorByUUID(const NimBLEUUID& uuid) const; + NimBLEDescriptor* getDescriptorByHandle(uint16_t handle) const; + NimBLEService* getService() const; + NimBLECharacteristicCallbacks* getCallbacks() const; /*********************** Template Functions ************************/ - /** - * @brief Template to set the characteristic value to val. - * @param [in] s The value to set. - */ - template - void setValue(const T &s) { m_value.setValue(s); } - - /** - * @brief Template to convert the characteristic data to . - * @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 sizeof(). - * @return The data converted to or NULL if skipSizeCheck is false and the data is less than sizeof(). - * @details Use: getValue(×tamp, skipSizeCheck); - */ - template - T getValue(time_t *timestamp = nullptr, bool skipSizeCheck = false) { - return m_value.getValue(timestamp, skipSizeCheck); - } - /** * @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. @@ -143,14 +83,14 @@ public: * @param[in] is_notification if true sends a notification, false sends an indication. * @details Only used if the has a `c_str()` method. */ - template -#ifdef _DOXYGEN_ + template +# ifdef _DOXYGEN_ void -#else +# else typename std::enable_if::value, void>::type -#endif - notify(const T& value, bool is_notification = true) { - notify((uint8_t*)value.c_str(), value.length(), is_notification); +# endif + notify(const T& value, bool is_notification = true) const { + notify(reinterpret_cast(value.c_str()), value.length(), is_notification); } /** @@ -159,39 +99,35 @@ public: * @param[in] value The value to set. * @details Only used if the has a `c_str()` method. */ - template -#ifdef _DOXYGEN_ + template +# ifdef _DOXYGEN_ void -#else +# else typename std::enable_if::value, void>::type -#endif - indicate(const T& value) { - indicate((uint8_t*)value.c_str(), value.length()); +# endif + indicate(const T& value) const { + indicate(reinterpret_cast(value.c_str()), value.length()); } -private: + private: + friend class NimBLEServer; + friend class NimBLEService; - friend class NimBLEServer; - friend class NimBLEService; + void setService(NimBLEService* pService); + void setSubscribe(const ble_gap_event* event, NimBLEConnInfo& connInfo); + void readEvent(NimBLEConnInfo& connInfo) override; + 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; - void setService(NimBLEService *pService); - void setSubscribe(struct ble_gap_event *event); - static int handleGapEvent(uint16_t conn_handle, uint16_t attr_handle, - struct ble_gatt_access_ctxt *ctxt, void *arg); - - NimBLEUUID m_uuid; - uint16_t m_handle; - uint16_t m_properties; - NimBLECharacteristicCallbacks* m_pCallbacks; - NimBLEService* m_pService; - NimBLEAttValue m_value; - std::vector m_dscVec; - uint8_t m_removed; - - std::vector> m_subscribedVec; + NimBLECharacteristicCallbacks* m_pCallbacks{nullptr}; + NimBLEService* m_pService{nullptr}; + std::vector m_vDescriptors{}; + std::vector> m_subscribedVec{}; }; // NimBLECharacteristic - /** * @brief Callbacks that can be associated with a %BLE characteristic to inform of events. * @@ -200,14 +136,13 @@ private: * sub-classed instance of this class and will be notified when such an event happens. */ class NimBLECharacteristicCallbacks { -public: - virtual ~NimBLECharacteristicCallbacks(){} + public: + virtual ~NimBLECharacteristicCallbacks() {} virtual void onRead(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 onSubscribe(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue); }; #endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */ -#endif /*MAIN_NIMBLECHARACTERISTIC_H_*/ +#endif /*NIMBLE_CPP_CHARACTERISTIC_H_*/ diff --git a/src/NimBLEClient.cpp b/src/NimBLEClient.cpp index 0fbcbb4..9d96fbd 100644 --- a/src/NimBLEClient.cpp +++ b/src/NimBLEClient.cpp @@ -15,6 +15,8 @@ #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) #include "NimBLEClient.h" +#include "NimBLERemoteService.h" +#include "NimBLERemoteCharacteristic.h" #include "NimBLEDevice.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. * @return True on success. */ -bool NimBLEClient::secureConnection() { +bool NimBLEClient::secureConnection() const { NIMBLE_LOGD(LOG_TAG, ">> secureConnection()"); TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); - ble_task_data_t taskData = {this, cur_task, 0, nullptr}; + ble_task_data_t taskData = {const_cast(this), cur_task, 0, nullptr}; int retryCount = 1; @@ -542,7 +544,7 @@ void NimBLEClient::setConnectTimeout(uint32_t time) { * @brief Get the connection id for this client. * @return The connection id. */ -uint16_t NimBLEClient::getConnId() { +uint16_t NimBLEClient::getConnId() const { return m_conn_id; } // getConnId @@ -610,7 +612,7 @@ bool NimBLEClient::setConnection(uint16_t conn_id) { /** * @brief Retrieve the address of the peer. */ -NimBLEAddress NimBLEClient::getPeerAddress() { +NimBLEAddress NimBLEClient::getPeerAddress() const { return m_peerAddress; } // getPeerAddress @@ -776,7 +778,7 @@ bool NimBLEClient::discoverAttributes() { return false; } - for(auto chr: svc->m_characteristicVector) { + for(auto chr: svc->m_vChars) { if (!chr->retrieveDescriptors()) { 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. * @return true on success otherwise false if an error occurred */ -bool NimBLEClient::retrieveServices(const NimBLEUUID *uuid_filter) { +bool NimBLEClient::retrieveServices(const NimBLEUUID *uuidFilter) { /** * Design * ------ @@ -811,10 +813,10 @@ bool NimBLEClient::retrieveServices(const NimBLEUUID *uuid_filter) { TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); 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); } 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); } @@ -972,7 +974,7 @@ NimBLERemoteCharacteristic* NimBLEClient::getCharacteristic(const uint16_t handl * @brief Get the current mtu of this connection. * @returns The MTU value. */ -uint16_t NimBLEClient::getMTU() { +uint16_t NimBLEClient::getMTU() const { return ble_att_mtu(m_conn_id); } // getMTU @@ -1086,7 +1088,7 @@ int NimBLEClient::handleGapEvent(struct ble_gap_event *event, void *arg) { continue; } - auto cVector = &it->m_characteristicVector; + auto cVector = &it->m_vChars; NIMBLE_LOGD(LOG_TAG, "checking service %s for handle: %d", it->getUUID().toString().c_str(), event->notify_rx.attr_handle); @@ -1325,20 +1327,20 @@ bool NimBLEClientCallbacks::onConnParamsUpdateRequest(NimBLEClient* pClient, con return true; } -void NimBLEClientCallbacks::onPassKeyEntry(const NimBLEConnInfo& connInfo){ +void NimBLEClientCallbacks::onPassKeyEntry(NimBLEConnInfo& connInfo){ NIMBLE_LOGD("NimBLEClientCallbacks", "onPassKeyEntry: default: 123456"); NimBLEDevice::injectPassKey(connInfo, 123456); } //onPassKeyEntry -void NimBLEClientCallbacks::onAuthenticationComplete(const NimBLEConnInfo& connInfo){ +void NimBLEClientCallbacks::onAuthenticationComplete(NimBLEConnInfo& connInfo){ NIMBLE_LOGD("NimBLEClientCallbacks", "onAuthenticationComplete: default"); } -void NimBLEClientCallbacks::onIdentity(const NimBLEConnInfo& connInfo){ +void NimBLEClientCallbacks::onIdentity(NimBLEConnInfo& connInfo){ NIMBLE_LOGD("NimBLEClientCallbacks", "onIdentity: default"); } // onIdentity -void NimBLEClientCallbacks::onConfirmPIN(const NimBLEConnInfo& connInfo, uint32_t pin){ +void NimBLEClientCallbacks::onConfirmPIN(NimBLEConnInfo& connInfo, uint32_t pin){ NIMBLE_LOGD("NimBLEClientCallbacks", "onConfirmPIN: default: true"); NimBLEDevice::injectConfirmPIN(connInfo, true); } diff --git a/src/NimBLEClient.h b/src/NimBLEClient.h index acef487..b571e90 100644 --- a/src/NimBLEClient.h +++ b/src/NimBLEClient.h @@ -23,15 +23,14 @@ #include "NimBLEConnInfo.h" #include "NimBLEAttValue.h" #include "NimBLEAdvertisedDevice.h" -#include "NimBLERemoteService.h" #include #include class NimBLERemoteService; class NimBLERemoteCharacteristic; -class NimBLEClientCallbacks; class NimBLEAdvertisedDevice; +class NimBLEClientCallbacks; /** * @brief A model of a %BLE client. @@ -42,7 +41,7 @@ public: bool connect(const NimBLEAddress &address, bool deleteAttributes = true); bool connect(bool deleteAttributes = true); int disconnect(uint8_t reason = BLE_ERR_REM_USER_CONN_TERM); - NimBLEAddress getPeerAddress(); + NimBLEAddress getPeerAddress() const; void setPeerAddress(const NimBLEAddress &address); int getRssi(); std::vector* getServices(bool refresh = false); @@ -60,12 +59,12 @@ public: void setClientCallbacks(NimBLEClientCallbacks *pClientCallbacks, bool deleteCallbacks = true); std::string toString(); - uint16_t getConnId(); + uint16_t getConnId() const; void clearConnection(); bool setConnection(NimBLEConnInfo &conn_info); bool setConnection(uint16_t conn_id); - uint16_t getMTU(); - bool secureConnection(); + uint16_t getMTU() const; + bool secureConnection() const; void setConnectTimeout(uint32_t timeout); void setConnectionParams(uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t timeout, @@ -93,17 +92,17 @@ private: const struct ble_gatt_svc *service, void *arg); static void dcTimerCb(ble_npl_event *event); - bool retrieveServices(const NimBLEUUID *uuid_filter = nullptr); + bool retrieveServices(const NimBLEUUID *uuidFilter = nullptr); - NimBLEAddress m_peerAddress; - int m_lastErr; - uint16_t m_conn_id; - bool m_connEstablished; - bool m_deleteCallbacks; - int32_t m_connectTimeout; - NimBLEClientCallbacks* m_pClientCallbacks; - ble_task_data_t* m_pTaskData; - ble_npl_callout m_dcTimer; + NimBLEAddress m_peerAddress; + mutable int m_lastErr; + uint16_t m_conn_id; + bool m_connEstablished; + bool m_deleteCallbacks; + int32_t m_connectTimeout; + NimBLEClientCallbacks* m_pClientCallbacks; + mutable ble_task_data_t* m_pTaskData; + ble_npl_callout m_dcTimer; #if CONFIG_BT_NIMBLE_EXT_ADV uint8_t m_phyMask; #endif @@ -149,27 +148,27 @@ public: * @brief Called when server requests a passkey for pairing. * @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. * @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. */ - virtual void onAuthenticationComplete(const NimBLEConnInfo& connInfo); + virtual void onAuthenticationComplete(NimBLEConnInfo& connInfo); /** * @brief Called when using numeric comparision for pairing. * @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info. * @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. * @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 */ diff --git a/src/NimBLEConnInfo.h b/src/NimBLEConnInfo.h index 274e6d3..752617a 100644 --- a/src/NimBLEConnInfo.h +++ b/src/NimBLEConnInfo.h @@ -1,58 +1,66 @@ #ifndef 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" /** * @brief Connection information. */ class NimBLEConnInfo { -friend class NimBLEServer; -friend class NimBLEClient; -friend class NimBLECharacteristic; -friend class NimBLEDescriptor; + public: + /** @brief Gets the over-the-air address of the connected peer */ + NimBLEAddress getAddress() const { return NimBLEAddress(m_desc.peer_ota_addr); } + + /** @brief Gets the ID address of the connected peer */ + NimBLEAddress getIdAddress() const { return NimBLEAddress(m_desc.peer_id_addr); } + + /** @brief Gets the connection handle (also known as the connection id) of the connected peer */ + uint16_t getConnHandle() const { return m_desc.conn_handle; } + + /** @brief Gets the connection interval for this connection (in 1.25ms units) */ + uint16_t getConnInterval() const { return m_desc.conn_itvl; } + + /** @brief Gets the supervision timeout for this connection (in 10ms units) */ + uint16_t getConnTimeout() const { return m_desc.supervision_timeout; } + + /** @brief Gets the allowable latency for this connection (unit = number of intervals) */ + uint16_t getConnLatency() const { return m_desc.conn_latency; } + + /** @brief Gets the maximum transmission unit size for this connection (in bytes) */ + uint16_t getMTU() const { return ble_att_mtu(m_desc.conn_handle); } + + /** @brief Check if we are in the master role in this connection */ + bool isMaster() const { return (m_desc.role == BLE_GAP_ROLE_MASTER); } + + /** @brief Check if we are in the slave role in this connection */ + bool isSlave() const { return (m_desc.role == BLE_GAP_ROLE_SLAVE); } + + /** @brief Check if we are connected to a bonded peer */ + bool isBonded() const { return (m_desc.sec_state.bonded == 1); } + + /** @brief Check if the connection in encrypted */ + bool isEncrypted() const { return (m_desc.sec_state.encrypted == 1); } + + /** @brief Check if the the connection has been authenticated */ + bool isAuthenticated() const { return (m_desc.sec_state.authenticated == 1); } + + /** @brief Gets the key size used to encrypt the connection */ + 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; } -public: - /** @brief Gets the over-the-air address of the connected peer */ - NimBLEAddress getAddress() const { return NimBLEAddress(m_desc.peer_ota_addr); } - - /** @brief Gets the ID address of the connected peer */ - NimBLEAddress getIdAddress() const { return NimBLEAddress(m_desc.peer_id_addr); } - - /** @brief Gets the connection handle (also known as the connection id) of the connected peer */ - uint16_t getConnHandle() const { return m_desc.conn_handle; } - - /** @brief Gets the connection interval for this connection (in 1.25ms units) */ - uint16_t getConnInterval() const { return m_desc.conn_itvl; } - - /** @brief Gets the supervision timeout for this connection (in 10ms units) */ - uint16_t getConnTimeout() const { return m_desc.supervision_timeout; } - - /** @brief Gets the allowable latency for this connection (unit = number of intervals) */ - uint16_t getConnLatency() const { return m_desc.conn_latency; } - - /** @brief Gets the maximum transmission unit size for this connection (in bytes) */ - uint16_t getMTU() const { return ble_att_mtu(m_desc.conn_handle); } - - /** @brief Check if we are in the master role in this connection */ - bool isMaster() const { return (m_desc.role == BLE_GAP_ROLE_MASTER); } - - /** @brief Check if we are in the slave role in this connection */ - bool isSlave() const { return (m_desc.role == BLE_GAP_ROLE_SLAVE); } - - /** @brief Check if we are connected to a bonded peer */ - bool isBonded() const { return (m_desc.sec_state.bonded == 1); } - - /** @brief Check if the connection in encrypted */ - bool isEncrypted() const { return (m_desc.sec_state.encrypted == 1); } - - /** @brief Check if the the connection has been authenticated */ - bool isAuthenticated() const { return (m_desc.sec_state.authenticated == 1); } - - /** @brief Gets the key size used to encrypt the connection */ - uint8_t getSecKeySize() const { return m_desc.sec_state.key_size; } + NimBLEConnInfo(ble_gap_conn_desc desc) { m_desc = desc; } }; #endif diff --git a/src/NimBLEDescriptor.cpp b/src/NimBLEDescriptor.cpp index c919126..1f0bcad 100644 --- a/src/NimBLEDescriptor.cpp +++ b/src/NimBLEDescriptor.cpp @@ -15,17 +15,24 @@ #include "nimconfig.h" #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) -#include "NimBLEService.h" -#include "NimBLEDescriptor.h" -#include "NimBLELog.h" +# include "NimBLEService.h" +# include "NimBLEDescriptor.h" +# include "NimBLELog.h" -#include +# include -#define NULL_HANDLE (0xffff) - -static const char* LOG_TAG = "NimBLEDescriptor"; +static const char* LOG_TAG = "NimBLEDescriptor"; 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 @@ -34,239 +41,64 @@ 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] 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 - * @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; - +NimBLEDescriptor::NimBLEDescriptor(const NimBLEUUID& uuid, uint16_t properties, uint16_t max_len, NimBLECharacteristic* pCharacteristic) + : NimBLELocalValueAttribute{uuid, 0, max_len}, m_pCallbacks{&defaultCallbacks}, m_pCharacteristic{pCharacteristic} { // Check if this is the client configuration descriptor and set to removed if true. if (uuid == NimBLEUUID((uint16_t)0x2902)) { NIMBLE_LOGW(LOG_TAG, "Manually created 2902 descriptor has no functionality; please remove."); - m_removed = 1; - } else { - m_removed = 0; + setRemoved(NIMBLE_ATT_REMOVE_HIDE); } - if (properties & BLE_GATT_CHR_F_READ) { // convert uint16_t properties to uint8_t - m_properties |= BLE_ATT_F_READ; + // convert uint16_t properties to uint8_t for descriptor properties + 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)) { - m_properties |= BLE_ATT_F_WRITE; + if (properties & (NIMBLE_PROPERTY::WRITE_NR | NIMBLE_PROPERTY::WRITE)) { + descProperties |= BLE_ATT_F_WRITE; } - if (properties & BLE_GATT_CHR_F_READ_ENC) { - m_properties |= BLE_ATT_F_READ_ENC; + if (properties & NIMBLE_PROPERTY::READ_ENC) { + descProperties |= BLE_ATT_F_READ_ENC; } - if (properties & BLE_GATT_CHR_F_READ_AUTHEN) { - m_properties |= BLE_ATT_F_READ_AUTHEN; + if (properties & NIMBLE_PROPERTY::READ_AUTHEN) { + descProperties |= BLE_ATT_F_READ_AUTHEN; } - if (properties & BLE_GATT_CHR_F_READ_AUTHOR) { - m_properties |= BLE_ATT_F_READ_AUTHOR; + if (properties & NIMBLE_PROPERTY::READ_AUTHOR) { + descProperties |= BLE_ATT_F_READ_AUTHOR; } - if (properties & BLE_GATT_CHR_F_WRITE_ENC) { - m_properties |= BLE_ATT_F_WRITE_ENC; + if (properties & NIMBLE_PROPERTY::WRITE_ENC) { + descProperties |= BLE_ATT_F_WRITE_ENC; } - if (properties & BLE_GATT_CHR_F_WRITE_AUTHEN) { - m_properties |= BLE_ATT_F_WRITE_AUTHEN; + if (properties & NIMBLE_PROPERTY::WRITE_AUTHEN) { + descProperties |= BLE_ATT_F_WRITE_AUTHEN; } - if (properties & BLE_GATT_CHR_F_WRITE_AUTHOR) { - m_properties |= BLE_ATT_F_WRITE_AUTHOR; + if (properties & NIMBLE_PROPERTY::WRITE_AUTHOR) { + descProperties |= BLE_ATT_F_WRITE_AUTHOR; } + setProperties(descProperties); } // 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. * @return A pointer to the characteristic this descriptor belongs to. */ -NimBLECharacteristic* NimBLEDescriptor::getCharacteristic() { +NimBLECharacteristic* NimBLEDescriptor::getCharacteristic() const { return m_pCharacteristic; } // 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. * @param [in] pCallbacks An instance of a callback structure used to define any callbacks for the descriptor. */ void NimBLEDescriptor::setCallbacks(NimBLEDescriptorCallbacks* pCallbacks) { - if (pCallbacks != nullptr){ + if (pCallbacks != nullptr) { m_pCallbacks = pCallbacks; } else { m_pCallbacks = &defaultCallbacks; } } // 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`.\n - * @param [in] vec The std::vector reference to set the descriptor value from. - */ -void NimBLEDescriptor::setValue(const std::vector& vec) { - return setValue((uint8_t*)&vec[0], vec.size()); -} // setValue - - /** * @brief Set 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; } // setCharacteristic - /** * @brief 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]; - snprintf(hex, sizeof(hex), "%04x", m_handle); + snprintf(hex, sizeof(hex), "%04x", getHandle()); std::string res = "UUID: " + m_uuid.toString() + ", handle: 0x" + hex; return res; } // 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. @@ -294,18 +133,15 @@ std::string NimBLEDescriptor::toString() { * @param [in] connInfo A reference to a NimBLEConnInfo instance containing the peer info. */ void NimBLEDescriptorCallbacks::onRead(NimBLEDescriptor* pDescriptor, NimBLEConnInfo& connInfo) { - (void)pDescriptor; NIMBLE_LOGD("NimBLEDescriptorCallbacks", "onRead: default"); } // onRead - /** * @brief Callback function to support a write request. * @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. */ void NimBLEDescriptorCallbacks::onWrite(NimBLEDescriptor* pDescriptor, NimBLEConnInfo& connInfo) { - (void)pDescriptor; NIMBLE_LOGD("NimBLEDescriptorCallbacks", "onWrite: default"); } // onWrite diff --git a/src/NimBLEDescriptor.h b/src/NimBLEDescriptor.h index e33334c..435e103 100644 --- a/src/NimBLEDescriptor.h +++ b/src/NimBLEDescriptor.h @@ -12,94 +12,52 @@ * Author: kolban */ -#ifndef MAIN_NIMBLEDESCRIPTOR_H_ -#define MAIN_NIMBLEDESCRIPTOR_H_ +#ifndef NIMBLE_CPP_DESCRIPTOR_H_ +#define NIMBLE_CPP_DESCRIPTOR_H_ #include "nimconfig.h" #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) -#include "NimBLECharacteristic.h" -#include "NimBLEUUID.h" -#include "NimBLEAttValue.h" -#include "NimBLEConnInfo.h" - -#include - -class NimBLEService; -class NimBLECharacteristic; +class NimBLEDescriptor; class NimBLEDescriptorCallbacks; +# include "NimBLELocalValueAttribute.h" +# include "NimBLECharacteristic.h" +# include "NimBLEUUID.h" +# include "NimBLEAttValue.h" +# include "NimBLEConnInfo.h" + +# include /** - * @brief A model of a %BLE descriptor. + * @brief A model of a BLE descriptor. */ -class NimBLEDescriptor { -public: - NimBLEDescriptor(const char* uuid, uint16_t properties, - uint16_t max_len, +class NimBLEDescriptor : public NimBLELocalValueAttribute { + public: + 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, NimBLECharacteristic* pCharacteristic = nullptr); + ~NimBLEDescriptor() = default; - NimBLEDescriptor(NimBLEUUID uuid, uint16_t properties, - uint16_t max_len, - NimBLECharacteristic* pCharacteristic = nullptr); - - ~NimBLEDescriptor(); - - uint16_t getHandle(); - NimBLEUUID getUUID(); - std::string toString(); + std::string toString() const; 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& vec); - - /*********************** Template Functions ************************/ - - /** - * @brief Template to set the characteristic value to val. - * @param [in] s The value to set. - */ - template - void setValue(const T &s) { m_value.setValue(s); } - - /** - * @brief Template to convert the descriptor data to . - * @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 sizeof(). - * @return The data converted to or NULL if skipSizeCheck is false and the data is less than sizeof(). - * @details Use: getValue(×tamp, skipSizeCheck); - */ - template - T getValue(time_t *timestamp = nullptr, bool skipSizeCheck = false) { - return m_value.getValue(timestamp, skipSizeCheck); - } - -private: + private: friend class NimBLECharacteristic; 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; - uint16_t m_handle; - NimBLEDescriptorCallbacks* m_pCallbacks; - NimBLECharacteristic* m_pCharacteristic; - uint8_t m_properties; - NimBLEAttValue m_value; - uint8_t m_removed; + NimBLEDescriptorCallbacks* m_pCallbacks{nullptr}; + NimBLECharacteristic* m_pCharacteristic{nullptr}; }; // NimBLEDescriptor - /** * @brief Callbacks that can be associated with a %BLE descriptors to inform of events. * @@ -108,13 +66,13 @@ private: * sub-classed instance of this class and will be notified when such an event happens. */ class NimBLEDescriptorCallbacks { -public: - virtual ~NimBLEDescriptorCallbacks(){} + public: + virtual ~NimBLEDescriptorCallbacks() = default; virtual void onRead(NimBLEDescriptor* pDescriptor, NimBLEConnInfo& connInfo); virtual void onWrite(NimBLEDescriptor* pDescriptor, NimBLEConnInfo& connInfo); }; -#include "NimBLE2904.h" +# include "NimBLE2904.h" #endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */ -#endif /* MAIN_NIMBLEDESCRIPTOR_H_ */ +#endif /* NIMBLE_CPP_DESCRIPTOR_H_ */ diff --git a/src/NimBLEDevice.cpp b/src/NimBLEDevice.cpp index 0d8286a..74e3fd1 100644 --- a/src/NimBLEDevice.cpp +++ b/src/NimBLEDevice.cpp @@ -54,8 +54,11 @@ # include "esp32-hal-bt.h" #endif -#include "NimBLELog.h" +#if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) +#include "NimBLEClient.h" +#endif +#include "NimBLELog.h" #include static const char* LOG_TAG = "NimBLEDevice"; diff --git a/src/NimBLEDevice.h b/src/NimBLEDevice.h index 3f1b625..121df58 100644 --- a/src/NimBLEDevice.h +++ b/src/NimBLEDevice.h @@ -31,7 +31,7 @@ #endif #if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) -#include "NimBLEClient.h" +class NimBLEClient; #endif #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) @@ -46,6 +46,7 @@ #endif #include +#include #define BLEDevice NimBLEDevice #define BLEClient NimBLEClient @@ -236,6 +237,12 @@ private: #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 // MAIN_NIMBLEDEVICE_H_ diff --git a/src/NimBLELocalAttribute.h b/src/NimBLELocalAttribute.h new file mode 100644 index 0000000..8de1328 --- /dev/null +++ b/src/NimBLELocalAttribute.h @@ -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_ diff --git a/src/NimBLELocalValueAttribute.h b/src/NimBLELocalValueAttribute.h new file mode 100644 index 0000000..756622f --- /dev/null +++ b/src/NimBLELocalValueAttribute.h @@ -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 +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& vec) { m_value.setValue(vec); } + + /** + * @brief Template to set the value to val. + * @param [in] val The value to set. + */ + template + void setValue(const T& val) { + m_value.setValue(val); + } + + /** + * @brief Template to convert the data to . + * @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 sizeof(). + * @return The data converted to or NULL if skipSizeCheck is false and the data is less than sizeof(). + * @details Use: getValue(×tamp, skipSizeCheck); + */ + template + T getValue(time_t* timestamp = nullptr, bool skipSizeCheck = false) const { + return m_value.getValue(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_ diff --git a/src/NimBLERemoteCharacteristic.cpp b/src/NimBLERemoteCharacteristic.cpp index 1678858..f6d1207 100644 --- a/src/NimBLERemoteCharacteristic.cpp +++ b/src/NimBLERemoteCharacteristic.cpp @@ -15,17 +15,25 @@ #include "nimconfig.h" #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) -#include "NimBLERemoteCharacteristic.h" -#include "NimBLEUtils.h" -#include "NimBLELog.h" +# include "NimBLERemoteCharacteristic.h" +# include "NimBLERemoteDescriptor.h" +# include "NimBLERemoteService.h" +# include "NimBLEClient.h" +# include "NimBLEUtils.h" +# include "NimBLELog.h" -#include +# include + +typedef struct { + const NimBLEUUID* uuid; + void* task_data; +} desc_filter_t; static const char* LOG_TAG = "NimBLERemoteCharacteristic"; /** * @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: * struct ble_gatt_chr { * uint16_t def_handle; @@ -34,34 +42,12 @@ static const char* LOG_TAG = "NimBLERemoteCharacteristic"; * ble_uuid_any_t uuid; * }; */ - NimBLERemoteCharacteristic::NimBLERemoteCharacteristic(NimBLERemoteService *pRemoteService, - const struct ble_gatt_chr *chr) -{ - NIMBLE_LOGD(LOG_TAG, ">> NimBLERemoteCharacteristic()"); - switch (chr->uuid.u.type) { - case BLE_UUID_TYPE_16: - 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(&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 - +NimBLERemoteCharacteristic::NimBLERemoteCharacteristic(const NimBLERemoteService* svc, const ble_gatt_chr* chr) + : NimBLERemoteValueAttribute{chr->uuid, chr->val_handle}, + m_pRemoteService{svc}, + m_properties{chr->properties}, + m_notifyCallback{}, + m_vDescriptors{} {} // NimBLERemoteCharacteristic /** *@brief Destructor. @@ -70,266 +56,114 @@ NimBLERemoteCharacteristic::~NimBLERemoteCharacteristic() { deleteDescriptors(); } // ~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. */ -int NimBLERemoteCharacteristic::descriptorDiscCB(uint16_t conn_handle, - const struct ble_gatt_error *error, - uint16_t chr_val_handle, - const struct ble_gatt_dsc *dsc, - void *arg) -{ +int NimBLERemoteCharacteristic::descriptorDiscCB( + uint16_t conn_handle, const ble_gatt_error* error, uint16_t chr_val_handle, const ble_gatt_dsc* dsc, void* arg) { int rc = error->status; - NIMBLE_LOGD(LOG_TAG, "Descriptor Discovered >> status: %d handle: %d", - rc, (rc == 0) ? dsc->handle : -1); + NIMBLE_LOGD(LOG_TAG, "Descriptor Discovery >> status: %d handle: %d", rc, (rc == 0) ? dsc->handle : -1); - desc_filter_t *filter = (desc_filter_t*)arg; - const NimBLEUUID *uuid_filter = filter->uuid; - ble_task_data_t *pTaskData = (ble_task_data_t*)filter->task_data; - NimBLERemoteCharacteristic *characteristic = (NimBLERemoteCharacteristic*)pTaskData->pATT; + auto filter = (desc_filter_t*)arg; + auto pTaskData = (ble_task_data_t*)filter->task_data; + const auto pChr = (NimBLERemoteCharacteristic*)pTaskData->pATT; + const NimBLEUUID* uuidFilter = filter->uuid; - if (characteristic->getRemoteService()->getClient()->getConnId() != conn_handle){ - return 0; - } - - 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 (pChr->getHandle() != chr_val_handle) { + rc = BLE_HS_EDONE; // descriptor not for this characteristic } if (rc == 0) { - pChar->m_endHandle = chr->def_handle - 1; - rc = BLE_HS_EDONE; - } else if (rc == BLE_HS_EDONE) { - pChar->m_endHandle = pChar->getRemoteService()->getEndHandle(); - } else { - pTaskData->rc = rc; + if (uuidFilter != nullptr) { + if (ble_uuid_cmp(uuidFilter->getBase(), &dsc->uuid.u) == 0) { + rc = BLE_HS_EDONE; // Found the descriptor, stop the search + } else { + 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; } - /** * @brief Populate the descriptors (if any) for this characteristic. * @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()); - // If this is the last handle then there are no descriptors - if (m_handle == getRemoteService()->getEndHandle()) { - return true; - } + TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); + ble_task_data_t taskData = {const_cast(this), cur_task, 0, nullptr}; + desc_filter_t filter = {uuidFilter, &taskData}; - int rc = 0; - TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); - ble_task_data_t taskData = {this, cur_task, 0, nullptr}; - - // If we don't know the end handle of this characteristic retrieve the next one in the service - // The end handle is the next characteristic definition handle -1. - if (m_endHandle == 0) { - rc = ble_gattc_disc_all_chrs(getRemoteService()->getClient()->getConnId(), - m_handle, + int rc = ble_gattc_disc_all_dscs(getClient()->getConnId(), + getHandle(), 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, - &filter); - + NimBLERemoteCharacteristic::descriptorDiscCB, + &filter); if (rc != 0) { NIMBLE_LOGE(LOG_TAG, "ble_gattc_disc_all_dscs: rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc)); return false; } -#ifdef ulTaskNotifyValueClear +# ifdef ulTaskNotifyValueClear // Clear the task notification value to ensure we block ulTaskNotifyValueClear(cur_task, ULONG_MAX); -#endif +# endif ulTaskNotifyTake(pdTRUE, portMAX_DELAY); if (taskData.rc != 0) { - NIMBLE_LOGE(LOG_TAG, "Failed to retrieve descriptors; startHandle:%d endHandle:%d taskData.rc=%d", - m_handle, m_endHandle, taskData.rc); + NIMBLE_LOGE(LOG_TAG, + "<< 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 - /** * @brief Get the descriptor instance with the given UUID that belongs to this characteristic. * @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()); + NimBLERemoteDescriptor* pDsc = nullptr; + size_t prev_size = m_vDescriptors.size(); - for(auto &it: m_descriptorVector) { - if(it->getUUID() == uuid) { - NIMBLE_LOGD(LOG_TAG, "<< getDescriptor: found the descriptor with uuid: %s", uuid.toString().c_str()); - return it; + for (const auto& it : m_vDescriptors) { + if (it->getUUID() == uuid) { + pDsc = it; + goto Done; } } - size_t prev_size = m_descriptorVector.size(); - if(retrieveDescriptors(&uuid)) { - if(m_descriptorVector.size() > prev_size) { - return m_descriptorVector.back(); + if (retrieveDescriptors(&uuid)) { + if (m_vDescriptors.size() > prev_size) { + pDsc = m_vDescriptors.back(); + goto Done; } // If the request was successful but 16/32 bit uuid not found // try again with the 128 bit uuid. - if(uuid.bitSize() == BLE_UUID_TYPE_16 || - uuid.bitSize() == BLE_UUID_TYPE_32) - { + if (uuid.bitSize() == BLE_UUID_TYPE_16 || uuid.bitSize() == BLE_UUID_TYPE_32) { NimBLEUUID uuid128(uuid); uuid128.to128(); - if(retrieveDescriptors(&uuid128)) { - if(m_descriptorVector.size() > prev_size) { - return m_descriptorVector.back(); + if (retrieveDescriptors(&uuid128)) { + if (m_vDescriptors.size() > prev_size) { + pDsc = m_vDescriptors.back(); } } } else { @@ -339,20 +173,20 @@ NimBLERemoteDescriptor* NimBLERemoteCharacteristic::getDescriptor(const NimBLEUU uuid16.to16(); // 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(retrieveDescriptors(&uuid16)) { - if(m_descriptorVector.size() > prev_size) { - return m_descriptorVector.back(); + if (retrieveDescriptors(&uuid16)) { + if (m_vDescriptors.size() > prev_size) { + pDsc = m_vDescriptors.back(); } } } } } - NIMBLE_LOGD(LOG_TAG, "<< getDescriptor: Not found"); - return nullptr; +Done: + NIMBLE_LOGD(LOG_TAG, "<< getDescriptor: %sfound", pDsc ? "" : "not "); + return pDsc; } // getDescriptor - /** * @brief Get a pointer to the vector of found descriptors. * @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. * @return A pointer to the vector of descriptors for this characteristic. */ -std::vector* NimBLERemoteCharacteristic::getDescriptors(bool refresh) { - if(refresh) { +const std::vector& NimBLERemoteCharacteristic::getDescriptors(bool refresh) const { + if (refresh) { deleteDescriptors(); - - if (!retrieveDescriptors()) { - NIMBLE_LOGE(LOG_TAG, "Error: Failed to get descriptors"); - } - else{ - NIMBLE_LOGI(LOG_TAG, "Found %d descriptor(s)", m_descriptorVector.size()); - } + retrieveDescriptors(); } - return &m_descriptorVector; -} // getDescriptors + return m_vDescriptors; +} // getDescriptors /** * @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. */ -std::vector::iterator NimBLERemoteCharacteristic::begin() { - return m_descriptorVector.begin(); +std::vector::iterator NimBLERemoteCharacteristic::begin() const { + return m_vDescriptors.begin(); } - /** * @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. */ -std::vector::iterator NimBLERemoteCharacteristic::end() { - return m_descriptorVector.end(); +std::vector::iterator NimBLERemoteCharacteristic::end() const { + 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. * @return The remote service associated with this characteristic. */ -NimBLERemoteService* NimBLERemoteCharacteristic::getRemoteService() { +const NimBLERemoteService* NimBLERemoteCharacteristic::getRemoteService() const { return m_pRemoteService; } // 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. * @param [in] val 0x00 to unsubscribe, 0x01 for notifications, 0x02 for indications. @@ -564,23 +236,20 @@ int NimBLERemoteCharacteristic::onReadCB(uint16_t conn_handle, * If NULL is provided then no callback is performed. * @return false if writing to the descriptor failed. */ -bool NimBLERemoteCharacteristic::setNotify(uint16_t val, notify_callback notifyCallback, bool response) { - NIMBLE_LOGD(LOG_TAG, ">> setNotify(): %s, %02x", toString().c_str(), val); - - m_notifyCallback = notifyCallback; +bool NimBLERemoteCharacteristic::setNotify(uint16_t val, notify_callback notifyCallback, bool response) const { + NIMBLE_LOGD(LOG_TAG, ">> setNotify()"); + m_notifyCallback = notifyCallback; NimBLERemoteDescriptor* desc = getDescriptor(NimBLEUUID((uint16_t)0x2902)); - if(desc == nullptr) { + if (desc == nullptr) { NIMBLE_LOGW(LOG_TAG, "<< setNotify(): Callback set, CCCD not found"); return true; } NIMBLE_LOGD(LOG_TAG, "<< setNotify()"); - - return desc->writeValue((uint8_t *)&val, 2, response); + return desc->writeValue(reinterpret_cast(&val), 2, response); } // setNotify - /** * @brief Subscribe for notifications or indications. * @param [in] notifications If true, subscribe for notifications, false subscribe for indications. @@ -589,71 +258,127 @@ bool NimBLERemoteCharacteristic::setNotify(uint16_t val, notify_callback notifyC * If NULL is provided then no callback is performed. * @return false if writing to the descriptor failed. */ -bool NimBLERemoteCharacteristic::subscribe(bool notifications, notify_callback notifyCallback, bool response) { - if(notifications) { - return setNotify(0x01, notifyCallback, response); - } else { - return setNotify(0x02, notifyCallback, response); - } +bool NimBLERemoteCharacteristic::subscribe(bool notifications, notify_callback notifyCallback, bool response) const { + return setNotify(notifications ? 0x01 : 0x02, notifyCallback, response); } // subscribe - /** * @brief Unsubscribe for notifications or indications. * @param [in] response bool if true, require a write response from the descriptor write operation. * @return false if writing to the descriptor failed. */ -bool NimBLERemoteCharacteristic::unsubscribe(bool response) { +bool NimBLERemoteCharacteristic::unsubscribe(bool response) const { return setNotify(0x00, nullptr, response); } // unsubscribe - /** * @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 * them. This method does just that. */ -void NimBLERemoteCharacteristic::deleteDescriptors() { +void NimBLERemoteCharacteristic::deleteDescriptors() const { NIMBLE_LOGD(LOG_TAG, ">> deleteDescriptors"); - for(auto &it: m_descriptorVector) { + for (const auto& it : m_vDescriptors) { delete it; } - m_descriptorVector.clear(); + std::vector().swap(m_vDescriptors); + NIMBLE_LOGD(LOG_TAG, "<< deleteDescriptors"); } // deleteDescriptors - /** * @brief Delete descriptor by UUID * @param [in] uuid The UUID of the descriptor to be deleted. * @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"); - for(auto it = m_descriptorVector.begin(); it != m_descriptorVector.end(); ++it) { - if((*it)->getUUID() == uuid) { - delete *it; - m_descriptorVector.erase(it); + for (auto it = m_vDescriptors.begin(); it != m_vDescriptors.end(); ++it) { + if ((*it)->getUUID() == uuid) { + delete (*it); + m_vDescriptors.erase(it); break; } } NIMBLE_LOGD(LOG_TAG, "<< deleteDescriptor"); - - return m_descriptorVector.size(); + return m_vDescriptors.size(); } // 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; * @return a String representation. */ -std::string NimBLERemoteCharacteristic::toString() { +std::string NimBLERemoteCharacteristic::toString() const { std::string res = "Characteristic: uuid: " + m_uuid.toString(); - char val[6]; + char val[6]; res += ", handle: "; snprintf(val, sizeof(val), "%d", getHandle()); res += val; @@ -662,145 +387,18 @@ std::string NimBLERemoteCharacteristic::toString() { res += val; res += ", props: "; res += " 0x"; - snprintf(val, sizeof(val), "%02x", m_charProp); + snprintf(val, sizeof(val), "%02x", m_properties); res += val; - for(auto &it: m_descriptorVector) { + for (const auto& it : m_vDescriptors) { res += "\n" + it->toString(); } return res; } // toString - -/** - * @brief Write a new value to the remote characteristic from a std::vector. - * @param [in] vec A std::vector 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& 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; -} +NimBLEClient* NimBLERemoteCharacteristic::getClient() const { + return getRemoteService()->getClient(); +} // getClient #endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */ diff --git a/src/NimBLERemoteCharacteristic.h b/src/NimBLERemoteCharacteristic.h index a0f8f55..64756bc 100644 --- a/src/NimBLERemoteCharacteristic.h +++ b/src/NimBLERemoteCharacteristic.h @@ -12,169 +12,67 @@ * Author: kolban */ -#ifndef COMPONENTS_NIMBLEREMOTECHARACTERISTIC_H_ -#define COMPONENTS_NIMBLEREMOTECHARACTERISTIC_H_ +#ifndef NIMBLE_CPP_REMOTE_CHARACTERISTIC_H_ +#define NIMBLE_CPP_REMOTE_CHARACTERISTIC_H_ #include "nimconfig.h" #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) -#include "NimBLERemoteService.h" -#include "NimBLERemoteDescriptor.h" - -#include -#include -#include "NimBLELog.h" +# include "NimBLERemoteValueAttribute.h" +# include +# include class NimBLERemoteService; class NimBLERemoteDescriptor; - -typedef std::function 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 { -public: +class NimBLERemoteCharacteristic : public NimBLERemoteValueAttribute { + public: + 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; + + typedef std::function notify_callback; + + bool subscribe(bool notifications = true, const notify_callback notifyCallback = nullptr, bool response = true) const; + bool unsubscribe(bool response = true) const; + + std::vector::iterator begin() const; + std::vector::iterator end() const; + NimBLERemoteDescriptor* getDescriptor(const NimBLEUUID& uuid) const; + const std::vector& getDescriptors(bool refresh = false) const; + + private: + friend class NimBLEClient; + friend class NimBLERemoteService; + + NimBLERemoteCharacteristic(const NimBLERemoteService* pRemoteService, const ble_gatt_chr* chr); ~NimBLERemoteCharacteristic(); - // Public member functions - bool canBroadcast(); - bool canIndicate(); - bool canNotify(); - bool canRead(); - bool canWrite(); - bool canWriteNoResponse(); - std::vector::iterator begin(); - std::vector::iterator end(); - NimBLERemoteDescriptor* getDescriptor(const NimBLEUUID &uuid); - std::vector* 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& v, bool response = false); - bool writeValue(const char* s, bool response = false); + bool setNotify(uint16_t val, notify_callback notifyCallback = nullptr, bool response = true) const; + bool retrieveDescriptors(const NimBLEUUID* uuidFilter = nullptr) const; + static int descriptorDiscCB( + uint16_t conn_handle, const ble_gatt_error* error, uint16_t chr_val_handle, const ble_gatt_dsc* dsc, void* arg); - /*********************** Template Functions ************************/ + const NimBLERemoteService* m_pRemoteService{nullptr}; + uint8_t m_properties{0}; + mutable notify_callback m_notifyCallback{nullptr}; + mutable std::vector m_vDescriptors{}; - /** - * @brief Template to set the remote characteristic value to 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 -#ifdef _DOXYGEN_ - bool -#else - typename std::enable_if::value && !Has_c_str_len::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 val. - * @param [in] s The value to write. - * @param [in] response True == request write response. - * @details Only used if the has a `c_str()` method. - */ - template -#ifdef _DOXYGEN_ - bool -#else - typename std::enable_if::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 . - * @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 sizeof(). - * @return The data converted to or NULL if skipSizeCheck is false and the data is - * less than sizeof(). - * @details Use: getValue(×tamp, skipSizeCheck); - */ - template - 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 . - * @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 sizeof(). - * @return The data converted to or NULL if skipSizeCheck is false and the data is - * less than sizeof(). - * @details Use: readValue(×tamp, skipSizeCheck); - */ - template - 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: - - NimBLERemoteCharacteristic(NimBLERemoteService *pRemoteservice, const struct ble_gatt_chr *chr); - - friend class NimBLEClient; - friend class NimBLERemoteService; - friend class NimBLERemoteDescriptor; - - // Private member functions - bool setNotify(uint16_t val, notify_callback notifyCallback = nullptr, bool response = true); - 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 - NimBLEUUID m_uuid; - uint8_t m_charProp; - uint16_t m_handle; - uint16_t m_defHandle; - uint16_t m_endHandle; - NimBLERemoteService* m_pRemoteService; - NimBLEAttValue m_value; - notify_callback m_notifyCallback; - - // We maintain a vector of descriptors owned by this characteristic. - std::vector m_descriptorVector; }; // NimBLERemoteCharacteristic #endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */ -#endif /* COMPONENTS_NIMBLEREMOTECHARACTERISTIC_H_ */ +#endif /* NIMBLE_CPP_REMOTE_CHARACTERISTIC_H_ */ diff --git a/src/NimBLERemoteDescriptor.cpp b/src/NimBLERemoteDescriptor.cpp index b4992f4..cbcc5fb 100644 --- a/src/NimBLERemoteDescriptor.cpp +++ b/src/NimBLERemoteDescriptor.cpp @@ -15,183 +15,33 @@ #include "nimconfig.h" #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) -#include "NimBLERemoteDescriptor.h" -#include "NimBLEUtils.h" -#include "NimBLELog.h" - -#include - -static const char* LOG_TAG = "NimBLERemoteDescriptor"; +# include "NimBLERemoteDescriptor.h" +# include "NimBLERemoteCharacteristic.h" /** * @brief Remote descriptor constructor. * @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. */ -NimBLERemoteDescriptor::NimBLERemoteDescriptor(NimBLERemoteCharacteristic* pRemoteCharacteristic, - const struct ble_gatt_dsc *dsc) -{ - 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(&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 - +NimBLERemoteDescriptor::NimBLERemoteDescriptor(const NimBLERemoteCharacteristic* pRemoteCharacteristic, + const ble_gatt_dsc* dsc) + : NimBLERemoteValueAttribute{dsc->uuid, dsc->handle}, m_pRemoteCharacteristic{pRemoteCharacteristic} {} // NimBLERemoteDescriptor /** * @brief Get the characteristic that owns this descriptor. * @return The characteristic that owns this descriptor. */ -NimBLERemoteCharacteristic* NimBLERemoteDescriptor::getRemoteCharacteristic() { - return m_pRemoteCharacteristic; +NimBLERemoteCharacteristic* NimBLERemoteDescriptor::getRemoteCharacteristic() const { + return const_cast(m_pRemoteCharacteristic); } // 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. * @return A string representation of this Remote Descriptor. */ -std::string NimBLERemoteDescriptor::toString() { +std::string NimBLERemoteDescriptor::toString() const { std::string res = "Descriptor: uuid: " + getUUID().toString(); - char val[6]; + char val[6]; res += ", handle: "; snprintf(val, sizeof(val), "%d", getHandle()); res += val; @@ -199,137 +49,8 @@ std::string NimBLERemoteDescriptor::toString() { return res; } // toString - -/** - * @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; +NimBLEClient* NimBLERemoteDescriptor::getClient() const { + return m_pRemoteCharacteristic->getClient(); } - -/** - * @brief Write a new value to a remote descriptor from a std::vector. - * @param [in] vec A std::vector 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& 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 */ diff --git a/src/NimBLERemoteDescriptor.h b/src/NimBLERemoteDescriptor.h index 756beb3..ece24b4 100644 --- a/src/NimBLERemoteDescriptor.h +++ b/src/NimBLERemoteDescriptor.h @@ -12,93 +12,34 @@ * Author: kolban */ -#ifndef COMPONENTS_NIMBLEREMOTEDESCRIPTOR_H_ -#define COMPONENTS_NIMBLEREMOTEDESCRIPTOR_H_ +#ifndef NIMBLE_CPP_REMOTE_DESCRIPTOR_H_ +#define NIMBLE_CPP_REMOTE_DESCRIPTOR_H_ #include "nimconfig.h" #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) -#include "NimBLERemoteCharacteristic.h" +# include "NimBLERemoteValueAttribute.h" class NimBLERemoteCharacteristic; +class NimBLEClient; + /** - * @brief A model of remote %BLE descriptor. + * @brief A model of remote BLE descriptor. */ -class NimBLERemoteDescriptor { -public: - uint16_t getHandle(); - NimBLERemoteCharacteristic* getRemoteCharacteristic(); - NimBLEUUID getUUID(); - NimBLEAttValue readValue(); - std::string toString(void); - bool writeValue(const uint8_t* data, size_t length, bool response = false); - bool writeValue(const std::vector& v, bool response = false); - bool writeValue(const char* s, bool response = false); +class NimBLERemoteDescriptor : public NimBLERemoteValueAttribute { + public: + NimBLERemoteCharacteristic* getRemoteCharacteristic() const; + std::string toString(void) const; + NimBLEClient* getClient() const override; + private: + friend class NimBLERemoteCharacteristic; - /*********************** Template Functions ************************/ + NimBLERemoteDescriptor(const NimBLERemoteCharacteristic* pRemoteCharacteristic, const ble_gatt_dsc* dsc); + ~NimBLERemoteDescriptor() = default; - /** - * @brief Template to set the remote descriptor value to 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 -#ifdef _DOXYGEN_ - bool -#else - typename std::enable_if::value && !Has_c_str_len::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 val. - * @param [in] s The value to write. - * @param [in] response True == request write response. - * @details Only used if the has a `c_str()` method. - */ - template -#ifdef _DOXYGEN_ - bool -#else - typename std::enable_if::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 . - * @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 sizeof(). - * @return The data converted to or NULL if skipSizeCheck is false and the data is - * less than sizeof(). - * @details Use: readValue(skipSizeCheck); - */ - template - T readValue(bool skipSizeCheck = false) { - NimBLEAttValue value = readValue(); - if(!skipSizeCheck && value.size() < sizeof(T)) return T(); - return *((T *)value.data()); - } - -private: - friend class NimBLERemoteCharacteristic; - - NimBLERemoteDescriptor (NimBLERemoteCharacteristic* pRemoteCharacteristic, - const struct ble_gatt_dsc *dsc); - 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; - NimBLEUUID m_uuid; - NimBLERemoteCharacteristic* m_pRemoteCharacteristic; + const NimBLERemoteCharacteristic* m_pRemoteCharacteristic; }; #endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */ -#endif /* COMPONENTS_NIMBLEREMOTEDESCRIPTOR_H_ */ +#endif /* NIMBLE_CPP_REMOTE_DESCRIPTOR_H_ */ diff --git a/src/NimBLERemoteService.cpp b/src/NimBLERemoteService.cpp index 51e6244..0a09e8d 100644 --- a/src/NimBLERemoteService.cpp +++ b/src/NimBLERemoteService.cpp @@ -15,12 +15,14 @@ #include "nimconfig.h" #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) -#include "NimBLERemoteService.h" -#include "NimBLEUtils.h" -#include "NimBLEDevice.h" -#include "NimBLELog.h" +# include "NimBLERemoteService.h" +# include "NimBLERemoteCharacteristic.h" +# include "NimBLEClient.h" +# include "NimBLEAttValue.h" +# include "NimBLEUtils.h" +# include "NimBLELog.h" -#include +# include static const char* LOG_TAG = "NimBLERemoteService"; @@ -29,28 +31,8 @@ static const char* LOG_TAG = "NimBLERemoteService"; * @param [in] pClient A pointer to the client this belongs to. * @param [in] service A pointer to the structure with the service information. */ -NimBLERemoteService::NimBLERemoteService(NimBLEClient* pClient, const struct ble_gatt_svc* service) { - - 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(&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()); -} - +NimBLERemoteService::NimBLERemoteService(NimBLEClient* pClient, const ble_gatt_svc* service) + : NimBLEAttribute{service->uuid, service->start_handle}, m_pClient{pClient}, m_endHandle{service->end_handle} {} /** * @brief When deleting the service make sure we delete all characteristics and descriptors. @@ -59,66 +41,62 @@ NimBLERemoteService::~NimBLERemoteService() { deleteCharacteristics(); } - /** * @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. */ -std::vector::iterator NimBLERemoteService::begin() { - return m_characteristicVector.begin(); +std::vector::iterator NimBLERemoteService::begin() const { + return m_vChars.begin(); } - /** * @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. */ -std::vector::iterator NimBLERemoteService::end() { - return m_characteristicVector.end(); +std::vector::iterator NimBLERemoteService::end() const { + return m_vChars.end(); } - /** * @brief Get the remote characteristic object for the characteristic UUID. * @param [in] uuid Remote characteristic uuid. * @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)); } // getCharacteristic - /** * @brief Get the characteristic object for the UUID. * @param [in] uuid Characteristic uuid. * @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()); + NimBLERemoteCharacteristic* pChar = nullptr; + size_t prev_size = m_vChars.size(); - for(auto &it: m_characteristicVector) { - if(it->getUUID() == uuid) { - NIMBLE_LOGD(LOG_TAG, "<< getCharacteristic: found the characteristic with uuid: %s", uuid.toString().c_str()); - return it; + for (const auto& it : m_vChars) { + if (it->getUUID() == uuid) { + pChar = it; + goto Done; } } - size_t prev_size = m_characteristicVector.size(); - if(retrieveCharacteristics(&uuid)) { - if(m_characteristicVector.size() > prev_size) { - return m_characteristicVector.back(); + if (retrieveCharacteristics(&uuid)) { + if (m_vChars.size() > prev_size) { + pChar = m_vChars.back(); + goto Done; } // If the request was successful but 16/32 bit uuid not found // try again with the 128 bit uuid. - if(uuid.bitSize() == BLE_UUID_TYPE_16 || - uuid.bitSize() == BLE_UUID_TYPE_32) - { + if (uuid.bitSize() == BLE_UUID_TYPE_16 || uuid.bitSize() == BLE_UUID_TYPE_32) { NimBLEUUID uuid128(uuid); uuid128.to128(); if (retrieveCharacteristics(&uuid128)) { - if(m_characteristicVector.size() > prev_size) { - return m_characteristicVector.back(); + if (m_vChars.size() > prev_size) { + pChar = m_vChars.back(); } } } else { @@ -128,283 +106,189 @@ NimBLERemoteCharacteristic* NimBLERemoteService::getCharacteristic(const NimBLEU uuid16.to16(); // 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(retrieveCharacteristics(&uuid16)) { - if(m_characteristicVector.size() > prev_size) { - return m_characteristicVector.back(); + if (retrieveCharacteristics(&uuid16)) { + if (m_vChars.size() > prev_size) { + pChar = m_vChars.back(); } } } } } - NIMBLE_LOGD(LOG_TAG, "<< getCharacteristic: not found"); - return nullptr; +Done: + NIMBLE_LOGD(LOG_TAG, "<< Characteristic %sfound", pChar ? "" : "not "); + return pChar; } // getCharacteristic - /** * @brief Get a pointer to the vector of found characteristics. * @param [in] refresh If true the current characteristics vector will cleared and * all characteristics for this service retrieved from the peripheral. * 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* NimBLERemoteService::getCharacteristics(bool refresh) { - if(refresh) { +const std::vector& NimBLERemoteService::getCharacteristics(bool refresh) const { + if (refresh) { deleteCharacteristics(); - - if (!retrieveCharacteristics()) { - NIMBLE_LOGE(LOG_TAG, "Error: Failed to get characteristics"); - } - else{ - NIMBLE_LOGI(LOG_TAG, "Found %d characteristics", m_characteristicVector.size()); - } + retrieveCharacteristics(); } - return &m_characteristicVector; -} // getCharacteristics + return m_vChars; +} // getCharacteristics /** * @brief Callback for Characteristic discovery. * @return success == 0 or error code. */ -int NimBLERemoteService::characteristicDiscCB(uint16_t conn_handle, - const struct ble_gatt_error *error, - const struct ble_gatt_chr *chr, void *arg) -{ - NIMBLE_LOGD(LOG_TAG,"Characteristic Discovered >> status: %d handle: %d", - error->status, (error->status == 0) ? chr->val_handle : -1); - - ble_task_data_t *pTaskData = (ble_task_data_t*)arg; - NimBLERemoteService *service = (NimBLERemoteService*)pTaskData->pATT; +int NimBLERemoteService::characteristicDiscCB(uint16_t conn_handle, + const ble_gatt_error* error, + const ble_gatt_chr* chr, + void* arg) { + NIMBLE_LOGD(LOG_TAG, "Characteristic Discovery >>"); + auto pTaskData = (ble_task_data_t*)arg; + const auto pSvc = (NimBLERemoteService*)pTaskData->pATT; // Make sure the discovery is for this device - if(service->getClient()->getConnId() != conn_handle){ + if (pSvc->getClient()->getConnId() != conn_handle) { return 0; } - if(error->status == 0) { - // Found a service - add it to the vector - NimBLERemoteCharacteristic* pRemoteCharacteristic = new NimBLERemoteCharacteristic(service, chr); - service->m_characteristicVector.push_back(pRemoteCharacteristic); - return 0; - } - - if(error->status == BLE_HS_EDONE) { - pTaskData->rc = 0; + if (error->status == 0) { + pSvc->m_vChars.push_back(new NimBLERemoteCharacteristic(pSvc, chr)); } else { - NIMBLE_LOGE(LOG_TAG, "characteristicDiscCB() rc=%d %s", - error->status, - NimBLEUtils::returnCodeToString(error->status)); - pTaskData->rc = error->status; + pTaskData->rc = error->status == BLE_HS_EDONE ? 0 : error->status; + NIMBLE_LOGD(LOG_TAG, "<< Characteristic Discovery"); + xTaskNotifyGive(pTaskData->task); } - xTaskNotifyGive(pTaskData->task); - - NIMBLE_LOGD(LOG_TAG,"<< Characteristic Discovered"); return error->status; } - /** * @brief Retrieve all the characteristics for this service. * This function will not return until we have all the characteristics. * @return True if successful. */ -bool NimBLERemoteService::retrieveCharacteristics(const NimBLEUUID *uuid_filter) { - NIMBLE_LOGD(LOG_TAG, ">> retrieveCharacteristics() for service: %s", getUUID().toString().c_str()); +bool NimBLERemoteService::retrieveCharacteristics(const NimBLEUUID* uuidFilter) const { + NIMBLE_LOGD(LOG_TAG, ">> retrieveCharacteristics()"); + int rc = 0; + TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); + ble_task_data_t taskData = {const_cast(this), cur_task, 0, nullptr}; - int rc = 0; - TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); - ble_task_data_t taskData = {this, cur_task, 0, nullptr}; - - if(uuid_filter == nullptr) { + if (uuidFilter == nullptr) { rc = ble_gattc_disc_all_chrs(m_pClient->getConnId(), - m_startHandle, - m_endHandle, - NimBLERemoteService::characteristicDiscCB, - &taskData); + getHandle(), + getEndHandle(), + NimBLERemoteService::characteristicDiscCB, + &taskData); } else { rc = ble_gattc_disc_chrs_by_uuid(m_pClient->getConnId(), - m_startHandle, - m_endHandle, - uuid_filter->getBase(), - NimBLERemoteService::characteristicDiscCB, - &taskData); + getHandle(), + getEndHandle(), + uuidFilter->getBase(), + NimBLERemoteService::characteristicDiscCB, + &taskData); } - if (rc != 0) { - NIMBLE_LOGE(LOG_TAG, "ble_gattc_disc_all_chrs: rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc)); - return false; + if (rc == 0) { +# ifdef ulTaskNotifyValueClear + // Clear the task notification value to ensure we block + ulTaskNotifyValueClear(cur_task, ULONG_MAX); +# endif + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); } -#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){ - 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()"); - return true; } - NIMBLE_LOGE(LOG_TAG, "Could not retrieve characteristics"); - return false; - + return taskData.rc == 0; } // retrieveCharacteristics - /** * @brief Get 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; } // 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. - * @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. */ -std::string NimBLERemoteService::getValue(const NimBLEUUID &characteristicUuid) { - NIMBLE_LOGD(LOG_TAG, ">> readValue: uuid: %s", characteristicUuid.toString().c_str()); - - std::string ret = ""; - NimBLERemoteCharacteristic* pChar = getCharacteristic(characteristicUuid); - - if(pChar != nullptr) { - ret = pChar->readValue(); +NimBLEAttValue NimBLERemoteService::getValue(const NimBLEUUID& uuid) const { + const auto pChar = getCharacteristic(uuid); + if (pChar) { + return pChar->readValue(); } - NIMBLE_LOGD(LOG_TAG, "<< readValue"); - return ret; + return NimBLEAttValue{}; } // readValue - /** * @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. * @returns true on success, false if not found or error */ -bool NimBLERemoteService::setValue(const NimBLEUUID &characteristicUuid, const std::string &value) { - NIMBLE_LOGD(LOG_TAG, ">> setValue: uuid: %s", characteristicUuid.toString().c_str()); - - bool ret = false; - NimBLERemoteCharacteristic* pChar = getCharacteristic(characteristicUuid); - - if(pChar != nullptr) { - ret = pChar->writeValue(value); +bool NimBLERemoteService::setValue(const NimBLEUUID& uuid, const NimBLEAttValue& value) const { + const auto pChar = getCharacteristic(uuid); + if (pChar) { + return pChar->writeValue(value); } - NIMBLE_LOGD(LOG_TAG, "<< setValue"); - return ret; + return false; } // setValue - /** * @brief Delete the characteristics in the characteristics vector. * @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 * them. This method does just that. */ -void NimBLERemoteService::deleteCharacteristics() { - NIMBLE_LOGD(LOG_TAG, ">> deleteCharacteristics"); - for(auto &it: m_characteristicVector) { +void NimBLERemoteService::deleteCharacteristics() const { + for (const auto& it : m_vChars) { delete it; } - m_characteristicVector.clear(); - NIMBLE_LOGD(LOG_TAG, "<< deleteCharacteristics"); + std::vector{}.swap(m_vChars); } // deleteCharacteristics - /** * @brief Delete characteristic by UUID * @param [in] uuid The UUID of the characteristic to be removed from the local database. * @return Number of characteristics left. */ -size_t NimBLERemoteService::deleteCharacteristic(const NimBLEUUID &uuid) { - NIMBLE_LOGD(LOG_TAG, ">> deleteCharacteristic"); - - for(auto it = m_characteristicVector.begin(); it != m_characteristicVector.end(); ++it) { - if((*it)->getUUID() == uuid) { - delete *it; - m_characteristicVector.erase(it); +size_t NimBLERemoteService::deleteCharacteristic(const NimBLEUUID& uuid) const { + for (auto it = m_vChars.begin(); it != m_vChars.end(); ++it) { + if ((*it)->getUUID() == uuid) { + delete (*it); + m_vChars.erase(it); break; } } - NIMBLE_LOGD(LOG_TAG, "<< deleteCharacteristic"); - - return m_characteristicVector.size(); + return m_vChars.size(); } // deleteCharacteristic - /** * @brief Create a string representation of this remote service. * @return A string representation of this remote service. */ -std::string NimBLERemoteService::toString() { - std::string res = "Service: uuid: " + m_uuid.toString(); - char val[6]; - res += ", start_handle: "; - snprintf(val, sizeof(val), "%d", m_startHandle); +std::string NimBLERemoteService::toString() const { + std::string res = "Service: uuid: " + m_uuid.toString() + ", start_handle: 0x"; + char val[5]; + snprintf(val, sizeof(val), "%04x", getHandle()); res += val; - snprintf(val, sizeof(val), "%04x", m_startHandle); - res += " 0x"; - res += val; - res += ", end_handle: "; - snprintf(val, sizeof(val), "%d", m_endHandle); - res += val; - snprintf(val, sizeof(val), "%04x", m_endHandle); - res += " 0x"; + res += ", end_handle: 0x"; + snprintf(val, sizeof(val), "%04x", getEndHandle()); res += val; - for (auto &it: m_characteristicVector) { - res += "\n" + it->toString(); + for (const auto& chr : m_vChars) { + res += "\n" + chr->toString(); } return res; diff --git a/src/NimBLERemoteService.h b/src/NimBLERemoteService.h index 0443cfd..a9ae274 100644 --- a/src/NimBLERemoteService.h +++ b/src/NimBLERemoteService.h @@ -12,74 +12,54 @@ * Author: kolban */ -#ifndef COMPONENTS_NIMBLEREMOTESERVICE_H_ -#define COMPONENTS_NIMBLEREMOTESERVICE_H_ +#ifndef NIMBLE_CPP_REMOTE_SERVICE_H_ +#define NIMBLE_CPP_REMOTE_SERVICE_H_ #include "nimconfig.h" #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) -#include "NimBLEClient.h" -#include "NimBLEUUID.h" -#include "NimBLERemoteCharacteristic.h" +# include "NimBLEAttribute.h" +# include -#include - -class NimBLEClient; class NimBLERemoteCharacteristic; - +class NimBLEClient; +class NimBLEAttValue; /** - * @brief A model of a remote %BLE service. + * @brief A model of a remote BLE service. */ -class NimBLERemoteService { -public: - virtual ~NimBLERemoteService(); +class NimBLERemoteService : public NimBLEAttribute { + public: + 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 - std::vector::iterator begin(); - std::vector::iterator end(); - 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* getCharacteristics(bool refresh = false); + const std::vector& getCharacteristics(bool refresh = false) const; + std::vector::iterator begin() const; + std::vector::iterator end() const; -private: - // Private constructor ... never meant to be created by a user application. - NimBLERemoteService(NimBLEClient* pClient, const struct ble_gatt_svc *service); - - // Friends + private: friend class NimBLEClient; - friend class NimBLERemoteCharacteristic; - // Private methods - bool retrieveCharacteristics(const NimBLEUUID *uuid_filter = nullptr); - static int characteristicDiscCB(uint16_t conn_handle, - const struct ble_gatt_error *error, - const struct ble_gatt_chr *chr, - void *arg); + NimBLERemoteService(NimBLEClient* pClient, const struct ble_gatt_svc* service); + ~NimBLERemoteService(); + bool retrieveCharacteristics(const NimBLEUUID* uuidFilter = nullptr) const; + static int characteristicDiscCB(uint16_t conn_handle, + const struct ble_gatt_error* error, + const struct ble_gatt_chr* chr, + void* arg); - uint16_t getStartHandle(); - uint16_t getEndHandle(); - void releaseSemaphores(); - - // Properties - - // We maintain a vector of characteristics owned by this service. - std::vector m_characteristicVector; - - NimBLEClient* m_pClient; - NimBLEUUID m_uuid; - uint16_t m_startHandle; - uint16_t m_endHandle; + mutable std::vector m_vChars{}; + NimBLEClient* m_pClient{nullptr}; + uint16_t m_endHandle{0}; }; // NimBLERemoteService #endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */ -#endif /* COMPONENTS_NIMBLEREMOTESERVICE_H_ */ +#endif /* NIMBLE_CPP_REMOTE_SERVICE_H_*/ diff --git a/src/NimBLERemoteValueAttribute.cpp b/src/NimBLERemoteValueAttribute.cpp new file mode 100644 index 0000000..444fa62 --- /dev/null +++ b/src/NimBLERemoteValueAttribute.cpp @@ -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 + +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(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(arg); + const auto pAtt = static_cast(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(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(arg); + const auto pAtt = static_cast(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(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 diff --git a/src/NimBLERemoteValueAttribute.h b/src/NimBLERemoteValueAttribute.h new file mode 100644 index 0000000..6eabb27 --- /dev/null +++ b/src/NimBLERemoteValueAttribute.h @@ -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 +# else +# include +# 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. + * @param [in] vec A std::vector 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& 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(str), length ? length : strlen(str), response); + } + + /** + * @brief Template to set the remote characteristic value to 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 +# ifdef _DOXYGEN_ + bool +# else + typename std::enable_if::value && !Has_c_str_len::value, bool>::type +# endif + writeValue(const T& v, bool response = false) const { + return writeValue(reinterpret_cast(&v), sizeof(T), response); + } + + /** + * @brief Template to set the remote characteristic value to val. + * @param [in] s The value to write. + * @param [in] response True == request write response. + * @details Only used if the has a `c_str()` method. + */ + template +# ifdef _DOXYGEN_ + bool +# else + typename std::enable_if::value, bool>::type +# endif + writeValue(const T& s, bool response = false) const { + return writeValue(reinterpret_cast(s.c_str()), s.length(), response); + } + + /** + * @brief Template to convert the remote characteristic data to . + * @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 sizeof(). + * @return The data converted to or NULL if skipSizeCheck is false and the data is + * less than sizeof(). + * @details Use: getValue(×tamp, skipSizeCheck); + */ + template + T getValue(time_t* timestamp = nullptr, bool skipSizeCheck = false) const { + return m_value.getValue(timestamp, skipSizeCheck); + } + + /** + * @brief Template to convert the remote characteristic data to . + * @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 sizeof(). + * @return The data converted to or NULL if skipSizeCheck is false and the data is + * less than sizeof(). + * @details Use: readValue(×tamp, skipSizeCheck); + */ + template + T readValue(time_t* timestamp = nullptr, bool skipSizeCheck = false) const { + readValue(); + return m_value.getValue(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_ diff --git a/src/NimBLEServer.cpp b/src/NimBLEServer.cpp index 072605c..f4c20e6 100644 --- a/src/NimBLEServer.cpp +++ b/src/NimBLEServer.cpp @@ -61,7 +61,7 @@ NimBLEServer::NimBLEServer() { * @brief Destructor: frees all resources / attributes created. */ NimBLEServer::~NimBLEServer() { - for(auto &it : m_svcVec) { + for(const auto &it : m_svcVec) { delete it; } @@ -218,7 +218,7 @@ void NimBLEServer::start() { // Get the assigned service handles and build a vector of characteristics // with Notify / Indicate capabilities for event handling 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); if(rc != 0) { 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 // we do it now. if((chr->m_properties & BLE_GATT_CHR_F_INDICATE) || (chr->m_properties & BLE_GATT_CHR_F_NOTIFY)) { 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; } } @@ -720,6 +727,68 @@ int NimBLEServer::handleGapEvent(struct ble_gap_event *event, void *arg) { } // 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(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. * @@ -762,7 +831,7 @@ void NimBLEServer::removeService(NimBLEService* service, bool deleteSvc) { // 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. // Otherwise, ignore the call and return. - if(service->m_removed > 0) { + if(service->getRemoved() > 0) { if(deleteSvc) { for(auto it = m_svcVec.begin(); it != m_svcVec.end(); ++it) { if ((*it) == service) { @@ -781,7 +850,7 @@ void NimBLEServer::removeService(NimBLEService* service, bool deleteSvc) { 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(); #if !CONFIG_BT_NIMBLE_EXT_ADV 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. // Else reset GATT and send service changed notification. - if(service->m_removed == 0) { + if(service->getRemoved() == 0) { m_svcVec.push_back(service); return; } - service->m_removed = 0; + service->setRemoved(0); serviceChanged(); } @@ -829,8 +898,8 @@ void NimBLEServer::resetGATT() { ble_svc_gatt_init(); for(auto it = m_svcVec.begin(); it != m_svcVec.end(); ) { - if ((*it)->m_removed > 0) { - if ((*it)->m_removed == NIMBLE_ATT_REMOVE_DELETE) { + if ((*it)->getRemoved() > 0) { + if ((*it)->getRemoved() == NIMBLE_ATT_REMOVE_DELETE) { delete *it; it = m_svcVec.erase(it); } else { @@ -1004,20 +1073,20 @@ uint32_t NimBLEServerCallbacks::onPassKeyDisplay(){ return 123456; } //onPassKeyDisplay -void NimBLEServerCallbacks::onConfirmPIN(const NimBLEConnInfo& connInfo, uint32_t pin){ +void NimBLEServerCallbacks::onConfirmPIN(NimBLEConnInfo& connInfo, uint32_t pin){ NIMBLE_LOGD("NimBLEServerCallbacks", "onConfirmPIN: default: true"); NimBLEDevice::injectConfirmPIN(connInfo, true); } // onConfirmPIN -void NimBLEServerCallbacks::onIdentity(const NimBLEConnInfo& connInfo){ +void NimBLEServerCallbacks::onIdentity(NimBLEConnInfo& connInfo){ NIMBLE_LOGD("NimBLEServerCallbacks", "onIdentity: default"); } // onIdentity -void NimBLEServerCallbacks::onAuthenticationComplete(const NimBLEConnInfo& connInfo){ +void NimBLEServerCallbacks::onAuthenticationComplete(NimBLEConnInfo& connInfo){ NIMBLE_LOGD("NimBLEServerCallbacks", "onAuthenticationComplete: default"); } // 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"); } // onAuthenticationComplete diff --git a/src/NimBLEServer.h b/src/NimBLEServer.h index bbb4ebf..251de22 100644 --- a/src/NimBLEServer.h +++ b/src/NimBLEServer.h @@ -18,10 +18,8 @@ #include "nimconfig.h" #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) -#define NIMBLE_ATT_REMOVE_HIDE 1 -#define NIMBLE_ATT_REMOVE_DELETE 2 - -#define onMtuChanged onMTUChange +class NimBLEServer; +class NimBLEServerCallbacks; #include "NimBLEUtils.h" #include "NimBLEAddress.h" @@ -31,13 +29,13 @@ #include "NimBLEAdvertising.h" #endif #include "NimBLEService.h" +#include "NimBLECharacteristic.h" #include "NimBLEConnInfo.h" +#define NIMBLE_ATT_REMOVE_HIDE 1 +#define NIMBLE_ATT_REMOVE_DELETE 2 -class NimBLEService; -class NimBLECharacteristic; -class NimBLEServerCallbacks; - +#define onMtuChanged onMTUChange /** * @brief The model of a %BLE server. @@ -123,6 +121,9 @@ private: bool setIndicateWait(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 @@ -182,14 +183,14 @@ public: * Should be passed back to NimBLEDevice::injectConfirmPIN * @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. * @param [in] connInfo A reference to a NimBLEConnInfo instance with information * about the peer connection parameters. */ - virtual void onAuthenticationComplete(const NimBLEConnInfo& connInfo); + virtual void onAuthenticationComplete(NimBLEConnInfo& connInfo); /** * @brief Called when the pairing procedure is complete. @@ -197,13 +198,13 @@ public: * @param [in] name The name of the connected peer device. * 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. * @param [in] connInfo A reference to a NimBLEConnInfo instance with information */ - virtual void onIdentity(const NimBLEConnInfo& connInfo); + virtual void onIdentity(NimBLEConnInfo& connInfo); }; // NimBLEServerCallbacks #endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */ diff --git a/src/NimBLEService.cpp b/src/NimBLEService.cpp index 07b1e46..0c915b3 100644 --- a/src/NimBLEService.cpp +++ b/src/NimBLEService.cpp @@ -17,91 +17,67 @@ #include "nimconfig.h" #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) -#include "NimBLEDevice.h" -#include "NimBLEService.h" -#include "NimBLEUtils.h" -#include "NimBLELog.h" +# include "NimBLEDevice.h" +# include "NimBLEService.h" +# include "NimBLEUtils.h" +# include "NimBLELog.h" -#include - -static const char* LOG_TAG = "NimBLEService"; // Tag for logging. - -#define NULL_HANDLE (0xffff) +# include +static const char* LOG_TAG = "NimBLEService"; /** * @brief Construct an instance of the NimBLEService * @param [in] uuid The UUID of the service. */ -NimBLEService::NimBLEService(const char* uuid) -: NimBLEService(NimBLEUUID(uuid)) { -} - +NimBLEService::NimBLEService(const char* uuid) : NimBLEService(NimBLEUUID(uuid)) {} /** * @brief Construct an instance of the BLEService * @param [in] uuid The UUID of the service. */ -NimBLEService::NimBLEService(const NimBLEUUID &uuid) { - m_uuid = uuid; - m_handle = NULL_HANDLE; - m_pSvcDef = nullptr; - m_removed = 0; - -} // NimBLEService - +NimBLEService::NimBLEService(const NimBLEUUID& uuid) + : NimBLELocalAttribute{uuid, 0}, m_pSvcDef{{0, getUUID().getBase(), nullptr, nullptr},{}} {} +/** + * @brief Destructor, make sure we release the resources allocated for the service. + */ NimBLEService::~NimBLEService() { - if(m_pSvcDef != nullptr) { - if(m_pSvcDef->characteristics != nullptr) { - for(int i=0; m_pSvcDef->characteristics[i].uuid != NULL; ++i) { - if(m_pSvcDef->characteristics[i].descriptors) { - delete(m_pSvcDef->characteristics[i].descriptors); - } - } - delete(m_pSvcDef->characteristics); + if (m_pSvcDef->characteristics) { + if (m_pSvcDef->characteristics->descriptors) { + delete[] m_pSvcDef->characteristics->descriptors; } - - delete(m_pSvcDef); + delete[] m_pSvcDef->characteristics; } - for(auto &it : m_chrVec) { + for (const auto& it : m_vChars) { delete it; } -} +} // ~NimBLEService /** * @brief Dump details of this BLE GATT service. */ -void NimBLEService::dump() { - NIMBLE_LOGD(LOG_TAG, "Service: uuid:%s, handle: 0x%2x", - m_uuid.toString().c_str(), - m_handle); +void NimBLEService::dump() const { + NIMBLE_LOGD(LOG_TAG, "Service: uuid:%s, handle: 0x%2x", getUUID().toString().c_str(), getHandle()); std::string res; - int count = 0; - char hex[5]; - for (auto &it: m_chrVec) { - if (count > 0) {res += "\n";} + int count = 0; + char hex[5]; + for (const auto& it : m_vChars) { + if (count > 0) { + res += "\n"; + } snprintf(hex, sizeof(hex), "%04x", it->getHandle()); count++; res += "handle: 0x"; res += hex; res += ", uuid: " + std::string(it->getUUID()); } + NIMBLE_LOGD(LOG_TAG, "Characteristics:\n%s", res.c_str()); } // 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 * 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()); // Rebuild the service definition if the server attributes have changed. - if(getServer()->m_svcChanged && m_pSvcDef != nullptr) { - if(m_pSvcDef[0].characteristics) { - if(m_pSvcDef[0].characteristics[0].descriptors) { - delete(m_pSvcDef[0].characteristics[0].descriptors); + if (getServer()->m_svcChanged) { + if (m_pSvcDef->characteristics) { + if (m_pSvcDef->characteristics->descriptors) { + delete[] m_pSvcDef->characteristics->descriptors; } - delete(m_pSvcDef[0].characteristics); + delete[] m_pSvcDef->characteristics; } - delete(m_pSvcDef); - m_pSvcDef = nullptr; + m_pSvcDef->type = 0; } - if(m_pSvcDef == nullptr) { - // 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* svc = new ble_gatt_svc_def[2]; - 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; - } + if (!m_pSvcDef->type) { + m_pSvcDef->type = BLE_GATT_SVC_TYPE_PRIMARY; + size_t numChrs = 0; + for (const auto& chr : m_vChars) { + if (chr->getRemoved()) { continue; } - - ++it; + ++numChrs; } - 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 // of the characteristics for the service. We create 1 extra and set it to null // for this purpose. - pChr_a = new ble_gatt_chr_def[numChrs + 1]{}; - int i = 0; - for(auto chr_it = m_chrVec.begin(); chr_it != m_chrVec.end(); ++chr_it) { - if((*chr_it)->m_removed > 0) { + ble_gatt_chr_def* pChrs = new ble_gatt_chr_def[numChrs + 1]{}; + for (const auto& chr : m_vChars) { + if (chr->getRemoved()) { continue; } - removedCount = 0; - for(auto it = (*chr_it)->m_dscVec.begin(); it != (*chr_it)->m_dscVec.end(); ) { - if ((*it)->m_removed > 0) { - if ((*it)->m_removed == NIMBLE_ATT_REMOVE_DELETE) { - delete *it; - it = (*chr_it)->m_dscVec.erase(it); - } else { - ++removedCount; - ++it; - } + size_t numDscs = 0; + for (const auto& dsc : chr->m_vDescriptors) { + if (dsc->getRemoved()) { continue; } - - ++it; + ++numDscs; } - 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 - pDsc_a = new ble_gatt_dsc_def[numDscs+1]; - int d = 0; - for(auto dsc_it = (*chr_it)->m_dscVec.begin(); dsc_it != (*chr_it)->m_dscVec.end(); ++dsc_it ) { - if((*dsc_it)->m_removed > 0) { + ble_gatt_dsc_def* pDscs = new ble_gatt_dsc_def[numDscs + 1]{}; + for (const auto& dsc : chr->m_vDescriptors) { + if (dsc->getRemoved()) { continue; } - pDsc_a[d].uuid = (*dsc_it)->m_uuid.getBase(); - pDsc_a[d].att_flags = (*dsc_it)->m_properties; - pDsc_a[d].min_key_size = 0; - pDsc_a[d].access_cb = NimBLEDescriptor::handleGapEvent; - pDsc_a[d].arg = (*dsc_it); - ++d; + + printf("adding disc %s\n", dsc->toString().c_str()); + + pDscs[j].uuid = dsc->getUUID().getBase(); + pDscs[j].att_flags = dsc->getProperties(); + pDscs[j].min_key_size = 0; + pDscs[j].access_cb = NimBLEServer::handleGattEvent; + pDscs[j].arg = dsc; + ++j; } - pDsc_a[numDscs].uuid = NULL; - pChr_a[i].descriptors = pDsc_a; + pChrs[i].descriptors = pDscs; } - pChr_a[i].uuid = (*chr_it)->m_uuid.getBase(); - pChr_a[i].access_cb = NimBLECharacteristic::handleGapEvent; - pChr_a[i].arg = (*chr_it); - pChr_a[i].flags = (*chr_it)->m_properties; - pChr_a[i].min_key_size = 0; - pChr_a[i].val_handle = &(*chr_it)->m_handle; + pChrs[i].uuid = chr->getUUID().getBase(); + pChrs[i].access_cb = NimBLEServer::handleGattEvent; + pChrs[i].arg = chr; + pChrs[i].flags = chr->getProperties(); + pChrs[i].min_key_size = 0; + pChrs[i].val_handle = &chr->m_handle; ++i; } - pChr_a[numChrs].uuid = NULL; - svc[0].characteristics = pChr_a; + m_pSvcDef->characteristics = pChrs; } - - // end of services must indicate to api with type = 0 - svc[1].type = 0; - m_pSvcDef = svc; } - int rc = ble_gatts_count_cfg((const ble_gatt_svc_def*)m_pSvcDef); + int rc = ble_gatts_count_cfg(m_pSvcDef); if (rc != 0) { NIMBLE_LOGE(LOG_TAG, "ble_gatts_count_cfg failed, rc= %d, %s", rc, NimBLEUtils::returnCodeToString(rc)); return false; } - rc = ble_gatts_add_svcs((const ble_gatt_svc_def*)m_pSvcDef); + rc = ble_gatts_add_svcs(m_pSvcDef); if (rc != 0) { NIMBLE_LOGE(LOG_TAG, "ble_gatts_add_svcs, rc= %d, %s", rc, NimBLEUtils::returnCodeToString(rc)); return false; - } NIMBLE_LOGD(LOG_TAG, "<< start()"); return true; } // 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. * @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) { return createCharacteristic(NimBLEUUID(uuid), properties, max_len); -} - +} // createCharacteristic /** * @brief Create a new BLE Characteristic associated with this service. @@ -273,59 +198,62 @@ NimBLECharacteristic* NimBLEService::createCharacteristic(const char* uuid, uint * @param [in] max_len - The maximum length in bytes that the characteristic value can hold. * @return The new BLE characteristic. */ -NimBLECharacteristic* NimBLEService::createCharacteristic(const NimBLEUUID &uuid, uint32_t properties, uint16_t max_len) { - NimBLECharacteristic* pCharacteristic = new NimBLECharacteristic(uuid, properties, max_len, this); - +NimBLECharacteristic* NimBLEService::createCharacteristic(const NimBLEUUID& uuid, uint32_t properties, uint16_t max_len) { + NimBLECharacteristic* pChar = new NimBLECharacteristic(uuid, properties, max_len, this); if (getCharacteristic(uuid) != nullptr) { - NIMBLE_LOGD(LOG_TAG, "<< Adding a duplicate characteristic with UUID: %s", - std::string(uuid).c_str()); + NIMBLE_LOGD(LOG_TAG, "Adding a duplicate characteristic with UUID: %s", std::string(uuid).c_str()); } - addCharacteristic(pCharacteristic); - return pCharacteristic; + addCharacteristic(pChar); + return pChar; } // createCharacteristic - /** * @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; - - if(pCharacteristic->m_removed > 0) { - for(auto& it : m_chrVec) { - if(it == pCharacteristic) { + if (pChar->getRemoved() > 0) { + for (const auto& chr : m_vChars) { + if (chr == pChar) { foundRemoved = true; - pCharacteristic->m_removed = 0; + pChar->setRemoved(0); } } } - if(!foundRemoved) { - m_chrVec.push_back(pCharacteristic); + // 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; + } } - pCharacteristic->setService(this); + if (!foundRemoved) { + m_vChars.push_back(pChar); + } + + pChar->setService(this); getServer()->serviceChanged(); } // addCharacteristic - /** * @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. */ -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 // is being called to delete the object and do so if requested. // Otherwise, ignore the call and return. - if(pCharacteristic->m_removed > 0) { - if(deleteChr) { - for(auto it = m_chrVec.begin(); it != m_chrVec.end(); ++it) { - if ((*it) == pCharacteristic) { - m_chrVec.erase(it); - delete *it; + if (pChar->getRemoved() > 0) { + if (deleteChr) { + for (auto it = m_vChars.begin(); it != m_vChars.end(); ++it) { + if ((*it) == pChar) { + delete (*it); + m_vChars.erase(it); break; } } @@ -334,80 +262,82 @@ void NimBLEService::removeCharacteristic(NimBLECharacteristic* pCharacteristic, 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(); } // 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. * @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. */ -NimBLECharacteristic* NimBLEService::getCharacteristic(const char* uuid, uint16_t instanceId) { - 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) { +NimBLECharacteristic* NimBLEService::getCharacteristic(const NimBLEUUID& uuid, uint16_t idx) const { uint16_t position = 0; - for (auto &it : m_chrVec) { - if (it->getUUID() == uuid) { - if (position == instanceId) { - return it; + for (const auto& chr : m_vChars) { + if (chr->getUUID() == uuid) { + if (position == idx) { + return chr; } position++; } } + return nullptr; -} +} // getCharacteristic /** * @brief Get a pointer to the characteristic object with the specified handle. * @param handle The handle of the characteristic. * @return A pointer to the characteristic object or nullptr if not found. */ -NimBLECharacteristic *NimBLEService::getCharacteristicByHandle(uint16_t handle) { - for (auto &it : m_chrVec) { - if (it->getHandle() == handle) { - return it; +NimBLECharacteristic* NimBLEService::getCharacteristicByHandle(uint16_t handle) const { + for (const auto& chr : m_vChars) { + if (chr->getHandle() == handle) { + return chr; } } + return nullptr; -} +} // getCharacteristicByHandle /** * @return A vector containing pointers to each characteristic associated with this service. */ -std::vector NimBLEService::getCharacteristics() { - return m_chrVec; -} +const std::vector& NimBLEService::getCharacteristics() const { + return m_vChars; +} // getCharacteristics /** * @return A vector containing pointers to each characteristic with the provided UUID associated with this service. */ -std::vector NimBLEService::getCharacteristics(const char *uuid) { +std::vector NimBLEService::getCharacteristics(const char* uuid) const { return getCharacteristics(NimBLEUUID(uuid)); -} +} // getCharacteristics /** * @return A vector containing pointers to each characteristic with the provided UUID associated with this service. */ -std::vector NimBLEService::getCharacteristics(const NimBLEUUID &uuid) { +std::vector NimBLEService::getCharacteristics(const NimBLEUUID& uuid) const { std::vector result; - for (auto &it : m_chrVec) { - if (it->getUUID() == uuid) { - result.push_back(it); + for (const auto& chr : m_vChars) { + if (chr->getUUID() == uuid) { + result.push_back(chr); } } + return result; -} +} // getCharacteristics /** * @brief Return a string representation of this service. @@ -416,31 +346,29 @@ std::vector NimBLEService::getCharacteristics(const NimB * * Its handle * @return A string representation of this service. */ -std::string NimBLEService::toString() { +std::string NimBLEService::toString() const { std::string res = "UUID: " + getUUID().toString(); - char hex[5]; + char hex[5]; snprintf(hex, sizeof(hex), "%04x", getHandle()); res += ", handle: 0x"; res += hex; return res; } // toString - /** * @brief Get the BLE server associated with this service. * @return The BLEServer associated with this service. */ -NimBLEServer* NimBLEService::getServer() { +NimBLEServer* NimBLEService::getServer() const { return NimBLEDevice::getServer(); -}// getServer - +} // getServer /** * @brief Checks if the service has been started. * @return True if the service has been started. */ -bool NimBLEService::isStarted() { - return m_pSvcDef != nullptr; -} +bool NimBLEService::isStarted() const { + return m_pSvcDef->type > 0; +} // isStarted #endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */ diff --git a/src/NimBLEService.h b/src/NimBLEService.h index 73cbd87..13bad3f 100644 --- a/src/NimBLEService.h +++ b/src/NimBLEService.h @@ -12,76 +12,59 @@ * Author: kolban */ -#ifndef MAIN_NIMBLESERVICE_H_ -#define MAIN_NIMBLESERVICE_H_ +#ifndef NIMBLE_CPP_SERVICE_H_ +#define NIMBLE_CPP_SERVICE_H_ #include "nimconfig.h" #if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) -#include "NimBLEServer.h" -#include "NimBLECharacteristic.h" -#include "NimBLEUUID.h" - - -class NimBLEServer; -class NimBLECharacteristic; +class NimBLEService; +# include "NimBLEAttribute.h" +# include "NimBLEServer.h" +# include "NimBLECharacteristic.h" /** - * @brief The model of a %BLE service. + * @brief The model of a BLE service. * */ -class NimBLEService { -public: - +class NimBLEService : public NimBLELocalAttribute { + public: NimBLEService(const char* uuid); - NimBLEService(const NimBLEUUID &uuid); + NimBLEService(const NimBLEUUID& uuid); ~NimBLEService(); - NimBLEServer* getServer(); - - NimBLEUUID getUUID(); - uint16_t getHandle(); - std::string toString(); - void dump(); - bool isStarted(); + NimBLEServer* getServer() const; + std::string toString() const; + void dump() const; + bool isStarted() const; bool start(); - NimBLECharacteristic* createCharacteristic(const char* uuid, - uint32_t properties = - NIMBLE_PROPERTY::READ | - NIMBLE_PROPERTY::WRITE, - uint16_t max_len = BLE_ATT_ATTR_MAX_LEN); - - NimBLECharacteristic* createCharacteristic(const NimBLEUUID &uuid, - uint32_t properties = - NIMBLE_PROPERTY::READ | - NIMBLE_PROPERTY::WRITE, - uint16_t max_len = BLE_ATT_ATTR_MAX_LEN); + uint32_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE, + uint16_t max_len = BLE_ATT_ATTR_MAX_LEN); + NimBLECharacteristic* createCharacteristic(const NimBLEUUID& uuid, + uint32_t properties = NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE, + uint16_t max_len = BLE_ATT_ATTR_MAX_LEN); void addCharacteristic(NimBLECharacteristic* pCharacteristic); void removeCharacteristic(NimBLECharacteristic* pCharacteristic, bool deleteChr = false); - NimBLECharacteristic* getCharacteristic(const char* uuid, uint16_t instanceId = 0); - NimBLECharacteristic* getCharacteristic(const NimBLEUUID &uuid, uint16_t instanceId = 0); - NimBLECharacteristic* getCharacteristicByHandle(uint16_t handle); + NimBLECharacteristic* getCharacteristic(const char* uuid, uint16_t instanceId = 0) const; + NimBLECharacteristic* getCharacteristic(const NimBLEUUID& uuid, uint16_t instanceId = 0) const; + NimBLECharacteristic* getCharacteristicByHandle(uint16_t handle) const; - std::vector getCharacteristics(); - std::vector getCharacteristics(const char* uuid); - std::vector getCharacteristics(const NimBLEUUID &uuid); + const std::vector& getCharacteristics() const; + std::vector getCharacteristics(const char* uuid) const; + std::vector getCharacteristics(const NimBLEUUID& uuid) const; + private: + friend class NimBLEServer; -private: - - 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 m_chrVec; - + std::vector 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 #endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_PERIPHERAL */ -#endif /* MAIN_NIMBLESERVICE_H_ */ +#endif /* NIMBLE_CPP_SERVICE_H_ */ diff --git a/src/NimBLEUUID.cpp b/src/NimBLEUUID.cpp index d5d400e..c9f9502 100644 --- a/src/NimBLEUUID.cpp +++ b/src/NimBLEUUID.cpp @@ -25,6 +25,12 @@ static const char* LOG_TAG = "NimBLEUUID"; static const uint8_t ble_base_uuid[] = { 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. * @@ -323,4 +329,4 @@ NimBLEUUID::operator std::string() const { return ble_uuid_to_str(&m_uuid.u, buf); } // operator std::string -#endif /* CONFIG_BT_ENABLED */ +# endif /* CONFIG_BT_ENABLED */ diff --git a/src/NimBLEUUID.h b/src/NimBLEUUID.h index 18d933e..7863995 100644 --- a/src/NimBLEUUID.h +++ b/src/NimBLEUUID.h @@ -41,6 +41,7 @@ class NimBLEUUID { * @brief Created a blank UUID. */ NimBLEUUID() = default; + NimBLEUUID(const ble_uuid_any_t& uuid); NimBLEUUID(const std::string& uuid); NimBLEUUID(uint16_t uuid); NimBLEUUID(uint32_t uuid);