From e3ee082dd72814252751a9ccd135bea9e29e2833 Mon Sep 17 00:00:00 2001 From: h2zero <32826625+h2zero@users.noreply.github.com> Date: Sun, 16 Jan 2022 20:11:18 -0700 Subject: [PATCH] Add NimBLEAttValue class. (#67) This is a specialized container class to hold BLE attribute values. - Removes the use of std::string previously used to store the values. - Allows for setting/getting/notifying values from std::string, std::vector, Arduino String, const char*, and uint8_t buffers. - Has operators retrieve the value as std::string, Arduino String, std::vector, uint8_t* or char pointers. - Includes iterators and subscript/random access operator. - Introduces a max length parameter to the creation of server characteristics/descriptors to limit the size of the memory footprint. - Nearly Seamless integration with existing code. - Adds a config option to enable/disable timestamp storage when the value is updated. - Adds a config option to specify the initial size of the value container if not specified in the constructor. --- Kconfig | 19 ++ docs/Command_line_config.md | 18 ++ src/NimBLEAttValue.h | 447 +++++++++++++++++++++++++++++ src/NimBLECharacteristic.cpp | 128 +++++---- src/NimBLECharacteristic.h | 123 +++++--- src/NimBLEClient.cpp | 14 +- src/NimBLEClient.h | 5 +- src/NimBLEDescriptor.cpp | 66 ++--- src/NimBLEDescriptor.h | 37 +-- src/NimBLERemoteCharacteristic.cpp | 54 ++-- src/NimBLERemoteCharacteristic.h | 110 ++++--- src/NimBLERemoteDescriptor.cpp | 64 ++--- src/NimBLERemoteDescriptor.h | 71 +++-- src/NimBLEService.cpp | 10 +- src/NimBLEService.h | 6 +- src/NimBLEUtils.h | 2 +- src/nimconfig.h | 23 ++ 17 files changed, 901 insertions(+), 296 deletions(-) create mode 100644 src/NimBLEAttValue.h diff --git a/Kconfig b/Kconfig index b2a655d..c13fde1 100644 --- a/Kconfig +++ b/Kconfig @@ -49,5 +49,24 @@ config NIMBLE_CPP_ENABLE_ADVERTISMENT_TYPE_TEXT Enabling this option will display advertisment types recieved while scanning as text messages in the debug log. This will use approximately 250 bytes of flash memory. + +config NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED + bool "Enable timestamps to be stored with attribute values." + default "n" + help + Enabling this option will store the timestamp when an attribute value is updated. + This allows for checking the last update time using getTimeStamp() + or getValue(time_t*). If disabled, the timestamp returned from these functions will be 0. + Disabling timestamps will reduce the memory used for each value. + +config NIMBLE_CPP_ATT_VALUE_INIT_LENGTH + int "Initial attribute value size (bytes) for empty values." + range 1 512 + default 20 + help + Sets the default allocation size (bytes) for each attribute if not specified + when the constructor is called. This is also the size used when a remote + characteristic or descriptor is constructed before a value is read/notifed. + Increasing this will reduce reallocations but increase memory footprint. endmenu diff --git a/docs/Command_line_config.md b/docs/Command_line_config.md index 796eb1c..3fe4acb 100644 --- a/docs/Command_line_config.md +++ b/docs/Command_line_config.md @@ -6,6 +6,24 @@ Sets the number of simultaneous connections (esp controller max is 9) - Default value is 3
+`CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED` + +Enable/disable storing the timestamp when an attribute value is updated +This allows for checking the last update time using getTimeStamp() or getValue(time_t*) +If disabled, the timestamp returned from these functions will be 0. +Disabling timestamps will reduce the memory used for each value. +1 = Enabled, 0 = Disabled; Default = Disabled +
+ +`CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH` + +Set the default allocation size (bytes) for each attribute. +If not specified when the constructor is called. This is also the size used when a remote +characteristic or descriptor is constructed before a value is read/notifed. +Increasing this will reduce reallocations but increase memory footprint. +Default value is 20. Range: 1 : 512 (BLE_ATT_ATTR_MAX_LEN) +
+ `CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU` Sets the default MTU size. diff --git a/src/NimBLEAttValue.h b/src/NimBLEAttValue.h new file mode 100644 index 0000000..11cd3f8 --- /dev/null +++ b/src/NimBLEAttValue.h @@ -0,0 +1,447 @@ +/* + * NimBLEAttValue.h + * + * Created: on March 18, 2021 + * Author H2zero + * + */ + +#ifndef MAIN_NIMBLEATTVALUE_H_ +#define MAIN_NIMBLEATTVALUE_H_ +#include "nimconfig.h" +#if defined(CONFIG_BT_ENABLED) + +#ifdef NIMBLE_CPP_ARDUINO_STRING_AVAILABLE +#include +#endif + +#include "NimBLELog.h" + +/**** FIX COMPILATION ****/ +#undef min +#undef max +/**************************/ + +#include +#include + +#ifndef CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED +# define CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED 0 +#endif + +#if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED +# include +#endif + +#if !defined(CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH) +# define CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH 20 +#elif CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH > BLE_ATT_ATTR_MAX_LEN +# error CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH cannot be larger than 512 (BLE_ATT_ATTR_MAX_LEN) +#elif CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH < 1 +# error CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH cannot be less than 1; Range = 1 : 512 +#endif + + +/* Used to determine if the type passed to a template has a c_str() and length() method. */ +template +struct Has_c_str_len : std::false_type {}; + +template +struct Has_c_str_len().c_str())), + decltype(void(std::declval().length()))> : std::true_type {}; + + +/** + * @brief A specialized container class to hold BLE attribute values. + * @details This class is designed to be more memory efficient than using\n + * standard container types for value storage, while being convertable to\n + * many different container classes. + */ +class NimBLEAttValue +{ + uint8_t* m_attr_value = nullptr; + uint16_t m_attr_max_len = 0; + uint16_t m_attr_len = 0; + uint16_t m_capacity = 0; +#if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED + time_t m_timestamp = 0; +#endif + void deepCopy(const NimBLEAttValue & source); + +public: + /** + * @brief Default constructor. + * @param[in] init_len The initial size in bytes. + * @param[in] max_len The max size in bytes that the value can be. + */ + NimBLEAttValue(uint16_t init_len = CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH, + uint16_t max_len = BLE_ATT_ATTR_MAX_LEN); + + /** + * @brief Construct with an initial value from a buffer. + * @param value A pointer to the initial value to set. + * @param[in] len The size in bytes of the value to set. + * @param[in] max_len The max size in bytes that the value can be. + */ + NimBLEAttValue(const uint8_t *value, uint16_t len, + uint16_t max_len = BLE_ATT_ATTR_MAX_LEN); + + /** + * @brief Construct with an initializer list. + * @param list An initializer list containing the initial value to set. + * @param[in] max_len The max size in bytes that the value can be. + */ + NimBLEAttValue(std::initializer_list list, + uint16_t max_len = BLE_ATT_ATTR_MAX_LEN) + :NimBLEAttValue(list.begin(), (uint16_t)list.size(), max_len){} + + /** + * @brief Construct with an initial value from a const char string. + * @param value A pointer to the initial value to set. + * @param[in] max_len The max size in bytes that the value can be. + */ + NimBLEAttValue(const char *value, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN) + :NimBLEAttValue((uint8_t*)value, (uint16_t)strlen(value), max_len){} + + /** + * @brief Construct with an initial value from a std::string. + * @param str A std::string containing to the initial value to set. + * @param[in] max_len The max size in bytes that the value can be. + */ + NimBLEAttValue(const std::string str, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN) + :NimBLEAttValue((uint8_t*)str.data(), (uint16_t)str.length(), max_len){} + + /** + * @brief Construct with an initial value from a std::vector. + * @param vec A std::vector containing to the initial value to set. + * @param[in] max_len The max size in bytes that the value can be. + */ + NimBLEAttValue(const std::vector vec, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN) + :NimBLEAttValue(&vec[0], (uint16_t)vec.size(), max_len){} + +#ifdef NIMBLE_CPP_ARDUINO_STRING_AVAILABLE + /** + * @brief Construct with an initial value from an Arduino String. + * @param str An Arduino String containing to the initial value to set. + * @param[in] max_len The max size in bytes that the value can be. + */ + NimBLEAttValue(const String str, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN) + :NimBLEAttValue((uint8_t*)str.c_str(), str.length(), max_len){} +#endif + + /** @brief Copy constructor */ + NimBLEAttValue(const NimBLEAttValue & source) { deepCopy(source); } + + /** @brief Move constructor */ + NimBLEAttValue(NimBLEAttValue && source) { *this = std::move(source); } + + /** @brief Destructor */ + ~NimBLEAttValue(); + + /** @brief Returns the max size in bytes */ + uint16_t max_size() const { return m_attr_max_len; } + + /** @brief Returns the currently allocated capacity in bytes */ + uint16_t capacity() const { return m_capacity; } + + /** @brief Returns the current length of the value in bytes */ + uint16_t length() const { return m_attr_len; } + + /** @brief Returns the current size of the value in bytes */ + uint16_t size() const { return m_attr_len; } + + /** @brief Returns a pointer to the internal buffer of the value */ + const uint8_t* data() const { return m_attr_value; } + + /** @brief Returns a pointer to the internal buffer of the value as a const char* */ + const char* c_str() const { return (const char*)m_attr_value; } + + /** @brief Iterator begin */ + const uint8_t* begin() const { return m_attr_value; } + + /** @brief Iterator end */ + const uint8_t* end() const { return m_attr_value + m_attr_len; } + +#if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED + /** @brief Returns a timestamp of when the value was last updated */ + time_t getTimeStamp() const { return m_timestamp; } + + /** @brief Set the timestamp to the current time */ + void setTimeStamp() { m_timestamp = time(nullptr); } + + /** + * @brief Set the timestamp to the specified time + * @param[in] t The timestamp value to set + */ + void setTimeStamp(time_t t) { m_timestamp = t; } +#else + time_t getTimeStamp() const { return 0; } + void setTimeStamp() { } + void setTimeStamp(time_t t) { } +#endif + + /** + * @brief Set the value from a buffer + * @param[in] value A ponter to a buffer containing the value. + * @param[in] len The length of the value in bytes. + * @returns True if successful. + */ + bool setValue(const uint8_t *value, uint16_t len); + + /** + * @brief Set value to the value of const char*. + * @param [in] s A ponter to a const char value to set. + */ + bool setValue(const char* s) { + return setValue((uint8_t*)s, (uint16_t)strlen(s)); } + + /** + * @brief Get a pointer to the value buffer with timestamp. + * @param[in] timestamp A ponter to a time_t variable to store the timestamp. + * @returns A pointer to the internal value buffer. + */ + const uint8_t* getValue(time_t *timestamp); + + /** + * @brief Append data to the value. + * @param[in] value A ponter to a data buffer with the value to append. + * @param[in] len The length of the value to append in bytes. + * @returns A reference to the appended NimBLEAttValue. + */ + NimBLEAttValue& append(const uint8_t *value, uint16_t len); + + + /*********************** Template Functions ************************/ + + /** + * @brief Template to set value to the value of val. + * @param [in] s The value to set. + * @details Only used for types without a `c_str()` method. + */ + template +#ifdef _DOXYGEN_ + bool +#else + typename std::enable_if::value, bool>::type +#endif + setValue(const T &s) { + return setValue((uint8_t*)&s, sizeof(T)); + } + + /** + * @brief Template to set value to the value of val. + * @param [in] s The value to set. + * @details Only used if the has a `c_str()` method. + */ + template +#ifdef _DOXYGEN_ + bool +#else + typename std::enable_if::value, bool>::type +#endif + setValue(const T & s) { + return setValue((uint8_t*)s.c_str(), (uint16_t)s.length()); + } + + /** + * @brief Template to return the value as a . + * @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\n + * sizeof(). + * @return The data converted to or NULL if skipSizeCheck is false and the data is\n + * less than sizeof(). + * @details Use: getValue(×tamp, skipSizeCheck); + */ + template + T getValue(time_t *timestamp = nullptr, bool skipSizeCheck = false) { + if(!skipSizeCheck && size() < sizeof(T)) { + return T(); + } + return *((T *)getValue(timestamp)); + } + + + /*********************** Operators ************************/ + + /** @brief Subscript operator */ + uint8_t operator [](int pos) const { + assert(pos < m_attr_len && "out of range"); return m_attr_value[pos]; } + + /** @brief Operator; Get the value as a std::vector. */ + operator std::vector() const { + return std::vector(m_attr_value, m_attr_value + m_attr_len); } + + /** @brief Operator; Get the value as a std::string. */ + operator std::string() const { + return std::string((char*)m_attr_value, m_attr_len); } + + /** @brief Operator; Get the value as a const uint8_t*. */ + operator const uint8_t*() const { return m_attr_value; } + + /** @brief Operator; Append another NimBLEAttValue. */ + NimBLEAttValue& operator +=(const NimBLEAttValue & source) { + return append(source.data(), source.size()); } + + /** @brief Operator; Set the value from a std::string source. */ + NimBLEAttValue& operator =(const std::string & source) { + setValue((uint8_t*)source.data(), (uint16_t)source.size()); return *this; } + + /** @brief Move assignment operator */ + NimBLEAttValue& operator =(NimBLEAttValue && source); + + /** @brief Copy assignment operator */ + NimBLEAttValue& operator =(const NimBLEAttValue & source); + + /** @brief Equality operator */ + bool operator ==(const NimBLEAttValue & source) { + return (m_attr_len == source.size()) ? + memcmp(m_attr_value, source.data(), m_attr_len) == 0 : false; } + + /** @brief Inequality operator */ + bool operator !=(const NimBLEAttValue & source){ return !(*this == source); } + +#ifdef NIMBLE_CPP_ARDUINO_STRING_AVAILABLE + /** @brief Operator; Get the value as an Arduino String value. */ + operator String() const { return String((char*)m_attr_value); } +#endif + +}; + + +inline NimBLEAttValue::NimBLEAttValue(uint16_t init_len, uint16_t max_len) { + m_attr_value = (uint8_t*)calloc(init_len + 1, 1); + assert(m_attr_value && "No Mem"); + m_attr_max_len = std::min(BLE_ATT_ATTR_MAX_LEN, (int)max_len); + m_attr_len = 0; + m_capacity = init_len; + setTimeStamp(0); +} + +inline NimBLEAttValue::NimBLEAttValue(const uint8_t *value, uint16_t len, uint16_t max_len) +: NimBLEAttValue(len, max_len) { + memcpy(m_attr_value, value, len); + m_attr_value[len] = '\0'; + m_attr_len = len; +} + +inline NimBLEAttValue::~NimBLEAttValue() { + if(m_attr_value != nullptr) { + free(m_attr_value); + } +} + +inline NimBLEAttValue& NimBLEAttValue::operator =(NimBLEAttValue && source) { + if (this != &source){ + free(m_attr_value); + + m_attr_value = source.m_attr_value; + m_attr_max_len = source.m_attr_max_len; + m_attr_len = source.m_attr_len; + m_capacity = source.m_capacity; + setTimeStamp(source.getTimeStamp()); + source.m_attr_value = nullptr; + } + return *this; +} + +inline NimBLEAttValue& NimBLEAttValue::operator =(const NimBLEAttValue & source) { + if (this != &source) { + deepCopy(source); + } + return *this; +} + +inline void NimBLEAttValue::deepCopy(const NimBLEAttValue & source) { + uint8_t* res = (uint8_t*)realloc( m_attr_value, source.m_capacity + 1); + assert(res && "deepCopy: realloc failed"); + + ble_npl_hw_enter_critical(); + m_attr_value = res; + m_attr_max_len = source.m_attr_max_len; + m_attr_len = source.m_attr_len; + m_capacity = source.m_capacity; + setTimeStamp(source.getTimeStamp()); + memcpy(m_attr_value, source.m_attr_value, m_attr_len + 1); + ble_npl_hw_exit_critical(0); +} + +inline const uint8_t* NimBLEAttValue::getValue(time_t *timestamp) { + if(timestamp != nullptr) { +#if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED + *timestamp = m_timestamp; +#else + *timestamp = 0; +#endif + } + return m_attr_value; +} + +inline bool NimBLEAttValue::setValue(const uint8_t *value, uint16_t len) { + if (len > m_attr_max_len) { + NIMBLE_LOGE("NimBLEAttValue", "value exceeds max, len=%u, max=%u", + len, m_attr_max_len); + return false; + } + + uint8_t *res = m_attr_value; + if (len > m_capacity) { + res = (uint8_t*)realloc(m_attr_value, (len + 1)); + m_capacity = len; + } + assert(res && "setValue: realloc failed"); + +#if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED + time_t t = time(nullptr); +#else + time_t t = 0; +#endif + + ble_npl_hw_enter_critical(); + m_attr_value = res; + memcpy(m_attr_value, value, len); + m_attr_value[len] = '\0'; + m_attr_len = len; + setTimeStamp(t); + ble_npl_hw_exit_critical(0); + return true; +} + +inline NimBLEAttValue& NimBLEAttValue::append(const uint8_t *value, uint16_t len) { + if (len < 1) { + return *this; + } + + if ((m_attr_len + len) > m_attr_max_len) { + NIMBLE_LOGE("NimBLEAttValue", "val > max, len=%u, max=%u", + len, m_attr_max_len); + return *this; + } + + uint8_t* res = m_attr_value; + uint16_t new_len = m_attr_len + len; + if (new_len > m_capacity) { + res = (uint8_t*)realloc(m_attr_value, (new_len + 1)); + m_capacity = new_len; + } + assert(res && "append: realloc failed"); + +#if CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED + time_t t = time(nullptr); +#else + time_t t = 0; +#endif + + ble_npl_hw_enter_critical(); + m_attr_value = res; + memcpy(m_attr_value + m_attr_len, value, len); + m_attr_len = new_len; + m_attr_value[m_attr_len] = '\0'; + setTimeStamp(t); + ble_npl_hw_exit_critical(0); + + return *this; +} + +#endif /*(CONFIG_BT_ENABLED) */ +#endif /* MAIN_NIMBLEATTVALUE_H_ */ diff --git a/src/NimBLECharacteristic.cpp b/src/NimBLECharacteristic.cpp index cb447d6..3b95030 100644 --- a/src/NimBLECharacteristic.cpp +++ b/src/NimBLECharacteristic.cpp @@ -30,26 +30,29 @@ static const char* LOG_TAG = "NimBLECharacteristic"; * @brief Construct a characteristic * @param [in] uuid - UUID (const char*) for the characteristic. * @param [in] properties - Properties for the characteristic. + * @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, NimBLEService* pService) -: NimBLECharacteristic(NimBLEUUID(uuid), properties, 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 * @param [in] uuid - UUID for the characteristic. * @param [in] properties - Properties for the characteristic. + * @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, NimBLEService* pService) { +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_value = ""; - m_timestamp = 0; m_removed = 0; } // NimBLECharacteristic @@ -231,17 +234,14 @@ NimBLEUUID NimBLECharacteristic::getUUID() { /** * @brief Retrieve the current value of the characteristic. - * @return A std::string containing the current characteristic value. + * @return The NimBLEAttValue containing the current characteristic value. */ -std::string NimBLECharacteristic::getValue(time_t *timestamp) { - ble_npl_hw_enter_critical(); - std::string retVal = m_value; +NimBLEAttValue NimBLECharacteristic::getValue(time_t *timestamp) { if(timestamp != nullptr) { - *timestamp = m_timestamp; + m_value.getValue(timestamp); } - ble_npl_hw_exit_critical(0); - return retVal; + return m_value; } // getValue @@ -250,10 +250,7 @@ std::string NimBLECharacteristic::getValue(time_t *timestamp) { * @return The length of the current characteristic data. */ size_t NimBLECharacteristic::getDataLength() { - ble_npl_hw_enter_critical(); - size_t len = m_value.length(); - ble_npl_hw_exit_critical(0); - return len; + return m_value.size(); } @@ -286,25 +283,26 @@ int NimBLECharacteristic::handleGapEvent(uint16_t conn_handle, uint16_t attr_han } ble_npl_hw_enter_critical(); - rc = os_mbuf_append(ctxt->om, (uint8_t*)pCharacteristic->m_value.data(), - pCharacteristic->m_value.length()); + 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: { - if (ctxt->om->om_len > BLE_ATT_ATTR_MAX_LEN) { + 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[BLE_ATT_ATTR_MAX_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) > BLE_ATT_ATTR_MAX_LEN) { + 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); @@ -384,36 +382,58 @@ void NimBLECharacteristic::setSubscribe(struct ble_gap_event *event) { /** - * @brief Send an indication.\n - * An indication is a transmission of up to the first 20 bytes of the characteristic value.\n - * An indication will block waiting for a positive confirmation from the client. + * @brief Send an indication. */ void NimBLECharacteristic::indicate() { - NIMBLE_LOGD(LOG_TAG, ">> indicate: length: %d", getDataLength()); notify(false); - NIMBLE_LOGD(LOG_TAG, "<< indicate"); } // indicate /** - * @brief Send a notification.\n - * A notification is a transmission of up to the first 20 bytes of the characteristic value.\n - * A notification will not block; it is a fire and forget. + * @brief Send an indication. + * @param[in] value A pointer to the data to send. + * @param[in] length The length of the data to send. + */ +void NimBLECharacteristic::indicate(const uint8_t* value, size_t length) { + notify(value, length, false); +} // indicate + + +/** + * @brief Send an indication. + * @param[in] value A std::vector containing the value to send as the notification value. + */ +void NimBLECharacteristic::indicate(const std::vector& value) { + notify(value.data(), value.size(), false); +} // indicate + + +/** + * @brief Send a notification or indication. * @param[in] is_notification if true sends a notification, false sends an indication. */ void NimBLECharacteristic::notify(bool is_notification) { - notify(getValue(), is_notification); -} + notify(m_value.data(), m_value.length(), is_notification); +} // notify + /** - * @brief Send a notification.\n - * A notification is a transmission of up to the first 20 bytes of the characteristic value.\n - * A notification will not block; it is a fire and forget. - * @param[in] value An optional value to send as the notification, else the current characteristic value is used. + * @brief Send a notification or indication. + * @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. */ -void NimBLECharacteristic::notify(std::string value, bool is_notification) { - size_t length = value.length(); +void NimBLECharacteristic::notify(const std::vector& value, bool is_notification) { + notify(value.data(), value.size(), is_notification); +} // notify + + +/** + * @brief Send 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. + */ +void NimBLECharacteristic::notify(const uint8_t* value, size_t length, bool is_notification) { NIMBLE_LOGD(LOG_TAG, ">> notify: length: %d", length); if(!(m_properties & NIMBLE_PROPERTY::NOTIFY) && @@ -472,7 +492,7 @@ void NimBLECharacteristic::notify(std::string value, bool is_notification) { // 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((uint8_t*)value.data(), length); + os_mbuf *om = ble_hs_mbuf_from_flat(value, length); if(!is_notification && (m_properties & NIMBLE_PROPERTY::INDICATE)) { if(!NimBLEDevice::getServer()->setIndicateWait(it.first)) { @@ -516,40 +536,30 @@ NimBLECharacteristicCallbacks* NimBLECharacteristic::getCallbacks() { /** - * @brief Set the value of the characteristic. - * @param [in] data The data to set for the characteristic. - * @param [in] length The length of the data in bytes. + * @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()); + NIMBLE_LOGD(LOG_TAG, ">> setValue: length=%d, data=%s, characteristic UUID=%s", + length, pHex, getUUID().toString().c_str()); free(pHex); #endif - if (length > BLE_ATT_ATTR_MAX_LEN) { - NIMBLE_LOGE(LOG_TAG, "Size %d too large, must be no bigger than %d", length, BLE_ATT_ATTR_MAX_LEN); - return; - } - - time_t t = time(nullptr); - ble_npl_hw_enter_critical(); - m_value = std::string((char*)data, length); - m_timestamp = t; - ble_npl_hw_exit_critical(0); - + m_value.setValue(data, length); NIMBLE_LOGD(LOG_TAG, "<< setValue"); } // setValue /** - * @brief Set the value of the characteristic from string data.\n - * We set the value of the characteristic from the bytes contained in the string. - * @param [in] value the std::string value of the characteristic. + * @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::string &value) { - setValue((uint8_t*)(value.data()), value.length()); -} // setValue +void NimBLECharacteristic::setValue(const std::vector& vec) { + return setValue((uint8_t*)&vec[0], vec.size()); +}// setValue /** diff --git a/src/NimBLECharacteristic.h b/src/NimBLECharacteristic.h index 9af1120..0f84e2d 100644 --- a/src/NimBLECharacteristic.h +++ b/src/NimBLECharacteristic.h @@ -44,6 +44,7 @@ typedef enum { #include "NimBLEService.h" #include "NimBLEDescriptor.h" +#include "NimBLEAttValue.h" #include #include @@ -65,11 +66,13 @@ public: 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(); @@ -77,66 +80,93 @@ public: uint16_t getHandle(); NimBLEUUID getUUID(); std::string toString(); - - void setCallbacks(NimBLECharacteristicCallbacks* pCallbacks); - NimBLECharacteristicCallbacks* - getCallbacks(); - void indicate(); + void indicate(const uint8_t* value, size_t length); + void indicate(const std::vector& value); void notify(bool is_notification = true); - void notify(std::string value, bool is_notification = true); - + void notify(const uint8_t* value, size_t length, bool is_notification = true); + void notify(const std::vector& value, bool is_notification = true); size_t getSubscribedCount(); - - NimBLEDescriptor* createDescriptor(const char* uuid, - uint32_t properties = - NIMBLE_PROPERTY::READ | - NIMBLE_PROPERTY::WRITE, - uint16_t max_len = 100); - NimBLEDescriptor* createDescriptor(const NimBLEUUID &uuid, - uint32_t properties = - NIMBLE_PROPERTY::READ | - NIMBLE_PROPERTY::WRITE, - uint16_t max_len = 100); - 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); - - std::string getValue(time_t *timestamp = nullptr); + NimBLEService* getService(); + uint16_t getProperties(); + NimBLEAttValue getValue(time_t *timestamp = nullptr); size_t getDataLength(); - /** - * @brief A template to convert the 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) { - std::string value = getValue(); - if(!skipSizeCheck && value.size() < sizeof(T)) return T(); - const char *pData = value.data(); - return *((T *)pData); - } - void setValue(const uint8_t* data, size_t size); - void setValue(const std::string &value); + void setValue(const std::vector& vec); + void setCallbacks(NimBLECharacteristicCallbacks* pCallbacks); + 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(); + + + /*********************** Template Functions ************************/ + /** - * @brief Convenience template to set the characteristic value to val. + * @brief Template to set the characteristic value to val. * @param [in] s The value to set. */ template - void setValue(const T &s) { - setValue((uint8_t*)&s, sizeof(T)); + 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); } - NimBLEService* getService(); - uint16_t getProperties(); + /** + * @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. + * @param[in] value The value to set. + * @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_ + void +#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); + } + + /** + * @brief Template to send an indication 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. + * @param[in] value The value to set. + * @details Only used if the has a `c_str()` method. + */ + template +#ifdef _DOXYGEN_ + void +#else + typename std::enable_if::value, void>::type +#endif + indicate(const T& value) { + indicate((uint8_t*)value.c_str(), value.length()); + } private: @@ -153,9 +183,8 @@ private: uint16_t m_properties; NimBLECharacteristicCallbacks* m_pCallbacks; NimBLEService* m_pService; - std::string m_value; + NimBLEAttValue m_value; std::vector m_dscVec; - time_t m_timestamp; uint8_t m_removed; std::vector> m_subscribedVec; @@ -188,7 +217,7 @@ public: ERROR_INDICATE_FAILURE }Status; - virtual ~NimBLECharacteristicCallbacks(); + virtual ~NimBLECharacteristicCallbacks(); virtual void onRead(NimBLECharacteristic* pCharacteristic); virtual void onRead(NimBLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc); virtual void onWrite(NimBLECharacteristic* pCharacteristic); diff --git a/src/NimBLEClient.cpp b/src/NimBLEClient.cpp index 810159e..bc796cb 100644 --- a/src/NimBLEClient.cpp +++ b/src/NimBLEClient.cpp @@ -768,11 +768,11 @@ int NimBLEClient::serviceDiscoveredCB( * @param [in] characteristicUUID The characteristic whose value we wish to read. * @returns characteristic value or an empty string if not found */ -std::string NimBLEClient::getValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &characteristicUUID) { +NimBLEAttValue NimBLEClient::getValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &characteristicUUID) { NIMBLE_LOGD(LOG_TAG, ">> getValue: serviceUUID: %s, characteristicUUID: %s", serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); - std::string ret = ""; + NimBLEAttValue ret; NimBLERemoteService* pService = getService(serviceUUID); if(pService != nullptr) { @@ -796,7 +796,7 @@ std::string NimBLEClient::getValue(const NimBLEUUID &serviceUUID, const NimBLEUU * @returns true if successful otherwise false */ bool NimBLEClient::setValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &characteristicUUID, - const std::string &value, bool response) + const NimBLEAttValue &value, bool response) { NIMBLE_LOGD(LOG_TAG, ">> setValue: serviceUUID: %s, characteristicUUID: %s", serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); @@ -857,7 +857,7 @@ uint16_t NimBLEClient::getMTU() { * @param [in] arg A pointer to the client instance that registered for this callback. */ /*STATIC*/ - int NimBLEClient::handleGapEvent(struct ble_gap_event *event, void *arg) { +int NimBLEClient::handleGapEvent(struct ble_gap_event *event, void *arg) { NimBLEClient* client = (NimBLEClient*)arg; int rc; @@ -976,11 +976,7 @@ uint16_t NimBLEClient::getMTU() { (*characteristic)->toString().c_str()); uint32_t data_len = OS_MBUF_PKTLEN(event->notify_rx.om); - time_t t = time(nullptr); - ble_npl_hw_enter_critical(); - (*characteristic)->m_value = std::string((char *)event->notify_rx.om->om_data, data_len); - (*characteristic)->m_timestamp = t; - ble_npl_hw_exit_critical(0); + (*characteristic)->m_value.setValue(event->notify_rx.om->om_data, data_len); if ((*characteristic)->m_notifyCallback != nullptr) { NIMBLE_LOGD(LOG_TAG, "Invoking callback for notification on characteristic %s", diff --git a/src/NimBLEClient.h b/src/NimBLEClient.h index 37700b7..7c93c30 100644 --- a/src/NimBLEClient.h +++ b/src/NimBLEClient.h @@ -21,6 +21,7 @@ #include "NimBLEUUID.h" #include "NimBLEUtils.h" #include "NimBLEConnInfo.h" +#include "NimBLEAttValue.h" #include "NimBLEAdvertisedDevice.h" #include "NimBLERemoteService.h" @@ -51,9 +52,9 @@ public: NimBLERemoteService* getService(const NimBLEUUID &uuid); void deleteServices(); size_t deleteService(const NimBLEUUID &uuid); - std::string getValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &characteristicUUID); + NimBLEAttValue getValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &characteristicUUID); bool setValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &characteristicUUID, - const std::string &value, bool response = false); + const NimBLEAttValue &value, bool response = false); NimBLERemoteCharacteristic* getCharacteristic(const uint16_t handle); bool isConnected(); void setClientCallbacks(NimBLEClientCallbacks *pClientCallbacks, diff --git a/src/NimBLEDescriptor.cpp b/src/NimBLEDescriptor.cpp index 5b0db1a..3429d13 100644 --- a/src/NimBLEDescriptor.cpp +++ b/src/NimBLEDescriptor.cpp @@ -28,27 +28,32 @@ static NimBLEDescriptorCallbacks defaultCallbacks; /** - * @brief NimBLEDescriptor constructor. + * @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), max_len, properties, pCharacteristic) { + NimBLECharacteristic* pCharacteristic) +: NimBLEDescriptor(NimBLEUUID(uuid), properties, max_len, pCharacteristic) { } /** - * @brief NimBLEDescriptor constructor. + * @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_value.attr_len = 0; // Initial length is 0. - m_value.attr_max_len = max_len; // Maximum length of the data. m_handle = NULL_HANDLE; // Handle is initially unknown. m_pCharacteristic = pCharacteristic; m_pCallbacks = &defaultCallbacks; // No initial callback. - m_value.attr_value = (uint8_t*) calloc(max_len,1); // Allocate storage for the value. m_properties = 0; m_removed = 0; @@ -84,7 +89,6 @@ NimBLEDescriptor::NimBLEDescriptor(NimBLEUUID uuid, uint16_t properties, uint16_ * @brief NimBLEDescriptor destructor. */ NimBLEDescriptor::~NimBLEDescriptor() { - free(m_value.attr_value); // Release the storage we created in the constructor. } // ~NimBLEDescriptor /** @@ -101,7 +105,7 @@ uint16_t NimBLEDescriptor::getHandle() { * @return The length (in bytes) of the value of this descriptor. */ size_t NimBLEDescriptor::getLength() { - return m_value.attr_len; + return m_value.size(); } // getLength @@ -115,10 +119,14 @@ NimBLEUUID NimBLEDescriptor::getUUID() { /** * @brief Get the value of this descriptor. - * @return A pointer to the value of this descriptor. + * @return The NimBLEAttValue of this descriptor. */ -uint8_t* NimBLEDescriptor::getValue() { - return m_value.attr_value; +NimBLEAttValue NimBLEDescriptor::getValue(time_t *timestamp) { + if (timestamp != nullptr) { + m_value.getValue(timestamp); + } + + return m_value; } // getValue @@ -127,7 +135,7 @@ uint8_t* NimBLEDescriptor::getValue() { * @return A std::string instance containing a copy of the descriptor's value. */ std::string NimBLEDescriptor::getStringValue() { - return std::string((char *) m_value.attr_value, m_value.attr_len); + return std::string(m_value); } @@ -163,23 +171,25 @@ int NimBLEDescriptor::handleGapEvent(uint16_t conn_handle, uint16_t attr_handle, } ble_npl_hw_enter_critical(); - rc = os_mbuf_append(ctxt->om, pDescriptor->getValue(), pDescriptor->getLength()); + 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: { - if (ctxt->om->om_len > pDescriptor->m_value.attr_max_len) { + 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[pDescriptor->m_value.attr_max_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) > pDescriptor->m_value.attr_max_len) { + 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); @@ -231,27 +241,19 @@ void NimBLEDescriptor::setHandle(uint16_t handle) { * @param [in] length The length of the data in bytes. */ void NimBLEDescriptor::setValue(const uint8_t* data, size_t length) { - if (length > m_value.attr_max_len) { - NIMBLE_LOGE(LOG_TAG, "Size %d too large, must be no bigger than %d", length, m_value.attr_max_len); - return; - } - - ble_npl_hw_enter_critical(); - m_value.attr_len = length; - memcpy(m_value.attr_value, data, length); - ble_npl_hw_exit_critical(0); - + m_value.setValue(data, length); } // setValue /** - * @brief Set the value of the descriptor. - * @param [in] value The value of the descriptor in string form. + * @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::string &value) { - setValue((uint8_t*) value.data(), value.length()); +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 descriptior belongs to. diff --git a/src/NimBLEDescriptor.h b/src/NimBLEDescriptor.h index fe6c733..4ee9a62 100644 --- a/src/NimBLEDescriptor.h +++ b/src/NimBLEDescriptor.h @@ -20,17 +20,10 @@ #include "NimBLECharacteristic.h" #include "NimBLEUUID.h" +#include "NimBLEAttValue.h" #include - -typedef struct -{ - uint16_t attr_max_len; /*!< attribute max value length */ - uint16_t attr_len; /*!< attribute current value length */ - uint8_t *attr_value; /*!< the pointer to attribute value */ -} attr_value_t; - class NimBLEService; class NimBLECharacteristic; class NimBLEDescriptorCallbacks; @@ -54,24 +47,36 @@ public: uint16_t getHandle(); NimBLEUUID getUUID(); std::string toString(); - void setCallbacks(NimBLEDescriptorCallbacks* pCallbacks); + NimBLECharacteristic* getCharacteristic(); size_t getLength(); - uint8_t* getValue(); + NimBLEAttValue getValue(time_t *timestamp = nullptr); std::string getStringValue(); void setValue(const uint8_t* data, size_t size); - void setValue(const std::string &value); - NimBLECharacteristic* getCharacteristic(); + void setValue(const std::vector& vec); + + /*********************** Template Functions ************************/ /** - * @brief Convenience template to set the descriptor value to val. + * @brief Template to set the characteristic value to val. * @param [in] s The value to set. */ template - void setValue(const T &s) { - setValue((uint8_t*)&s, sizeof(T)); + 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: @@ -89,7 +94,7 @@ private: NimBLEDescriptorCallbacks* m_pCallbacks; NimBLECharacteristic* m_pCharacteristic; uint8_t m_properties; - attr_value_t m_value; + NimBLEAttValue m_value; uint8_t m_removed; }; // NimBLEDescriptor diff --git a/src/NimBLERemoteCharacteristic.cpp b/src/NimBLERemoteCharacteristic.cpp index 8567967..3667742 100644 --- a/src/NimBLERemoteCharacteristic.cpp +++ b/src/NimBLERemoteCharacteristic.cpp @@ -58,7 +58,6 @@ static const char* LOG_TAG = "NimBLERemoteCharacteristic"; m_charProp = chr->properties; m_pRemoteService = pRemoteService; m_notifyCallback = nullptr; - m_timestamp = 0; NIMBLE_LOGD(LOG_TAG, "<< NimBLERemoteCharacteristic(): %s", m_uuid.toString().c_str()); } // NimBLERemoteCharacteristic @@ -414,15 +413,12 @@ NimBLEUUID NimBLERemoteCharacteristic::getUUID() { * @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. */ -std::string NimBLERemoteCharacteristic::getValue(time_t *timestamp) { - ble_npl_hw_enter_critical(); - std::string value = m_value; +NimBLEAttValue NimBLERemoteCharacteristic::getValue(time_t *timestamp) { if(timestamp != nullptr) { - *timestamp = m_timestamp; + *timestamp = m_value.getTimeStamp(); } - ble_npl_hw_exit_critical(0); - return value; + return m_value; } @@ -470,12 +466,12 @@ float NimBLERemoteCharacteristic::readFloat() { * @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. */ -std::string NimBLERemoteCharacteristic::readValue(time_t *timestamp) { +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(); - std::string value; + NimBLEAttValue value; if (!pClient->isConnected()) { NIMBLE_LOGE(LOG_TAG, "Disconnected"); @@ -526,14 +522,11 @@ std::string NimBLERemoteCharacteristic::readValue(time_t *timestamp) { } } while(rc != 0 && retryCount--); - time_t t = time(nullptr); - ble_npl_hw_enter_critical(); + value.setTimeStamp(); m_value = value; - m_timestamp = t; if(timestamp != nullptr) { - *timestamp = m_timestamp; + *timestamp = value.getTimeStamp(); } - ble_npl_hw_exit_critical(0); NIMBLE_LOGD(LOG_TAG, "<< readValue length: %d rc=%d", value.length(), rc); return value; @@ -558,17 +551,17 @@ int NimBLERemoteCharacteristic::onReadCB(uint16_t conn_handle, NIMBLE_LOGI(LOG_TAG, "Read complete; status=%d conn_handle=%d", error->status, conn_handle); - std::string *strBuf = (std::string*)pTaskData->buf; + NimBLEAttValue *valBuf = (NimBLEAttValue*)pTaskData->buf; int rc = error->status; if(rc == 0) { if(attr) { uint16_t data_len = OS_MBUF_PKTLEN(attr->om); - if(((*strBuf).length() + data_len) > BLE_ATT_ATTR_MAX_LEN) { + 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); - (*strBuf) += std::string((char*) attr->om->om_data, data_len); + valBuf->append(attr->om->om_data, data_len); return 0; } } @@ -719,22 +712,33 @@ std::string NimBLERemoteCharacteristic::toString() { /** - * @brief Write the new value for the characteristic. - * @param [in] newValue The new value to write. - * @param [in] response Do we expect a response? - * @return false if not connected or cant perform write for some reason. + * @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::string &newValue, bool response) { - return writeValue((uint8_t*)newValue.c_str(), newValue.length(), response); +bool NimBLERemoteCharacteristic::writeValue(const std::vector& vec, bool response) { + return writeValue((uint8_t*)&vec[0], vec.size(), response); } // writeValue /** - * @brief Write the new value for the characteristic from a data buffer. + * @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 cant perform write for some reason. + * @return false if not connected or otherwise cannot perform write. */ bool NimBLERemoteCharacteristic::writeValue(const uint8_t* data, size_t length, bool response) { diff --git a/src/NimBLERemoteCharacteristic.h b/src/NimBLERemoteCharacteristic.h index 41ae816..353d832 100644 --- a/src/NimBLERemoteCharacteristic.h +++ b/src/NimBLERemoteCharacteristic.h @@ -23,6 +23,7 @@ #include #include +#include "NimBLELog.h" class NimBLERemoteService; class NimBLERemoteDescriptor; @@ -60,47 +61,15 @@ public: uint16_t getHandle(); uint16_t getDefHandle(); NimBLEUUID getUUID(); - std::string readValue(time_t *timestamp = nullptr); - - /** - * @brief A 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) { - std::string value = readValue(timestamp); - if(!skipSizeCheck && value.size() < sizeof(T)) return T(); - const char *pData = value.data(); - return *((T *)pData); - } + NimBLEAttValue readValue(time_t *timestamp = nullptr); + std::string toString(); + NimBLERemoteService* getRemoteService(); uint8_t readUInt8() __attribute__ ((deprecated("Use template readValue()"))); uint16_t readUInt16() __attribute__ ((deprecated("Use template readValue()"))); uint32_t readUInt32() __attribute__ ((deprecated("Use template readValue()"))); float readFloat() __attribute__ ((deprecated("Use template readValue()"))); - std::string getValue(time_t *timestamp = nullptr); - - /** - * @brief A 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) { - std::string value = getValue(timestamp); - if(!skipSizeCheck && value.size() < sizeof(T)) return T(); - const char *pData = value.data(); - return *((T *)pData); - } + NimBLEAttValue getValue(time_t *timestamp = nullptr); bool subscribe(bool notifications = true, notify_callback notifyCallback = nullptr, @@ -113,20 +82,74 @@ public: bool writeValue(const uint8_t* data, size_t length, bool response = false); - bool writeValue(const std::string &newValue, - bool response = false); + bool writeValue(const std::vector& v, bool response = false); + bool writeValue(const char* s, bool response = false); + + + /*********************** Template Functions ************************/ + /** - * @brief Convenience template to set the remote characteristic value to val. + * @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 - bool writeValue(const T &s, bool response = false) { +#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); } - std::string toString(); - NimBLERemoteService* getRemoteService(); + /** + * @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: @@ -156,9 +179,8 @@ private: uint16_t m_defHandle; uint16_t m_endHandle; NimBLERemoteService* m_pRemoteService; - std::string m_value; + NimBLEAttValue m_value; notify_callback m_notifyCallback; - time_t m_timestamp; // We maintain a vector of descriptors owned by this characteristic. std::vector m_descriptorVector; diff --git a/src/NimBLERemoteDescriptor.cpp b/src/NimBLERemoteDescriptor.cpp index 64ce06d..cae9103 100644 --- a/src/NimBLERemoteDescriptor.cpp +++ b/src/NimBLERemoteDescriptor.cpp @@ -86,11 +86,7 @@ NimBLEUUID NimBLERemoteDescriptor::getUUID() { * @deprecated Use readValue(). */ uint8_t NimBLERemoteDescriptor::readUInt8() { - std::string value = readValue(); - if (value.length() >= 1) { - return (uint8_t) value[0]; - } - return 0; + return readValue(); } // readUInt8 @@ -100,11 +96,7 @@ uint8_t NimBLERemoteDescriptor::readUInt8() { * @deprecated Use readValue(). */ uint16_t NimBLERemoteDescriptor::readUInt16() { - std::string value = readValue(); - if (value.length() >= 2) { - return *(uint16_t*) value.data(); - } - return 0; + return readValue(); } // readUInt16 @@ -114,11 +106,7 @@ uint16_t NimBLERemoteDescriptor::readUInt16() { * @deprecated Use readValue(). */ uint32_t NimBLERemoteDescriptor::readUInt32() { - std::string value = readValue(); - if (value.length() >= 4) { - return *(uint32_t*) value.data(); - } - return 0; + return readValue(); } // readUInt32 @@ -126,11 +114,11 @@ uint32_t NimBLERemoteDescriptor::readUInt32() { * @brief Read the value of the remote descriptor. * @return The value of the remote descriptor. */ -std::string NimBLERemoteDescriptor::readValue() { +NimBLEAttValue NimBLERemoteDescriptor::readValue() { NIMBLE_LOGD(LOG_TAG, ">> Descriptor readValue: %s", toString().c_str()); NimBLEClient* pClient = getRemoteCharacteristic()->getRemoteService()->getClient(); - std::string value; + NimBLEAttValue value; if (!pClient->isConnected()) { NIMBLE_LOGE(LOG_TAG, "Disconnected"); @@ -204,17 +192,17 @@ int NimBLERemoteDescriptor::onReadCB(uint16_t conn_handle, NIMBLE_LOGD(LOG_TAG, "Read complete; status=%d conn_handle=%d", error->status, conn_handle); - std::string *strBuf = (std::string*)pTaskData->buf; + NimBLEAttValue *valBuf = (NimBLEAttValue*)pTaskData->buf; int rc = error->status; if(rc == 0) { if(attr) { uint16_t data_len = OS_MBUF_PKTLEN(attr->om); - if(((*strBuf).length() + data_len) > BLE_ATT_ATTR_MAX_LEN) { + 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); - (*strBuf) += std::string((char*) attr->om->om_data, data_len); + valBuf->append(attr->om->om_data, data_len); return 0; } } @@ -267,11 +255,33 @@ int NimBLERemoteDescriptor::onWriteCB(uint16_t conn_handle, /** - * @brief Write data to the BLE Remote Descriptor. + * @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 True if successful + * @return false if not connected or otherwise cannot perform write. */ bool NimBLERemoteDescriptor::writeValue(const uint8_t* data, size_t length, bool response) { @@ -352,14 +362,4 @@ bool NimBLERemoteDescriptor::writeValue(const uint8_t* data, size_t length, bool } // writeValue -/** - * @brief Write data represented as a string to the BLE Remote Descriptor. - * @param [in] newValue The data to send to the remote descriptor. - * @param [in] response True if we expect a response. - * @return True if successful - */ -bool NimBLERemoteDescriptor::writeValue(const std::string &newValue, bool response) { - return writeValue((uint8_t*) newValue.data(), newValue.length(), response); -} // writeValue - #endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */ diff --git a/src/NimBLERemoteDescriptor.h b/src/NimBLERemoteDescriptor.h index 13e8351..28863df 100644 --- a/src/NimBLERemoteDescriptor.h +++ b/src/NimBLERemoteDescriptor.h @@ -29,10 +29,53 @@ public: uint16_t getHandle(); NimBLERemoteCharacteristic* getRemoteCharacteristic(); NimBLEUUID getUUID(); - std::string readValue(); + NimBLEAttValue readValue(); + + uint8_t readUInt8() __attribute__ ((deprecated("Use template readValue()"))); + uint16_t readUInt16() __attribute__ ((deprecated("Use template readValue()"))); + uint32_t readUInt32() __attribute__ ((deprecated("Use template 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); + + + /*********************** Template Functions ************************/ /** - * @brief A template to convert the remote descriptor data to . + * @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 @@ -40,28 +83,10 @@ public: * @details Use: readValue(skipSizeCheck); */ template - T readValue(bool skipSizeCheck = false) { - std::string value = readValue(); + T readValue(bool skipSizeCheck = false) { + NimBLEAttValue value = readValue(); if(!skipSizeCheck && value.size() < sizeof(T)) return T(); - const char *pData = value.data(); - return *((T *)pData); - } - - uint8_t readUInt8() __attribute__ ((deprecated("Use template readValue()"))); - uint16_t readUInt16() __attribute__ ((deprecated("Use template readValue()"))); - uint32_t readUInt32() __attribute__ ((deprecated("Use template readValue()"))); - std::string toString(void); - bool writeValue(const uint8_t* data, size_t length, bool response = false); - bool writeValue(const std::string &newValue, bool response = false); - - /** - * @brief Convenience template to set the remote descriptor value to val. - * @param [in] s The value to write. - * @param [in] response True == request write response. - */ - template - bool writeValue(const T &s, bool response = false) { - return writeValue((uint8_t*)&s, sizeof(T), response); + return *((T *)value.data()); } private: diff --git a/src/NimBLEService.cpp b/src/NimBLEService.cpp index 18e15bc..a0f025b 100644 --- a/src/NimBLEService.cpp +++ b/src/NimBLEService.cpp @@ -250,10 +250,11 @@ uint16_t NimBLEService::getHandle() { * @brief Create a new BLE Characteristic associated with this service. * @param [in] uuid - The UUID of the characteristic. * @param [in] properties - The properties of the characteristic. + * @param [in] max_len - The maximum length in bytes that the characteristic value can hold. * @return The new BLE characteristic. */ -NimBLECharacteristic* NimBLEService::createCharacteristic(const char* uuid, uint32_t properties) { - return createCharacteristic(NimBLEUUID(uuid), properties); +NimBLECharacteristic* NimBLEService::createCharacteristic(const char* uuid, uint32_t properties, uint16_t max_len) { + return createCharacteristic(NimBLEUUID(uuid), properties, max_len); } @@ -261,10 +262,11 @@ NimBLECharacteristic* NimBLEService::createCharacteristic(const char* uuid, uint * @brief Create a new BLE Characteristic associated with this service. * @param [in] uuid - The UUID of the characteristic. * @param [in] properties - The properties of the characteristic. + * @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) { - NimBLECharacteristic* pCharacteristic = new NimBLECharacteristic(uuid, properties, this); +NimBLECharacteristic* NimBLEService::createCharacteristic(const NimBLEUUID &uuid, uint32_t properties, uint16_t max_len) { + NimBLECharacteristic* pCharacteristic = new NimBLECharacteristic(uuid, properties, max_len, this); if (getCharacteristic(uuid) != nullptr) { NIMBLE_LOGD(LOG_TAG, "<< Adding a duplicate characteristic with UUID: %s", diff --git a/src/NimBLEService.h b/src/NimBLEService.h index fbdd2e1..21ec1af 100644 --- a/src/NimBLEService.h +++ b/src/NimBLEService.h @@ -50,12 +50,14 @@ public: NimBLECharacteristic* createCharacteristic(const char* uuid, uint32_t properties = NIMBLE_PROPERTY::READ | - NIMBLE_PROPERTY::WRITE); + 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); + NIMBLE_PROPERTY::WRITE, + uint16_t max_len = BLE_ATT_ATTR_MAX_LEN); void addCharacteristic(NimBLECharacteristic* pCharacteristic); void removeCharacteristic(NimBLECharacteristic* pCharacteristic, bool deleteChr = false); diff --git a/src/NimBLEUtils.h b/src/NimBLEUtils.h index 2fe4b3c..006d935 100644 --- a/src/NimBLEUtils.h +++ b/src/NimBLEUtils.h @@ -29,7 +29,7 @@ typedef struct { void *pATT; TaskHandle_t task; int rc; - std::string *buf; + void *buf; } ble_task_data_t; diff --git a/src/nimconfig.h b/src/nimconfig.h index ea1840a..9c19031 100644 --- a/src/nimconfig.h +++ b/src/nimconfig.h @@ -25,6 +25,13 @@ #define CONFIG_BT_NIMBLE_ROLE_BROADCASTER #endif +/* Enables the use of Arduino String class for attribute values */ +#if defined __has_include +# if __has_include () +# define NIMBLE_CPP_ARDUINO_STRING_AVAILABLE +# endif +#endif + #endif /* CONFIG_BT_ENABLED */ #ifdef _DOXYGEN_ @@ -32,6 +39,22 @@ /** @brief Un-comment to change the number of simultaneous connections (esp controller max is 9) */ #define CONFIG_BT_NIMBLE_MAX_CONNECTIONS 3 +/** @brief Un-comment to enable storing the timestamp when an attribute value is updated\n + * This allows for checking the last update time using getTimeStamp() or getValue(time_t*)\n + * If disabled, the timestamp returned from these functions will be 0.\n + * Disabling timestamps will reduce the memory used for each value.\n + * 1 = Enabled, 0 = Disabled; Default = Disabled + */ +#define CONFIG_NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED 0 + +/** @brief Uncomment to set the default allocation size (bytes) for each attribute if\n + * not specified when the constructor is called. This is also the size used when a remote\n + * characteristic or descriptor is constructed before a value is read/notifed.\n + * Increasing this will reduce reallocations but increase memory footprint.\n + * Default value is 20. Range: 1 : 512 (BLE_ATT_ATTR_MAX_LEN) + */ +#define CONFIG_NIMBLE_CPP_ATT_VALUE_INIT_LENGTH 20 + /** @brief Un-comment to change the default MTU size */ #define CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU 255