Skip to content

Instantly share code, notes, and snippets.

@beegee-tokyo
Last active April 22, 2020 10:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save beegee-tokyo/846d71fca439b81e8aa848a2d4fac989 to your computer and use it in GitHub Desktop.
Save beegee-tokyo/846d71fca439b81e8aa848a2d4fac989 to your computer and use it in GitHub Desktop.
Dirty patch for ESP32 BLE server to allow multiple clients to connect and subscribe to Notify or Indicate
/** Patch for adding multi client subscriptions to NOTIFY and INDICATE
* All functions adapted to support data for up to 10 clients
* Check lines 23, 24, 25, 34, 35, 43, 44, 52, 53, 55, 57, 66, 67, 69, 71
* for changes
*/
/*
* BLE2902.cpp
*
* Created on: Jun 25, 2017
* Author: kolban
*/
/*
* See also:
* https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "BLE2902.h"
BLE2902::BLE2902() : BLEDescriptor(BLEUUID((uint16_t) 0x2902)) {
uint8_t data[10][2] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
for (int index=0; index<10; index++) {
setValue((uint8_t*)data, 2, index);
}
} // BLE2902
/**
* @brief Get the notifications value.
* @return The notifications value. True if notifications are enabled and false if not.
*/
bool BLE2902::getNotifications(uint16_t connId) {
return (getValue(connId)[0] & (1 << 0)) != 0;
} // getNotifications
/**
* @brief Get the indications value.
* @return The indications value. True if indications are enabled and false if not.
*/
bool BLE2902::getIndications(uint16_t connId) {
return (getValue(connId)[0] & (1 << 1)) != 0;
} // getIndications
/**
* @brief Set the indications flag.
* @param [in] flag The indications flag.
*/
void BLE2902::setIndications(bool flag, uint16_t connId) {
uint8_t *pValue = getValue(connId);
if (flag) {
pValue[connId] |= 1<<1;
} else {
pValue[connId] &= ~(1<<1);
}
} // setIndications
/**
* @brief Set the notifications flag.
* @param [in] flag The notifications flag.
*/
void BLE2902::setNotifications(bool flag, uint16_t connId) {
uint8_t *pValue = getValue(connId);
if (flag) {
pValue[connId] |= 1<<0;
} else {
pValue[connId] &= ~(1<<0);
}
} // setNotifications
#endif
/** Patch for adding multi client subscriptions to NOTIFY and INDICATE
* Check lines 30 to 33
* for changes
*/
/*
* BLE2902.h
*
* Created on: Jun 25, 2017
* Author: kolban
*/
#ifndef COMPONENTS_CPP_UTILS_BLE2902_H_
#define COMPONENTS_CPP_UTILS_BLE2902_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include "BLEDescriptor.h"
/**
* @brief Descriptor for Client Characteristic Configuration.
*
* This is a convenience descriptor for the Client Characteristic Configuration which has a UUID of 0x2902.
*
* See also:
* https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
*/
class BLE2902: public BLEDescriptor {
public:
BLE2902();
bool getNotifications(uint16_t connId);
bool getIndications(uint16_t connId);
void setNotifications(bool flag, uint16_t connId);
void setIndications(bool flag, uint16_t connId);
}; // BLE2902
#endif /* CONFIG_BT_ENABLED */
#endif /* COMPONENTS_CPP_UTILS_BLE2902_H_ */
/**
* To enable multiple clients to connect the
* BLEServerCallbacks onConnect()
* must restart advertising with
* pAdvertising->start();
* as BLEServer stops advertising after a client connected successfully
*/
#include "setup.h"
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLE2902.h>
// List of Service and Characteristic UUIDs
#define SERVICE_UUID 0x181A
#define NOTIFICATION_UUID 0x2A08
#define TEMP_UUID 0x2A1F
#define HUMID_UUID 0x2A6F
#define STATUS_UUID 0x2A3D
/** Characteristic for client notification */
BLECharacteristic *pCharacteristicNotify;
/** Characteristic for temperature in celsius */
BLECharacteristic *pCharacteristicTemp;
/** Characteristic for humidity in percent */
BLECharacteristic *pCharacteristicHumid;
/** Characteristic for environment status */
BLECharacteristic *pCharacteristicStatus;
/** BLE Advertiser */
BLEAdvertising* pAdvertising;
/** BLE Service */
BLEService *pService;
/** BLE Server */
BLEServer *pServer;
/** Flag if a client is connected */
bool bleConnected = false;
/** Last temperature reading */
float bleTemperature;
/** Last humidity reading */
float bleHumidity;
/** Last environment status */
String bleStatus;
/**
* MyServerCallbacks
* Callbacks for client connection and disconnection
*/
class MyServerCallbacks: public BLEServerCallbacks {
// TODO this doesn't take into account several clients being connected
void onConnect(BLEServer* pServer) {
bleConnected = true;
pAdvertising->start();
};
void onDisconnect(BLEServer* pServer) {
bleConnected = false;
}
};
/**
* MyCallbackHandler
* Callbacks for characteristic read and write events
*/
class MyCallbackHandler: public BLECharacteristicCallbacks {
void onRead(BLECharacteristic* pCharacteristic) {
if (pCharacteristic == pCharacteristicNotify) {
uint8_t notifData[8];
time_t now;
struct tm timeinfo;
time(&now); // get time (as epoch)
localtime_r(&now, &timeinfo); // update tm struct with current time
uint16_t year = timeinfo.tm_year+1900;
notifData[1] = year>>8;
notifData[0] = year;
notifData[2] = timeinfo.tm_mon+1;
notifData[3] = timeinfo.tm_mday;
notifData[4] = timeinfo.tm_hour;
notifData[5] = timeinfo.tm_min;
notifData[6] = timeinfo.tm_sec;
pCharacteristic->setValue(notifData, 8);
} else if (pCharacteristic == pCharacteristicTemp) {
uint8_t tempData[2];
uint16_t bleTemp100 = (uint16_t)(bleTemperature*10);
tempData[1] = bleTemp100>>8;
tempData[0] = bleTemp100;
pCharacteristic->setValue(tempData, 2);
} else if (pCharacteristic == pCharacteristicHumid) {
uint8_t humidData[2];
uint16_t bleHumid100 = (uint16_t)(bleHumidity*100);
humidData[1] = bleHumid100>>8;
humidData[0] = bleHumid100;
pCharacteristic->setValue(humidData, 2);
} else if (pCharacteristic == pCharacteristicStatus) {
size_t dataLen = bleStatus.length();
pCharacteristic->setValue((uint8_t*)&bleStatus[0], dataLen);
}
}
void onWrite(BLECharacteristic *pCharacteristic) {
std::string value = pCharacteristic->getValue();
int len = value.length();
String strValue = "";
if (value.length() > 0) {
Serial.println("*********");
Serial.print("New value: ");
for (int i = 0; i < value.length(); i++) {
Serial.print(value[i]);
strValue += value[i];
}
Serial.println();
Serial.println("*********");
}
}
};
/**
* initBLE
* Setup BLE
* Setup callbacks for server and pCharacteristicStatus
* Start advertising the BLE service
*/
void initBLE() {
// Initialize BLE
BLEDevice::init(apName);
// BLEDevice::setPower(ESP_PWR_LVL_P7);
// Create BLE Server
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// Create BLE Service
pService = pServer->createService(BLEUUID((uint16_t)SERVICE_UUID));
// Create BLE Characteristic for Alert
pCharacteristicNotify = pService->createCharacteristic(
BLEUUID((uint16_t)NOTIFICATION_UUID),
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_NOTIFY
);
// Create a BLE Descriptor for Alert
pCharacteristicNotify->addDescriptor(new BLE2902());
// Create BLE Characteristic for Temperature
pCharacteristicTemp = pService->createCharacteristic(
BLEUUID((uint16_t)TEMP_UUID),
BLECharacteristic::PROPERTY_READ
);
// Create BLE Characteristic for Humidity
pCharacteristicHumid = pService->createCharacteristic(
BLEUUID((uint16_t)HUMID_UUID),
BLECharacteristic::PROPERTY_READ
);
// Create BLE Characteristic for Status
pCharacteristicStatus = pService->createCharacteristic(
BLEUUID((uint16_t)STATUS_UUID),
BLECharacteristic::PROPERTY_READ
);
// Start the service
pService->start();
// Start advertising
pAdvertising = pServer->getAdvertising();
pAdvertising->start();
// Setup callback handler
pCharacteristicNotify->setCallbacks(new MyCallbackHandler());
pCharacteristicTemp->setCallbacks(new MyCallbackHandler());
pCharacteristicHumid->setCallbacks(new MyCallbackHandler());
pCharacteristicStatus->setCallbacks(new MyCallbackHandler());
}
/**
* bleStop
* Stop advertising the BLE service
*/
void bleStop() {
pAdvertising->stop();
}
/** Patch for adding multi client subscriptions to NOTIFY and INDICATE
* Check functions
* void BLECharacteristic::indicate()
* void BLECharacteristic::notify()
* for changes
*/
/*
* BLECharacteristic.cpp
*
* Created on: Jun 22, 2017
* Author: kolban
*/
#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)
/**
* @brief Construct a characteristic
* @param [in] uuid - UUID (const char*) for the characteristic.
* @param [in] properties - Properties for the characteristic.
*/
BLECharacteristic::BLECharacteristic(const char* uuid, uint32_t properties) : BLECharacteristic(BLEUUID(uuid), properties) {
}
/**
* @brief Construct a characteristic
* @param [in] uuid - UUID for the characteristic.
* @param [in] properties - Properties for the characteristic.
*/
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
/**
* @brief Destructor.
*/
BLECharacteristic::~BLECharacteristic() {
//free(m_value.attr_value); // Release the storage for the value.
} // ~BLECharacteristic
/**
* @brief Associate a descriptor with this characteristic.
* @param [in] pDescriptor
* @return N/A.
*/
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()");
} // addDescriptor
/**
* @brief Register a new characteristic with the ESP runtime.
* @param [in] pService The service with which to associate this characteristic.
*/
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; // Save the service for to which this characteristic belongs.
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_attr_value_t value;
value.attr_len = m_value.getLength();
value.attr_max_len = ESP_GATT_MAX_ATTR_LEN;
value.attr_value = m_value.getData();
*/
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(),
//&value,
nullptr,
&control); // Whether to auto respond or not.
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");
// Now that we have registered the characteristic, we must also register all the descriptors associated with this
// characteristic. We iterate through each of those and invoke the registration call to register them with the
// ESP environment.
BLEDescriptor* pDescriptor = m_descriptorMap.getFirst();
while (pDescriptor != nullptr) {
pDescriptor->executeCreate(this);
pDescriptor = m_descriptorMap.getNext();
} // End while
ESP_LOGD(LOG_TAG, "<< executeCreate");
} // executeCreate
/**
* @brief Return the BLE Descriptor for the given UUID if associated with this characteristic.
* @param [in] descriptorUUID The UUID of the descriptor that we wish to retrieve.
* @return The BLE Descriptor. If no such descriptor is associated with the characteristic, nullptr is returned.
*/
BLEDescriptor* BLECharacteristic::getDescriptorByUUID(const char* descriptorUUID) {
return m_descriptorMap.getByUUID(BLEUUID(descriptorUUID));
} // getDescriptorByUUID
/**
* @brief Return the BLE Descriptor for the given UUID if associated with this characteristic.
* @param [in] descriptorUUID The UUID of the descriptor that we wish to retrieve.
* @return The BLE Descriptor. If no such descriptor is associated with the characteristic, nullptr is returned.
*/
BLEDescriptor* BLECharacteristic::getDescriptorByUUID(BLEUUID descriptorUUID) {
return m_descriptorMap.getByUUID(descriptorUUID);
} // getDescriptorByUUID
/**
* @brief Get the handle of the characteristic.
* @return The handle of the characteristic.
*/
uint16_t BLECharacteristic::getHandle() {
return m_handle;
} // getHandle
esp_gatt_char_prop_t BLECharacteristic::getProperties() {
return m_properties;
} // getProperties
/**
* @brief Get the service associated with this characteristic.
*/
BLEService* BLECharacteristic::getService() {
return m_pService;
} // getService
/**
* @brief Get the UUID of the characteristic.
* @return The UUID of the characteristic.
*/
BLEUUID BLECharacteristic::getUUID() {
return m_bleUUID;
} // getUUID
/**
* @brief Retrieve the current value of the characteristic.
* @return A pointer to storage containing the current characteristic value.
*/
std::string BLECharacteristic::getValue() {
return m_value.getValue();
} // getValue
void BLECharacteristic::handleGATTServerEvent(
esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t* param) {
// ESP_LOGD(LOG_TAG, ">> handleGATTServerEvent: %s",
// BLEUtils::gattServerEventTypeToString(event).c_str());
// BLEUtils::dumpGattServerEvent(event, gatts_if, param);
switch(event) {
// Events handled:
// ESP_GATTS_ADD_CHAR_EVT
// ESP_GATTS_WRITE_EVT
// ESP_GATTS_READ_EVT
//
// ESP_GATTS_EXEC_WRITE_EVT
// When we receive this event it is an indication that a previous write long needs to be committed.
//
// exec_write:
// - uint16_t conn_id
// - uint32_t trans_id
// - esp_bd_addr_t bda
// - uint8_t exec_write_flag
//
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); // Invoke the onWrite callback handler.
}
} 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;
} // ESP_GATTS_EXEC_WRITE_EVT
// ESP_GATTS_ADD_CHAR_EVT - Indicate that a characteristic was added to the service.
// add_char:
// - esp_gatt_status_t status
// - uint16_t attr_handle
// - uint16_t service_handle
// - esp_bt_uuid_t char_uuid
case ESP_GATTS_ADD_CHAR_EVT: {
if (getUUID().equals(BLEUUID(param->add_char.char_uuid)) &&
getHandle() == param->add_char.attr_handle &&
getService()->getHandle()==param->add_char.service_handle) {
m_semaphoreCreateEvt.give();
}
break;
} // ESP_GATTS_ADD_CHAR_EVT
// ESP_GATTS_WRITE_EVT - A request to write the value of a characteristic has arrived.
//
// write:
// - uint16_t conn_id
// - uint16_t trans_id
// - esp_bd_addr_t bda
// - uint16_t handle
// - uint16_t offset
// - bool need_rsp
// - bool is_prep
// - uint16_t len
// - uint8_t *value
//
case ESP_GATTS_WRITE_EVT: {
// We check if this write request is for us by comparing the handles in the event. If it is for us
// we save the new value. Next we look at the need_rsp flag which indicates whether or not we need
// to send a response. If we do, then we formulate a response and send it.
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));
}
} // Response needed
if (m_pCallbacks != nullptr && param->write.is_prep != true) {
m_pCallbacks->onWrite(this); // Invoke the onWrite callback handler.
}
} // Match on handles.
break;
} // ESP_GATTS_WRITE_EVT
// ESP_GATTS_READ_EVT - A request to read the value of a characteristic has arrived.
//
// read:
// - uint16_t conn_id
// - uint32_t trans_id
// - esp_bd_addr_t bda
// - uint16_t handle
// - uint16_t offset
// - bool is_long
// - bool need_rsp
//
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); // Invoke the read callback.
}
// Here's an interesting thing. The read request has the option of saying whether we need a response
// or not. What would it "mean" to receive a read request and NOT send a response back? That feels like
// a very strange read.
//
// We have to handle the case where the data we wish to send back to the client is greater than the maximum
// packet size of 22 bytes. In this case, we become responsible for chunking the data into uints of 22 bytes.
// The apparent algorithm is as follows.
// If the is_long flag is set then this is a follow on from an original read and we will already have sent at least 22 bytes.
// If the is_long flag is not set then we need to check how much data we are going to send. If we are sending LESS than
// 22 bytes, then we "just" send it and thats the end of the story.
// If we are sending 22 bytes exactly, we just send it BUT we will get a follow on request.
// If we are sending more than 22 bytes, we send the first 22 bytes and we will get a follow on request.
// Because of follow on request processing, we need to maintain an offset of how much data we have already sent
// so that when a follow on request arrives, we know where to start in the data to send the next sequence.
// Note that the indication that the client will send a follow on request is that we sent exactly 22 bytes as a response.
// If our payload is divisible by 22 then the last response will be a response of 0 bytes in length.
//
// The following code has deliberately not been factored to make it fewer statements because this would cloud the
// the logic flow comprehension.
//
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) {
// This is the last in the chain
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 {
// There will be more to come.
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) {
// Too big for a single shot entry.
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 {
// Will fit in a single packet with no callbacks required.
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));
}
} // Response needed
} // Handle matches this characteristic.
break;
} // ESP_GATTS_READ_EVT
// ESP_GATTS_CONF_EVT
//
// conf:
// - esp_gatt_status_t status – The status code.
// - uint16_t conn_id – The connection used.
//
case ESP_GATTS_CONF_EVT: {
m_semaphoreConfEvt.give();
break;
}
default: {
break;
} // default
} // switch event
// Give each of the descriptors associated with this characteristic the opportunity to handle the
// event.
m_descriptorMap.handleGATTServerEvent(event, gatts_if, param);
} // handleGATTServerEvent
/**
* @brief Send an indication.
* An indication is a transmission of up to the first 20 bytes of the characteristic value. An indication
* will block waiting a positive confirmation from the client.
* @return N/A
*/
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;
}
// Test to see if we have a 0x2902 descriptor. If we do, then check to see if indications are enabled
// and, if not, prevent the indication.
BLE2902 *p2902 = (BLE2902*)getDescriptorByUUID((uint16_t)0x2902);
//////////////////////////////////////////////////////////////////////
if (p2902 == nullptr) {
ESP_LOGD(LOG_TAG, "<< no descriptor; 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;
}
uint16_t numClients = getService()->getServer()->getConnectedCount();
for (int index=0; index < numClients; index++) {
if (p2902->getIndications(index)) {
m_semaphoreConfEvt.take("indicate");
esp_err_t errRc = ::esp_ble_gatts_send_indicate(
getService()->getServer()->getGattsIf(),
index,
getHandle(), length, (uint8_t*)m_value.getValue().data(), true); // The need_confirm = true makes this an indication.
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_send_indicate: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
}
m_semaphoreConfEvt.wait("indicate");
}
}
//////////////////////////////////////////////////////////////////////
// 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); // The need_confirm = true makes this an indication.
// 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");
} // indicate
/**
* @brief Send a notify.
* A notification is a transmission of up to the first 20 bytes of the characteristic value. An notification
* will not block; it is a fire and forget.
* @return N/A.
*/
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;
}
// Test to see if we have a 0x2902 descriptor. If we do, then check to see if notification is enabled
// and, if not, prevent the notification.
BLE2902 *p2902 = (BLE2902*)getDescriptorByUUID((uint16_t)0x2902);
//////////////////////////////////////////////////////////////////////
if (p2902 == nullptr) {
ESP_LOGD(LOG_TAG, "<< no descriptor; 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;
}
uint16_t numClients = getService()->getServer()->getConnectedCount();
for (int index=0; index < numClients; index++) {
ESP_LOGD(LOG_TAG, "<< notify: client=%d flags=%d", index, p2902->getNotifications(index));
if (p2902->getNotifications(index)) {
esp_err_t errRc = ::esp_ble_gatts_send_indicate(
getService()->getServer()->getGattsIf(),
index,
getHandle(), length, (uint8_t*)m_value.getValue().data(), false); // The need_confirm = false makes this a notify.
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_send_indicate: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
}
}
}
//////////////////////////////////////////////////////////////////////
// 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); // The need_confirm = false makes this a notify.
// 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");
} // Notify
/**
* @brief Set the permission to broadcast.
* A characteristics has properties associated with it which define what it is capable of doing.
* One of these is the broadcast flag.
* @param [in] value The flag value of the property.
* @return N/A
*/
void BLECharacteristic::setBroadcastProperty(bool value) {
//ESP_LOGD(LOG_TAG, "setBroadcastProperty(%d)", 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);
}
} // setBroadcastProperty
/** Patch for adding multi client subscriptions to NOTIFY and INDICATE
* Check functions void BLECharacteristic::indicate() and void BLECharacteristic::notify()
* for changes
*/
/**
* @brief Set the callback handlers for this characteristic.
* @param [in] pCallbacks An instance of a callbacks structure used to define any callbacks for the characteristic.
*/
void BLECharacteristic::setCallbacks(BLECharacteristicCallbacks* pCallbacks) {
ESP_LOGD(LOG_TAG, ">> setCallbacks: 0x%x", (uint32_t)pCallbacks);
m_pCallbacks = pCallbacks;
ESP_LOGD(LOG_TAG, "<< setCallbacks");
} // setCallbacks
/**
* @brief Set the BLE handle associated with this characteristic.
* A user program will request that a characteristic be created against a service. When the characteristic has been
* registered, the service will be given a "handle" that it knows the characteristic as. This handle is unique to the
* server/service but it is told to the service, not the characteristic associated with the service. This internally
* exposed function can be invoked by the service against this model of the characteristic to allow the characteristic
* to learn its own handle. Once the characteristic knows its own handle, it will be able to see incoming GATT events
* that will be propagated down to it which contain a handle value and now know that the event is destined for it.
* @param [in] handle The handle associated with this characteristic.
*/
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");
} // setHandle
/**
* @brief Set the Indicate property value.
* @param [in] value Set to true if we are to allow indicate messages.
*/
void BLECharacteristic::setIndicateProperty(bool value) {
//ESP_LOGD(LOG_TAG, "setIndicateProperty(%d)", 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);
}
} // setIndicateProperty
/**
* @brief Set the Notify property value.
* @param [in] value Set to true if we are to allow notification messages.
*/
void BLECharacteristic::setNotifyProperty(bool value) {
//ESP_LOGD(LOG_TAG, "setNotifyProperty(%d)", 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);
}
} // setNotifyProperty
/**
* @brief Set the Read property value.
* @param [in] value Set to true if we are to allow reads.
*/
void BLECharacteristic::setReadProperty(bool value) {
//ESP_LOGD(LOG_TAG, "setReadProperty(%d)", 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);
}
} // setReadProperty
/**
* @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.
*/
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");
} // setValue
/**
* @brief Set the value of the characteristic from string data.
* We set the value of the characteristic from the bytes contained in the
* string.
* @param [in] Set the value of the characteristic.
* @return N/A.
*/
void BLECharacteristic::setValue(std::string value) {
setValue((uint8_t*)(value.data()), value.length());
} // setValue
/**
* @brief Set the Write No Response property value.
* @param [in] value Set to true if we are to allow writes with no response.
*/
void BLECharacteristic::setWriteNoResponseProperty(bool value) {
//ESP_LOGD(LOG_TAG, "setWriteNoResponseProperty(%d)", 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);
}
} // setWriteNoResponseProperty
/**
* @brief Set the Write property value.
* @param [in] value Set to true if we are to allow writes.
*/
void BLECharacteristic::setWriteProperty(bool value) {
//ESP_LOGD(LOG_TAG, "setWriteProperty(%d)", 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);
}
} // setWriteProperty
/**
* @brief Return a string representation of the characteristic.
* @return A string representation of the characteristic.
*/
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();
} // toString
BLECharacteristicCallbacks::~BLECharacteristicCallbacks() {}
/**
* @brief Callback function to support a read request.
* @param [in] pCharacteristic The characteristic that is the source of the event.
*/
void BLECharacteristicCallbacks::onRead(BLECharacteristic *pCharacteristic) {
ESP_LOGD("BLECharacteristicCallbacks", ">> onRead: default");
ESP_LOGD("BLECharacteristicCallbacks", "<< onRead");
} // onRead
/**
* @brief Callback function to support a write request.
* @param [in] pCharacteristic The characteristic that is the source of the event.
*/
void BLECharacteristicCallbacks::onWrite(BLECharacteristic *pCharacteristic) {
ESP_LOGD("BLECharacteristicCallbacks", ">> onWrite: default");
ESP_LOGD("BLECharacteristicCallbacks", "<< onWrite");
} // onWrite
#endif /* CONFIG_BT_ENABLED */
/** Patch for adding multi client subscriptions to NOTIFY and INDICATE
* Check lines 51
* and functions
* uint8_t* BLEDescriptor::getValue(uint16_t connId)
* void BLEDescriptor::handleGATTServerEvent()->case ESP_GATTS_WRITE_EVT:
* void BLEDescriptor::handleGATTServerEvent()->case ESP_GATTS_READ_EVT:
* void BLEDescriptor::setValue(uint8_t* data, size_t length, uint16_t connId)
* void BLEDescriptor::setValue(std::string value, uint16_t connId)
* for changes
*/
/*
* BLEDescriptor.cpp
*
* Created on: Jun 22, 2017
* Author: kolban
*/
#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 "BLEService.h"
#include "BLEDescriptor.h"
#include "BLEUtils.h"
#include "GeneralUtils.h"
#ifdef ARDUINO_ARCH_ESP32
#include "esp32-hal-log.h"
#endif
static const char* LOG_TAG = "BLEDescriptor";
#define NULL_HANDLE (0xffff)
/**
* @brief BLEDescriptor constructor.
*/
BLEDescriptor::BLEDescriptor(const char* uuid) : BLEDescriptor(BLEUUID(uuid)) {
}
/**
* @brief BLEDescriptor constructor.
*/
BLEDescriptor::BLEDescriptor(BLEUUID uuid) {
m_bleUUID = uuid;
m_value.attr_value = (uint8_t *)malloc(ESP_GATT_MAX_ATTR_LEN *10); // Allocate storage for the values (10 values).
m_value.attr_len = 0;
m_value.attr_max_len = ESP_GATT_MAX_ATTR_LEN;
m_handle = NULL_HANDLE;
m_pCharacteristic = nullptr; // No initial characteristic.
m_pCallback = nullptr; // No initial callback.
} // BLEDescriptor
/**
* @brief BLEDescriptor destructor.
*/
BLEDescriptor::~BLEDescriptor() {
free(m_value.attr_value);
} // ~BLEDescriptor
/**
* @brief Execute the creation of the descriptor with the BLE runtime in ESP.
* @param [in] pCharacteristic The characteristic to which to register this descriptor.
*/
void BLEDescriptor::executeCreate(BLECharacteristic* pCharacteristic) {
ESP_LOGD(LOG_TAG, ">> executeCreate(): %s", toString().c_str());
if (m_handle != NULL_HANDLE) {
ESP_LOGE(LOG_TAG, "Descriptor already has a handle.");
return;
}
m_pCharacteristic = pCharacteristic; // Save the characteristic associated with this service.
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_descr(
pCharacteristic->getService()->getHandle(),
getUUID().getNative(),
(esp_gatt_perm_t)(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE),
&m_value,
&control);
if (errRc != ESP_OK) {
ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_add_char_descr: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
return;
}
m_semaphoreCreateEvt.wait("executeCreate");
ESP_LOGD(LOG_TAG, "<< executeCreate");
} // executeCreate
/**
* @brief Get the BLE handle for this descriptor.
* @return The handle for this descriptor.
*/
uint16_t BLEDescriptor::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 BLEDescriptor::getLength() {
return m_value.attr_len;
} // getLength
/**
* @brief Get the UUID of the descriptor.
*/
BLEUUID BLEDescriptor::getUUID() {
return m_bleUUID;
} // getUUID
/**
* @brief Get the value of this descriptor.
* @return A pointer to the value of this descriptor.
*/
uint8_t* BLEDescriptor::getValue(uint16_t connId) {
return (uint8_t*)&m_value.attr_value[connId*2];
} // getValue
/**
* @brief Handle GATT server events for the descripttor.
* @param [in] event
* @param [in] gatts_if
* @param [in] param
*/
void BLEDescriptor::handleGATTServerEvent(
esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t *param) {
ESP_LOGD(LOG_TAG, ">> handleGATTServerEvent: %s",
BLEUtils::gattServerEventTypeToString(event).c_str());
BLEUtils::dumpGattServerEvent(event, gatts_if, param);
switch(event) {
// ESP_GATTS_ADD_CHAR_DESCR_EVT
//
// add_char_descr:
// - esp_gatt_status_t status
// - uint16_t attr_handle
// - uint16_t service_handle
// - esp_bt_uuid_t char_uuid
case ESP_GATTS_ADD_CHAR_DESCR_EVT: {
/*
ESP_LOGD(LOG_TAG, "DEBUG: m_pCharacteristic: %x", (uint32_t)m_pCharacteristic);
ESP_LOGD(LOG_TAG, "DEBUG: m_bleUUID: %s, add_char_descr.char_uuid: %s, equals: %d",
m_bleUUID.toString().c_str(),
BLEUUID(param->add_char_descr.char_uuid).toString().c_str(),
m_bleUUID.equals(BLEUUID(param->add_char_descr.char_uuid)));
ESP_LOGD(LOG_TAG, "DEBUG: service->getHandle: %x, add_char_descr.service_handle: %x",
m_pCharacteristic->getService()->getHandle(), param->add_char_descr.service_handle);
ESP_LOGD(LOG_TAG, "DEBUG: service->lastCharacteristic: %x",
(uint32_t)m_pCharacteristic->getService()->getLastCreatedCharacteristic());
*/
if (m_pCharacteristic != nullptr &&
m_bleUUID.equals(BLEUUID(param->add_char_descr.char_uuid)) &&
m_pCharacteristic->getService()->getHandle() == param->add_char_descr.service_handle &&
m_pCharacteristic == m_pCharacteristic->getService()->getLastCreatedCharacteristic()) {
setHandle(param->add_char_descr.attr_handle);
m_semaphoreCreateEvt.give();
}
break;
} // ESP_GATTS_ADD_CHAR_DESCR_EVT
// ESP_GATTS_WRITE_EVT - A request to write the value of a descriptor has arrived.
//
// write:
// - uint16_t conn_id
// - uint16_t trans_id
// - esp_bd_addr_t bda
// - uint16_t handle
// - uint16_t offset
// - bool need_rsp
// - bool is_prep
// - uint16_t len
// - uint8_t *value
case ESP_GATTS_WRITE_EVT: {
ESP_LOGD(LOG_TAG, "ESP_GATTS_WRITE_EVT: connID=%d m_handle=%d", param->write.conn_id, m_handle);
if (param->write.handle == m_handle) {
setValue(param->write.value, param->write.len, param->write.conn_id); // Set the value of the descriptor.
esp_gatt_rsp_t rsp; // Build a response.
rsp.attr_value.len = getLength();
rsp.attr_value.handle = m_handle;
rsp.attr_value.offset = 0;
rsp.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
memcpy(rsp.attr_value.value, getValue(param->write.conn_id), rsp.attr_value.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) { // Check the return code from the send of the response.
ESP_LOGE(LOG_TAG, "esp_ble_gatts_send_response: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
}
if (m_pCallback != nullptr) { // We have completed the write, if there is a user supplied callback handler, invoke it now.
m_pCallback->onWrite(this); // Invoke the onWrite callback handler.
}
} // End of ... this is our handle.
break;
} // ESP_GATTS_WRITE_EVT
// ESP_GATTS_READ_EVT - A request to read the value of a descriptor has arrived.
//
// read:
// - uint16_t conn_id
// - uint32_t trans_id
// - esp_bd_addr_t bda
// - uint16_t handle
// - uint16_t offset
// - bool is_long
// - bool need_rsp
//
case ESP_GATTS_READ_EVT: {
if (param->read.handle == m_handle) { // If this event is for this descriptor ... process it
if (m_pCallback != nullptr) { // If we have a user supplied callback, invoke it now.
m_pCallback->onRead(this); // Invoke the onRead callback method in the callback handler.
}
if (param->read.need_rsp) { // Do we need a response
ESP_LOGD(LOG_TAG, "Sending a response (esp_ble_gatts_send_response)");
esp_gatt_rsp_t rsp;
rsp.attr_value.len = getLength();
rsp.attr_value.handle = param->read.handle;
rsp.attr_value.offset = 0;
rsp.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
memcpy(rsp.attr_value.value, getValue(param->read.conn_id), rsp.attr_value.len);
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) { // Check the return code from the send of the response.
ESP_LOGE(LOG_TAG, "esp_ble_gatts_send_response: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
}
} // End of need a response.
} // End of this is our handle
break;
} // ESP_GATTS_READ_EVT
default: {
break;
}
}// switch event
} // handleGATTServerEvent
/**
* @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 BLEDescriptor::setCallbacks(BLEDescriptorCallbacks* pCallback) {
ESP_LOGD(LOG_TAG, ">> setCallbacks: 0x%x", (uint32_t)pCallback);
m_pCallback = pCallback;
ESP_LOGD(LOG_TAG, "<< setCallbacks");
} // setCallbacks
/**
* @brief Set the handle of this descriptor.
* Set the handle of this descriptor to be the supplied value.
* @param [in] handle The handle to be associated with this descriptor.
* @return N/A.
*/
void BLEDescriptor::setHandle(uint16_t handle) {
ESP_LOGD(LOG_TAG, ">> setHandle(0x%.2x): Setting descriptor handle to be 0x%.2x", handle, handle);
m_handle = handle;
ESP_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 BLEDescriptor::setValue(uint8_t* data, size_t length, uint16_t connId) {
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.attr_len = length;
memcpy((uint8_t*)&m_value.attr_value[connId*2], data, length);
} // setValue
/**
* @brief Set the value of the descriptor.
* @param [in] value The value of the descriptor in string form.
*/
void BLEDescriptor::setValue(std::string value, uint16_t connId) {
setValue((uint8_t *)value.data(), value.length(), connId);
} // setValue
/**
* @brief Return a string representation of the descriptor.
* @return A string representation of the descriptor.
*/
std::string BLEDescriptor::toString() {
std::stringstream stringstream;
stringstream << std::hex << std::setfill('0');
stringstream << "UUID: " << m_bleUUID.toString() + ", handle: 0x" << std::setw(2) << m_handle;
return stringstream.str();
} // toString
BLEDescriptorCallbacks::~BLEDescriptorCallbacks() {}
/**
* @brief Callback function to support a read request.
* @param [in] pDescriptor The descriptor that is the source of the event.
*/
void BLEDescriptorCallbacks::onRead(BLEDescriptor* pDescriptor) {
ESP_LOGD("BLEDescriptorCallbacks", ">> onRead: default");
ESP_LOGD("BLEDescriptorCallbacks", "<< onRead");
} // onRead
/**
* @brief Callback function to support a write request.
* @param [in] pDescriptor The descriptor that is the source of the event.
*/
void BLEDescriptorCallbacks::onWrite(BLEDescriptor* pDescriptor) {
ESP_LOGD("BLEDescriptorCallbacks", ">> onWrite: default");
ESP_LOGD("BLEDescriptorCallbacks", "<< onWrite");
} // onWrite
#endif /* CONFIG_BT_ENABLED */
/** Patch for adding multi client subscriptions to NOTIFY and INDICATE
* Check lines 37, 43 and 44 for changes
*/
/*
* BLEDescriptor.h
*
* Created on: Jun 22, 2017
* Author: kolban
*/
#ifndef COMPONENTS_CPP_UTILS_BLEDESCRIPTOR_H_
#define COMPONENTS_CPP_UTILS_BLEDESCRIPTOR_H_
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <string>
#include "BLEUUID.h"
#include "BLECharacteristic.h"
#include <esp_gatts_api.h>
#include "FreeRTOS.h"
class BLEService;
class BLECharacteristic;
class BLEDescriptorCallbacks;
/**
* @brief A model of a %BLE descriptor.
*/
class BLEDescriptor {
public:
BLEDescriptor(const char* uuid);
BLEDescriptor(BLEUUID uuid);
virtual ~BLEDescriptor();
uint16_t getHandle();
size_t getLength();
BLEUUID getUUID();
uint8_t* getValue(uint16_t connId);
void handleGATTServerEvent(
esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t* param);
void setCallbacks(BLEDescriptorCallbacks* pCallbacks);
void setValue(uint8_t* data, size_t size, uint16_t connId);
void setValue(std::string value, uint16_t connId);
std::string toString();
private:
friend class BLEDescriptorMap;
friend class BLECharacteristic;
BLEUUID m_bleUUID;
esp_attr_value_t m_value;
uint16_t m_handle;
BLECharacteristic* m_pCharacteristic;
BLEDescriptorCallbacks* m_pCallback;
void executeCreate(BLECharacteristic* pCharacteristic);
void setHandle(uint16_t handle);
FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt");
};
/**
* @brief Callbacks that can be associated with a %BLE descriptors to inform of events.
*
* When a server application creates a %BLE descriptor, we may wish to be informed when there is either
* a read or write request to the descriptors value. An application can register a
* sub-classed instance of this class and will be notified when such an event happens.
*/
class BLEDescriptorCallbacks {
public:
virtual ~BLEDescriptorCallbacks();
virtual void onRead(BLEDescriptor* pDescriptor);
virtual void onWrite(BLEDescriptor* pDescriptor);
};
#endif /* CONFIG_BT_ENABLED */
#endif /* COMPONENTS_CPP_UTILS_BLEDESCRIPTOR_H_ */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment