Last active
April 22, 2020 10:07
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** 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_ */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** 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 */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** 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 */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** 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