#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <sstream>
#include <string.h>
#include <iomanip>
#include <stdlib.h>
#include "sdkconfig.h"
#include <esp_log.h>
#include <esp_err.h>
#include "BLECharacteristic.h"
#include "BLEService.h"
#include "BLEUtils.h"
#include "BLE2902.h"
#include "GeneralUtils.h"
#ifdef ARDUINO_ARCH_ESP32
#include "esp32-hal-log.h"
#endif
static const char* LOG_TAG = "BLECharacteristic";
#define NULL_HANDLE (0xffff)
BLECharacteristic::BLECharacteristic(const char* uuid, uint32_t properties) : BLECharacteristic(BLEUUID(uuid), properties) {
}
BLECharacteristic::BLECharacteristic(BLEUUID uuid, uint32_t properties) {
m_bleUUID = uuid;
m_handle = NULL_HANDLE;
m_properties = (esp_gatt_char_prop_t)0;
m_pCallbacks = nullptr;
setBroadcastProperty((properties & PROPERTY_BROADCAST) !=0);
setReadProperty((properties & PROPERTY_READ) !=0);
setWriteProperty((properties & PROPERTY_WRITE) !=0);
setNotifyProperty((properties & PROPERTY_NOTIFY) !=0);
setIndicateProperty((properties & PROPERTY_INDICATE) !=0);
setWriteNoResponseProperty((properties & PROPERTY_WRITE_NR) !=0);
}
BLECharacteristic::~BLECharacteristic() {
}
void BLECharacteristic::addDescriptor(BLEDescriptor* pDescriptor) {
ESP_LOGD(LOG_TAG, ">> addDescriptor(): Adding %s to %s", pDescriptor->toString().c_str(), toString().c_str());
m_descriptorMap.setByUUID(pDescriptor->getUUID(), pDescriptor);
ESP_LOGD(LOG_TAG, "<< addDescriptor()");
}
void BLECharacteristic::executeCreate(BLEService* pService) {
ESP_LOGD(LOG_TAG, ">> executeCreate()");
if (m_handle != NULL_HANDLE) {
ESP_LOGE(LOG_TAG, "Characteristic already has a handle.");
return;
}
m_pService = pService;
ESP_LOGD(LOG_TAG, "Registering characteristic (esp_ble_gatts_add_char): uuid: %s, service: %s",
getUUID().toString().c_str(),
m_pService->toString().c_str());
esp_attr_control_t control;
control.auto_rsp = ESP_GATT_RSP_BY_APP;
m_semaphoreCreateEvt.take("executeCreate");
esp_err_t errRc = ::esp_ble_gatts_add_char(
m_pService->getHandle(),
getUUID().getNative(),
static_cast<esp_gatt_perm_t>(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE),
getProperties(),
nullptr,
&control);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_add_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
m_semaphoreCreateEvt.wait("executeCreate");
BLEDescriptor* pDescriptor = m_descriptorMap.getFirst();
while (pDescriptor != nullptr) {
pDescriptor->executeCreate(this);
pDescriptor = m_descriptorMap.getNext();
}
ESP_LOGD(LOG_TAG, "<< executeCreate");
}
BLEDescriptor* BLECharacteristic::getDescriptorByUUID(const char* descriptorUUID) {
return m_descriptorMap.getByUUID(BLEUUID(descriptorUUID));
}
BLEDescriptor* BLECharacteristic::getDescriptorByUUID(BLEUUID descriptorUUID) {
return m_descriptorMap.getByUUID(descriptorUUID);
}
uint16_t BLECharacteristic::getHandle() {
return m_handle;
}
esp_gatt_char_prop_t BLECharacteristic::getProperties() {
return m_properties;
}
BLEService* BLECharacteristic::getService() {
return m_pService;
}
BLEUUID BLECharacteristic::getUUID() {
return m_bleUUID;
}
std::string BLECharacteristic::getValue() {
return m_value.getValue();
}
void BLECharacteristic::handleGATTServerEvent(
esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t* param) {
switch(event) {
case ESP_GATTS_EXEC_WRITE_EVT: {
if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) {
m_value.commit();
if (m_pCallbacks != nullptr) {
m_pCallbacks->onWrite(this);
}
} else {
m_value.cancel();
}
esp_err_t errRc = ::esp_ble_gatts_send_response(
gatts_if,
param->write.conn_id,
param->write.trans_id, ESP_GATT_OK, nullptr);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gatts_send_response: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
}
break;
}
case ESP_GATTS_ADD_CHAR_EVT: {
if (getUUID().equals(BLEUUID(param->add_char.char_uuid)) &&
getService()->getHandle()==param->add_char.service_handle) {
m_semaphoreCreateEvt.give();
}
break;
}
case ESP_GATTS_WRITE_EVT: {
if (param->write.handle == m_handle) {
if (param->write.is_prep) {
m_value.addPart(param->write.value, param->write.len);
} else {
setValue(param->write.value, param->write.len);
}
ESP_LOGD(LOG_TAG, " - Response to write event: New value: handle: %.2x, uuid: %s",
getHandle(), getUUID().toString().c_str());
char* pHexData = BLEUtils::buildHexData(nullptr, param->write.value, param->write.len);
ESP_LOGD(LOG_TAG, " - Data: length: %d, data: %s", param->write.len, pHexData);
free(pHexData);
if (param->write.need_rsp) {
esp_gatt_rsp_t rsp;
rsp.attr_value.len = param->write.len;
rsp.attr_value.handle = m_handle;
rsp.attr_value.offset = param->write.offset;
rsp.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
memcpy(rsp.attr_value.value, param->write.value, param->write.len);
esp_err_t errRc = ::esp_ble_gatts_send_response(
gatts_if,
param->write.conn_id,
param->write.trans_id, ESP_GATT_OK, &rsp);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gatts_send_response: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
}
}
if (m_pCallbacks != nullptr && param->write.is_prep != true) {
m_pCallbacks->onWrite(this);
}
}
break;
}
case ESP_GATTS_READ_EVT: {
ESP_LOGD(LOG_TAG, "- Testing: 0x%.2x == 0x%.2x", param->read.handle, m_handle);
if (param->read.handle == m_handle) {
if (m_pCallbacks != nullptr) {
m_pCallbacks->onRead(this);
}
if (param->read.need_rsp) {
ESP_LOGD(LOG_TAG, "Sending a response (esp_ble_gatts_send_response)");
esp_gatt_rsp_t rsp;
std::string value = m_value.getValue();
if (param->read.is_long) {
if (value.length() - m_value.getReadOffset() < 22) {
rsp.attr_value.len = value.length() - m_value.getReadOffset();
rsp.attr_value.offset = m_value.getReadOffset();
memcpy(rsp.attr_value.value, value.data() + rsp.attr_value.offset, rsp.attr_value.len);
m_value.setReadOffset(0);
} else {
rsp.attr_value.len = 22;
rsp.attr_value.offset = m_value.getReadOffset();
memcpy(rsp.attr_value.value, value.data() + rsp.attr_value.offset, rsp.attr_value.len);
m_value.setReadOffset(rsp.attr_value.offset + 22);
}
} else {
if (value.length() > 21) {
m_value.setReadOffset(22);
rsp.attr_value.len = 22;
rsp.attr_value.offset = 0;
memcpy(rsp.attr_value.value, value.data(), rsp.attr_value.len);
} else {
rsp.attr_value.len = value.length();
rsp.attr_value.offset = 0;
memcpy(rsp.attr_value.value, value.data(), rsp.attr_value.len);
}
}
rsp.attr_value.handle = param->read.handle;
rsp.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
char *pHexData = BLEUtils::buildHexData(nullptr, rsp.attr_value.value, rsp.attr_value.len);
ESP_LOGD(LOG_TAG, " - Data: length=%d, data=%s, offset=%d", rsp.attr_value.len, pHexData, rsp.attr_value.offset);
free(pHexData);
esp_err_t errRc = ::esp_ble_gatts_send_response(
gatts_if, param->read.conn_id,
param->read.trans_id,
ESP_GATT_OK,
&rsp);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "esp_ble_gatts_send_response: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
}
}
}
break;
}
case ESP_GATTS_CONF_EVT: {
m_semaphoreConfEvt.give();
break;
}
default: {
break;
}
}
BLEDescriptor *pDescriptor = m_descriptorMap.getFirst();
while(pDescriptor != nullptr) {
pDescriptor->handleGATTServerEvent(event, gatts_if, param);
pDescriptor = m_descriptorMap.getNext();
}
}
void BLECharacteristic::indicate() {
ESP_LOGD(LOG_TAG, ">> indicate: length: %d", m_value.getValue().length());
assert(getService() != nullptr);
assert(getService()->getServer() != nullptr);
GeneralUtils::hexDump((uint8_t*)m_value.getValue().data(), m_value.getValue().length());
if (getService()->getServer()->getConnectedCount() == 0) {
ESP_LOGD(LOG_TAG, "<< indicate: No connected clients.");
return;
}
BLE2902 *p2902 = (BLE2902*)getDescriptorByUUID((uint16_t)0x2902);
if (p2902 != nullptr && !p2902->getIndications()) {
ESP_LOGD(LOG_TAG, "<< indications disabled; ignoring");
return;
}
if (m_value.getValue().length() > 20) {
ESP_LOGD(LOG_TAG, "- Truncating to 20 bytes (maximum notify size)");
}
size_t length = m_value.getValue().length();
if (length > 20) {
length = 20;
}
m_semaphoreConfEvt.take("indicate");
esp_err_t errRc = ::esp_ble_gatts_send_indicate(
getService()->getServer()->getGattsIf(),
getService()->getServer()->getConnId(),
getHandle(), length, (uint8_t*)m_value.getValue().data(), true);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_send_indicate: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
m_semaphoreConfEvt.wait("indicate");
ESP_LOGD(LOG_TAG, "<< indicate");
}
void BLECharacteristic::notify() {
ESP_LOGD(LOG_TAG, ">> notify: length: %d", m_value.getValue().length());
assert(getService() != nullptr);
assert(getService()->getServer() != nullptr);
GeneralUtils::hexDump((uint8_t*)m_value.getValue().data(), m_value.getValue().length());
if (getService()->getServer()->getConnectedCount() == 0) {
ESP_LOGD(LOG_TAG, "<< notify: No connected clients.");
return;
}
BLE2902 *p2902 = (BLE2902*)getDescriptorByUUID((uint16_t)0x2902);
if (p2902 != nullptr && !p2902->getNotifications()) {
ESP_LOGD(LOG_TAG, "<< notifications disabled; ignoring");
return;
}
if (m_value.getValue().length() > 20) {
ESP_LOGD(LOG_TAG, "- Truncating to 20 bytes (maximum notify size)");
}
size_t length = m_value.getValue().length();
if (length > 20) {
length = 20;
}
esp_err_t errRc = ::esp_ble_gatts_send_indicate(
getService()->getServer()->getGattsIf(),
getService()->getServer()->getConnId(),
getHandle(), length, (uint8_t*)m_value.getValue().data(), false);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_send_indicate: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
ESP_LOGD(LOG_TAG, "<< notify");
}
void BLECharacteristic::setBroadcastProperty(bool value) {
if (value) {
m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_BROADCAST);
} else {
m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST);
}
}
void BLECharacteristic::setCallbacks(BLECharacteristicCallbacks* pCallbacks) {
m_pCallbacks = pCallbacks;
}
void BLECharacteristic::setHandle(uint16_t handle) {
ESP_LOGD(LOG_TAG, ">> setHandle: handle=0x%.2x, characteristic uuid=%s", handle, getUUID().toString().c_str());
m_handle = handle;
ESP_LOGD(LOG_TAG, "<< setHandle");
}
void BLECharacteristic::setIndicateProperty(bool value) {
if (value) {
m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_INDICATE);
} else {
m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_INDICATE);
}
}
void BLECharacteristic::setNotifyProperty(bool value) {
if (value) {
m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_NOTIFY);
} else {
m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY);
}
}
void BLECharacteristic::setReadProperty(bool value) {
if (value) {
m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_READ);
} else {
m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_READ);
}
}
void BLECharacteristic::setValue(uint8_t* data, size_t length) {
char *pHex = BLEUtils::buildHexData(nullptr, data, length);
ESP_LOGD(LOG_TAG, ">> setValue: length=%d, data=%s, characteristic UUID=%s", length, pHex, getUUID().toString().c_str());
free(pHex);
if (length > ESP_GATT_MAX_ATTR_LEN) {
ESP_LOGE(LOG_TAG, "Size %d too large, must be no bigger than %d", length, ESP_GATT_MAX_ATTR_LEN);
return;
}
m_value.setValue(data, length);
ESP_LOGD(LOG_TAG, "<< setValue");
}
void BLECharacteristic::setValue(std::string value) {
setValue((uint8_t*)(value.data()), value.length());
}
void BLECharacteristic::setWriteNoResponseProperty(bool value) {
if (value) {
m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_WRITE_NR);
} else {
m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR);
}
}
void BLECharacteristic::setWriteProperty(bool value) {
if (value) {
m_properties = (esp_gatt_char_prop_t)(m_properties | ESP_GATT_CHAR_PROP_BIT_WRITE);
} else {
m_properties = (esp_gatt_char_prop_t)(m_properties & ~ESP_GATT_CHAR_PROP_BIT_WRITE);
}
}
std::string BLECharacteristic::toString() {
std::stringstream stringstream;
stringstream << std::hex << std::setfill('0');
stringstream << "UUID: " << m_bleUUID.toString() + ", handle: 0x" << std::setw(2) << m_handle;
stringstream << " " <<
((m_properties & ESP_GATT_CHAR_PROP_BIT_READ)?"Read ":"") <<
((m_properties & ESP_GATT_CHAR_PROP_BIT_WRITE)?"Write ":"") <<
((m_properties & ESP_GATT_CHAR_PROP_BIT_WRITE_NR)?"WriteNoResponse ":"") <<
((m_properties & ESP_GATT_CHAR_PROP_BIT_BROADCAST)?"Broadcast ":"") <<
((m_properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY)?"Notify ":"") <<
((m_properties & ESP_GATT_CHAR_PROP_BIT_INDICATE)?"Indicate ":"");
return stringstream.str();
}
#endif