Skip to content

Commit

Permalink
Add NimBLEAttValue class.
Browse files Browse the repository at this point in the history
New wrapper class for attribute values:

* Allows for the use of Arduino Strings for characteristic/descriptor values.
* Converts to std::string, String, vectors, raw uint8_t or char pointers.
* Introduces a max length paramter to the creation of server characteristics/descriptors to limit the size
of the memory footprint.
* Nearly Seamless integration with existing code.
* Add a macro to enable/disable timestamps to save ram / improve performance when not needed.
  • Loading branch information
h2zero committed Jan 16, 2022
1 parent ce29abb commit 04626ec
Show file tree
Hide file tree
Showing 16 changed files with 645 additions and 286 deletions.
2 changes: 1 addition & 1 deletion examples/NimBLE_Server/NimBLE_Server.ino
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ class CharacteristicCallbacks: public NimBLECharacteristicCallbacks {
/** Handler class for descriptor actions */
class DescriptorCallbacks : public NimBLEDescriptorCallbacks {
void onWrite(NimBLEDescriptor* pDescriptor) {
std::string dscVal((char*)pDescriptor->getValue(), pDescriptor->getLength());
std::string dscVal = pDescriptor->getValue();
Serial.print("Descriptor witten value:");
Serial.println(dscVal.c_str());
};
Expand Down
276 changes: 276 additions & 0 deletions src/NimBLEAttValue.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
/*
* 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 <Arduino.h>
#endif

#include "NimBLELog.h"

/**** FIX COMPILATION ****/
#undef min
#undef max
/**************************/

#include <string>
#include <vector>

#ifdef NIMBLE_ATT_VALUE_TIMESTAMP_ENABLED
#include <time.h>
#endif

#define NIMBLE_ATT_VALUE_INIT_LENGTH 20

template <typename T, typename = void, typename = void>
struct Has_c_str_len : std::false_type {};

template <typename T>
struct Has_c_str_len<T, decltype(void(std::declval<T &>().c_str())),
decltype(void(std::declval<T &>().length()))> : std::true_type {};


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;
#ifdef NIMBLE_ATT_VALUE_TIMESTAMP_ENABLED
time_t m_timestamp = 0;
#endif
void deepCopy(const NimBLEAttValue & source);

public:
NimBLEAttValue(uint16_t init_len = NIMBLE_ATT_VALUE_INIT_LENGTH, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN);
NimBLEAttValue(const uint8_t *value, uint16_t len, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN);
NimBLEAttValue(const NimBLEAttValue & source) { deepCopy(source); }
NimBLEAttValue(NimBLEAttValue && source);
NimBLEAttValue(std::initializer_list<uint8_t> list, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN)
:NimBLEAttValue(list.begin(), (uint16_t)list.size(), max_len){}
NimBLEAttValue(const char *value, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN)
:NimBLEAttValue((uint8_t*)value, (uint16_t)strlen(value), max_len){}
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){}
NimBLEAttValue(const std::vector<uint8_t> vec, uint16_t max_len = BLE_ATT_ATTR_MAX_LEN)
:NimBLEAttValue(&vec[0], (uint16_t)vec.size(), max_len){}
~NimBLEAttValue();

uint16_t max_size() const { return m_attr_max_len; }
uint16_t capacity() const { return m_attr_max_len; }
uint16_t length() const { return m_attr_len; }
uint16_t size() const { return m_attr_len; }
const uint8_t* data() const { return m_attr_value; }
const char* c_str() const { return (const char*)m_attr_value; }
#ifdef NIMBLE_ATT_VALUE_TIMESTAMP_ENABLED
time_t getTimeStamp() const { return m_timestamp; }
void setTimeStamp() { m_timestamp = time(nullptr); }
void setTimeStamp(time_t t) { m_timestamp = t; }
#else
time_t getTimeStamp() const { return 0; }
void setTimeStamp() { }
void setTimeStamp(time_t t) { }
#endif
bool setValue(const uint8_t *value, uint16_t len);
uint8_t* getValue(time_t *timestamp);
NimBLEAttValue& append(const uint8_t *value, uint16_t len);


/*********************** Template Functions ************************/

template<typename T>
typename std::enable_if<!Has_c_str_len<T>::value, bool>::type
setValue(const T &s) {
return setValue((uint8_t*)&s, sizeof(T));
}

template<typename T>
typename std::enable_if<Has_c_str_len<T>::value, bool>::type
setValue(const T & s) {
return setValue((uint8_t*)s.c_str(), (uint16_t)s.length());
}

template<typename T>
bool setValue(const char* s) {
return setValue((uint8_t*)s, (uint16_t)strlen(s));
}

template<typename T>
T getValue(time_t *timestamp = nullptr, bool skipSizeCheck = false) {
if(!skipSizeCheck && size() < sizeof(T)) {
return T();
}
return *((T *)getValue(timestamp));
}


/*********************** Operators ************************/

operator std::vector<uint8_t>() const { return std::vector<uint8_t>(m_attr_value, m_attr_value + m_attr_len); }
operator std::string() const { return std::string((char*)m_attr_value, m_attr_len); }
operator const uint8_t*() const { return m_attr_value; }
NimBLEAttValue& operator +=(const NimBLEAttValue & source) { return append(source.data(), source.size()); }
NimBLEAttValue& operator =(const std::string & source) {
setValue((uint8_t*)source.data(), (uint16_t)source.size()); return *this; }
NimBLEAttValue& operator =(NimBLEAttValue && source);
NimBLEAttValue& operator =(const NimBLEAttValue & source);
bool operator ==(const NimBLEAttValue & source) {
return (m_attr_len == source.size()) ? memcmp(m_attr_value, source.data(), m_attr_len) == 0 : false; }
bool operator !=(const NimBLEAttValue & source){ return !(*this == source); }

#ifdef NIMBLE_CPP_ARDUINO_STRING_AVAILABLE
NimBLEAttValue(const String str):NimBLEAttValue((uint8_t*)str.c_str(), str.length()){}
operator String() const { return String((char*)m_attr_value); }
#endif
};

inline NimBLEAttValue::NimBLEAttValue(const uint8_t *value, uint16_t len, uint16_t max_len)
: NimBLEAttValue(len, max_len) {
m_attr_len = len;
memcpy(m_attr_value, value, len);
m_attr_value[len] = '\0';
}

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(NimBLEAttValue && source) {
*this = std::move(source);
}

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 uint8_t* NimBLEAttValue::getValue(time_t *timestamp) {
if(timestamp != nullptr) {
#ifdef NIMBLE_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");

#ifdef NIMBLE_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");

#ifdef NIMBLE_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_ */
Loading

0 comments on commit 04626ec

Please sign in to comment.