summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorkolban <kolban1@kolban.com>2017-09-10 13:41:31 -0500
committerkolban <kolban1@kolban.com>2017-09-10 13:41:31 -0500
commit0eecee13a75bf13fd1752d3d9b6bc60709a265d2 (patch)
tree7d1492d47841ffa8b103cc9e111772af77b35481 /src
parent0.1.0 release (diff)
downloadthermostat-0eecee13a75bf13fd1752d3d9b6bc60709a265d2.tar.gz
thermostat-0eecee13a75bf13fd1752d3d9b6bc60709a265d2.tar.bz2
thermostat-0eecee13a75bf13fd1752d3d9b6bc60709a265d2.zip
0.2.0
Diffstat (limited to 'src')
-rw-r--r--src/BLE2902.cpp69
-rw-r--r--src/BLE2902.h34
-rw-r--r--src/BLEAddress.cpp93
-rw-r--r--src/BLEAddress.h34
-rw-r--r--src/BLEAdvertisedDevice.cpp410
-rw-r--r--src/BLEAdvertisedDevice.h106
-rw-r--r--src/BLEAdvertising.cpp155
-rw-r--r--src/BLEAdvertising.h34
-rw-r--r--src/BLECharacteristic.cpp693
-rw-r--r--src/BLECharacteristic.h132
-rw-r--r--src/BLECharacteristicCallbacks.cpp34
-rw-r--r--src/BLECharacteristicMap.cpp136
-rw-r--r--src/BLEClient.cpp327
-rw-r--r--src/BLEClient.h76
-rw-r--r--src/BLEDescriptor.cpp278
-rw-r--r--src/BLEDescriptor.h54
-rw-r--r--src/BLEDescriptorMap.cpp149
-rw-r--r--src/BLEDevice.cpp222
-rw-r--r--src/BLEDevice.h54
-rw-r--r--src/BLERemoteCharacteristic.cpp336
-rw-r--r--src/BLERemoteCharacteristic.h63
-rw-r--r--src/BLERemoteDescriptor.cpp11
-rw-r--r--src/BLERemoteDescriptor.h20
-rw-r--r--src/BLERemoteService.cpp220
-rw-r--r--src/BLERemoteService.h63
-rw-r--r--src/BLEScan.cpp276
-rw-r--r--src/BLEScan.h67
-rw-r--r--src/BLEServer.cpp353
-rw-r--r--src/BLEServer.h117
-rw-r--r--src/BLEServerCallbacks.cpp22
-rw-r--r--src/BLEService.cpp353
-rw-r--r--src/BLEService.h99
-rw-r--r--src/BLEServiceMap.cpp95
-rw-r--r--src/BLEUUID.cpp326
-rw-r--r--src/BLEUUID.h37
-rw-r--r--src/BLEUtils.cpp1352
-rw-r--r--src/BLEUtils.h60
-rw-r--r--src/BLEValue.cpp116
-rw-r--r--src/BLEValue.h36
-rw-r--r--src/FreeRTOS.cpp165
-rw-r--r--src/FreeRTOS.h49
-rw-r--r--src/GeneralUtils.cpp388
-rw-r--r--src/GeneralUtils.h28
43 files changed, 7742 insertions, 0 deletions
diff --git a/src/BLE2902.cpp b/src/BLE2902.cpp
new file mode 100644
index 0000000..8984da8
--- /dev/null
+++ b/src/BLE2902.cpp
@@ -0,0 +1,69 @@
+/*
+ * 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[2] = {0,0};
+ setValue(data, 2);
+} // BLE2902
+
+
+/**
+ * @brief Get the notifications value.
+ * @return The notifications value. True if notifications are enabled and false if not.
+ */
+bool BLE2902::getNotifications() {
+ return (getValue()[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() {
+ return (getValue()[0] & (1 << 1)) != 0;
+} // getIndications
+
+
+/**
+ * @brief Set the indications flag.
+ * @param [in] flag The indications flag.
+ */
+void BLE2902::setIndications(bool flag) {
+ uint8_t *pValue = getValue();
+ if (flag) {
+ pValue[0] |= 1<<1;
+ } else {
+ pValue[0] &= ~(1<<1);
+ }
+} // setIndications
+
+
+/**
+ * @brief Set the notifications flag.
+ * @param [in] flag The notifications flag.
+ */
+void BLE2902::setNotifications(bool flag) {
+ uint8_t *pValue = getValue();
+ if (flag) {
+ pValue[0] |= 1<<0;
+ } else {
+ pValue[0] &= ~(1<<0);
+ }
+} // setNotifications
+
+
+#endif
diff --git a/src/BLE2902.h b/src/BLE2902.h
new file mode 100644
index 0000000..397360a
--- /dev/null
+++ b/src/BLE2902.h
@@ -0,0 +1,34 @@
+/*
+ * 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();
+ bool getIndications();
+ void setNotifications(bool flag);
+ void setIndications(bool flag);
+
+}; // BLE2902
+
+#endif /* CONFIG_BT_ENABLED */
+#endif /* COMPONENTS_CPP_UTILS_BLE2902_H_ */
diff --git a/src/BLEAddress.cpp b/src/BLEAddress.cpp
new file mode 100644
index 0000000..d2f7f8b
--- /dev/null
+++ b/src/BLEAddress.cpp
@@ -0,0 +1,93 @@
+/*
+ * BLEAddress.cpp
+ *
+ * Created on: Jul 2, 2017
+ * Author: kolban
+ */
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+
+#include "BLEAddress.h"
+#include <string>
+#include <sstream>
+#include <iomanip>
+#include <string.h>
+#include <stdio.h>
+
+
+/**
+ * @brief Create an address from the native ESP32 representation.
+ * @param [in] address The native representation.
+ */
+BLEAddress::BLEAddress(esp_bd_addr_t address) {
+ memcpy(m_address, address, ESP_BD_ADDR_LEN);
+} // BLEAddress
+
+
+/**
+ * @brief Create an address from a hex string
+ *
+ * A hex string is of the format:
+ * ```
+ * 00:00:00:00:00:00
+ * ```
+ * which is 17 characters in length.
+ *
+ * @param [in] stringAddress The hex representation of the address.
+ */
+BLEAddress::BLEAddress(std::string stringAddress) {
+ if (stringAddress.length() != 17) {
+ return;
+ }
+ int data[6];
+ sscanf(stringAddress.c_str(), "%x:%x:%x:%x:%x:%x", &data[0], &data[1], &data[2], &data[3], &data[4], &data[5]);
+ m_address[0] = (uint8_t)data[0];
+ m_address[1] = (uint8_t)data[1];
+ m_address[2] = (uint8_t)data[2];
+ m_address[3] = (uint8_t)data[3];
+ m_address[4] = (uint8_t)data[4];
+ m_address[5] = (uint8_t)data[5];
+} // BLEAddress
+
+
+/**
+ * @brief Determine if this address equals another.
+ * @param [in] otherAddress The other address to compare against.
+ * @return True if the addresses are equal.
+ */
+bool BLEAddress::equals(BLEAddress otherAddress) {
+ return memcmp(otherAddress.getNative(), m_address, 6) == 0;
+} // equals
+
+
+/**
+ * @brief Return the native representation of the address.
+ * @return The native representation of the address.
+ */
+esp_bd_addr_t *BLEAddress::getNative() {
+ return &m_address;
+} // getNative
+
+
+/**
+ * @brief Convert a BLE address to a string.
+ *
+ * A string representation of an address is in the format:
+ *
+ * ```
+ * xx:xx:xx:xx:xx:xx
+ * ```
+ *
+ * @return The string representation of the address.
+ */
+std::string BLEAddress::toString() {
+ std::stringstream stream;
+ stream << std::setfill('0') << std::setw(2) << std::hex << (int)((uint8_t *)(m_address))[0] << ':';
+ stream << std::setfill('0') << std::setw(2) << std::hex << (int)((uint8_t *)(m_address))[1] << ':';
+ stream << std::setfill('0') << std::setw(2) << std::hex << (int)((uint8_t *)(m_address))[2] << ':';
+ stream << std::setfill('0') << std::setw(2) << std::hex << (int)((uint8_t *)(m_address))[3] << ':';
+ stream << std::setfill('0') << std::setw(2) << std::hex << (int)((uint8_t *)(m_address))[4] << ':';
+ stream << std::setfill('0') << std::setw(2) << std::hex << (int)((uint8_t *)(m_address))[5];
+ return stream.str();
+} // toString
+#endif
diff --git a/src/BLEAddress.h b/src/BLEAddress.h
new file mode 100644
index 0000000..7eff4da
--- /dev/null
+++ b/src/BLEAddress.h
@@ -0,0 +1,34 @@
+/*
+ * BLEAddress.h
+ *
+ * Created on: Jul 2, 2017
+ * Author: kolban
+ */
+
+#ifndef COMPONENTS_CPP_UTILS_BLEADDRESS_H_
+#define COMPONENTS_CPP_UTILS_BLEADDRESS_H_
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+#include <esp_gap_ble_api.h> // ESP32 BLE
+#include <string>
+
+
+/**
+ * @brief A %BLE device address.
+ *
+ * Every %BLE device has a unique address which can be used to identify it and form connections.
+ */
+class BLEAddress {
+public:
+ BLEAddress(esp_bd_addr_t address);
+ BLEAddress(std::string stringAddress);
+ bool equals(BLEAddress otherAddress);
+ esp_bd_addr_t* getNative();
+ std::string toString();
+
+private:
+ esp_bd_addr_t m_address;
+};
+
+#endif /* CONFIG_BT_ENABLED */
+#endif /* COMPONENTS_CPP_UTILS_BLEADDRESS_H_ */
diff --git a/src/BLEAdvertisedDevice.cpp b/src/BLEAdvertisedDevice.cpp
new file mode 100644
index 0000000..97ec1e3
--- /dev/null
+++ b/src/BLEAdvertisedDevice.cpp
@@ -0,0 +1,410 @@
+/*
+ * BLEAdvertisedDevice.cpp
+ *
+ * During the scanning procedure, we will be finding advertised BLE devices. This class
+ * models a found device.
+ *
+ *
+ * See also:
+ * https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile
+ *
+ * Created on: Jul 3, 2017
+ * Author: kolban
+ */
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+#include <esp_log.h>
+#include <sstream>
+#include "BLEAdvertisedDevice.h"
+#include "BLEUtils.h"
+static const char* LOG_TAG="BLEAdvertisedDevice";
+
+BLEAdvertisedDevice::BLEAdvertisedDevice() {
+ m_adFlag = 0;
+ m_appearance = 0;
+ m_deviceType = 0;
+ m_manufacturerData = "";
+ m_name = "";
+ m_rssi = -9999;
+ m_txPower = 0;
+ m_pScan = nullptr;
+
+ m_haveAppearance = false;
+ m_haveManufacturerData = false;
+ m_haveName = false;
+ m_haveRSSI = false;
+ m_haveServiceUUID = false;
+ m_haveTXPower = false;
+
+} // BLEAdvertisedDevice
+
+
+/**
+ * @brief Get the address.
+ *
+ * Every %BLE device exposes an address that is used to identify it and subsequently connect to it.
+ * Call this function to obtain the address of the advertised device.
+ *
+ * @return The address of the advertised device.
+ */
+BLEAddress BLEAdvertisedDevice::getAddress() {
+ return m_address;
+} // getAddress
+
+
+/**
+ * @brief Get the appearance.
+ *
+ * A %BLE device can declare its own appearance. The appearance is how it would like to be shown to an end user
+ * typcially in the form of an icon.
+ *
+ * @return The appearance of the advertised device.
+ */
+uint16_t BLEAdvertisedDevice::getApperance() {
+ return m_appearance;
+}
+
+
+/**
+ * @brief Get the manufacturer data.
+ * @return The manufacturer data of the advertised device.
+ */
+std::string BLEAdvertisedDevice::getManufacturerData() {
+ return m_manufacturerData;
+}
+
+
+/**
+ * @brief Get the name.
+ * @return The name of the advertised device.
+ */
+std::string BLEAdvertisedDevice::getName() {
+ return m_name;
+} // getName
+
+
+/**
+ * @brief Get the RSSI.
+ * @return The RSSI of the advertised device.
+ */
+int BLEAdvertisedDevice::getRSSI() {
+ return m_rssi;
+} // getRSSI
+
+
+/**
+ * @brief Get the scan object that created this advertisement.
+ * @return The scan object.
+ */
+BLEScan* BLEAdvertisedDevice::getScan() {
+ return m_pScan;
+} // getScan
+
+
+/**
+ * @brief Get the Service UUID.
+ * @return The Service UUID of the advertised device.
+ */
+BLEUUID BLEAdvertisedDevice::getServiceUUID() {
+ return m_serviceUUID;
+} // getServiceUUID
+
+
+/**
+ * @brief Get the TX Power.
+ * @return The TX Power of the advertised device.
+ */
+int8_t BLEAdvertisedDevice::getTXPower() {
+ return m_txPower;
+} // getTXPower
+
+/**
+ * @brief Does this advertisement have an appearance value?
+ * @return True if there is an appearance value present.
+ */
+bool BLEAdvertisedDevice::haveAppearance() {
+ return m_haveAppearance;
+} // haveAppearance
+
+
+/**
+ * @brief Does this advertisement have manufacturer data?
+ * @return True if there is manufacturer data present.
+ */
+bool BLEAdvertisedDevice::haveManufacturerData() {
+ return m_haveManufacturerData;
+} // haveManufacturerData
+
+
+/**
+ * @brief Does this advertisement have a name value?
+ * @return True if there is a name value present.
+ */
+bool BLEAdvertisedDevice::haveName() {
+ return m_haveName;
+} // haveName
+
+
+/**
+ * @brief Does this advertisement have a signal strength value?
+ * @return True if there is a signal strength value present.
+ */
+bool BLEAdvertisedDevice::haveRSSI() {
+ return m_haveRSSI;
+} // haveRSSI
+
+
+/**
+ * @brief Does this advertisement have a service UUID value?
+ * @return True if there is a service UUID value present.
+ */
+bool BLEAdvertisedDevice::haveServiceUUID() {
+ return m_haveServiceUUID;
+} // haveServiceUUID
+
+
+/**
+ * @brief Does this advertisement have a transmission power value?
+ * @return True if there is a transmission power value present.
+ */
+bool BLEAdvertisedDevice::haveTXPower() {
+ return m_haveTXPower;
+} // haveTXPower
+
+
+/**
+ * @brief Parse the advertising pay load.
+ *
+ * The pay load is a buffer of bytes that is either 31 bytes long or terminated by
+ * a 0 length value. Each entry in the buffer has the format:
+ * [length][type][data...]
+ *
+ * The length does not include itself but does include everything after it until the next record. A record
+ * with a length value of 0 indicates a terminator.
+ *
+ * https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile
+ */
+void BLEAdvertisedDevice::parseAdvertisement(uint8_t* payload) {
+ uint8_t length;
+ uint8_t ad_type;
+ uint8_t sizeConsumed = 0;
+ bool finished = false;
+
+ while(!finished) {
+ length = *payload; // Retrieve the length of the record.
+ payload++; // Skip to type
+ sizeConsumed += 1 + length; // increase the size consumed.
+
+ if (length != 0) { // A length of 0 indicate that we have reached the end.
+ ad_type = *payload;
+ payload++;
+ length--;
+
+ char* pHex = BLEUtils::buildHexData(nullptr, payload, length);
+ ESP_LOGD(LOG_TAG, "Type: 0x%.2x (%s), length: %d, data: %s",
+ ad_type, BLEUtils::advTypeToString(ad_type), length, pHex);
+ free(pHex);
+
+
+
+ switch(ad_type) {
+ case ESP_BLE_AD_TYPE_NAME_CMPL: { // Adv Data Type: 0x09
+ setName(std::string(reinterpret_cast<char*>(payload), length));
+ break;
+ } // ESP_BLE_AD_TYPE_NAME_CMPL
+
+ case ESP_BLE_AD_TYPE_TX_PWR: { // Adv Data Type: 0x0A
+ setTXPower(*payload);
+ break;
+ } // ESP_BLE_AD_TYPE_TX_PWR
+
+ case ESP_BLE_AD_TYPE_APPEARANCE: { // Adv Data Type: 0x19
+ setAppearance(*reinterpret_cast<uint16_t*>(payload));
+ break;
+ } // ESP_BLE_AD_TYPE_APPEARANCE
+
+ case ESP_BLE_AD_TYPE_FLAG: { // Adv Data Type: 0x01
+ setAdFlag(*payload);
+ break;
+ } // ESP_BLE_AD_TYPE_FLAG
+
+ case ESP_BLE_AD_TYPE_16SRV_CMPL: { // Adv Data Type: 0x03
+ setServiceUUID(BLEUUID(*reinterpret_cast<uint16_t*>(payload)));
+ break;
+ } // ESP_BLE_AD_TYPE_16SRV_CMPL
+
+ case ESP_BLE_AD_TYPE_16SRV_PART: { // Adv Data Type: 0x02
+ setServiceUUID(BLEUUID(*reinterpret_cast<uint16_t*>(payload)));
+ break;
+ } // ESP_BLE_AD_TYPE_16SRV_PART
+
+ case ESP_BLE_AD_TYPE_32SRV_CMPL: { // Adv Data Type: 0x05
+ setServiceUUID(BLEUUID(*reinterpret_cast<uint32_t*>(payload)));
+ break;
+ } // ESP_BLE_AD_TYPE_32SRV_CMPL
+
+ case ESP_BLE_AD_TYPE_32SRV_PART: { // Adv Data Type: 0x04
+ setServiceUUID(BLEUUID(*reinterpret_cast<uint32_t*>(payload)));
+ break;
+ } // ESP_BLE_AD_TYPE_32SRV_PART
+
+ case ESP_BLE_AD_TYPE_128SRV_CMPL: { // Adv Data Type: 0x07
+ setServiceUUID(BLEUUID(payload, 16, false));
+ break;
+ } // ESP_BLE_AD_TYPE_128SRV_CMPL
+
+ case ESP_BLE_AD_TYPE_128SRV_PART: { // Adv Data Type: 0x06
+ setServiceUUID(BLEUUID(payload, 16, false));
+ break;
+ } // ESP_BLE_AD_TYPE_128SRV_PART
+
+ // See CSS Part A 1.4 Manufacturer Specific Data
+ case ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE: {
+ setManufacturerData(std::string(reinterpret_cast<char*>(payload), length));
+ break;
+ } // ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE
+
+ default: {
+ ESP_LOGD(LOG_TAG, "Unhandled type");
+ break;
+ }
+ } // switch
+ payload += length;
+ } // Length <> 0
+
+
+ if (sizeConsumed >=31 || length == 0) {
+ finished = true;
+ }
+ } // !finished
+} // parseAdvertisement
+
+
+/**
+ * @brief Set the address of the advertised device.
+ * @param [in] address The address of the advertised device.
+ */
+void BLEAdvertisedDevice::setAddress(BLEAddress address) {
+ m_address = address;
+} // setAddress
+
+
+/**
+ * @brief Set the adFlag for this device.
+ * @param [in] The discovered adFlag.
+ */
+void BLEAdvertisedDevice::setAdFlag(uint8_t adFlag) {
+ m_adFlag = adFlag;
+} // setAdFlag
+
+
+/**
+ * @brief Set the appearance for this device.
+ * @param [in] The discovered appearance.
+ */
+void BLEAdvertisedDevice::setAppearance(uint16_t appearance) {
+ m_appearance = appearance;
+ m_haveAppearance = true;
+ ESP_LOGD(LOG_TAG, "- appearance: %d", m_appearance);
+} // setAppearance
+
+
+/**
+ * @brief Set the manufacturer data for this device.
+ * @param [in] The discovered manufacturer data.
+ */
+void BLEAdvertisedDevice::setManufacturerData(std::string manufacturerData) {
+ m_manufacturerData = manufacturerData;
+ m_haveManufacturerData = true;
+ char* pHex = BLEUtils::buildHexData(nullptr, (uint8_t*)m_manufacturerData.data(), (uint8_t)m_manufacturerData.length());
+ ESP_LOGD(LOG_TAG, "- manufacturer data: %s", pHex);
+ free(pHex);
+} // setManufacturerData
+
+
+/**
+ * @brief Set the name for this device.
+ * @param [in] name The discovered name.
+ */
+void BLEAdvertisedDevice::setName(std::string name) {
+ m_name = name;
+ m_haveName = true;
+ ESP_LOGD(LOG_TAG, "- name: %s", m_name.c_str());
+} // setName
+
+
+/**
+ * @brief Set the RSSI for this device.
+ * @param [in] rssi The discovered RSSI.
+ */
+void BLEAdvertisedDevice::setRSSI(int rssi) {
+ m_rssi = rssi;
+ m_haveRSSI = true;
+ ESP_LOGD(LOG_TAG, "- rssi: %d", m_rssi);
+} // setRSSI
+
+
+/**
+ * @brief Set the Scan that created this advertised device.
+ * @param pScan The Scan that created this advertised device.
+ */
+void BLEAdvertisedDevice::setScan(BLEScan* pScan) {
+ m_pScan = pScan;
+} // setScan
+
+/**
+ * @brief Set the Service UUID for this device.
+ * @param [in] serviceUUID The discovered serviceUUID
+ */
+void BLEAdvertisedDevice::setServiceUUID(const char* serviceUUID) {
+ return setServiceUUID(BLEUUID(serviceUUID));
+} // setRSSI
+
+/**
+ * @brief Set the Service UUID for this device.
+ * @param [in] serviceUUID The discovered serviceUUID
+ */
+void BLEAdvertisedDevice::setServiceUUID(BLEUUID serviceUUID) {
+ m_serviceUUID = serviceUUID;
+ m_haveServiceUUID = true;
+ ESP_LOGD(LOG_TAG, "- serviceUUID: %s", serviceUUID.toString().c_str());
+} // setRSSI
+
+
+/**
+ * @brief Set the power level for this device.
+ * @param [in] txPower The discovered power level.
+ */
+void BLEAdvertisedDevice::setTXPower(int8_t txPower) {
+ m_txPower = txPower;
+ m_haveTXPower = true;
+ ESP_LOGD(LOG_TAG, "- txPower: %d", m_txPower);
+} // setTXPower
+
+
+/**
+ * @brief Create a string representation of this device.
+ * @return A string representation of this device.
+ */
+std::string BLEAdvertisedDevice::toString() {
+ std::stringstream ss;
+ ss << "Name: " << getName() << ", Address: " << getAddress().toString();
+ if (haveAppearance()) {
+ ss << ", appearance: " << getApperance();
+ }
+ if (haveManufacturerData()) {
+ char *pHex = BLEUtils::buildHexData(nullptr, (uint8_t*)getManufacturerData().data(), getManufacturerData().length());
+ ss << ", manufacturer data: " << pHex;
+ free(pHex);
+ }
+ if (haveServiceUUID()) {
+ ss << ", serviceUUID: " << getServiceUUID().toString();
+ }
+ if (haveTXPower()) {
+ ss << ", txPower: " << (int)getTXPower();
+ }
+ return ss.str();
+} // toString
+
+#endif /* CONFIG_BT_ENABLED */
+
diff --git a/src/BLEAdvertisedDevice.h b/src/BLEAdvertisedDevice.h
new file mode 100644
index 0000000..2fb2652
--- /dev/null
+++ b/src/BLEAdvertisedDevice.h
@@ -0,0 +1,106 @@
+/*
+ * BLEAdvertisedDevice.h
+ *
+ * Created on: Jul 3, 2017
+ * Author: kolban
+ */
+
+#ifndef COMPONENTS_CPP_UTILS_BLEADVERTISEDDEVICE_H_
+#define COMPONENTS_CPP_UTILS_BLEADVERTISEDDEVICE_H_
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+#include <esp_gattc_api.h>
+
+#include <map>
+
+#include "BLEAddress.h"
+#include "BLEScan.h"
+#include "BLEUUID.h"
+
+
+class BLEScan;
+/**
+ * @brief A representation of a %BLE advertised device found by a scan.
+ *
+ * When we perform a %BLE scan, the result will be a set of devices that are advertising. This
+ * class provides a model of a detected device.
+ */
+class BLEAdvertisedDevice {
+public:
+ BLEAdvertisedDevice();
+
+ BLEAddress getAddress();
+ uint16_t getApperance();
+ std::string getManufacturerData();
+ std::string getName();
+ int getRSSI();
+ BLEScan* getScan();
+ BLEUUID getServiceUUID();
+ int8_t getTXPower();
+
+ bool haveAppearance();
+ bool haveManufacturerData();
+ bool haveName();
+ bool haveRSSI();
+ bool haveServiceUUID();
+ bool haveTXPower();
+
+ std::string toString();
+
+private:
+ friend class BLEScan;
+
+ void parseAdvertisement(uint8_t* payload);
+ void setAddress(BLEAddress address);
+ void setAdFlag(uint8_t adFlag);
+ void setAdvertizementResult(uint8_t* payload);
+ void setAppearance(uint16_t appearance);
+ void setManufacturerData(std::string manufacturerData);
+ void setName(std::string name);
+ void setRSSI(int rssi);
+ void setScan(BLEScan* pScan);
+ void setServiceUUID(const char* serviceUUID);
+ void setServiceUUID(BLEUUID serviceUUID);
+ void setTXPower(int8_t txPower);
+
+ bool m_haveAppearance;
+ bool m_haveManufacturerData;
+ bool m_haveName;
+ bool m_haveRSSI;
+ bool m_haveServiceUUID;
+ bool m_haveTXPower;
+
+
+ BLEAddress m_address = BLEAddress((uint8_t*)"\0\0\0\0\0\0");
+ uint8_t m_adFlag;
+ uint16_t m_appearance;
+ int m_deviceType;
+ std::string m_manufacturerData;
+ std::string m_name;
+ BLEScan* m_pScan;
+ int m_rssi;
+ BLEUUID m_serviceUUID;
+ int8_t m_txPower;
+};
+
+/**
+ * @brief A callback handler for callbacks associated device scanning.
+ *
+ * When we are performing a scan as a %BLE client, we may wish to know when a new device that is advertising
+ * has been found. This class can be sub-classed and registered such that when a scan is performed and
+ * a new advertised device has been found, we will be called back to be notified.
+ */
+class BLEAdvertisedDeviceCallbacks {
+public:
+ virtual ~BLEAdvertisedDeviceCallbacks() {}
+ /**
+ * @brief Called when a new scan result is detected.
+ *
+ * As we are scanning, we will find new devices. When found, this call back is invoked with a reference to the
+ * device that was found. During any individual scan, a device will only be detected one time.
+ */
+ virtual void onResult(BLEAdvertisedDevice advertisedDevice) = 0;
+};
+
+#endif /* CONFIG_BT_ENABLED */
+#endif /* COMPONENTS_CPP_UTILS_BLEADVERTISEDDEVICE_H_ */
diff --git a/src/BLEAdvertising.cpp b/src/BLEAdvertising.cpp
new file mode 100644
index 0000000..ad076dd
--- /dev/null
+++ b/src/BLEAdvertising.cpp
@@ -0,0 +1,155 @@
+/*
+ * BLEAdvertising.cpp
+ *
+ * This class encapsulates advertising a BLE Server.
+ * Created on: Jun 21, 2017
+ * Author: kolban
+ */
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+#include "BLEAdvertising.h"
+#include <esp_log.h>
+#include <esp_err.h>
+#include "BLEUtils.h"
+#include "GeneralUtils.h"
+
+static const char* LOG_TAG = "BLEAdvertising";
+
+
+/**
+ * @brief Construct a default advertising object.
+ *
+ */
+BLEAdvertising::BLEAdvertising() {
+ m_advData.set_scan_rsp = false;
+ m_advData.include_name = true;
+ m_advData.include_txpower = true;
+ m_advData.min_interval = 0x20;
+ m_advData.max_interval = 0x40;
+ m_advData.appearance = 0x00;
+ m_advData.manufacturer_len = 0;
+ m_advData.p_manufacturer_data = nullptr;
+ m_advData.service_data_len = 0;
+ m_advData.p_service_data = nullptr;
+ m_advData.service_uuid_len = 0;
+ m_advData.p_service_uuid = nullptr;
+ m_advData.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT);
+
+ m_advParams.adv_int_min = 0x20;
+ m_advParams.adv_int_max = 0x40;
+ m_advParams.adv_type = ADV_TYPE_IND;
+ m_advParams.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
+ m_advParams.channel_map = ADV_CHNL_ALL;
+ m_advParams.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY;
+} // BLEAdvertising
+
+
+/**
+ * @brief Set the device appearance in the advertising data.
+ * The appearance attribute is of type 0x19. The codes for distinct appearances can be found here:
+ * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.gap.appearance.xml.
+ * @param [in] appearance The appearance of the device in the advertising data.
+ * @return N/A.
+ */
+void BLEAdvertising::setAppearance(uint16_t appearance) {
+ m_advData.appearance = appearance;
+} // setAppearance
+
+
+/**
+ * @brief Set the service UUID.
+ * We maintain a class member called m_advData (esp_ble_adv_data_t) that is passed to the
+ * ESP-IDF advertising functions. In this method, we see two fields within that structure
+ * namely service_uuid_len and p_service_uuid to be the information supplied in the passed
+ * in service uuid.
+ * @param [in] uuid The UUID of the service.
+ * @return N/A.
+ */
+void BLEAdvertising::setServiceUUID(const char* serviceUUID) {
+ return setServiceUUID(BLEUUID(serviceUUID));
+}
+/**
+ * @brief Set the service UUID.
+ * We maintain a class member called m_advData (esp_ble_adv_data_t) that is passed to the
+ * ESP-IDF advertising functions. In this method, we see two fields within that structure
+ * namely service_uuid_len and p_service_uuid to be the information supplied in the passed
+ * in service uuid.
+ * @param [in] uuid The UUID of the service.
+ * @return N/A.
+ */
+void BLEAdvertising::setServiceUUID(BLEUUID serviceUUID) {
+ ESP_LOGD(LOG_TAG, ">> setServiceUUID - %s", serviceUUID.toString().c_str());
+ m_serviceUUID = serviceUUID; // Save the new service UUID
+ esp_bt_uuid_t* espUUID = m_serviceUUID.getNative();
+ switch(espUUID->len) {
+ case ESP_UUID_LEN_16: {
+ m_advData.service_uuid_len = 2;
+ m_advData.p_service_uuid = reinterpret_cast<uint8_t*>(&espUUID->uuid.uuid16);
+ break;
+ }
+ case ESP_UUID_LEN_32: {
+ m_advData.service_uuid_len = 4;
+ m_advData.p_service_uuid = reinterpret_cast<uint8_t*>(&espUUID->uuid.uuid32);
+ break;
+ }
+ case ESP_UUID_LEN_128: {
+ m_advData.service_uuid_len = 16;
+ m_advData.p_service_uuid = reinterpret_cast<uint8_t*>(&espUUID->uuid.uuid128);
+ break;
+ }
+ } // switch
+ ESP_LOGD(LOG_TAG, "<< setServiceUUID");
+} // setServiceUUID
+
+
+/**
+ * @brief Start advertising.
+ * Start advertising.
+ * @return N/A.
+ */
+void BLEAdvertising::start() {
+ ESP_LOGD(LOG_TAG, ">> start");
+
+ if (m_advData.service_uuid_len > 0) {
+ uint8_t hexData[16*2+1];
+ BLEUtils::buildHexData(hexData, m_advData.p_service_uuid, m_advData.service_uuid_len);
+ ESP_LOGD(LOG_TAG, " - Service: service_uuid_len=%d, p_service_uuid=0x%x (data=%s)",
+ m_advData.service_uuid_len,
+ (uint32_t)m_advData.p_service_uuid,
+ (m_advData.service_uuid_len > 0?(char *)hexData:"N/A")
+ );
+ } // We have a service to advertise
+
+
+ // Set the configuration for advertising.
+ esp_err_t errRc = ::esp_ble_gap_config_adv_data(&m_advData);
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "<< esp_ble_gap_config_adv_data: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ return;
+ }
+
+ // Start advertising.
+ errRc = ::esp_ble_gap_start_advertising(&m_advParams);
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "<< esp_ble_gap_start_advertising: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ return;
+ }
+ ESP_LOGD(LOG_TAG, "<< start");
+} // start
+
+
+/**
+ * @brief Stop advertising.
+ * Stop advertising.
+ * @return N/A.
+ */
+void BLEAdvertising::stop() {
+ ESP_LOGD(LOG_TAG, ">> stop");
+ esp_err_t errRc = ::esp_ble_gap_stop_advertising();
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_ble_gap_stop_advertising: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ return;
+ }
+ ESP_LOGD(LOG_TAG, "<< stop");
+} // stop
+#endif /* CONFIG_BT_ENABLED */
diff --git a/src/BLEAdvertising.h b/src/BLEAdvertising.h
new file mode 100644
index 0000000..2d0b51c
--- /dev/null
+++ b/src/BLEAdvertising.h
@@ -0,0 +1,34 @@
+/*
+ * BLEAdvertising.h
+ *
+ * Created on: Jun 21, 2017
+ * Author: kolban
+ */
+
+#ifndef COMPONENTS_CPP_UTILS_BLEADVERTISING_H_
+#define COMPONENTS_CPP_UTILS_BLEADVERTISING_H_
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+#include <esp_gap_ble_api.h>
+#include "BLEUUID.h"
+
+/**
+ * @brief Perform and manage %BLE advertising.
+ *
+ * A %BLE server will want to perform advertising in order to make itself known to %BLE clients.
+ */
+class BLEAdvertising {
+public:
+ BLEAdvertising();
+ void start();
+ void stop();
+ void setAppearance(uint16_t appearance);
+ void setServiceUUID(const char* serviceUUID);
+ void setServiceUUID(BLEUUID serviceUUID);
+private:
+ esp_ble_adv_data_t m_advData;
+ esp_ble_adv_params_t m_advParams;
+ BLEUUID m_serviceUUID;
+};
+#endif /* CONFIG_BT_ENABLED */
+#endif /* COMPONENTS_CPP_UTILS_BLEADVERTISING_H_ */
diff --git a/src/BLECharacteristic.cpp b/src/BLECharacteristic.cpp
new file mode 100644
index 0000000..f5b8200
--- /dev/null
+++ b/src/BLECharacteristic.cpp
@@ -0,0 +1,693 @@
+/*
+ * 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"
+
+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");
+
+ std::string strValue = m_value.getValue();
+
+ esp_attr_value_t value;
+ value.attr_len = strValue.length();
+ value.attr_max_len = ESP_GATT_MAX_ATTR_LEN;
+ value.attr_value = (uint8_t*)strValue.data();
+
+ 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,
+ &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) {
+ 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)) &&
+ 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.
+ BLEDescriptor *pDescriptor = m_descriptorMap.getFirst();
+ while(pDescriptor != nullptr) {
+ pDescriptor->handleGATTServerEvent(event, gatts_if, param);
+ pDescriptor = m_descriptorMap.getNext();
+ }
+
+} // 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 && !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 && !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
+
+
+/**
+ * @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) {
+ m_pCallbacks = pCallbacks;
+} // 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
+
+#endif /* CONFIG_BT_ENABLED */
diff --git a/src/BLECharacteristic.h b/src/BLECharacteristic.h
new file mode 100644
index 0000000..b39c022
--- /dev/null
+++ b/src/BLECharacteristic.h
@@ -0,0 +1,132 @@
+/*
+ * BLECharacteristic.h
+ *
+ * Created on: Jun 22, 2017
+ * Author: kolban
+ */
+
+#ifndef COMPONENTS_CPP_UTILS_BLECHARACTERISTIC_H_
+#define COMPONENTS_CPP_UTILS_BLECHARACTERISTIC_H_
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+#include <string>
+#include <map>
+#include "BLEUUID.h"
+#include <esp_gatts_api.h>
+#include "BLEDescriptor.h"
+#include "BLEValue.h"
+#include "FreeRTOS.h"
+
+class BLEService;
+class BLEDescriptor;
+class BLECharacteristicCallbacks;
+
+/**
+ * @brief A management structure for %BLE descriptors.
+ */
+class BLEDescriptorMap {
+public:
+ void setByUUID(const char* uuid, BLEDescriptor *pDescriptor);
+ void setByUUID(BLEUUID uuid, BLEDescriptor *pDescriptor);
+ void setByHandle(uint16_t handle, BLEDescriptor *pDescriptor);
+ BLEDescriptor *getByUUID(const char* uuid);
+ BLEDescriptor *getByUUID(BLEUUID uuid);
+ BLEDescriptor *getByHandle(uint16_t handle);
+ std::string toString();
+ void handleGATTServerEvent(
+ esp_gatts_cb_event_t event,
+ esp_gatt_if_t gatts_if,
+ esp_ble_gatts_cb_param_t *param);
+ BLEDescriptor *getFirst();
+ BLEDescriptor *getNext();
+private:
+ std::map<std::string, BLEDescriptor *> m_uuidMap;
+ std::map<uint16_t, BLEDescriptor *> m_handleMap;
+ std::map<std::string, BLEDescriptor *>::iterator m_iterator;
+};
+
+
+/**
+ * @brief The model of a %BLE Characteristic.
+ *
+ * A %BLE Characteristic is an identified value container that manages a value. It is exposed by a %BLE server and
+ * can be read and written to by a %BLE client.
+ */
+class BLECharacteristic {
+public:
+ BLECharacteristic(const char* uuid, uint32_t properties = 0);
+ BLECharacteristic(BLEUUID uuid, uint32_t properties = 0);
+ virtual ~BLECharacteristic();
+
+ void addDescriptor(BLEDescriptor* pDescriptor);
+ BLEDescriptor* getDescriptorByUUID(const char* descriptorUUID);
+ BLEDescriptor* getDescriptorByUUID(BLEUUID descriptorUUID);
+ //size_t getLength();
+ BLEUUID getUUID();
+ std::string getValue();
+
+ void indicate();
+ void notify();
+ void setBroadcastProperty(bool value);
+ void setCallbacks(BLECharacteristicCallbacks* pCallbacks);
+ void setIndicateProperty(bool value);
+ void setNotifyProperty(bool value);
+ void setReadProperty(bool value);
+ void setValue(uint8_t* data, size_t size);
+ void setValue(std::string value);
+ void setWriteProperty(bool value);
+ void setWriteNoResponseProperty(bool value);
+ std::string toString();
+
+
+ static const uint32_t PROPERTY_READ = 1<<0;
+ static const uint32_t PROPERTY_WRITE = 1<<1;
+ static const uint32_t PROPERTY_NOTIFY = 1<<2;
+ static const uint32_t PROPERTY_BROADCAST = 1<<3;
+ static const uint32_t PROPERTY_INDICATE = 1<<4;
+ static const uint32_t PROPERTY_WRITE_NR = 1<<5;
+
+private:
+ friend class BLEServer;
+ friend class BLEService;
+ friend class BLEDescriptor;
+ friend class BLECharacteristicMap;
+
+ BLEUUID m_bleUUID;
+ BLEDescriptorMap m_descriptorMap;
+ uint16_t m_handle;
+ esp_gatt_char_prop_t m_properties;
+ BLECharacteristicCallbacks* m_pCallbacks;
+ BLEService* m_pService;
+ BLEValue m_value;
+
+ void handleGATTServerEvent(
+ esp_gatts_cb_event_t event,
+ esp_gatt_if_t gatts_if,
+ esp_ble_gatts_cb_param_t* param);
+
+ void executeCreate(BLEService* pService);
+ uint16_t getHandle();
+ esp_gatt_char_prop_t getProperties();
+ BLEService* getService();
+ void setHandle(uint16_t handle);
+ FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt");
+ FreeRTOS::Semaphore m_semaphoreConfEvt = FreeRTOS::Semaphore("ConfEvt");
+}; // BLECharacteristic
+
+
+/**
+ * @brief Callbacks that can be associated with a %BLE characteristic to inform of events.
+ *
+ * When a server application creates a %BLE characteristic, we may wish to be informed when there is either
+ * a read or write request to the characteristic's value. An application can register a
+ * sub-classed instance of this class and will be notified when such an event happens.
+ */
+class BLECharacteristicCallbacks {
+public:
+ virtual ~BLECharacteristicCallbacks();
+ virtual void onRead(BLECharacteristic* pCharacteristic);
+ virtual void onWrite(BLECharacteristic* pCharacteristic);
+};
+#endif /* CONFIG_BT_ENABLED */
+#endif /* COMPONENTS_CPP_UTILS_BLECHARACTERISTIC_H_ */
diff --git a/src/BLECharacteristicCallbacks.cpp b/src/BLECharacteristicCallbacks.cpp
new file mode 100644
index 0000000..b733865
--- /dev/null
+++ b/src/BLECharacteristicCallbacks.cpp
@@ -0,0 +1,34 @@
+/*
+ * BLECharacteristicCallbacks.cpp
+ *
+ * Created on: Jul 2, 2017
+ * Author: kolban
+ */
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+#include "BLECharacteristic.h"
+#include <esp_log.h>
+static const char* LOG_TAG = "BLECharacteristicCallbacks";
+
+
+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(LOG_TAG, ">> onRead: default");
+ ESP_LOGD(LOG_TAG, "<< 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(LOG_TAG, ">> onWrite: default");
+ ESP_LOGD(LOG_TAG, "<< onWrite");
+} // onWrite
+#endif /* CONFIG_BT_ENABLED */
diff --git a/src/BLECharacteristicMap.cpp b/src/BLECharacteristicMap.cpp
new file mode 100644
index 0000000..f475e83
--- /dev/null
+++ b/src/BLECharacteristicMap.cpp
@@ -0,0 +1,136 @@
+/*
+ * BLECharacteristicMap.cpp
+ *
+ * Created on: Jun 22, 2017
+ * Author: kolban
+ */
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+#include <sstream>
+#include <iomanip>
+#include "BLEService.h"
+
+/**
+ * @brief Return the characteristic by UUID.
+ * @param [in] UUID The UUID to look up the characteristic.
+ * @return The characteristic.
+ */
+BLECharacteristic* BLECharacteristicMap::getByUUID(const char* uuid) {
+ return getByUUID(BLEUUID(uuid));
+}
+
+/**
+ * @brief Return the characteristic by UUID.
+ * @param [in] UUID The UUID to look up the characteristic.
+ * @return The characteristic.
+ */
+BLECharacteristic* BLECharacteristicMap::getByUUID(BLEUUID uuid) {
+ for (auto &myPair : m_uuidMap) {
+ if (myPair.second->getUUID().equals(uuid)) {
+ return myPair.second;
+ }
+ }
+ //return m_uuidMap.at(uuid.toString());
+ return nullptr;
+} // getByUUID
+
+
+/**
+ * @brief Return the characteristic by handle.
+ * @param [in] handle The handle to look up the characteristic.
+ * @return The characteristic.
+ */
+BLECharacteristic* BLECharacteristicMap::getByHandle(uint16_t handle) {
+ return m_handleMap.at(handle);
+} // getByHandle
+
+
+/**
+ * @brief Set the characteristic by UUID.
+ * @param [in] uuid The uuid of the characteristic.
+ * @param [in] characteristic The characteristic to cache.
+ * @return N/A.
+ */
+void BLECharacteristicMap::setByUUID(
+ BLEUUID uuid,
+ BLECharacteristic *pCharacteristic) {
+ m_uuidMap.insert(std::pair<std::string, BLECharacteristic *>(uuid.toString(), pCharacteristic));
+} // setByUUID
+
+
+/**
+ * @brief Set the characteristic by handle.
+ * @param [in] handle The handle of the characteristic.
+ * @param [in] characteristic The characteristic to cache.
+ * @return N/A.
+ */
+void BLECharacteristicMap::setByHandle(uint16_t handle,
+ BLECharacteristic *characteristic) {
+ m_handleMap.insert(std::pair<uint16_t, BLECharacteristic *>(handle, characteristic));
+} // setByHandle
+
+
+/**
+ * @brief Return a string representation of the characteristic map.
+ * @return A string representation of the characteristic map.
+ */
+std::string BLECharacteristicMap::toString() {
+ std::stringstream stringStream;
+ stringStream << std::hex << std::setfill('0');
+ int count=0;
+ for (auto &myPair: m_uuidMap) {
+ if (count > 0) {
+ stringStream << "\n";
+ }
+ count++;
+ stringStream << "handle: 0x" << std::setw(2) << myPair.second->getHandle() << ", uuid: " + myPair.second->getUUID().toString();
+ }
+ return stringStream.str();
+} // toString
+
+
+/**
+ * @breif Pass the GATT server event onwards to each of the characteristics found in the mapping
+ * @param [in] event
+ * @param [in] gatts_if
+ * @param [in] param
+ */
+void BLECharacteristicMap::handleGATTServerEvent(
+ esp_gatts_cb_event_t event,
+ esp_gatt_if_t gatts_if,
+ esp_ble_gatts_cb_param_t *param) {
+ // Invoke the handler for every Service we have.
+ for (auto &myPair : m_uuidMap) {
+ myPair.second->handleGATTServerEvent(event, gatts_if, param);
+ }
+} // handleGATTServerEvent
+
+
+/**
+ * @brief Get the first characteristic in the map.
+ * @return The first characteristic in the map.
+ */
+BLECharacteristic* BLECharacteristicMap::getFirst() {
+ m_iterator = m_uuidMap.begin();
+ if (m_iterator == m_uuidMap.end()) {
+ return nullptr;
+ }
+ BLECharacteristic *pRet = m_iterator->second;
+ m_iterator++;
+ return pRet;
+} // getFirst
+
+
+/**
+ * @brief Get the next characteristic in the map.
+ * @return The next characteristic in the map.
+ */
+BLECharacteristic* BLECharacteristicMap::getNext() {
+ if (m_iterator == m_uuidMap.end()) {
+ return nullptr;
+ }
+ BLECharacteristic *pRet = m_iterator->second;
+ m_iterator++;
+ return pRet;
+} // getNext
+#endif /* CONFIG_BT_ENABLED */
diff --git a/src/BLEClient.cpp b/src/BLEClient.cpp
new file mode 100644
index 0000000..a5d60cf
--- /dev/null
+++ b/src/BLEClient.cpp
@@ -0,0 +1,327 @@
+/*
+ * BLEDevice.cpp
+ *
+ * Created on: Mar 22, 2017
+ * Author: kolban
+ */
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+#include <esp_log.h>
+#include <bt.h>
+#include <esp_bt_main.h>
+#include <esp_gap_ble_api.h>
+#include <esp_gattc_api.h>
+#include "BLEClient.h"
+#include "BLEUtils.h"
+#include "BLEService.h"
+#include "GeneralUtils.h"
+#include <string>
+#include <sstream>
+#include <unordered_set>
+
+/*
+ * Design
+ * ------
+ * When we perform a searchService() requests, we are asking the BLE server to return each of the services
+ * that it exposes. For each service, we received an ESP_GATTC_SEARCH_RES_EVT event which contains details
+ * of the exposed service including its UUID.
+ *
+ * The objects we will invent for a BLEClient will be as follows:
+ * * BLERemoteService - A model of a remote service.
+ * * BLERemoteCharacteristic - A model of a remote characteristic
+ * * BLERemoteDescriptor - A model of a remote descriptor.
+ *
+ * Since there is a hierarchical relationship here, we will have the idea that from a BLERemoteService will own
+ * zero or more remote characteristics and a BLERemoteCharacteristic will own zero or more remote BLEDescriptors.
+ *
+ * We will assume that a BLERemoteService contains a map that maps BLEUUIDs to the set of owned characteristics
+ * and that a BLECharacteristic contains a map that maps BLEUUIDs to the set of owned descriptors.
+ *
+ *
+ */
+static const char* LOG_TAG = "BLEClient";
+
+BLEClient::BLEClient() {
+ m_pClientCallbacks = nullptr;
+ m_conn_id = 0;
+ m_gattc_if = 0;
+ m_haveServices = false;
+} // BLEClient
+
+/**
+ * @brief Connect to the partner.
+ * @param [in] address The address of the partner.
+ */
+bool BLEClient::connect(BLEAddress address) {
+ ESP_LOGD(LOG_TAG, ">> connect(%s)", address.toString().c_str());
+// We need the connection handle that we get from registering the application. We register the app
+// and then block on its completion. When the event has arrived, we will have the handle.
+ m_semaphoreRegEvt.take("connect");
+ esp_err_t errRc = esp_ble_gattc_app_register(0);
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_ble_gattc_app_register: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ return false;
+ }
+ m_semaphoreRegEvt.wait("connect");
+
+ m_peerAddress = address;
+
+ m_semaphoreOpenEvt.take("connect");
+ errRc = ::esp_ble_gattc_open(
+ getGattcIf(),
+ *getPeerAddress().getNative(), // address
+ 1 // direct connection
+ );
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_ble_gattc_open: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ return false;
+ }
+
+ uint32_t rc = m_semaphoreOpenEvt.wait("connect");
+ ESP_LOGD(LOG_TAG, "<< connect(), rc=%d", rc==ESP_GATT_OK);
+ return rc == ESP_GATT_OK;
+} // connect
+
+
+/**
+ * @brief Disconnect from the peer.
+ * @return N/A.
+ */
+void BLEClient::disconnect() {
+ ESP_LOGD(LOG_TAG, ">> disconnect()");
+ esp_err_t errRc = ::esp_ble_gattc_close(getGattcIf(), getConnId());
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_ble_gattc_close: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ return;
+ }
+ m_peerAddress = BLEAddress("00:00:00:00:00:00");
+ ESP_LOGD(LOG_TAG, "<< disconnect()");
+} // disconnect
+
+
+/**
+ * @brief Handle GATT Client events
+ */
+void BLEClient::gattClientEventHandler(
+ esp_gattc_cb_event_t event,
+ esp_gatt_if_t gattc_if,
+ esp_ble_gattc_cb_param_t* evtParam) {
+
+ // Execute handler code based on the type of event received.
+ switch(event) {
+ //
+ // ESP_GATTC_NOTIFY_EVT
+ //
+ // notify
+ // uint16_t conn_id
+ // esp_bd_addr_t remote_bda
+ // esp_gatt_srvc_id_t srvc_id
+ // esp_gatt_id_t char_id
+ // esp_gatt_id_t descr_id
+ // uint16_t value_len
+ // uint8_t* value
+ // bool is_notify
+ //
+ case ESP_GATTC_NOTIFY_EVT: {
+ BLERemoteService *pBLERemoteService = getService(BLEUUID(evtParam->notify.srvc_id.id.uuid));
+ if (pBLERemoteService == nullptr) {
+ ESP_LOGE(LOG_TAG, "Could not find service with UUID %s for notification", BLEUUID(evtParam->notify.srvc_id.id.uuid).toString().c_str());
+ break;
+ }
+ BLERemoteCharacteristic* pBLERemoteCharacteristic = pBLERemoteService->getCharacteristic(BLEUUID(evtParam->notify.char_id.uuid));
+ if (pBLERemoteCharacteristic == nullptr) {
+ ESP_LOGE(LOG_TAG, "Could not find characteristic with UUID %s for notification", BLEUUID(evtParam->notify.char_id.uuid).toString().c_str());
+ break;
+ }
+ if (pBLERemoteCharacteristic->m_notifyCallback != nullptr) {
+ pBLERemoteCharacteristic->m_notifyCallback(pBLERemoteCharacteristic, evtParam->notify.value, evtParam->notify.value_len, evtParam->notify.is_notify);
+ }
+ break;
+ } // ESP_GATTC_NOTIFY_EVT
+
+ //
+ // ESP_GATTC_OPEN_EVT
+ //
+ // open:
+ // - esp_gatt_status_t status
+ // - uint16_t conn_id
+ // - esp_bd_addr_t remote_bda
+ // - uint16_t mtu
+ //
+ case ESP_GATTC_OPEN_EVT: {
+ m_conn_id = evtParam->open.conn_id;
+ if (m_pClientCallbacks != nullptr) {
+ m_pClientCallbacks->onConnect(this);
+ }
+ m_semaphoreOpenEvt.give();
+ break;
+ } // ESP_GATTC_OPEN_EVT
+
+
+ //
+ // ESP_GATTC_REG_EVT
+ //
+ // reg:
+ // esp_gatt_status_t status
+ // uint16_t app_id
+ //
+ case ESP_GATTC_REG_EVT: {
+ m_gattc_if = gattc_if;
+ m_semaphoreRegEvt.give();
+ break;
+ } // ESP_GATTC_REG_EVT
+
+
+ //
+ // ESP_GATTC_SEARCH_CMPL_EVT
+ //
+ // search_cmpl:
+ // - esp_gatt_status_t status
+ // - uint16_t conn_id
+ //
+ case ESP_GATTC_SEARCH_CMPL_EVT: {
+ m_semaphoreSearchCmplEvt.give();
+ break;
+ } // ESP_GATTC_SEARCH_CMPL_EVT
+
+
+ //
+ // ESP_GATTC_SEARCH_RES_EVT
+ //
+ // search_res:
+ // - uint16_t conn_id
+ // - esp_gatt_srvc_id_t srvc_id
+ //
+ case ESP_GATTC_SEARCH_RES_EVT: {
+ BLEUUID uuid = BLEUUID(evtParam->search_res.srvc_id);
+ BLERemoteService* pRemoteService = new BLERemoteService(evtParam->search_res.srvc_id, this);
+ m_servicesMap.insert(std::pair<std::string, BLERemoteService *>(uuid.toString(), pRemoteService));
+ break;
+ } // ESP_GATTC_SEARCH_RES_EVT
+
+
+ default: {
+ break;
+ }
+ } // Switch
+
+ for (auto &myPair : m_servicesMap) {
+ myPair.second->gattClientEventHandler(event, gattc_if, evtParam);
+ }
+
+} // gattClientEventHandler
+
+
+/**
+ * @brief Retrieve the address of the peer.
+ *
+ * Returns the address of the %BLE peer to which this client is connected.
+ */
+BLEAddress BLEClient::getPeerAddress() {
+ return m_peerAddress;
+} // getAddress
+
+
+uint16_t BLEClient::getConnId() {
+ return m_conn_id;
+} // getConnId
+
+
+esp_gatt_if_t BLEClient::getGattcIf() {
+ return m_gattc_if;
+} // getGattcIf
+
+
+
+/**
+ * @brief Get the service object corresponding to the uuid.
+ * @param [in] uuid The UUID of the service being sought.
+ * @return A reference to the Service or nullptr if don't know about it.
+ */
+BLERemoteService* BLEClient::getService(const char* uuid) {
+ return getService(BLEUUID(uuid));
+}
+
+
+/**
+ * @brief Get the service object corresponding to the uuid.
+ * @param [in] uuid The UUID of the service being sought.
+ * @return A reference to the Service or nullptr if don't know about it.
+ */
+BLERemoteService* BLEClient::getService(BLEUUID uuid) {
+// Design
+// ------
+// We wish to retrieve the service given its UUID. It is possible that we have not yet asked the
+// device what services it has in which case we have nothing to match against. If we have not
+// asked the device about its services, then we do that now. Once we get the results we can then
+// examine the services map to see if it has the service we are looking for.
+ if (!m_haveServices) {
+ getServices();
+ }
+ std::string v = uuid.toString();
+ for (auto &myPair : m_servicesMap) {
+ if (myPair.first == v) {
+ return myPair.second;
+ }
+ }
+ return nullptr;
+} // getService
+
+
+/**
+ * @brief Ask the remote %BLE server for its services.
+ * A %BLE Server exposes a set of services for its partners. Here we ask the server for its set of
+ * services and wait until we have received them all.
+ * @return N/A
+ */
+std::map<std::string, BLERemoteService*>* BLEClient::getServices() {
+/*
+ * Design
+ * ------
+ * We invoke esp_ble_gattc_search_service. This will request a list of the service exposed by the
+ * peer BLE partner to be returned as events. Each event will be an an instance of ESP_GATTC_SEARCH_RES_EVT
+ * and will culminate with an ESP_GATTC_SEARCH_CMPL_EVT when all have been received.
+ */
+ ESP_LOGD(LOG_TAG, ">> getServices");
+ m_servicesMap.empty();
+ esp_err_t errRc = esp_ble_gattc_search_service(
+ getGattcIf(),
+ getConnId(),
+ NULL // Filter UUID
+ );
+ m_semaphoreSearchCmplEvt.take("getServices");
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_ble_gattc_search_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ return &m_servicesMap;
+ }
+ m_semaphoreSearchCmplEvt.wait("getServices");
+ m_haveServices = true; // Remember that we now have services.
+ ESP_LOGD(LOG_TAG, "<< getServices");
+ return &m_servicesMap;
+} // getServices
+
+
+/**
+ * @brief Set the callbacks that will be invoked.
+ */
+void BLEClient::setClientCallbacks(BLEClientCallbacks* pClientCallbacks) {
+ m_pClientCallbacks = pClientCallbacks;
+} // setClientCallbacks
+
+
+/**
+ * @brief Return a string representation of this client.
+ * @return A string representation of this client.
+ */
+std::string BLEClient::toString() {
+ std::ostringstream ss;
+ ss << "peer address: " << m_peerAddress.toString();
+ ss << "\nServices:\n";
+ for (auto &myPair : m_servicesMap) {
+ ss << myPair.second->toString() << "\n";
+ // myPair.second is the value
+ }
+ return ss.str();
+} // toString
+
+#endif // CONFIG_BT_ENABLED
diff --git a/src/BLEClient.h b/src/BLEClient.h
new file mode 100644
index 0000000..898f98c
--- /dev/null
+++ b/src/BLEClient.h
@@ -0,0 +1,76 @@
+/*
+ * BLEDevice.h
+ *
+ * Created on: Mar 22, 2017
+ * Author: kolban
+ */
+
+#ifndef MAIN_BLEDEVICE_H_
+#define MAIN_BLEDEVICE_H_
+
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+
+#include <esp_gattc_api.h>
+#include <string.h>
+#include <map>
+#include <string>
+#include <BLERemoteService.h>
+#include "BLEService.h"
+#include "BLEAddress.h"
+
+class BLERemoteService;
+class BLEClientCallbacks;
+
+/**
+ * @brief A model of a %BLE client.
+ */
+class BLEClient {
+public:
+ BLEClient();
+ bool connect(BLEAddress address);
+ void disconnect();
+ BLEAddress getPeerAddress();
+ std::map<std::string, BLERemoteService*>* getServices();
+ BLERemoteService* getService(const char* uuid);
+ BLERemoteService* getService(BLEUUID uuid);
+ void setClientCallbacks(BLEClientCallbacks *pClientCallbacks);
+ std::string toString();
+
+private:
+ friend class BLEDevice;
+ friend class BLERemoteCharacteristic;
+ friend class BLERemoteService;
+
+ void gattClientEventHandler(
+ esp_gattc_cb_event_t event,
+ esp_gatt_if_t gattc_if,
+ esp_ble_gattc_cb_param_t* param);
+
+ uint16_t getConnId();
+ esp_gatt_if_t getGattcIf();
+ BLEAddress m_peerAddress = BLEAddress((uint8_t*)"\0\0\0\0\0\0");
+ uint16_t m_conn_id;
+// int m_deviceType;
+ esp_gatt_if_t m_gattc_if;
+
+ BLEClientCallbacks* m_pClientCallbacks;
+ FreeRTOS::Semaphore m_semaphoreRegEvt = FreeRTOS::Semaphore("RegEvt");
+ FreeRTOS::Semaphore m_semaphoreOpenEvt = FreeRTOS::Semaphore("OpenEvt");
+ FreeRTOS::Semaphore m_semaphoreSearchCmplEvt = FreeRTOS::Semaphore("SearchCmplEvt");
+ std::map<std::string, BLERemoteService*> m_servicesMap;
+ bool m_haveServices; // Have we previously obtain the set of services.
+}; // class BLEDevice
+
+
+/**
+ * @brief Callbacks associated with a %BLE client.
+ */
+class BLEClientCallbacks {
+public:
+ virtual ~BLEClientCallbacks() {};
+ virtual void onConnect(BLEClient *pClient) = 0;
+};
+
+#endif // CONFIG_BT_ENABLED
+#endif /* MAIN_BLEDEVICE_H_ */
diff --git a/src/BLEDescriptor.cpp b/src/BLEDescriptor.cpp
new file mode 100644
index 0000000..4a7fda6
--- /dev/null
+++ b/src/BLEDescriptor.cpp
@@ -0,0 +1,278 @@
+/*
+ * 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 "GeneralUtils.h"
+
+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); // Allocate storage for the value.
+ 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.
+
+} // 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() {
+ return m_value.attr_value;
+} // 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) {
+ 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: {
+ if (param->write.handle == m_handle) {
+ setValue(param->write.value, param->write.len);
+ esp_gatt_rsp_t rsp;
+ 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(), 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) {
+ ESP_LOGE(LOG_TAG, "esp_ble_gatts_send_response: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ }
+ }
+ 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: {
+ ESP_LOGD(LOG_TAG, "- Testing: Sought handle: 0x%.2x == descriptor handle: 0x%.2x ?", param->read.handle, m_handle);
+ if (param->read.handle == m_handle) {
+ ESP_LOGD(LOG_TAG, "Sending a response (esp_ble_gatts_send_response)");
+ if (param->read.need_rsp) {
+ 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(), 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) {
+ ESP_LOGE(LOG_TAG, "esp_ble_gatts_send_response: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ }
+ }
+ } // ESP_GATTS_READ_EVT
+ break;
+ } // ESP_GATTS_READ_EVT
+ default: {
+ break;
+ }
+ }// switch event
+} // handleGATTServerEvent
+
+
+/**
+ * @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) {
+ 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(m_value.attr_value, 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) {
+ setValue((uint8_t *)value.data(), value.length());
+} // 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
+#endif /* CONFIG_BT_ENABLED */
diff --git a/src/BLEDescriptor.h b/src/BLEDescriptor.h
new file mode 100644
index 0000000..1d32d50
--- /dev/null
+++ b/src/BLEDescriptor.h
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+/**
+ * @brief A model of a %BLE descriptor.
+ */
+class BLEDescriptor {
+public:
+ BLEDescriptor(const char* uuid);
+ BLEDescriptor(BLEUUID uuid);
+ virtual ~BLEDescriptor();
+
+ size_t getLength();
+ BLEUUID getUUID();
+ uint8_t* getValue();
+ void handleGATTServerEvent(
+ esp_gatts_cb_event_t event,
+ esp_gatt_if_t gatts_if,
+ esp_ble_gatts_cb_param_t* param);
+ void setValue(uint8_t* data, size_t size);
+ void setValue(std::string value);
+ 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;
+ void executeCreate(BLECharacteristic* pCharacteristic);
+ uint16_t getHandle();
+ void setHandle(uint16_t handle);
+ FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt");
+};
+#endif /* CONFIG_BT_ENABLED */
+#endif /* COMPONENTS_CPP_UTILS_BLEDESCRIPTOR_H_ */
diff --git a/src/BLEDescriptorMap.cpp b/src/BLEDescriptorMap.cpp
new file mode 100644
index 0000000..b211652
--- /dev/null
+++ b/src/BLEDescriptorMap.cpp
@@ -0,0 +1,149 @@
+/*
+ * BLEDescriptorMap.cpp
+ *
+ * Created on: Jun 22, 2017
+ * Author: kolban
+ */
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+#include <sstream>
+#include <iomanip>
+#include "BLECharacteristic.h"
+#include "BLEDescriptor.h"
+#include <esp_gatts_api.h> // ESP32 BLE
+
+/**
+ * @brief Return the descriptor by UUID.
+ * @param [in] UUID The UUID to look up the descriptor.
+ * @return The descriptor. If not present, then nullptr is returned.
+ */
+BLEDescriptor* BLEDescriptorMap::getByUUID(const char* uuid) {
+ return getByUUID(BLEUUID(uuid));
+}
+
+
+/**
+ * @brief Return the descriptor by UUID.
+ * @param [in] UUID The UUID to look up the descriptor.
+ * @return The descriptor. If not present, then nullptr is returned.
+ */
+BLEDescriptor* BLEDescriptorMap::getByUUID(BLEUUID uuid) {
+ for (auto &myPair : m_uuidMap) {
+ if (myPair.second->getUUID().equals(uuid)) {
+ return myPair.second;
+ }
+ }
+ //return m_uuidMap.at(uuid.toString());
+ return nullptr;
+} // getByUUID
+
+
+/**
+ * @brief Return the descriptor by handle.
+ * @param [in] handle The handle to look up the descriptor.
+ * @return The descriptor.
+ */
+BLEDescriptor* BLEDescriptorMap::getByHandle(uint16_t handle) {
+ return m_handleMap.at(handle);
+} // getByHandle
+
+
+/**
+ * @brief Set the descriptor by UUID.
+ * @param [in] uuid The uuid of the descriptor.
+ * @param [in] characteristic The descriptor to cache.
+ * @return N/A.
+ */
+void BLEDescriptorMap::setByUUID(const char* uuid, BLEDescriptor *pDescriptor){
+ m_uuidMap.insert(std::pair<std::string, BLEDescriptor *>(uuid, pDescriptor));
+} // setByUUID
+
+
+
+/**
+ * @brief Set the descriptor by UUID.
+ * @param [in] uuid The uuid of the descriptor.
+ * @param [in] characteristic The descriptor to cache.
+ * @return N/A.
+ */
+void BLEDescriptorMap::setByUUID(BLEUUID uuid, BLEDescriptor *pDescriptor) {
+ m_uuidMap.insert(std::pair<std::string, BLEDescriptor *>(uuid.toString(), pDescriptor));
+} // setByUUID
+
+
+/**
+ * @brief Set the descriptor by handle.
+ * @param [in] handle The handle of the descriptor.
+ * @param [in] descriptor The descriptor to cache.
+ * @return N/A.
+ */
+void BLEDescriptorMap::setByHandle(uint16_t handle,
+ BLEDescriptor *pDescriptor) {
+ m_handleMap.insert(std::pair<uint16_t, BLEDescriptor *>(handle, pDescriptor));
+} // setByHandle
+
+
+/**
+ * @brief Return a string representation of the descriptor map.
+ * @return A string representation of the descriptor map.
+ */
+std::string BLEDescriptorMap::toString() {
+ std::stringstream stringStream;
+ stringStream << std::hex << std::setfill('0');
+ int count=0;
+ for (auto &myPair: m_uuidMap) {
+ if (count > 0) {
+ stringStream << "\n";
+ }
+ count++;
+ stringStream << "handle: 0x" << std::setw(2) << myPair.second->getHandle() << ", uuid: " + myPair.second->getUUID().toString();
+ }
+ return stringStream.str();
+} // toString
+
+
+/**
+ * @breif Pass the GATT server event onwards to each of the descriptors found in the mapping
+ * @param [in] event
+ * @param [in] gatts_if
+ * @param [in] param
+ */
+void BLEDescriptorMap::handleGATTServerEvent(
+ esp_gatts_cb_event_t event,
+ esp_gatt_if_t gatts_if,
+ esp_ble_gatts_cb_param_t *param) {
+ // Invoke the handler for every descriptor we have.
+ for (auto &myPair : m_uuidMap) {
+ myPair.second->handleGATTServerEvent(event, gatts_if, param);
+ }
+} // handleGATTServerEvent
+
+
+/**
+ * @brief Get the first descriptor in the map.
+ * @return The first descriptor in the map.
+ */
+BLEDescriptor* BLEDescriptorMap::getFirst() {
+ m_iterator = m_uuidMap.begin();
+ if (m_iterator == m_uuidMap.end()) {
+ return nullptr;
+ }
+ BLEDescriptor *pRet = m_iterator->second;
+ m_iterator++;
+ return pRet;
+} // getFirst
+
+
+/**
+ * @brief Get the next descriptor in the map.
+ * @return The next descriptor in the map.
+ */
+BLEDescriptor* BLEDescriptorMap::getNext() {
+ if (m_iterator == m_uuidMap.end()) {
+ return nullptr;
+ }
+ BLEDescriptor *pRet = m_iterator->second;
+ m_iterator++;
+ return pRet;
+} // getNext
+#endif /* CONFIG_BT_ENABLED */
diff --git a/src/BLEDevice.cpp b/src/BLEDevice.cpp
new file mode 100644
index 0000000..04cae14
--- /dev/null
+++ b/src/BLEDevice.cpp
@@ -0,0 +1,222 @@
+/*
+ * BLE.cpp
+ *
+ * Created on: Mar 16, 2017
+ * Author: kolban
+ */
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_err.h>
+#include <nvs_flash.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/event_groups.h>
+#include <bt.h> // ESP32 BLE
+#include <esp_bt_main.h> // ESP32 BLE
+#include <esp_gap_ble_api.h> // ESP32 BLE
+// ESP32 BLE
+#include <esp_gatts_api.h> // ESP32 BLE
+#include <esp_err.h> // ESP32 ESP-IDF
+#include <esp_log.h> // ESP32 ESP-IDF
+#include <map> // Part of C++ STL
+#include <sstream>
+#include <iomanip>
+
+#include "BLEDevice.h"
+#include "BLEClient.h"
+#include "BLEUtils.h"
+#include "GeneralUtils.h"
+
+static const char* LOG_TAG = "BLEDevice";
+
+BLEServer *BLEDevice::m_bleServer = nullptr;
+BLEScan *BLEDevice::m_pScan = nullptr;
+BLEClient *BLEDevice::m_pClient = nullptr;
+
+#include <esp_gattc_api.h>
+
+
+BLEClient* BLEDevice::createClient() {
+ m_pClient = new BLEClient();
+ return m_pClient;
+} // createClient
+
+
+/**
+ * @brief Handle GATT server events.
+ *
+ * @param [in] event
+ * @param [in] gatts_if
+ * @param [in] param
+ */
+void BLEDevice::gattServerEventHandler(
+ esp_gatts_cb_event_t event,
+ esp_gatt_if_t gatts_if,
+ esp_ble_gatts_cb_param_t *param
+) {
+ ESP_LOGD(LOG_TAG, "gattServerEventHandler [esp_gatt_if: %d] ... %s",
+ gatts_if,
+ BLEUtils::gattServerEventTypeToString(event).c_str());
+ BLEUtils::dumpGattServerEvent(event, gatts_if, param);
+ if (BLEDevice::m_bleServer != nullptr) {
+ BLEDevice::m_bleServer->handleGATTServerEvent(event, gatts_if, param);
+ }
+} // gattServerEventHandler
+
+
+/**
+ * @brief Handle GATT client events.
+ *
+ * Handler for the GATT client events.
+ * * `ESP_GATTC_OPEN_EVT` – Invoked when a connection is opened.
+ * * `ESP_GATTC_PREP_WRITE_EVT` – Response to write a characteristic.
+ * * `ESP_GATTC_READ_CHAR_EVT` – Response to read a characteristic.
+ * * `ESP_GATTC_REG_EVT` – Invoked when a GATT client has been registered.
+ *
+ * @param [in] event
+ * @param [in] gattc_if
+ * @param [in] param
+ */
+void BLEDevice::gattClientEventHandler(
+ esp_gattc_cb_event_t event,
+ esp_gatt_if_t gattc_if,
+ esp_ble_gattc_cb_param_t *param) {
+
+ ESP_LOGD(LOG_TAG, "gattClientEventHandler [esp_gatt_if: %d] ... %s",
+ gattc_if, BLEUtils::gattClientEventTypeToString(event).c_str());
+ BLEUtils::dumpGattClientEvent(event, gattc_if, param);
+
+ switch(event) {
+ default: {
+ break;
+ }
+ } // switch
+
+ // If we have a client registered, call it.
+ if (BLEDevice::m_pClient != nullptr) {
+ BLEDevice::m_pClient->gattClientEventHandler(event, gattc_if, param);
+ }
+
+} // gattClientEventHandler
+
+
+/**
+ * @brief Handle GAP events.
+ */
+void BLEDevice::gapEventHandler(
+ esp_gap_ble_cb_event_t event,
+ esp_ble_gap_cb_param_t *param) {
+
+ BLEUtils::dumpGapEvent(event, param);
+
+ switch(event) {
+ case ESP_GAP_BLE_SEC_REQ_EVT: {
+ esp_err_t errRc = ::esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_ble_gap_security_rsp: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ }
+ break;
+ }
+
+ default: {
+ break;
+ }
+ } // switch
+
+ if (BLEDevice::m_bleServer != nullptr) {
+ BLEDevice::m_bleServer->handleGAPEvent(event, param);
+ }
+
+ if (BLEDevice::m_pScan != nullptr) {
+ BLEDevice::getScan()->gapEventHandler(event, param);
+ }
+} // gapEventHandler
+
+
+/**
+ * @brief Initialize the %BLE environment.
+ * @param deviceName The device name of the device.
+ */
+void BLEDevice::init(std::string deviceName) {
+ esp_err_t errRc = ::nvs_flash_init();
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "nvs_flash_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ return;
+ }
+
+ esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
+ errRc = esp_bt_controller_init(&bt_cfg);
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_bt_controller_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ return;
+ }
+
+ errRc = esp_bt_controller_enable(ESP_BT_MODE_BTDM);
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_bt_controller_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ return;
+ }
+
+ errRc = esp_bluedroid_init();
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_bluedroid_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ return;
+ }
+
+ errRc = esp_bluedroid_enable();
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_bluedroid_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ return;
+ }
+
+ errRc = esp_ble_gap_register_callback(BLEDevice::gapEventHandler);
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_ble_gap_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ return;
+ }
+
+ errRc = esp_ble_gattc_register_callback(BLEDevice::gattClientEventHandler);
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_ble_gattc_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ return;
+ }
+
+ errRc = esp_ble_gatts_register_callback(BLEDevice::gattServerEventHandler);
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_ble_gatts_register_callback: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ return;
+ }
+
+ errRc = ::esp_ble_gap_set_device_name(deviceName.c_str());
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_ble_gap_set_device_name: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ return;
+ };
+
+ esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE;
+ errRc = ::esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t));
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_ble_gap_set_security_param: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ return;
+ };
+
+ vTaskDelay(200/portTICK_PERIOD_MS); // Delay for 200 msecs as a workaround to an apparent Arduino environment issue.
+} // init
+
+
+
+/**
+ * @brief Retrieve the Scan object that we use for scanning.
+ * @return The scanning object reference.
+ */
+BLEScan* BLEDevice::getScan() {
+ if (m_pScan == nullptr) {
+ m_pScan = new BLEScan();
+ }
+ return m_pScan;
+} // getScan
+
+
+
+#endif // CONFIG_BT_ENABLED
diff --git a/src/BLEDevice.h b/src/BLEDevice.h
new file mode 100644
index 0000000..9d767c1
--- /dev/null
+++ b/src/BLEDevice.h
@@ -0,0 +1,54 @@
+/*
+ * BLEDevice.h
+ *
+ * Created on: Mar 16, 2017
+ * Author: kolban
+ */
+
+#ifndef MAIN_BLEDevice_H_
+#define MAIN_BLEDevice_H_
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+#include <esp_gap_ble_api.h> // ESP32 BLE
+#include <esp_gattc_api.h> // ESP32 BLE
+#include <map> // Part of C++ STL
+#include <string>
+
+#include "BLEServer.h"
+#include "BLEClient.h"
+#include "BLEUtils.h"
+#include "BLEScan.h"
+#include "BLEAddress.h"
+/**
+ * @brief %BLE functions.
+ */
+class BLEDevice {
+public:
+ static void dumpDevices();
+ static BLEClient *createClient();
+
+ static void init(std::string deviceName);
+ //static void scan(int duration, esp_ble_scan_type_t scan_type = BLE_SCAN_TYPE_PASSIVE);
+ static BLEScan *getScan();
+ static BLEServer *m_bleServer;
+ static BLEScan *m_pScan;
+ static BLEClient *m_pClient;
+
+private:
+ static esp_gatt_if_t getGattcIF();
+
+ static void gattClientEventHandler(
+ esp_gattc_cb_event_t event,
+ esp_gatt_if_t gattc_if,
+ esp_ble_gattc_cb_param_t *param);
+ static void gattServerEventHandler(
+ esp_gatts_cb_event_t event,
+ esp_gatt_if_t gatts_if,
+ esp_ble_gatts_cb_param_t *param);
+ static void gapEventHandler(
+ esp_gap_ble_cb_event_t event,
+ esp_ble_gap_cb_param_t *param);
+}; // class BLE
+
+#endif // CONFIG_BT_ENABLED
+#endif /* MAIN_BLEDevice_H_ */
diff --git a/src/BLERemoteCharacteristic.cpp b/src/BLERemoteCharacteristic.cpp
new file mode 100644
index 0000000..5227bf6
--- /dev/null
+++ b/src/BLERemoteCharacteristic.cpp
@@ -0,0 +1,336 @@
+/*
+ * BLERemoteCharacteristic.cpp
+ *
+ * Created on: Jul 8, 2017
+ * Author: kolban
+ */
+
+#include "BLERemoteCharacteristic.h"
+
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+
+#include <esp_gattc_api.h>
+#include <esp_log.h>
+#include <esp_err.h>
+
+#include <sstream>
+#include "BLEUtils.h"
+#include "GeneralUtils.h"
+
+
+static const char* LOG_TAG = "BLERemoteCharacteristic";
+
+BLERemoteCharacteristic::BLERemoteCharacteristic(
+ esp_gatt_id_t charId,
+ esp_gatt_char_prop_t charProp,
+ BLERemoteService* pRemoteService) {
+ m_charId = charId;
+ m_charProp = charProp;
+ m_pRemoteService = pRemoteService;
+ m_notifyCallback = nullptr;
+} // BLERemoteCharacteristic
+
+
+static bool compareSrvcId(esp_gatt_srvc_id_t id1, esp_gatt_srvc_id_t id2) {
+ if (id1.id.inst_id != id2.id.inst_id) {
+ return false;
+ }
+ if (!BLEUUID(id1.id.uuid).equals(BLEUUID(id2.id.uuid))) {
+ return false;
+ }
+ return true;
+} // compareSrvcId
+
+
+static bool compareGattId(esp_gatt_id_t id1, esp_gatt_id_t id2) {
+ if (id1.inst_id != id2.inst_id) {
+ return false;
+ }
+ if (!BLEUUID(id1.uuid).equals(BLEUUID(id2.uuid))) {
+ return false;
+ }
+ return true;
+} // compareCharId
+
+
+/**
+ * @brief Handle GATT Client events.
+ * When an event arrives for a GATT client we give this characteristic the opportunity to
+ * take a look at it to see if there is interest in it.
+ * @param [in] event The type of event.
+ * @param [in] gattc_if The interface on which the event was received.
+ * @param [in] evtParam Payload data for the event.
+ * @returns N/A
+ */
+void BLERemoteCharacteristic::gattClientEventHandler(
+ esp_gattc_cb_event_t event,
+ esp_gatt_if_t gattc_if,
+ esp_ble_gattc_cb_param_t* evtParam) {
+ switch(event) {
+ //
+ // ESP_GATTC_READ_CHAR_EVT
+ // This event indicates that the server has responded to the read request.
+ //
+ // read:
+ // esp_gatt_status_t status
+ // uint16_t conn_id
+ // esp_gatt_srvc_id_t srvc_id
+ // esp_gatt_id_t char_id
+ // esp_gatt_id_t descr_id
+ // uint8_t* value
+ // uint16_t value_type
+ // uint16_t value_len
+ case ESP_GATTC_READ_CHAR_EVT: {
+ if (compareSrvcId(evtParam->read.srvc_id, *m_pRemoteService->getSrvcId()) == false) {
+ break;
+ }
+
+ if (evtParam->read.conn_id != m_pRemoteService->getClient()->getConnId()) {
+ break;
+ }
+
+ if (compareGattId(evtParam->read.char_id, m_charId) == false) {
+ break;
+ }
+
+ if (evtParam->read.status == ESP_GATT_OK) {
+ m_value = std::string((char*)evtParam->read.value, evtParam->read.value_len);
+ } else {
+ m_value = "";
+ }
+
+ m_semaphoreReadCharEvt.give();
+ break;
+ } // ESP_GATTC_READ_CHAR_EVT
+
+
+ //
+ // ESP_GATTC_REG_FOR_NOTIFY_EVT
+ //
+ // reg_for_notify:
+ // - esp_gatt_status_t status
+ // - esp_gatt_srvc_id_t srvc_id
+ // - esp_gatt_id_t char_id
+ case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
+ if (compareSrvcId(evtParam->reg_for_notify.srvc_id, *m_pRemoteService->getSrvcId()) == false) {
+ break;
+ }
+ if (compareGattId(evtParam->reg_for_notify.char_id, m_charId) == false) {
+ break;
+ }
+ m_semaphoreRegForNotifyEvt.give();
+ break;
+ } // ESP_GATTC_REG_FOR_NOTIFY_EVT
+
+
+ //
+ // ESP_GATTC_WRITE_CHAR_EVT
+ //
+ // write:
+ // esp_gatt_status_t status
+ // uint16_t conn_id
+ // esp_gatt_srvc_id_t srvc_id
+ // esp_gatt_id_t char_id
+ // esp_gatt_id_t descr_id
+ case ESP_GATTC_WRITE_CHAR_EVT: {
+ if (compareSrvcId(evtParam->write.srvc_id, *m_pRemoteService->getSrvcId()) == false) {
+ break;
+ }
+ if (evtParam->write.conn_id != m_pRemoteService->getClient()->getConnId()) {
+ break;
+ }
+ if (compareGattId(evtParam->write.char_id, m_charId) == false) {
+ break;
+ }
+ m_semaphoreWriteCharEvt.give();
+ break;
+ } // ESP_GATTC_WRITE_CHAR_EVT
+
+
+ default: {
+ break;
+ }
+ }
+}; // gattClientEventHandler
+
+
+BLEUUID BLERemoteCharacteristic::getUUID() {
+ return BLEUUID(m_charId.uuid);
+}
+
+/**
+ * @brief Read an unsigned 16 bit value
+ * @return The unsigned 16 bit value.
+ */
+uint16_t BLERemoteCharacteristic::readUInt16(void) {
+ std::string value = readValue();
+ if (value.length() >= 2) {
+ return *(uint16_t*)(value.data());
+ }
+ return 0;
+} // readUInt16
+
+
+/**
+ * @brief Read an unsigned 32 bit value.
+ * @return the unsigned 32 bit value.
+ */
+uint32_t BLERemoteCharacteristic::readUInt32(void) {
+ std::string value = readValue();
+ if (value.length() >= 4) {
+ return *(uint32_t*)(value.data());
+ }
+ return 0;
+} // readUInt32
+
+
+/**
+ * @brief Read a byte value
+ * @return The value as a byte
+ */
+uint8_t BLERemoteCharacteristic::readUInt8(void) {
+ std::string value = readValue();
+ if (value.length() >= 1) {
+ return (uint8_t)value[0];
+ }
+ return 0;
+} // readUInt8
+
+
+/**
+ * @brief Read the value of the remote characteristic.
+ * @return The value of the remote characteristic.
+ */
+std::string BLERemoteCharacteristic::readValue() {
+ ESP_LOGD(LOG_TAG, ">> readValue()");
+
+ m_semaphoreReadCharEvt.take("readValue");
+
+ // Ask the BLE subsystem to retrieve the value for the remote hosted characteristic.
+ esp_err_t errRc = ::esp_ble_gattc_read_char(
+ m_pRemoteService->getClient()->getGattcIf(),
+ m_pRemoteService->getClient()->getConnId(),
+ m_pRemoteService->getSrvcId(),
+ &m_charId,
+ ESP_GATT_AUTH_REQ_NONE);
+
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_ble_gattc_read_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ return "";
+ }
+
+ // Block waiting for the event that indicates that the read has completed. When it has, the std::string found
+ // in m_value will contain our data.
+ m_semaphoreReadCharEvt.wait("readValue");
+
+ ESP_LOGD(LOG_TAG, "<< readValue()");
+ return m_value;
+} // readValue
+
+
+/**
+ * @brief Register for notifications.
+ * @param [in] notifyCallback A callback to be invoked for a notification.
+ * @return N/A.
+ */
+void BLERemoteCharacteristic::registerForNotify(
+ void (*notifyCallback)(
+ BLERemoteCharacteristic* pBLERemoteCharacteristic,
+ uint8_t* pData,
+ size_t length,
+ bool isNotify)) {
+ ESP_LOGD(LOG_TAG, ">> registerForNotify()");
+
+ m_notifyCallback = notifyCallback; // Save the notification callback.
+
+ m_semaphoreRegForNotifyEvt.take("registerForNotify");
+
+ esp_err_t errRc = ::esp_ble_gattc_register_for_notify(
+ m_pRemoteService->getClient()->getGattcIf(),
+ *m_pRemoteService->getClient()->getPeerAddress().getNative(),
+ m_pRemoteService->getSrvcId(),
+ &m_charId);
+
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_ble_gattc_register_for_notify: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ return;
+ }
+
+ m_semaphoreRegForNotifyEvt.wait("registerForNotify");
+
+ ESP_LOGD(LOG_TAG, "<< registerForNotify()");
+} // registerForNotify
+
+
+/**
+ * @brief Convert a BLERemoteCharacteristic to a string representation;
+ * @return a String representation.
+ */
+std::string BLERemoteCharacteristic::toString() {
+ std::ostringstream ss;
+ ss << "Characteristic: uuid: " << BLEUUID(m_charId.uuid).toString() <<
+ ", props: " << BLEUtils::characteristicPropertiesToString(m_charProp) <<
+ ", inst_id: " << (int)m_charId.inst_id;
+ return ss.str();
+} // toString
+
+
+/**
+ * @brief Write the new value for the characteristic.
+ * @param [in] newValue The new value to write.
+ * @param [in] response Do we expect a response?
+ * @return N/A.
+ */
+void BLERemoteCharacteristic::writeValue(std::string newValue, bool response) {
+ ESP_LOGD(LOG_TAG, ">> writeValue(), length: %d", newValue.length());
+
+ m_semaphoreWriteCharEvt.take("writeValue");
+
+ esp_err_t errRc = ::esp_ble_gattc_write_char(
+ m_pRemoteService->getClient()->getGattcIf(),
+ m_pRemoteService->getClient()->getConnId(),
+ m_pRemoteService->getSrvcId(),
+ &m_charId,
+ newValue.length(),
+ (uint8_t*)newValue.data(),
+ response?ESP_GATT_WRITE_TYPE_RSP:ESP_GATT_WRITE_TYPE_NO_RSP,
+ ESP_GATT_AUTH_REQ_NONE
+ );
+
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_ble_gattc_write_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ return;
+ }
+
+ m_semaphoreWriteCharEvt.wait("writeValue");
+
+ ESP_LOGD(LOG_TAG, "<< writeValue");
+} // writeValue
+
+
+/**
+ * @brief Write the new value for the characteristic.
+ *
+ * This is a convenience function. Many BLE characteristics are a single byte of data.
+ * @param [in] newValue The new byte value to write.
+ * @param [in] response Whether we require a response from the write.
+ * @return N/A.
+ */
+void BLERemoteCharacteristic::writeValue(uint8_t newValue, bool response) {
+ writeValue(std::string(reinterpret_cast<char*>(&newValue), 1), response);
+} // writeValue
+
+
+/**
+ * @brief Write the new value for the characteristic from a data buffer.
+ * @param [in] data A pointer to a data buffer.
+ * @param [in] length The length of the data in the data buffer.
+ * @param [in] response Whether we require a response from the write.
+ */
+void BLERemoteCharacteristic::writeValue(uint8_t* data, size_t length, bool response) {
+ writeValue(std::string((char *)data, length), response);
+} // writeValue
+
+
+#endif /* CONFIG_BT_ENABLED */
diff --git a/src/BLERemoteCharacteristic.h b/src/BLERemoteCharacteristic.h
new file mode 100644
index 0000000..b5b22b6
--- /dev/null
+++ b/src/BLERemoteCharacteristic.h
@@ -0,0 +1,63 @@
+/*
+ * BLERemoteCharacteristic.h
+ *
+ * Created on: Jul 8, 2017
+ * Author: kolban
+ */
+
+#ifndef COMPONENTS_CPP_UTILS_BLEREMOTECHARACTERISTIC_H_
+#define COMPONENTS_CPP_UTILS_BLEREMOTECHARACTERISTIC_H_
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+
+#include <string>
+
+#include <esp_gattc_api.h>
+
+#include "BLERemoteService.h"
+#include "BLEUUID.h"
+#include "FreeRTOS.h"
+
+class BLERemoteService;
+
+/**
+ * @brief A model of a remote %BLE characteristic.
+ */
+class BLERemoteCharacteristic {
+public:
+ BLERemoteCharacteristic(esp_gatt_id_t charId, esp_gatt_char_prop_t charProp, BLERemoteService* pRemoteService);
+
+ // Public member functions
+ BLEUUID getUUID();
+ std::string readValue(void);
+ uint8_t readUInt8(void);
+ uint16_t readUInt16(void);
+ uint32_t readUInt32(void);
+ void registerForNotify(void (*notifyCallback)(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify));
+ void writeValue(uint8_t* data, size_t length, bool response = false);
+ void writeValue(std::string newValue, bool response = false);
+ void writeValue(uint8_t newValue, bool response = false);
+ std::string toString(void);
+
+private:
+ friend class BLEClient;
+ friend class BLERemoteService;
+
+ // Private member functions
+ void gattClientEventHandler(
+ esp_gattc_cb_event_t event,
+ esp_gatt_if_t gattc_if,
+ esp_ble_gattc_cb_param_t *evtParam);
+
+ // Private properties
+ esp_gatt_id_t m_charId;
+ esp_gatt_char_prop_t m_charProp;
+ BLERemoteService* m_pRemoteService;
+ FreeRTOS::Semaphore m_semaphoreReadCharEvt = FreeRTOS::Semaphore("ReadCharEvt");
+ FreeRTOS::Semaphore m_semaphoreRegForNotifyEvt = FreeRTOS::Semaphore("RegForNotifyEvt");
+ FreeRTOS::Semaphore m_semaphoreWriteCharEvt = FreeRTOS::Semaphore("WriteCharEvt");
+ std::string m_value;
+ void (*m_notifyCallback)(BLERemoteCharacteristic *pBLERemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify);
+}; // BLERemoteCharacteristic
+#endif /* CONFIG_BT_ENABLED */
+#endif /* COMPONENTS_CPP_UTILS_BLEREMOTECHARACTERISTIC_H_ */
diff --git a/src/BLERemoteDescriptor.cpp b/src/BLERemoteDescriptor.cpp
new file mode 100644
index 0000000..2be312a
--- /dev/null
+++ b/src/BLERemoteDescriptor.cpp
@@ -0,0 +1,11 @@
+/*
+ * BLERemoteDescriptor.cpp
+ *
+ * Created on: Jul 8, 2017
+ * Author: kolban
+ */
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+#include "BLERemoteDescriptor.h"
+
+#endif /* CONFIG_BT_ENABLED */
diff --git a/src/BLERemoteDescriptor.h b/src/BLERemoteDescriptor.h
new file mode 100644
index 0000000..a8d944d
--- /dev/null
+++ b/src/BLERemoteDescriptor.h
@@ -0,0 +1,20 @@
+/*
+ * BLERemoteDescriptor.h
+ *
+ * Created on: Jul 8, 2017
+ * Author: kolban
+ */
+
+#ifndef COMPONENTS_CPP_UTILS_BLEREMOTEDESCRIPTOR_H_
+#define COMPONENTS_CPP_UTILS_BLEREMOTEDESCRIPTOR_H_
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+
+/**
+ * @brief A model of remote %BLE descriptor.
+ */
+class BLERemoteDescriptor {
+public:
+};
+#endif /* CONFIG_BT_ENABLED */
+#endif /* COMPONENTS_CPP_UTILS_BLEREMOTEDESCRIPTOR_H_ */
diff --git a/src/BLERemoteService.cpp b/src/BLERemoteService.cpp
new file mode 100644
index 0000000..a29a758
--- /dev/null
+++ b/src/BLERemoteService.cpp
@@ -0,0 +1,220 @@
+/*
+ * BLERemoteService.cpp
+ *
+ * Created on: Jul 8, 2017
+ * Author: kolban
+ */
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+
+#include <sstream>
+#include "BLERemoteService.h"
+#include "BLEUtils.h"
+#include "GeneralUtils.h"
+#include <esp_log.h>
+#include <esp_err.h>
+
+static const char* LOG_TAG = "BLERemoteService";
+
+BLERemoteService::BLERemoteService(
+ esp_gatt_srvc_id_t srvcId,
+ BLEClient *pClient) {
+
+ m_srvcId = srvcId;
+ m_pClient = pClient;
+ m_uuid = BLEUUID(m_srvcId);
+ m_haveCharacteristics = false;
+}
+
+
+BLERemoteService::~BLERemoteService() {
+ removeCharacteristics();
+}
+
+static bool compareSrvcId(esp_gatt_srvc_id_t id1, esp_gatt_srvc_id_t id2) {
+ if (id1.id.inst_id != id2.id.inst_id) {
+ return false;
+ }
+ if (!BLEUUID(id1.id.uuid).equals(BLEUUID(id2.id.uuid))) {
+ return false;
+ }
+ return true;
+} // compareSrvcId
+
+
+/**
+ * @brief Handle GATT Client events
+ */
+void BLERemoteService::gattClientEventHandler(
+ esp_gattc_cb_event_t event,
+ esp_gatt_if_t gattc_if,
+ esp_ble_gattc_cb_param_t *evtParam) {
+ switch(event) {
+ //
+ // ESP_GATTC_GET_CHAR_EVT
+ //
+ // get_char:
+ // - esp_gatt_status_t status
+ // - uin1t6_t conn_id
+ // - esp_gatt_srvc_id_t srvc_id
+ // - esp_gatt_id_t char_id
+ // - esp_gatt_char_prop_t char_prop
+ //
+ case ESP_GATTC_GET_CHAR_EVT: {
+ // Is this event for this service? If yes, then the local srvc_id and the event srvc_id will be
+ // the same.
+ if (compareSrvcId(m_srvcId, evtParam->get_char.srvc_id) == false) {
+ break;
+ }
+
+ // If the status is NOT OK then we have a problem and continue.
+ if (evtParam->get_char.status != ESP_GATT_OK) {
+ m_semaphoreGetCharEvt.give();
+ break;
+ }
+
+ // This is an indication that we now have the characteristic details for a characteristic owned
+ // by this service so remember it.
+ m_characteristicMap.insert(std::pair<std::string, BLERemoteCharacteristic*>(
+ BLEUUID(evtParam->get_char.char_id.uuid).toString(),
+ new BLERemoteCharacteristic(evtParam->get_char.char_id, evtParam->get_char.char_prop, this) ));
+
+
+ // Now that we have received a characteristic, lets ask for the next one.
+ esp_err_t errRc = ::esp_ble_gattc_get_characteristic(
+ m_pClient->getGattcIf(),
+ m_pClient->getConnId(),
+ &m_srvcId,
+ &evtParam->get_char.char_id);
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_ble_gattc_get_characteristic: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ break;
+ }
+
+ //m_semaphoreGetCharEvt.give();
+ break;
+ } // ESP_GATTC_GET_CHAR_EVT
+
+ default: {
+ break;
+ }
+ } // switch
+
+ // Send the event to each of the characteristics owned by this service.
+ for (auto &myPair : m_characteristicMap) {
+ myPair.second->gattClientEventHandler(event, gattc_if, evtParam);
+ }
+} // gattClientEventHandler
+
+
+/**
+ * @brief Get the characteristic object for the UUID.
+ * @param [in] uuid Characteristic uuid.
+ * @return Reference to the characteristic object.
+ */
+BLERemoteCharacteristic* BLERemoteService::getCharacteristic(const char* uuid) {
+ return getCharacteristic(BLEUUID(uuid));
+}
+
+
+/**
+ * @brief Get the characteristic object for the UUID.
+ * @param [in] uuid Characteristic uuid.
+ * @return Reference to the characteristic object.
+ */
+BLERemoteCharacteristic* BLERemoteService::getCharacteristic(BLEUUID uuid) {
+// Design
+// ------
+// We wish to retrieve the characteristic given its UUID. It is possible that we have not yet asked the
+// device what characteristics it has in which case we have nothing to match against. If we have not
+// asked the device about its characteristics, then we do that now. Once we get the results we can then
+// examine the characteristics map to see if it has the characteristic we are looking for.
+ if (!m_haveCharacteristics) {
+ getCharacteristics();
+ }
+ std::string v = uuid.toString();
+ for (auto &myPair : m_characteristicMap) {
+ if (myPair.first == v) {
+ return myPair.second;
+ }
+ }
+ return nullptr;
+} // getCharacteristic
+
+
+/**
+ * @brief Retrieve all the characteristics for this service.
+ * @return N/A
+ */
+void BLERemoteService::getCharacteristics() {
+
+ ESP_LOGD(LOG_TAG, ">> getCharacteristics() for service: %s", getUUID().toString().c_str());
+
+ removeCharacteristics(); // Forget any previous characteristics.
+
+ m_semaphoreGetCharEvt.take("getCharacteristics");
+
+ esp_err_t errRc = ::esp_ble_gattc_get_characteristic(
+ m_pClient->getGattcIf(),
+ m_pClient->getConnId(),
+ &m_srvcId,
+ nullptr);
+
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_ble_gattc_get_characteristic: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ return;
+ }
+
+ m_semaphoreGetCharEvt.wait("getCharacteristics"); // Wait for the characteristics to become available.
+
+ m_haveCharacteristics = true; // Remember that we have received the characteristics.
+ ESP_LOGD(LOG_TAG, "<< getCharacteristics()");
+} // getCharacteristics
+
+
+BLEClient* BLERemoteService::getClient() {
+ return m_pClient;
+}
+
+esp_gatt_srvc_id_t* BLERemoteService::getSrvcId() {
+ return &m_srvcId;
+}
+
+BLEUUID BLERemoteService::getUUID() {
+ return m_uuid;
+}
+
+
+/**
+ * @brief Delete the characteristics in the characteristics map.
+ * We maintain a map called m_characteristicsMap that contains pointers to BLERemoteCharacteristic
+ * object references. Since we allocated these in this class, we are also responsible for deleteing
+ * them. This method does just that.
+ * @return N/A.
+ */
+void BLERemoteService::removeCharacteristics() {
+ for (auto &myPair : m_characteristicMap) {
+ delete myPair.second;
+ }
+ m_characteristicMap.empty();
+} // removeCharacteristics
+
+
+
+/**
+ * @brief Create a string representation of this remote service.
+ * @return A string representation of this remote service.
+ */
+std::string BLERemoteService::toString() {
+ std::ostringstream ss;
+ ss << "Service: uuid: " + m_uuid.toString();
+ for (auto &myPair : m_characteristicMap) {
+ ss << "\n" << myPair.second->toString();
+ // myPair.second is the value
+ }
+ return ss.str();
+} // toString
+
+
+
+#endif /* CONFIG_BT_ENABLED */
diff --git a/src/BLERemoteService.h b/src/BLERemoteService.h
new file mode 100644
index 0000000..4393fbc
--- /dev/null
+++ b/src/BLERemoteService.h
@@ -0,0 +1,63 @@
+/*
+ * BLERemoteService.h
+ *
+ * Created on: Jul 8, 2017
+ * Author: kolban
+ */
+
+#ifndef COMPONENTS_CPP_UTILS_BLEREMOTESERVICE_H_
+#define COMPONENTS_CPP_UTILS_BLEREMOTESERVICE_H_
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+
+#include <map>
+
+#include "BLEClient.h"
+#include "BLERemoteCharacteristic.h"
+#include "BLEUUID.h"
+#include "FreeRTOS.h"
+
+class BLEClient;
+class BLERemoteCharacteristic;
+
+
+/**
+ * @brief A model of a remote %BLE service.
+ */
+class BLERemoteService {
+public:
+ BLERemoteService(esp_gatt_srvc_id_t srvcId, BLEClient* pClient);
+ virtual ~BLERemoteService();
+
+ // Public methods
+ BLERemoteCharacteristic* getCharacteristic(const char* uuid);
+ BLERemoteCharacteristic* getCharacteristic(BLEUUID uuid);
+ void getCharacteristics(void);
+ BLEClient* getClient(void);
+ BLEUUID getUUID(void);
+ std::string toString(void);
+
+private:
+ // Friends
+ friend class BLEClient;
+ friend class BLERemoteCharacteristic;
+
+ // Private methods
+ esp_gatt_srvc_id_t* getSrvcId(void);
+ void gattClientEventHandler(
+ esp_gattc_cb_event_t event,
+ esp_gatt_if_t gattc_if,
+ esp_ble_gattc_cb_param_t* evtParam);
+ void removeCharacteristics();
+
+ // Properties
+ std::map<std::string, BLERemoteCharacteristic *> m_characteristicMap;
+ bool m_haveCharacteristics; // Have we previously obtained the characteristics.
+ BLEClient* m_pClient;
+ FreeRTOS::Semaphore m_semaphoreGetCharEvt = FreeRTOS::Semaphore("GetCharEvt");
+ esp_gatt_srvc_id_t m_srvcId;
+ BLEUUID m_uuid;
+}; // BLERemoteService
+
+#endif /* CONFIG_BT_ENABLED */
+#endif /* COMPONENTS_CPP_UTILS_BLEREMOTESERVICE_H_ */
diff --git a/src/BLEScan.cpp b/src/BLEScan.cpp
new file mode 100644
index 0000000..9fdcffd
--- /dev/null
+++ b/src/BLEScan.cpp
@@ -0,0 +1,276 @@
+/*
+ * BLEScan.cpp
+ *
+ * Created on: Jul 1, 2017
+ * Author: kolban
+ */
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+
+
+#include <esp_log.h>
+#include <esp_err.h>
+
+#include <map>
+
+#include "BLEAdvertisedDevice.h"
+#include "BLEScan.h"
+#include "BLEUtils.h"
+#include "GeneralUtils.h"
+
+static const char* LOG_TAG = "BLEScan";
+
+
+BLEScan::BLEScan() {
+ m_scan_params.scan_type = BLE_SCAN_TYPE_PASSIVE; // Default is a passive scan.
+ m_scan_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
+ m_scan_params.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL;
+ setInterval(100);
+ setWindow(100);
+ m_pAdvertisedDeviceCallbacks = nullptr;
+ m_stopped = true;
+} // BLEScan
+
+
+
+/**
+ * @brief Clear the history of previously detected advertised devices.
+ * @return N/A
+ */
+/*
+void BLEScan::clearAdvertisedDevices() {
+ for (int i=0; i<m_vectorAvdertisedDevices.size(); i++) {
+ delete m_vectorAvdertisedDevices[i];
+ }
+ m_vectorAvdertisedDevices.clear();
+} // clearAdvertisedDevices
+*/
+
+
+/**
+ * @brief Handle GAP events related to scans.
+ * @param [in] event The event type for this event.
+ * @param [in] param Parameter data for this event.
+ */
+void BLEScan::gapEventHandler(
+ esp_gap_ble_cb_event_t event,
+ esp_ble_gap_cb_param_t* param) {
+
+ switch(event) {
+
+ // ESP_GAP_BLE_SCAN_RESULT_EVT
+ // ---------------------------
+ // scan_rst:
+ // esp_gap_search_evt_t search_evt
+ // esp_bd_addr_t bda
+ // esp_bt_dev_type_t dev_type
+ // esp_ble_addr_type_t ble_addr_type
+ // esp_ble_evt_type_t ble_evt_type
+ // int rssi
+ // uint8_t ble_adv[ESP_BLE_ADV_DATA_LEN_MAX]
+ // int flag
+ // int num_resps
+ // uint8_t adv_data_len
+ // uint8_t scan_rsp_len
+ case ESP_GAP_BLE_SCAN_RESULT_EVT: {
+
+ switch(param->scan_rst.search_evt) {
+ case ESP_GAP_SEARCH_INQ_CMPL_EVT: {
+ m_stopped = true;
+ m_semaphoreScanEnd.give();
+ break;
+ } // ESP_GAP_SEARCH_INQ_CMPL_EVT
+
+ case ESP_GAP_SEARCH_INQ_RES_EVT: {
+ if (m_stopped) { // If we are not scanning, nothing to do with the extra results.
+ break;
+ }
+
+// Examine our list of previously scanned addresses and, if we found this one already,
+// ignore it.
+ BLEAddress advertisedAddress(param->scan_rst.bda);
+ bool found = false;
+ /*
+ for (int i=0; i<m_vectorAvdertisedDevices.size(); i++) {
+ if (m_vectorAvdertisedDevices[i]->getAddress().equals(advertisedAddress)) {
+ found = true;
+ break;
+ }
+ }
+ */
+ for (int i=0; i<m_scanResults.getCount(); i++) {
+ if (m_scanResults.getDevice(i).getAddress().equals(advertisedAddress)) {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ ESP_LOGD(LOG_TAG, "Ignoring %s, already seen it.", advertisedAddress.toString().c_str());
+ break;
+ }
+
+ // We now construct a model of the advertised device that we have just found for the first
+ // time.
+ BLEAdvertisedDevice advertisedDevice;
+ advertisedDevice.setAddress(advertisedAddress);
+ advertisedDevice.setRSSI(param->scan_rst.rssi);
+ advertisedDevice.setAdFlag(param->scan_rst.flag);
+ advertisedDevice.parseAdvertisement((uint8_t*)param->scan_rst.ble_adv);
+ advertisedDevice.setScan(this);
+
+ //m_vectorAvdertisedDevices.push_back(pAdvertisedDevice);
+ if (m_pAdvertisedDeviceCallbacks) {
+ m_pAdvertisedDeviceCallbacks->onResult(advertisedDevice);
+ }
+
+ m_scanResults.m_vectorAdvertisedDevices.push_back(advertisedDevice);
+
+ break;
+ } // ESP_GAP_SEARCH_INQ_RES_EVT
+
+ default: {
+ break;
+ }
+ } // switch - search_evt
+
+
+ break;
+ } // ESP_GAP_BLE_SCAN_RESULT_EVT
+
+ default: {
+ break;
+ } // default
+ } // End switch
+} // gapEventHandler
+
+
+/*
+void BLEScan::onResults() {
+ ESP_LOGD(LOG_TAG, ">> onResults: default");
+ ESP_LOGD(LOG_TAG, "<< onResults");
+} // onResults
+*/
+
+
+/**
+ * @brief Should we perform an active or passive scan?
+ * The default is a passive scan. An active scan means that we will wish a scan response.
+ * @param [in] active If true, we perform an active scan otherwise a passive scan.
+ * @return N/A.
+ */
+void BLEScan::setActiveScan(bool active) {
+ if (active) {
+ m_scan_params.scan_type = BLE_SCAN_TYPE_ACTIVE;
+ } else {
+ m_scan_params.scan_type = BLE_SCAN_TYPE_PASSIVE;
+ }
+} // setActiveScan
+
+
+/**
+ * @brief Set the call backs to be invoked.
+ * @param [in] pAdvertisedDeviceCallbacks Call backs to be invoked.
+ */
+void BLEScan::setAdvertisedDeviceCallbacks(BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks) {
+ m_pAdvertisedDeviceCallbacks = pAdvertisedDeviceCallbacks;
+} // setAdvertisedDeviceCallbacks
+
+
+/**
+ * @brief Set the interval to scan.
+ * @param [in] The interval in msecs.
+ */
+void BLEScan::setInterval(uint16_t intervalMSecs) {
+ m_scan_params.scan_interval = intervalMSecs / 0.625;
+} // setInterval
+
+
+/**
+ * @brief Set the window to actively scan.
+ * @param [in] windowMSecs How long to actively scan.
+ */
+void BLEScan::setWindow(uint16_t windowMSecs) {
+ m_scan_params.scan_window = windowMSecs / 0.625;
+} // setWindow
+
+
+/**
+ * @brief Start scanning.
+ * @param [in] duration The duration in seconds for which to scan.
+ * @return N/A.
+ */
+BLEScanResults BLEScan::start(uint32_t duration) {
+ ESP_LOGD(LOG_TAG, ">> start(%d)", duration);
+
+ m_semaphoreScanEnd.take("start");
+
+ m_scanResults.m_vectorAdvertisedDevices.empty();
+
+ esp_err_t errRc = ::esp_ble_gap_set_scan_params(&m_scan_params);
+
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_ble_gap_set_scan_params: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc));
+ m_semaphoreScanEnd.give();
+ return m_scanResults;
+ }
+
+ errRc = ::esp_ble_gap_start_scanning(duration);
+
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_ble_gap_start_scanning: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc));
+ m_semaphoreScanEnd.give();
+ return m_scanResults;
+ }
+
+ m_stopped = false;
+
+ m_semaphoreScanEnd.take("start");
+ m_semaphoreScanEnd.give();
+
+ ESP_LOGD(LOG_TAG, "<< start()");
+ return m_scanResults;
+} // start
+
+
+/**
+ * @brief Stop an in progress scan.
+ * @return N/A.
+ */
+void BLEScan::stop() {
+ ESP_LOGD(LOG_TAG, ">> stop()");
+
+ esp_err_t errRc = ::esp_ble_gap_stop_scanning();
+
+ m_stopped = true;
+
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_ble_gap_stop_scanning: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc));
+ return;
+ }
+
+ m_semaphoreScanEnd.give();
+
+ ESP_LOGD(LOG_TAG, "<< stop()");
+} // stop
+
+
+/**
+ * @brief Return the count of devices found in the last scan.
+ * @return The number of devices found in the last scan.
+ */
+int BLEScanResults::getCount() {
+ return m_vectorAdvertisedDevices.size();
+} // getCount
+
+
+/**
+ * @brief Return the specified device at the given index.
+ * The index should be between 0 and getCount()-1.
+ * @param [in] i The index of the device.
+ * @return The device at the specified index.
+ */
+BLEAdvertisedDevice BLEScanResults::getDevice(uint32_t i) {
+ return m_vectorAdvertisedDevices.at(i);
+}
+
+#endif /* CONFIG_BT_ENABLED */
diff --git a/src/BLEScan.h b/src/BLEScan.h
new file mode 100644
index 0000000..f9575ea
--- /dev/null
+++ b/src/BLEScan.h
@@ -0,0 +1,67 @@
+/*
+ * BLEScan.h
+ *
+ * Created on: Jul 1, 2017
+ * Author: kolban
+ */
+
+#ifndef COMPONENTS_CPP_UTILS_BLESCAN_H_
+#define COMPONENTS_CPP_UTILS_BLESCAN_H_
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+#include <esp_gap_ble_api.h>
+
+#include <vector>
+#include "BLEAdvertisedDevice.h"
+#include "BLEClient.h"
+#include "FreeRTOS.h"
+
+class BLEAdvertisedDevice;
+class BLEAdvertisedDeviceCallbacks;
+class BLEClient;
+class BLEScan;
+
+class BLEScanResults {
+public:
+ int getCount();
+ BLEAdvertisedDevice getDevice(uint32_t i);
+private:
+ friend BLEScan;
+ std::vector<BLEAdvertisedDevice> m_vectorAdvertisedDevices;
+};
+
+/**
+ * @brief Perform and manage %BLE scans.
+ *
+ * Scanning is associated with a %BLE client that is attempting to locate BLE servers.
+ */
+class BLEScan {
+public:
+ BLEScan();
+
+ //virtual void onResults();
+ void setActiveScan(bool active);
+ void setAdvertisedDeviceCallbacks(BLEAdvertisedDeviceCallbacks* pAdvertisedDeviceCallbacks);
+ void setInterval(uint16_t intervalMSecs);
+ void setWindow(uint16_t windowMSecs);
+ BLEScanResults start(uint32_t duration);
+ void stop();
+
+private:
+ friend class BLEDevice;
+ void gapEventHandler(
+ esp_gap_ble_cb_event_t event,
+ esp_ble_gap_cb_param_t* param);
+ void parseAdvertisement(BLEClient* pRemoteDevice, uint8_t *payload);
+
+
+ esp_ble_scan_params_t m_scan_params;
+ BLEAdvertisedDeviceCallbacks* m_pAdvertisedDeviceCallbacks;
+ bool m_stopped;
+ FreeRTOS::Semaphore m_semaphoreScanEnd = FreeRTOS::Semaphore("ScanEnd");
+ //std::vector<BLEAdvertisedDevice*> m_vectorAvdertisedDevices;
+ BLEScanResults m_scanResults;
+}; // BLEScan
+
+#endif /* CONFIG_BT_ENABLED */
+#endif /* COMPONENTS_CPP_UTILS_BLESCAN_H_ */
diff --git a/src/BLEServer.cpp b/src/BLEServer.cpp
new file mode 100644
index 0000000..be9773d
--- /dev/null
+++ b/src/BLEServer.cpp
@@ -0,0 +1,353 @@
+/*
+ * BLEServer.cpp
+ *
+ * Created on: Apr 16, 2017
+ * Author: kolban
+ */
+
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+#include <esp_log.h>
+#include <bt.h>
+#include <esp_bt_main.h>
+#include <esp_gap_ble_api.h>
+#include <esp_gatts_api.h>
+#include "BLEDevice.h"
+#include "BLEServer.h"
+#include "BLEService.h"
+#include "BLEUtils.h"
+#include <string.h>
+#include <string>
+#include <gatt_api.h>
+#include <unordered_set>
+
+static const char* LOG_TAG = "BLEServer";
+
+
+/**
+ * @brief Construct a %BLE Server
+ *
+ * This class is not designed to be individually instantiated. Instead one should create a server by asking
+ * the BLEDevice class.
+ */
+BLEServer::BLEServer() {
+ m_appId = -1;
+ m_gatts_if = -1;
+ m_connectedCount = 0;
+ m_connId = -1;
+ BLEDevice::m_bleServer = this;
+ m_pServerCallbacks = nullptr;
+
+ createApp(0);
+} // BLEServer
+
+
+void BLEServer::createApp(uint16_t appId) {
+ m_appId = appId;
+ registerApp();
+}
+
+
+/**
+ * @brief Create a %BLE Service.
+ *
+ * With a %BLE server, we can host one or more services. Invoking this function causes the creation of a definition
+ * of a new service. Every service must have a unique UUID.
+ * @param [in] uuid The UUID of the new service.
+ * @return A reference to the new service object.
+ */
+BLEService* BLEServer::createService(const char* uuid) {
+ return createService(BLEUUID(uuid));
+}
+
+
+/**
+ * @brief Create a %BLE Service.
+ *
+ * With a %BLE server, we can host one or more services. Invoking this function causes the creation of a definition
+ * of a new service. Every service must have a unique UUID.
+ * @param [in] uuid The UUID of the new service.
+ * @return A reference to the new service object.
+ */
+BLEService* BLEServer::createService(BLEUUID uuid) {
+ ESP_LOGD(LOG_TAG, ">> createService - %s", uuid.toString().c_str());
+ m_semaphoreCreateEvt.take("createService");
+
+ // Check that a service with the supplied UUID does not already exist.
+ if (m_serviceMap.getByUUID(uuid) != nullptr) {
+ ESP_LOGE(LOG_TAG, "<< Attempt to create a new service with uuid %s but a service with that UUID already exists.",
+ uuid.toString().c_str());
+ m_semaphoreCreateEvt.give();
+ return nullptr;
+ }
+
+ BLEService* pService = new BLEService(uuid);
+ m_serviceMap.setByUUID(uuid, pService); // Save a reference to this service being on this server.
+ pService->executeCreate(this); // Perform the API calls to actually create the service.
+
+ m_semaphoreCreateEvt.wait("createService");
+
+ ESP_LOGD(LOG_TAG, "<< createService");
+ return pService;
+} // createService
+
+
+/**
+ * @brief Retrieve the advertising object that can be used to advertise the existence of the server.
+ *
+ * @return An advertising object.
+ */
+BLEAdvertising* BLEServer::getAdvertising() {
+ return &m_bleAdvertising;
+}
+
+uint16_t BLEServer::getConnId() {
+ return m_connId;
+}
+
+
+/**
+ * @brief Return the number of connected clients.
+ * @return The number of connected clients.
+ */
+uint32_t BLEServer::getConnectedCount() {
+ return m_connectedCount;
+} // getConnectedCount
+
+
+uint16_t BLEServer::getGattsIf() {
+ return m_gatts_if;
+}
+
+/**
+ * @brief Handle a receiver GAP event.
+ *
+ * @param [in] event
+ * @param [in] param
+ */
+void BLEServer::handleGAPEvent(
+ esp_gap_ble_cb_event_t event,
+ esp_ble_gap_cb_param_t* param) {
+ ESP_LOGD(LOG_TAG, "BLEServer ... handling GAP event!");
+ switch(event) {
+ case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: {
+ /*
+ esp_ble_adv_params_t adv_params;
+ adv_params.adv_int_min = 0x20;
+ adv_params.adv_int_max = 0x40;
+ adv_params.adv_type = ADV_TYPE_IND;
+ adv_params.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
+ adv_params.channel_map = ADV_CHNL_ALL;
+ adv_params.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY;
+ ESP_LOGD(tag, "Starting advertising");
+ esp_err_t errRc = ::esp_ble_gap_start_advertising(&adv_params);
+ if (errRc != ESP_OK) {
+ ESP_LOGE(tag, "esp_ble_gap_start_advertising: rc=%d %s", errRc, espToString(errRc));
+ return;
+ }
+ */
+ break;
+ }
+ default:
+ break;
+ }
+} // handleGAPEvent
+
+
+
+/**
+ * @brief Handle a GATT Server Event.
+ *
+ * @param [in] event
+ * @param [in] gatts_if
+ * @param [in] param
+ *
+ */
+void BLEServer::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());
+
+ // Invoke the handler for every Service we have.
+ m_serviceMap.handleGATTServerEvent(event, gatts_if, param);
+
+ switch(event) {
+
+
+ // ESP_GATTS_CONNECT_EVT
+ // connect:
+ // - uint16_t conn_id
+ // - esp_bd_addr_t remote_bda
+ // - bool is_connected
+ case ESP_GATTS_CONNECT_EVT: {
+ m_connId = param->connect.conn_id; // Save the connection id.
+ if (m_pServerCallbacks != nullptr) {
+ m_pServerCallbacks->onConnect(this);
+ }
+ m_connectedCount++;
+ break;
+ } // ESP_GATTS_CONNECT_EVT
+
+
+ // ESP_GATTS_REG_EVT
+ // reg:
+ // - esp_gatt_status_t status
+ // - uint16_t app_id
+ case ESP_GATTS_REG_EVT: {
+ m_gatts_if = gatts_if;
+
+ m_semaphoreRegisterAppEvt.give();
+ break;
+ } // ESP_GATTS_REG_EVT
+
+
+ // ESP_GATTS_CREATE_EVT
+ // Called when a new service is registered as having been created.
+ //
+ // create:
+ // * esp_gatt_status_t status
+ // * uint16_t service_handle
+ // * esp_gatt_srvc_id_t service_id
+ //
+ case ESP_GATTS_CREATE_EVT: {
+ BLEService* pService = m_serviceMap.getByUUID(param->create.service_id.id.uuid);
+ m_serviceMap.setByHandle(param->create.service_handle, pService);
+ //pService->setHandle(param->create.service_handle);
+ m_semaphoreCreateEvt.give();
+ break;
+ } // ESP_GATTS_CREATE_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: {
+ break;
+ } // ESP_GATTS_READ_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: {
+ break;
+ }
+
+ // ESP_GATTS_DISCONNECT_EVT
+ // If we receive a disconnect event then invoke the callback for disconnects (if one is present).
+ // we also want to start advertising again.
+ case ESP_GATTS_DISCONNECT_EVT: {
+ m_connectedCount--;
+ if (m_pServerCallbacks != nullptr) {
+ m_pServerCallbacks->onDisconnect(this);
+ }
+ startAdvertising();
+ break;
+ } // ESP_GATTS_DISCONNECT_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: {
+ break;
+ } // ESP_GATTS_ADD_CHAR_EVT
+
+
+ default: {
+ break;
+ }
+ }
+ ESP_LOGD(LOG_TAG, "<< handleGATTServerEvent");
+} // handleGATTServerEvent
+
+
+/**
+ * @brief Register the app.
+ *
+ * @return N/A
+ */
+void BLEServer::registerApp() {
+ ESP_LOGD(LOG_TAG, ">> registerApp - %d", m_appId);
+ m_semaphoreRegisterAppEvt.take("registerApp"); // Take the mutex, will be released by ESP_GATTS_REG_EVT event.
+ ::esp_ble_gatts_app_register(m_appId);
+ m_semaphoreRegisterAppEvt.wait("registerApp");
+ ESP_LOGD(LOG_TAG, "<< registerApp");
+} // registerApp
+
+
+/**
+ * @brief Set the callbacks.
+ *
+ * As a %BLE server operates, it will generate server level events such as a new client connecting or a previous client
+ * disconnecting. This function can be called to register a callback handler that will be invoked when these
+ * events are detected.
+ *
+ * @param [in] pCallbacks The callbacks to be invoked.
+ */
+void BLEServer::setCallbacks(BLEServerCallbacks* pCallbacks) {
+ m_pServerCallbacks = pCallbacks;
+} // setCallbacks
+
+
+/**
+ * @brief Start advertising.
+ *
+ * Start the server advertising its existence. This is a convenience function and is equivalent to
+ * retrieving the advertising object and invoking start upon it.
+ */
+void BLEServer::startAdvertising() {
+ ESP_LOGD(LOG_TAG, ">> startAdvertising");
+ m_bleAdvertising.start();
+ ESP_LOGD(LOG_TAG, "<< startAdvertising");
+} // startAdvertising
+
+
+/*
+void BLEServer::addCharacteristic(BLECharacteristic *characteristic, BLEService *pService) {
+ ESP_LOGD(tag, "Adding characteristic (esp_ble_gatts_add_char): uuid=%s, serviceHandle=0x%.2x",
+ characteristic->m_bleUUID.toString().c_str(),
+ pService->getHandle());
+
+ m_characteristicMap.setByUUID(characteristic->m_bleUUID, characteristic);
+
+ esp_err_t errRc = ::esp_ble_gatts_add_char(
+ pService->getHandle(),
+ characteristic->getUUID().getNative(),
+ (esp_gatt_perm_t)(ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE),
+ characteristic->getProperties(),
+ &characteristic->m_value,
+ NULL);
+
+ if (errRc != ESP_OK) {
+ ESP_LOGE(tag, "esp_ble_gatts_add_char: rc=%d %s", errRc, espToString(errRc));
+ return;
+ }
+}
+*/
+
+#endif // CONFIG_BT_ENABLED
diff --git a/src/BLEServer.h b/src/BLEServer.h
new file mode 100644
index 0000000..c6307bc
--- /dev/null
+++ b/src/BLEServer.h
@@ -0,0 +1,117 @@
+/*
+ * BLEServer.h
+ *
+ * Created on: Apr 16, 2017
+ * Author: kolban
+ */
+
+#ifndef COMPONENTS_CPP_UTILS_BLESERVER_H_
+#define COMPONENTS_CPP_UTILS_BLESERVER_H_
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+#include <esp_gatts_api.h>
+
+#include <string>
+#include <string.h>
+
+#include "BLEUUID.h"
+#include "BLEAdvertising.h"
+#include "BLECharacteristic.h"
+#include "BLEService.h"
+#include "FreeRTOS.h"
+
+class BLEServerCallbacks;
+
+
+/**
+ * @brief A data structure that manages the %BLE servers owned by a BLE server.
+ */
+class BLEServiceMap {
+public:
+ BLEService* getByHandle(uint16_t handle);
+ BLEService* getByUUID(const char* uuid);
+ BLEService* getByUUID(BLEUUID uuid);
+ void handleGATTServerEvent(
+ esp_gatts_cb_event_t event,
+ esp_gatt_if_t gatts_if,
+ esp_ble_gatts_cb_param_t* param);
+ void setByHandle(uint16_t handle, BLEService* service);
+ void setByUUID(const char* uuid, BLEService* service);
+ void setByUUID(BLEUUID uuid, BLEService* service);
+ std::string toString();
+
+private:
+ std::map<std::string, BLEService*> m_uuidMap;
+ std::map<uint16_t, BLEService*> m_handleMap;
+};
+
+
+/**
+ * @brief The model of a %BLE server.
+ */
+class BLEServer {
+public:
+ BLEServer();
+
+
+ uint32_t getConnectedCount();
+ BLEService* createService(const char* uuid);
+ BLEService* createService(BLEUUID uuid);
+ BLEAdvertising* getAdvertising();
+ void setCallbacks(BLEServerCallbacks *pCallbacks);
+ void startAdvertising();
+
+
+private:
+ friend class BLEService;
+ friend class BLECharacteristic;
+ friend class BLEDevice;
+ esp_ble_adv_data_t m_adv_data;
+ uint16_t m_appId;
+ BLEAdvertising m_bleAdvertising;
+ uint16_t m_connId;
+ uint32_t m_connectedCount;
+ uint16_t m_gatts_if;
+ FreeRTOS::Semaphore m_semaphoreRegisterAppEvt = FreeRTOS::Semaphore("RegisterAppEvt");
+ FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt");
+ BLEServiceMap m_serviceMap;
+ BLEServerCallbacks* m_pServerCallbacks;
+
+ void createApp(uint16_t appId);
+ uint16_t getConnId();
+ uint16_t getGattsIf();
+ void handleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
+ void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
+ void registerApp();
+}; // BLEServer
+
+
+/**
+ * @brief Callbacks associated with the operation of a %BLE server.
+ */
+class BLEServerCallbacks {
+public:
+ virtual ~BLEServerCallbacks() {};
+ /**
+ * @brief Handle a new client connection.
+ *
+ * When a new client connects, we are invoked.
+ *
+ * @param [in] pServer A reference to the %BLE server that received the client connection.
+ */
+ virtual void onConnect(BLEServer* pServer);
+
+ /**
+ * @brief Handle an existing client disconnection.
+ *
+ * When an existing client disconnects, we are invoked.
+ *
+ * @param [in] pServer A reference to the %BLE server that received the existing client disconnection.
+ */
+ virtual void onDisconnect(BLEServer* pServer);
+}; // BLEServerCallbacks
+
+
+
+#endif /* CONFIG_BT_ENABLED */
+#endif /* COMPONENTS_CPP_UTILS_BLESERVER_H_ */
diff --git a/src/BLEServerCallbacks.cpp b/src/BLEServerCallbacks.cpp
new file mode 100644
index 0000000..8808720
--- /dev/null
+++ b/src/BLEServerCallbacks.cpp
@@ -0,0 +1,22 @@
+/*
+ * BLEServerCallbacks.cpp
+ *
+ * Created on: Jul 4, 2017
+ * Author: kolban
+ */
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+#include "BLEServer.h"
+#include <esp_log.h>
+static const char* LOG_TAG = "BLEServerCallbacks";
+
+void BLEServerCallbacks::onConnect(BLEServer* pServer) {
+ ESP_LOGD(LOG_TAG, ">> onConnect(): Default");
+ ESP_LOGD(LOG_TAG, "<< onConnect()");
+}
+
+void BLEServerCallbacks::onDisconnect(BLEServer* pServer) {
+ ESP_LOGD(LOG_TAG, ">> onDisconnect(): Default");
+ ESP_LOGD(LOG_TAG, "<< onDisconnect()");
+}
+#endif /* CONFIG_BT_ENABLED */
diff --git a/src/BLEService.cpp b/src/BLEService.cpp
new file mode 100644
index 0000000..ec16db8
--- /dev/null
+++ b/src/BLEService.cpp
@@ -0,0 +1,353 @@
+/*
+ * BLEService.cpp
+ *
+ * Created on: Mar 25, 2017
+ * Author: kolban
+ */
+
+// A service is identified by a UUID. A service is also the container for one or more characteristics.
+
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+#include <esp_err.h>
+#include <esp_gatts_api.h>
+#include <esp_log.h>
+
+#include <iomanip>
+#include <sstream>
+#include <string>
+
+#include "BLEServer.h"
+#include "BLEService.h"
+#include "BLEUtils.h"
+#include "GeneralUtils.h"
+
+#define NULL_HANDLE (0xffff)
+
+static const char* LOG_TAG = "BLEService"; // Tag for logging.
+
+/**
+ * @brief Construct an instance of the BLEService
+ * @param [in] uuid The UUID of the service.
+ */
+BLEService::BLEService(const char* uuid) : BLEService(BLEUUID(uuid)) {
+}
+
+
+/**
+ * @brief Construct an instance of the BLEService
+ * @param [in] uuid The UUID of the service.
+ */
+BLEService::BLEService(BLEUUID uuid) {
+ m_uuid = uuid;
+ m_handle = NULL_HANDLE;
+ m_pServer = nullptr;
+ //m_serializeMutex.setName("BLEService");
+ m_lastCreatedCharacteristic = nullptr;
+} // BLEService
+
+
+/**
+ * @brief Create the service.
+ * Create the service.
+ * @param [in] gatts_if The handle of the GATT server interface.
+ * @return N/A.
+ */
+void BLEService::executeCreate(BLEServer *pServer) {
+ ESP_LOGD(LOG_TAG, ">> executeCreate() - Creating service (esp_ble_gatts_create_service) service uuid: %s", getUUID().toString().c_str());
+
+ m_pServer = pServer;
+ esp_gatt_srvc_id_t srvc_id;
+ srvc_id.id.inst_id = 0;
+ srvc_id.id.uuid = *m_uuid.getNative();
+
+ m_semaphoreCreateEvt.take("executeCreate"); // Take the mutex and release at event ESP_GATTS_CREATE_EVT
+
+ esp_err_t errRc = ::esp_ble_gatts_create_service(getServer()->getGattsIf(), &srvc_id, 10);
+
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "esp_ble_gatts_create_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ return;
+ }
+
+ m_semaphoreCreateEvt.wait("executeCreate");
+
+ ESP_LOGD(LOG_TAG, "<< executeCreate");
+} // executeCreate
+
+
+/**
+ * @brief Dump details of this BLE GATT service.
+ * @return N/A.
+ */
+void BLEService::dump() {
+ ESP_LOGD(LOG_TAG, "Service: uuid:%s, handle: 0x%.2x",
+ m_uuid.toString().c_str(),
+ m_handle);
+ ESP_LOGD(LOG_TAG, "Characteristics:\n%s", m_characteristicMap.toString().c_str());
+} // dump
+
+/*
+void BLEService::setService(esp_gatt_srvc_id_t srvc_id) {
+ m_srvc_id = srvc_id;
+}
+*/
+
+/*
+esp_gatt_srvc_id_t BLEService::getService() {
+ return m_srvc_id;
+}
+*/
+
+
+/**
+ * @brief Get the UUID of the service.
+ * @return the UUID of the service.
+ */
+BLEUUID BLEService::getUUID() {
+ return m_uuid;
+} // getUUID
+
+
+/**
+ * @brief Start the service.
+ * Here we wish to start the service which means that we will respond to partner requests about it.
+ * Starting a service also means that we can create the corresponding characteristics.
+ * @return Start the service.
+ */
+void BLEService::start() {
+// We ask the BLE runtime to start the service and then create each of the characteristics.
+// We start the service through its local handle which was returned in the ESP_GATTS_CREATE_EVT event
+// obtained as a result of calling esp_ble_gatts_create_service().
+//
+ ESP_LOGD(LOG_TAG, ">> start(): Starting service (esp_ble_gatts_start_service): %s", toString().c_str());
+ if (m_handle == NULL_HANDLE) {
+ ESP_LOGE(LOG_TAG, "<< !!! We attempted to start a service but don't know its handle!");
+ return;
+ }
+
+
+ BLECharacteristic *pCharacteristic = m_characteristicMap.getFirst();
+
+ while(pCharacteristic != nullptr) {
+ m_lastCreatedCharacteristic = pCharacteristic;
+ pCharacteristic->executeCreate(this);
+
+ pCharacteristic = m_characteristicMap.getNext();
+ }
+ // Start each of the characteristics ... these are found in the m_characteristicMap.
+
+ m_semaphoreStartEvt.take("start");
+ esp_err_t errRc = ::esp_ble_gatts_start_service(m_handle);
+
+ if (errRc != ESP_OK) {
+ ESP_LOGE(LOG_TAG, "<< esp_ble_gatts_start_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc));
+ return;
+ }
+ m_semaphoreStartEvt.wait("start");
+
+ ESP_LOGD(LOG_TAG, "<< start()");
+} // start
+
+
+/**
+ * @brief Set the handle associated with this service.
+ * @param [in] handle The handle associated with the service.
+ */
+void BLEService::setHandle(uint16_t handle) {
+ ESP_LOGD(LOG_TAG, ">> setHandle - Handle=0x%.2x, service UUID=%s)", handle, getUUID().toString().c_str());
+ if (m_handle != NULL_HANDLE) {
+ ESP_LOGE(LOG_TAG, "!!! Handle is already set %.2x", m_handle);
+ return;
+ }
+ m_handle = handle;
+ ESP_LOGD(LOG_TAG, "<< setHandle");
+} // setHandle
+
+
+/**
+ * @brief Get the handle associated with this service.
+ * @return The handle associated with this service.
+ */
+uint16_t BLEService::getHandle() {
+ return m_handle;
+} // getHandle
+
+
+/**
+ * @brief Add a characteristic to the service.
+ * @param [in] pCharacteristic A pointer to the characteristic to be added.
+ */
+void BLEService::addCharacteristic(BLECharacteristic* pCharacteristic) {
+// We maintain a mapping of characteristics owned by this service. These are managed by the
+// BLECharacteristicMap class instance found in m_characteristicMap. We add the characteristic
+// to the map and then ask the service to add the characteristic at the BLE level (ESP-IDF).
+//
+ ESP_LOGD(LOG_TAG, ">> addCharacteristic()");
+ ESP_LOGD(LOG_TAG, "Adding characteristic (esp_ble_gatts_add_char): uuid=%s to service: %s",
+ pCharacteristic->getUUID().toString().c_str(),
+ toString().c_str());
+
+ // Check that we don't add the same characteristic twice.
+ if (m_characteristicMap.getByUUID(pCharacteristic->getUUID()) != nullptr) {
+ ESP_LOGE(LOG_TAG, "<< Attempt to add a characteristic but we already have one with this UUID");
+ return;
+ }
+
+ // Remember this characteristic in our map of characteristics. At this point, we can lookup by UUID
+ // but not by handle. The handle is allocated to us on the ESP_GATTS_ADD_CHAR_EVT.
+ m_characteristicMap.setByUUID(pCharacteristic->getUUID(), pCharacteristic);
+
+ ESP_LOGD(LOG_TAG, "<< addCharacteristic()");
+} // addCharacteristic
+
+
+/**
+ * @brief Create a new BLE Characteristic associated with this service.
+ * @param [in] uuid - The UUID of the characteristic.
+ * @param [in] properties - The properties of the characteristic.
+ * @return The new BLE characteristic.
+ */
+BLECharacteristic* BLEService::createCharacteristic(const char* uuid, uint32_t properties) {
+ return createCharacteristic(BLEUUID(uuid), properties);
+}
+
+/**
+ * @brief Create a new BLE Characteristic associated with this service.
+ * @param [in] uuid - The UUID of the characteristic.
+ * @param [in] properties - The properties of the characteristic.
+ * @return The new BLE characteristic.
+ */
+BLECharacteristic* BLEService::createCharacteristic(BLEUUID uuid, uint32_t properties) {
+ BLECharacteristic *pCharacteristic = new BLECharacteristic(uuid, properties);
+ addCharacteristic(pCharacteristic);
+ return pCharacteristic;
+} // createCharacteristic
+
+
+/**
+ * @brief Handle a GATTS server event.
+ */
+void BLEService::handleGATTServerEvent(
+ esp_gatts_cb_event_t event,
+ esp_gatt_if_t gatts_if,
+ esp_ble_gatts_cb_param_t *param) {
+
+
+ switch(event) {
+ // 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
+
+ // If we have reached the correct service, then locate the characteristic and remember the handle
+ // for that characteristic.
+ case ESP_GATTS_ADD_CHAR_EVT: {
+ if (m_handle == param->add_char.service_handle) {
+ BLECharacteristic *pCharacteristic = getCharacteristic(BLEUUID(param->add_char.char_uuid));
+ if (pCharacteristic == nullptr) {
+ ESP_LOGE(LOG_TAG, "Expected to find characteristic with UUID: %s, but didnt!",
+ BLEUUID(param->add_char.char_uuid).toString().c_str());
+ dump();
+ m_semaphoreAddCharEvt.give();
+ break;
+ }
+ pCharacteristic->setHandle(param->add_char.attr_handle);
+ m_characteristicMap.setByHandle(param->add_char.attr_handle, pCharacteristic);
+ //ESP_LOGD(tag, "Characteristic map: %s", m_characteristicMap.toString().c_str());
+ m_semaphoreAddCharEvt.give();
+ break;
+ } // Reached the correct service.
+ break;
+ } // ESP_GATTS_ADD_CHAR_EVT
+
+ // ESP_GATTS_START_EVT
+ //
+ // start:
+ // esp_gatt_status_t status
+ // uint16_t service_handle
+ case ESP_GATTS_START_EVT: {
+ if (param->start.service_handle == getHandle()) {
+ m_semaphoreStartEvt.give();
+ }
+ break;
+ } // ESP_GATTS_START_EVT
+
+
+ // ESP_GATTS_CREATE_EVT
+ // Called when a new service is registered as having been created.
+ //
+ // create:
+ // * esp_gatt_status_t status
+ // * uint16_t service_handle
+ // * esp_gatt_srvc_id_t service_id
+ // * - esp_gatt_id id
+ // * - esp_bt_uuid uuid
+ // * - uint8_t inst_id
+ // * - bool is_primary
+ //
+ case ESP_GATTS_CREATE_EVT: {
+ if (getUUID().equals(BLEUUID(param->create.service_id.id.uuid))) {
+ setHandle(param->create.service_handle);
+ m_semaphoreCreateEvt.give();
+ }
+ break;
+ } // ESP_GATTS_CREATE_EVT
+
+ default: {
+ break;
+ } // Default
+ } // Switch
+
+ m_characteristicMap.handleGATTServerEvent(event, gatts_if, param);
+} // handleGATTServerEvent
+
+
+BLECharacteristic* BLEService::getCharacteristic(const char* uuid) {
+ return getCharacteristic(BLEUUID(uuid));
+}
+
+
+BLECharacteristic* BLEService::getCharacteristic(BLEUUID uuid) {
+ return m_characteristicMap.getByUUID(uuid);
+}
+
+
+/**
+ * @brief Return a string representation of this service.
+ * A service is defined by:
+ * * Its UUID
+ * * Its handle
+ * @return A string representation of this service.
+ */
+std::string BLEService::toString() {
+ std::stringstream stringStream;
+ stringStream << "UUID: " << getUUID().toString() <<
+ ", handle: 0x" << std::hex << std::setfill('0') << std::setw(2) << getHandle();
+ return stringStream.str();
+} // toString
+
+
+/**
+ * @brief Get the last created characteristic.
+ * It is lamentable that this function has to exist. It returns the last created characteristic.
+ * We need this because the descriptor API is built around the notion that a new descriptor, when created,
+ * is associated with the last characteristics created and we need that information.
+ * @return The last created characteristic.
+ */
+BLECharacteristic* BLEService::getLastCreatedCharacteristic() {
+ return m_lastCreatedCharacteristic;
+} // getLastCreatedCharacteristic
+
+
+/**
+ * @brief Get the BLE server associated with this service.
+ * @return The BLEServer associated with this service.
+ */
+BLEServer* BLEService::getServer() {
+ return m_pServer;
+} // getServer
+
+#endif // CONFIG_BT_ENABLED
diff --git a/src/BLEService.h b/src/BLEService.h
new file mode 100644
index 0000000..86d0776
--- /dev/null
+++ b/src/BLEService.h
@@ -0,0 +1,99 @@
+/*
+ * BLEService.h
+ *
+ * Created on: Mar 25, 2017
+ * Author: kolban
+ */
+
+#ifndef COMPONENTS_CPP_UTILS_BLESERVICE_H_
+#define COMPONENTS_CPP_UTILS_BLESERVICE_H_
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+
+#include <esp_gatts_api.h>
+
+#include "BLECharacteristic.h"
+#include "BLEServer.h"
+#include "BLEUUID.h"
+#include "FreeRTOS.h"
+
+class BLEServer;
+
+/**
+ * @brief A data mapping used to manage the set of %BLE characteristics known to the server.
+ */
+class BLECharacteristicMap {
+public:
+ void setByUUID(const char* uuid, BLECharacteristic* pCharacteristic);
+ void setByUUID(BLEUUID uuid, BLECharacteristic* pCharacteristic);
+ void setByHandle(uint16_t handle, BLECharacteristic* pCharacteristic);
+ BLECharacteristic* getByUUID(const char* uuid);
+ BLECharacteristic* getByUUID(BLEUUID uuid);
+ BLECharacteristic* getByHandle(uint16_t handle);
+ BLECharacteristic* getFirst();
+ BLECharacteristic* getNext();
+ std::string toString();
+ void handleGATTServerEvent(
+ esp_gatts_cb_event_t event,
+ esp_gatt_if_t gatts_if,
+ esp_ble_gatts_cb_param_t* param);
+
+
+private:
+ std::map<std::string, BLECharacteristic*> m_uuidMap;
+ std::map<uint16_t, BLECharacteristic*> m_handleMap;
+ std::map<std::string, BLECharacteristic*>::iterator m_iterator;
+};
+
+
+/**
+ * @brief The model of a %BLE service.
+ *
+ */
+class BLEService {
+public:
+ BLEService(const char* uuid);
+ BLEService(BLEUUID uuid);
+
+ void addCharacteristic(BLECharacteristic* pCharacteristic);
+ BLECharacteristic* createCharacteristic(const char* uuid, uint32_t properties);
+ BLECharacteristic* createCharacteristic(BLEUUID uuid, uint32_t properties);
+ void dump();
+ void executeCreate(BLEServer* pServer);
+ BLECharacteristic* getCharacteristic(const char* uuid);
+ BLECharacteristic* getCharacteristic(BLEUUID uuid);
+ BLEUUID getUUID();
+ BLEServer* getServer();
+ void start();
+ std::string toString();
+
+private:
+ friend class BLEServer;
+ friend class BLEServiceMap;
+ friend class BLEDescriptor;
+ friend class BLECharacteristic;
+ friend class BLEDevice;
+
+ BLECharacteristicMap m_characteristicMap;
+ uint16_t m_handle;
+ BLECharacteristic* m_lastCreatedCharacteristic;
+ BLEServer* m_pServer;
+ //FreeRTOS::Semaphore m_serializeMutex;
+ FreeRTOS::Semaphore m_semaphoreAddCharEvt = FreeRTOS::Semaphore("AddCharEvt");
+ FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt");
+ FreeRTOS::Semaphore m_semaphoreStartEvt = FreeRTOS::Semaphore("StartEvt");
+ BLEUUID m_uuid;
+
+ uint16_t getHandle();
+ BLECharacteristic* getLastCreatedCharacteristic();
+ void handleGATTServerEvent(
+ esp_gatts_cb_event_t event,
+ esp_gatt_if_t gatts_if,
+ esp_ble_gatts_cb_param_t* param);
+ void setHandle(uint16_t handle);
+ //void setService(esp_gatt_srvc_id_t srvc_id);
+}; // BLEService
+
+
+#endif // CONFIG_BT_ENABLED
+#endif /* COMPONENTS_CPP_UTILS_BLESERVICE_H_ */
diff --git a/src/BLEServiceMap.cpp b/src/BLEServiceMap.cpp
new file mode 100644
index 0000000..8fdbd5a
--- /dev/null
+++ b/src/BLEServiceMap.cpp
@@ -0,0 +1,95 @@
+/*
+ * BLEServiceMap.cpp
+ *
+ * Created on: Jun 22, 2017
+ * Author: kolban
+ */
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+#include <sstream>
+#include <iomanip>
+#include "BLEService.h"
+
+
+/**
+ * @brief Return the service by UUID.
+ * @param [in] UUID The UUID to look up the service.
+ * @return The characteristic.
+ */
+BLEService* BLEServiceMap::getByUUID(const char* uuid) {
+ return getByUUID(BLEUUID(uuid));
+}
+
+/**
+ * @brief Return the service by UUID.
+ * @param [in] UUID The UUID to look up the service.
+ * @return The characteristic.
+ */
+BLEService* BLEServiceMap::getByUUID(BLEUUID uuid) {
+ for (auto &myPair : m_uuidMap) {
+ if (myPair.second->getUUID().equals(uuid)) {
+ return myPair.second;
+ }
+ }
+ //return m_uuidMap.at(uuid.toString());
+ return nullptr;
+} // getByUUID
+
+
+/**
+ * @brief Return the service by handle.
+ * @param [in] handle The handle to look up the service.
+ * @return The service.
+ */
+BLEService* BLEServiceMap::getByHandle(uint16_t handle) {
+ return m_handleMap.at(handle);
+} // getByHandle
+
+
+/**
+ * @brief Set the service by UUID.
+ * @param [in] uuid The uuid of the service.
+ * @param [in] characteristic The service to cache.
+ * @return N/A.
+ */
+void BLEServiceMap::setByUUID(BLEUUID uuid,
+ BLEService *service) {
+ m_uuidMap.insert(std::pair<std::string, BLEService *>(uuid.toString(), service));
+} // setByUUID
+
+
+/**
+ * @brief Set the service by handle.
+ * @param [in] handle The handle of the service.
+ * @param [in] service The service to cache.
+ * @return N/A.
+ */
+void BLEServiceMap::setByHandle(uint16_t handle,
+ BLEService* service) {
+ m_handleMap.insert(std::pair<uint16_t, BLEService *>(handle, service));
+} // setByHandle
+
+
+/**
+ * @brief Return a string representation of the service map.
+ * @return A string representation of the service map.
+ */
+std::string BLEServiceMap::toString() {
+ std::stringstream stringStream;
+ stringStream << std::hex << std::setfill('0');
+ for (auto &myPair: m_handleMap) {
+ stringStream << "handle: 0x" << std::setw(2) << myPair.first << ", uuid: " + myPair.second->getUUID().toString() << "\n";
+ }
+ return stringStream.str();
+} // toString
+
+void BLEServiceMap::handleGATTServerEvent(
+ esp_gatts_cb_event_t event,
+ esp_gatt_if_t gatts_if,
+ esp_ble_gatts_cb_param_t *param) {
+ // Invoke the handler for every Service we have.
+ for (auto &myPair : m_uuidMap) {
+ myPair.second->handleGATTServerEvent(event, gatts_if, param);
+ }
+}
+#endif /* CONFIG_BT_ENABLED */
diff --git a/src/BLEUUID.cpp b/src/BLEUUID.cpp
new file mode 100644
index 0000000..9a4fe45
--- /dev/null
+++ b/src/BLEUUID.cpp
@@ -0,0 +1,326 @@
+/*
+ * BLEUUID.cpp
+ *
+ * Created on: Jun 21, 2017
+ * Author: kolban
+ */
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+#include <esp_log.h>
+#include <string.h>
+#include <sstream>
+#include <iomanip>
+#include <stdio.h>
+#include "BLEUUID.h"
+static const char* LOG_TAG = "BLEUUID";
+
+
+/**
+ * @brief Copy memory from source to target but in reverse order.
+ *
+ * When we move memory from one location it is normally:
+ *
+ * ```
+ * [0][1][2]...[n] -> [0][1][2]...[n]
+ * ```
+ *
+ * with this function, it is:
+ *
+ * ```
+ * [0][1][2]...[n] -> [n][n-1][n-2]...[0]
+ * ```
+ *
+ * @param [in] target The target of the copy
+ * @param [in] source The source of the copy
+ * @param [in] size The number of bytes to copy
+ */
+static void memrcpy(uint8_t* target, uint8_t* source, uint32_t size) {
+ target+=(size-1); // Point target to the last byte of the target data
+ while (size > 0) {
+ *target = *source;
+ target--;
+ source++;
+ size--;
+ }
+} // memrcpy
+
+
+/**
+ * @brief Create a UUID from a string.
+ *
+ * Create a UUID from a string. There will be two possible stories here. Either the string represents
+ * a binary data field or the string represents a hex encoding of a UUID.
+ * For the hex encoding, here is an example:
+ *
+ * ```
+ * "beb5483e-36e1-4688-b7f5-ea07361b26a8"
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+ * 12345678-90ab-cdef-1234-567890abcdef
+ * ```
+ *
+ * This has a length of 36 characters. We need to parse this into 16 bytes.
+ *
+ * @param [in] value The string to build a UUID from.
+ */
+BLEUUID::BLEUUID(std::string value) {
+ m_valueSet = true;
+ if (value.length() == 2) {
+ m_uuid.len = ESP_UUID_LEN_16;
+ m_uuid.uuid.uuid16 = value[0] | (value[1] << 8);
+ } else if (value.length() == 4) {
+ m_uuid.len = ESP_UUID_LEN_32;
+ m_uuid.uuid.uuid32 = value[0] | (value[1] << 8) | (value[2] << 16) | (value[3] << 24);
+ } else if (value.length() == 16) {
+ m_uuid.len = ESP_UUID_LEN_128;
+ memrcpy(m_uuid.uuid.uuid128, (uint8_t*)value.data(), 16);
+ } else if (value.length() == 36) {
+// If the length of the string is 36 bytes then we will assume it is a long hex string in
+// UUID format.
+ m_uuid.len = ESP_UUID_LEN_128;
+ int vals[16];
+ sscanf(value.c_str(), "%2x%2x%2x%2x-%2x%2x-%2x%2x-%2x%2x-%2x%2x%2x%2x%2x%2x",
+ &vals[15],
+ &vals[14],
+ &vals[13],
+ &vals[12],
+ &vals[11],
+ &vals[10],
+ &vals[9],
+ &vals[8],
+ &vals[7],
+ &vals[6],
+ &vals[5],
+ &vals[4],
+ &vals[3],
+ &vals[2],
+ &vals[1],
+ &vals[0]
+ );
+
+ int i;
+ for (i=0; i<16; i++) {
+ m_uuid.uuid.uuid128[i] = vals[i];
+ }
+ }
+ else {
+ ESP_LOGE(LOG_TAG, "ERROR: UUID value not 2, 4, 16 or 36 bytes");
+ m_valueSet = false;
+ }
+} //BLEUUID(std::string)
+
+
+/**
+ * @brief Create a UUID from 16 bytes of memory.
+ *
+ * @param [in] pData The pointer to the start of the UUID.
+ * @param [in] size The size of the data.
+ * @param [in] msbFirst Is the MSB first in pData memory?
+ */
+BLEUUID::BLEUUID(uint8_t* pData, size_t size, bool msbFirst) {
+ if (size != 16) {
+ ESP_LOGE(LOG_TAG, "ERROR: UUID length not 16 bytes");
+ return;
+ }
+ m_uuid.len = ESP_UUID_LEN_128;
+ if (msbFirst) {
+ memrcpy(m_uuid.uuid.uuid128, pData, 16);
+ } else {
+ memcpy(m_uuid.uuid.uuid128, pData, 16);
+ }
+ m_valueSet = true;
+} // BLEUUID
+
+/**
+ * @brief Create a UUID from the 16bit value.
+ *
+ * @param [in] uuid The 16bit short form UUID.
+ */
+BLEUUID::BLEUUID(uint16_t uuid) {
+ m_uuid.len = ESP_UUID_LEN_16;
+ m_uuid.uuid.uuid16 = uuid;
+ m_valueSet = true;
+} // BLEUUID
+
+
+/**
+ * @brief Create a UUID from the 32bit value.
+ *
+ * @param [in] uuid The 32bit short form UUID.
+ */
+BLEUUID::BLEUUID(uint32_t uuid) {
+ m_uuid.len = ESP_UUID_LEN_32;
+ m_uuid.uuid.uuid32 = uuid;
+ m_valueSet = true;
+} // BLEUUID
+
+
+/**
+ * @brief Create a UUID from the native UUID.
+ *
+ * @param [in] uuid The native UUID.
+ */
+BLEUUID::BLEUUID(esp_bt_uuid_t uuid) {
+ m_uuid = uuid;
+ m_valueSet = true;
+} // BLEUUID
+
+
+/**
+ * @brief Create a UUID from the ESP32 esp_gatt_srvc_id_t.
+ *
+ * @param [in] srvcId The data to create the UUID from.
+ */
+BLEUUID::BLEUUID(esp_gatt_srvc_id_t srcvId) : BLEUUID(srcvId.id.uuid) {
+} // BLEUUID
+
+
+BLEUUID::BLEUUID() {
+ m_valueSet = false;
+} // BLEUUID
+
+
+/**
+ * @brief Compare a UUID against this UUID.
+ *
+ * @param [in] uuid The UUID to compare against.
+ * @return True if the UUIDs are equal and false otherwise.
+ */
+bool BLEUUID::equals(BLEUUID uuid) {
+ //ESP_LOGD(TAG, "Comparing: %s to %s", toString().c_str(), uuid.toString().c_str());
+ if (m_valueSet == false || uuid.m_valueSet == false) {
+ return false;
+ }
+
+ if (uuid.m_uuid.len != m_uuid.len) {
+ return uuid.toString() == toString();
+ }
+
+ if (uuid.m_uuid.len == ESP_UUID_LEN_16) {
+ return uuid.m_uuid.uuid.uuid16 == m_uuid.uuid.uuid16;
+ }
+
+ if (uuid.m_uuid.len == ESP_UUID_LEN_32) {
+ return uuid.m_uuid.uuid.uuid32 == m_uuid.uuid.uuid32;
+ }
+
+ return memcmp(uuid.m_uuid.uuid.uuid128, m_uuid.uuid.uuid128, 16) == 0;
+} // equals
+
+
+/**
+ * @brief Get the native UUID value.
+ *
+ * @return The native UUID value or NULL if not set.
+ */
+esp_bt_uuid_t* BLEUUID::getNative() {
+ //ESP_LOGD(TAG, ">> getNative()")
+ if (m_valueSet == false) {
+ ESP_LOGD(LOG_TAG, "<< Return of un-initialized UUID!");
+ return nullptr;
+ }
+ //ESP_LOGD(TAG, "<< getNative()");
+ return &m_uuid;
+} // getNative
+
+
+/**
+ * @brief Convert a UUID to its 128 bit representation.
+ *
+ * A UUID can be internally represented as 16bit, 32bit or the full 128bit. This method
+ * will convert 16 or 32 bit representations to the full 128bit.
+ */
+BLEUUID BLEUUID::to128() {
+ //ESP_LOGD(LOG_TAG, ">> toFull() - %s", toString().c_str());
+
+ // If we either don't have a value or are already a 128 bit UUID, nothing further to do.
+ if (m_valueSet == false || m_uuid.len == ESP_UUID_LEN_128) {
+ return *this;
+ }
+
+ // If we are 16 bit or 32 bit, then set the 4 bytes of the variable part of the UUID.
+ if (m_uuid.len == ESP_UUID_LEN_16) {
+ uint16_t temp = m_uuid.uuid.uuid16;
+ m_uuid.uuid.uuid128[15] = 0;
+ m_uuid.uuid.uuid128[14] = 0;
+ m_uuid.uuid.uuid128[13] = (temp >> 8) & 0xff;
+ m_uuid.uuid.uuid128[12] = temp & 0xff;
+
+ }
+ else if (m_uuid.len == ESP_UUID_LEN_32) {
+ uint32_t temp = m_uuid.uuid.uuid32;
+ m_uuid.uuid.uuid128[15] = (temp >> 24) & 0xff;
+ m_uuid.uuid.uuid128[14] = (temp >> 16) & 0xff;
+ m_uuid.uuid.uuid128[13] = (temp >> 8) & 0xff;
+ m_uuid.uuid.uuid128[12] = temp & 0xff;
+ }
+
+ // Set the fixed parts of the UUID.
+ m_uuid.uuid.uuid128[11] = 0x00;
+ m_uuid.uuid.uuid128[10] = 0x00;
+
+ m_uuid.uuid.uuid128[9] = 0x10;
+ m_uuid.uuid.uuid128[8] = 0x00;
+
+ m_uuid.uuid.uuid128[7] = 0x80;
+ m_uuid.uuid.uuid128[6] = 0x00;
+
+ m_uuid.uuid.uuid128[5] = 0x00;
+ m_uuid.uuid.uuid128[4] = 0x80;
+ m_uuid.uuid.uuid128[3] = 0x5f;
+ m_uuid.uuid.uuid128[2] = 0x9b;
+ m_uuid.uuid.uuid128[1] = 0x34;
+ m_uuid.uuid.uuid128[0] = 0xfb;
+
+ m_uuid.len = ESP_UUID_LEN_128;
+ //ESP_LOGD(TAG, "<< toFull <- %s", toString().c_str());
+ return *this;
+} // to128
+
+
+//01234567 8901 2345 6789 012345678901
+//0000180d-0000-1000-8000-00805f9b34fb
+//0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+
+/**
+ * @brief Get a string representation of the UUID.
+ *
+ * @return A string representation of the UUID.
+ */
+std::string BLEUUID::toString() {
+ if (m_valueSet == false) {
+ return "<NULL>";
+ }
+
+ if (m_uuid.len == ESP_UUID_LEN_16) {
+ std::stringstream ss;
+ ss << "0000" << std::hex << std::setfill('0') << std::setw(4) << m_uuid.uuid.uuid16 << "-0000-1000-8000-00805f9b34fb";
+ return ss.str();
+ }
+
+ if (m_uuid.len == ESP_UUID_LEN_32) {
+ std::stringstream ss;
+ ss << std::hex << std::setfill('0') << std::setw(8) << m_uuid.uuid.uuid32 << "-0000-1000-8000-00805f9b34fb";
+ return ss.str();
+ }
+
+ std::stringstream ss;
+ ss << std::hex << std::setfill('0') <<
+ std::setw(2) << (int)m_uuid.uuid.uuid128[15] <<
+ std::setw(2) << (int)m_uuid.uuid.uuid128[14] <<
+ std::setw(2) << (int)m_uuid.uuid.uuid128[13] <<
+ std::setw(2) << (int)m_uuid.uuid.uuid128[12] << "-" <<
+ std::setw(2) << (int)m_uuid.uuid.uuid128[11] <<
+ std::setw(2) << (int)m_uuid.uuid.uuid128[10] << "-" <<
+ std::setw(2) << (int)m_uuid.uuid.uuid128[9] <<
+ std::setw(2) << (int)m_uuid.uuid.uuid128[8] << "-" <<
+ std::setw(2) << (int)m_uuid.uuid.uuid128[7] <<
+ std::setw(2) << (int)m_uuid.uuid.uuid128[6] << "-" <<
+ std::setw(2) << (int)m_uuid.uuid.uuid128[5] <<
+ std::setw(2) << (int)m_uuid.uuid.uuid128[4] <<
+ std::setw(2) << (int)m_uuid.uuid.uuid128[3] <<
+ std::setw(2) << (int)m_uuid.uuid.uuid128[2] <<
+ std::setw(2) << (int)m_uuid.uuid.uuid128[1] <<
+ std::setw(2) << (int)m_uuid.uuid.uuid128[0];
+ return ss.str();
+} // toString
+#endif /* CONFIG_BT_ENABLED */
diff --git a/src/BLEUUID.h b/src/BLEUUID.h
new file mode 100644
index 0000000..c364796
--- /dev/null
+++ b/src/BLEUUID.h
@@ -0,0 +1,37 @@
+/*
+ * BLEUUID.h
+ *
+ * Created on: Jun 21, 2017
+ * Author: kolban
+ */
+
+#ifndef COMPONENTS_CPP_UTILS_BLEUUID_H_
+#define COMPONENTS_CPP_UTILS_BLEUUID_H_
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+#include <esp_gatt_defs.h>
+#include <string>
+
+/**
+ * @brief A model of a %BLE UUID.
+ */
+class BLEUUID {
+public:
+ BLEUUID(std::string uuid);
+ BLEUUID(uint16_t uuid);
+ BLEUUID(uint32_t uuid);
+ BLEUUID(esp_bt_uuid_t uuid);
+ BLEUUID(uint8_t* pData, size_t size, bool msbFirst);
+ BLEUUID(esp_gatt_srvc_id_t srcvId);
+ BLEUUID();
+ bool equals(BLEUUID uuid);
+ esp_bt_uuid_t* getNative();
+ BLEUUID to128();
+ std::string toString();
+
+private:
+ esp_bt_uuid_t m_uuid;
+ bool m_valueSet;
+}; // BLEUUID
+#endif /* CONFIG_BT_ENABLED */
+#endif /* COMPONENTS_CPP_UTILS_BLEUUID_H_ */
diff --git a/src/BLEUtils.cpp b/src/BLEUtils.cpp
new file mode 100644
index 0000000..468a7e1
--- /dev/null
+++ b/src/BLEUtils.cpp
@@ -0,0 +1,1352 @@
+/*
+ * BLEUtils.cpp
+ *
+ * Created on: Mar 25, 2017
+ * Author: kolban
+ */
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+#include "BLEUtils.h"
+#include "BLEUUID.h"
+#include "BLEClient.h"
+#include "BLEAddress.h"
+#include "GeneralUtils.h"
+
+#include <freertos/FreeRTOS.h>
+#include <freertos/event_groups.h>
+#include <bt.h> // ESP32 BLE
+#include <esp_bt_main.h> // ESP32 BLE
+#include <esp_gap_ble_api.h> // ESP32 BLE
+#include <esp_gattc_api.h> // ESP32 BLE
+#include <esp_err.h> // ESP32 ESP-IDF
+#include <esp_log.h> // ESP32 ESP-IDF
+#include <map> // Part of C++ STL
+#include <sstream>
+#include <iomanip>
+
+static const char* LOG_TAG = "BLEUtils";
+
+/*
+static std::map<std::string, BLEClient *> g_addressMap;
+static std::map<uint16_t, BLEClient *> g_connIdMap;
+*/
+
+typedef struct {
+ uint32_t assignedNumber;
+ std::string name;
+} characteristicMap_t;
+
+static characteristicMap_t g_characteristicsMappings[] = {
+ {0x2a00, "Device Name"},
+ {0x2a01, "Appearance"},
+ {0, ""}
+};
+
+/**
+ * @brief Mapping from service ids to names
+ */
+typedef struct {
+ std::string name;
+ std::string type;
+ uint32_t assignedNumber;
+} gattService_t;
+
+
+/**
+ * Definition of the service ids to names that we know about.
+ */
+static const gattService_t g_gattServices[] = {
+ {"Alert Notification Service", "org.bluetooth.service.alert_notification", 0x1811},
+ {"Automation IO", "org.bluetooth.service.automation_io", 0x1815 },
+ {"Battery Service","org.bluetooth.service.battery_service", 0x180F},
+ {"Blood Pressure", "org.bluetooth.service.blood_pressure", 0x1810},
+ {"Body Composition", "org.bluetooth.service.body_composition", 0x181B},
+ {"Bond Management", "org.bluetooth.service.bond_management", 0x181E},
+ {"Continuous Glucose Monitoring", "org.bluetooth.service.continuous_glucose_monitoring", 0x181F},
+ {"Current Time Service", "org.bluetooth.service.current_time", 0x1805},
+ {"Cycling Power", "org.bluetooth.service.cycling_power", 0x1818},
+ {"Cycling Speed and Cadence", "org.bluetooth.service.cycling_speed_and_cadence", 0x1816},
+ {"Device Information", "org.bluetooth.service.device_information", 0x180A},
+ {"Environmental Sensing", "org.bluetooth.service.environmental_sensing", 0x181A},
+ {"Generic Access", "org.bluetooth.service.generic_access", 0x1800},
+ {"Generic Attribute", "org.bluetooth.service.generic_attribute", 0x1801},
+ {"Glucose", "org.bluetooth.service.glucose", 0x1808},
+ {"Health Thermometer", "org.bluetooth.service.health_thermometer", 0x1809},
+ {"Heart Rate", "org.bluetooth.service.heart_rate", 0x180D},
+ {"HTTP Proxy", "org.bluetooth.service.http_proxy", 0x1823},
+ {"Human Interface Device", "org.bluetooth.service.human_interface_device", 0x1812},
+ {"Immediate Alert", "org.bluetooth.service.immediate_alert", 0x1802},
+ {"Indoor Positioning", "org.bluetooth.service.indoor_positioning", 0x1821},
+ {"Internet Protocol Support", "org.bluetooth.service.internet_protocol_support", 0x1820},
+ {"Link Loss", "org.bluetooth.service.link_loss", 0x1803},
+ {"Location and Navigation", "org.bluetooth.service.location_and_navigation", 0x1819},
+ {"Next DST Change Service", "org.bluetooth.service.next_dst_change", 0x1807},
+ {"Object Transfer", "org.bluetooth.service.object_transfer", 0x1825},
+ {"Phone Alert Status Service", "org.bluetooth.service.phone_alert_status", 0x180E},
+ {"Pulse Oximeter", "org.bluetooth.service.pulse_oximeter", 0x1822},
+ {"Reference Time Update Service", "org.bluetooth.service.reference_time_update", 0x1806},
+ {"Running Speed and Cadence", "org.bluetooth.service.running_speed_and_cadence", 0x1814},
+ {"Scan Parameters", "org.bluetooth.service.scan_parameters", 0x1813},
+ {"Transport Discovery", "org.bluetooth.service.transport_discovery", 0x1824},
+ {"Tx Power", "org.bluetooth.service.tx_power", 0x1804},
+ {"User Data", "org.bluetooth.service.user_data", 0x181C},
+ {"Weight Scale", "org.bluetooth.service.weight_scale", 0x181D},
+ {"", "", 0 }
+};
+
+/**
+ * @brief Convert characteristic properties into a string representation.
+ * @param [in] prop Characteristic properties.
+ * @return A string representation of characteristic properties.
+ */
+std::string BLEUtils::characteristicPropertiesToString(esp_gatt_char_prop_t prop) {
+ std::stringstream stream;
+ stream <<
+ "broadcast: " << ((prop & ESP_GATT_CHAR_PROP_BIT_BROADCAST)?"1":"0") <<
+ ", read: " << ((prop & ESP_GATT_CHAR_PROP_BIT_READ)?"1":"0") <<
+ ", write_nr: " << ((prop & ESP_GATT_CHAR_PROP_BIT_WRITE_NR)?"1":"0") <<
+ ", write: " << ((prop & ESP_GATT_CHAR_PROP_BIT_WRITE)?"1":"0") <<
+ ", notify: " << ((prop & ESP_GATT_CHAR_PROP_BIT_NOTIFY)?"1":"0") <<
+ ", indicate: " << ((prop & ESP_GATT_CHAR_PROP_BIT_INDICATE)?"1":"0") <<
+ ", auth: " << ((prop & ESP_GATT_CHAR_PROP_BIT_AUTH)?"1":"0");
+ return stream.str();
+} // characteristicPropertiesToString
+
+/**
+ * @brief Convert an esp_gatt_id_t to a string.
+ */
+static std::string gattIdToString(esp_gatt_id_t gattId) {
+ std::stringstream stream;
+ stream << "uuid: " << BLEUUID(gattId.uuid).toString() << ", inst_id: " << (int)gattId.inst_id;
+ //sprintf(buffer, "uuid: %s, inst_id: %d", uuidToString(gattId.uuid).c_str(), gattId.inst_id);
+ return stream.str();
+} // gattIdToString
+
+
+/**
+ * @brief Convert an esp_ble_addr_type_t to a string representation.
+ */
+const char* BLEUtils::addressTypeToString(esp_ble_addr_type_t type) {
+ switch(type) {
+ case BLE_ADDR_TYPE_PUBLIC:
+ return "BLE_ADDR_TYPE_PUBLIC";
+ case BLE_ADDR_TYPE_RANDOM:
+ return "BLE_ADDR_TYPE_RANDOM";
+ case BLE_ADDR_TYPE_RPA_PUBLIC:
+ return "BLE_ADDR_TYPE_RPA_PUBLIC";
+ case BLE_ADDR_TYPE_RPA_RANDOM:
+ return "BLE_ADDR_TYPE_RPA_RANDOM";
+ default:
+ return "Unknown addr_t";
+ }
+} // addressTypeToString
+
+
+/**
+ * @brief Given an advertising type, return a string representation of the type.
+ *
+ * For details see ...
+ * https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile
+ *
+ * @return A string representation of the type.
+ */
+const char* BLEUtils::advTypeToString(uint8_t advType) {
+ switch(advType) {
+ case ESP_BLE_AD_TYPE_FLAG:
+ return "ESP_BLE_AD_TYPE_FLAG";
+ case ESP_BLE_AD_TYPE_16SRV_PART:
+ return "ESP_BLE_AD_TYPE_16SRV_PART";
+ case ESP_BLE_AD_TYPE_16SRV_CMPL:
+ return "ESP_BLE_AD_TYPE_16SRV_CMPL";
+ case ESP_BLE_AD_TYPE_32SRV_PART:
+ return "ESP_BLE_AD_TYPE_32SRV_PART";
+ case ESP_BLE_AD_TYPE_32SRV_CMPL:
+ return "ESP_BLE_AD_TYPE_32SRV_CMPL";
+ case ESP_BLE_AD_TYPE_128SRV_PART:
+ return "ESP_BLE_AD_TYPE_128SRV_PART";
+ case ESP_BLE_AD_TYPE_128SRV_CMPL:
+ return "ESP_BLE_AD_TYPE_128SRV_CMPL";
+ case ESP_BLE_AD_TYPE_NAME_SHORT:
+ return "ESP_BLE_AD_TYPE_NAME_SHORT";
+ case ESP_BLE_AD_TYPE_NAME_CMPL:
+ return "ESP_BLE_AD_TYPE_NAME_CMPL";
+ case ESP_BLE_AD_TYPE_TX_PWR:
+ return "ESP_BLE_AD_TYPE_TX_PWR";
+ case ESP_BLE_AD_TYPE_DEV_CLASS:
+ return "ESP_BLE_AD_TYPE_DEV_CLASS";
+ case ESP_BLE_AD_TYPE_SM_TK:
+ return "ESP_BLE_AD_TYPE_SM_TK";
+ case ESP_BLE_AD_TYPE_SM_OOB_FLAG:
+ return "ESP_BLE_AD_TYPE_SM_OOB_FLAG";
+ case ESP_BLE_AD_TYPE_INT_RANGE:
+ return "ESP_BLE_AD_TYPE_INT_RANGE";
+ case ESP_BLE_AD_TYPE_SOL_SRV_UUID:
+ return "ESP_BLE_AD_TYPE_SOL_SRV_UUID";
+ case ESP_BLE_AD_TYPE_128SOL_SRV_UUID:
+ return "ESP_BLE_AD_TYPE_128SOL_SRV_UUID";
+ case ESP_BLE_AD_TYPE_SERVICE_DATA:
+ return "ESP_BLE_AD_TYPE_SERVICE_DATA";
+ case ESP_BLE_AD_TYPE_PUBLIC_TARGET:
+ return "ESP_BLE_AD_TYPE_PUBLIC_TARGET";
+ case ESP_BLE_AD_TYPE_RANDOM_TARGET:
+ return "ESP_BLE_Amap1D_TYPE_RANDOM_TARGET";
+ case ESP_BLE_AD_TYPE_APPEARANCE:
+ return "ESP_BLE_AD_TYPE_APPEARANCE";
+ case ESP_BLE_AD_TYPE_ADV_INT:
+ return "ESP_BLE_AD_TYPE_ADV_INT";
+ case ESP_BLE_AD_TYPE_32SOL_SRV_UUID:
+ return "ESP_BLE_AD_TYPE_32SOL_SRV_UUID";
+ case ESP_BLE_AD_TYPE_32SERVICE_DATA:
+ return "ESP_BLE_AD_TYPE_32SERVICE_DATA";
+ case ESP_BLE_AD_TYPE_128SERVICE_DATA:
+ return "ESP_BLE_AD_TYPE_128SERVICE_DATA";
+ case ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE:
+ return "ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE";
+ default:
+ ESP_LOGD(LOG_TAG, "Unknown adv data type: 0x%x", advType);
+ return "Unknown";
+ } // End switch
+} // advTypeToString
+
+
+esp_gatt_id_t BLEUtils::buildGattId(esp_bt_uuid_t uuid, uint8_t inst_id) {
+ esp_gatt_id_t retGattId;
+ retGattId.uuid = uuid;
+ retGattId.inst_id = inst_id;
+ return retGattId;
+}
+
+esp_gatt_srvc_id_t BLEUtils::buildGattSrvcId(esp_gatt_id_t gattId,
+ bool is_primary) {
+ esp_gatt_srvc_id_t retSrvcId;
+ retSrvcId.id = gattId;
+ retSrvcId.is_primary = is_primary;
+ return retSrvcId;
+}
+
+/**
+ * @brief Create a hex representation of data.
+ *
+ * @param [in] target Where to write the hex string. If this is null, we malloc storage.
+ * @param [in] source The start of the binary data.
+ * @param [in] length The length of the data to convert.
+ * @return A pointer to the formatted buffer.
+ */
+char *BLEUtils::buildHexData(uint8_t *target, uint8_t *source, uint8_t length) {
+// Guard against too much data.
+ if (length > 100) {
+ length = 100;
+ }
+
+
+ if (target == nullptr) {
+ target = (uint8_t *)malloc(length * 2 + 1);
+ if (target == nullptr) {
+ ESP_LOGE(LOG_TAG, "buildHexData: malloc failed");
+ return nullptr;
+ }
+ }
+ char *startOfData = (char *)target;
+
+ int i;
+ for (i=0; i<length; i++) {
+ sprintf((char *)target, "%.2x", (char)*source);
+ source++;
+ target +=2;
+ }
+
+// Handle the special case where there was no data.
+ if (length == 0) {
+ *startOfData = 0;
+ }
+
+ return startOfData;
+} // buildHexData
+
+
+/**
+ * @brief Build a printable string of memory range.
+ * Create a string representation of a piece of memory. Only printable characters will be included
+ * while those that are not printable will be replaced with '.'.
+ * @param [in] source Start of memory.
+ * @param [in] length Length of memory.
+ * @return A string representation of a piece of memory.
+ */
+std::string BLEUtils::buildPrintData(uint8_t* source, size_t length) {
+ std::ostringstream ss;
+ for (int i=0; i<length; i++) {
+ char c = *source;
+ if (isprint(c)) {
+ ss << c;
+ } else {
+ ss << '.';
+ }
+ source++;
+ }
+ return ss.str();
+} // buildPrintData
+
+
+std::string BLEUtils::gattCloseReasonToString(esp_gatt_conn_reason_t reason) {
+ switch(reason) {
+ case ESP_GATT_CONN_UNKNOWN:
+ return "ESP_GATT_CONN_UNKNOWN";
+ case ESP_GATT_CONN_L2C_FAILURE:
+ return "ESP_GATT_CONN_L2C_FAILURE";
+ case ESP_GATT_CONN_TIMEOUT:
+ return "ESP_GATT_CONN_TIMEOUT";
+ case ESP_GATT_CONN_TERMINATE_PEER_USER:
+ return "ESP_GATT_CONN_TERMINATE_PEER_USER";
+ case ESP_GATT_CONN_TERMINATE_LOCAL_HOST:
+ return "ESP_GATT_CONN_TERMINATE_LOCAL_HOST";
+ case ESP_GATT_CONN_FAIL_ESTABLISH:
+ return "ESP_GATT_CONN_FAIL_ESTABLISH";
+ case ESP_GATT_CONN_LMP_TIMEOUT:
+ return "ESP_GATT_CONN_LMP_TIMEOUT";
+ case ESP_GATT_CONN_CONN_CANCEL:
+ return "ESP_GATT_CONN_CONN_CANCEL";
+ case ESP_GATT_CONN_NONE:
+ return "ESP_GATT_CONN_NONE";
+ default:
+ return "Unknown";
+ }
+} // gattCloseReasonToString
+
+
+std::string BLEUtils::gattClientEventTypeToString(esp_gattc_cb_event_t eventType) {
+ switch(eventType) {
+ case ESP_GATTC_ACL_EVT:
+ return "ESP_GATTC_ACL_EVT";
+ case ESP_GATTC_ADV_DATA_EVT:
+ return "ESP_GATTC_ADV_DATA_EVT";
+ case ESP_GATTC_ADV_VSC_EVT:
+ return "ESP_GATTC_ADV_VSC_EVT";
+ case ESP_GATTC_BTH_SCAN_CFG_EVT:
+ return "ESP_GATTC_BTH_SCAN_CFG_EVT";
+ case ESP_GATTC_BTH_SCAN_DIS_EVT:
+ return "ESP_GATTC_BTH_SCAN_DIS_EVT";
+ case ESP_GATTC_BTH_SCAN_ENB_EVT:
+ return "ESP_GATTC_BTH_SCAN_ENB_EVT";
+ case ESP_GATTC_BTH_SCAN_PARAM_EVT:
+ return "ESP_GATTC_BTH_SCAN_PARAM_EVT";
+ case ESP_GATTC_BTH_SCAN_RD_EVT:
+ return "ESP_GATTC_BTH_SCAN_RD_EVT";
+ case ESP_GATTC_BTH_SCAN_THR_EVT:
+ return "ESP_GATTC_BTH_SCAN_THR_EVT";
+ case ESP_GATTC_CANCEL_OPEN_EVT:
+ return "ESP_GATTC_CANCEL_OPEN_EVT";
+ case ESP_GATTC_CFG_MTU_EVT:
+ return "ESP_GATTC_CFG_MTU_EVT";
+ case ESP_GATTC_CLOSE_EVT:
+ return "ESP_GATTC_CLOSE_EVT";
+ case ESP_GATTC_CONGEST_EVT:
+ return "ESP_GATTC_CONGEST_EVT";
+ case ESP_GATTC_CONNECT_EVT:
+ return "ESP_GATTC_CONNECT_EVT";
+ case ESP_GATTC_DISCONNECT_EVT:
+ return "ESP_GATTC_DISCONNECT_EVT";
+ case ESP_GATTC_ENC_CMPL_CB_EVT:
+ return "ESP_GATTC_ENC_CMPL_CB_EVT";
+ case ESP_GATTC_EXEC_EVT:
+ return "ESP_GATTC_EXEC_EVT";
+ case ESP_GATTC_GET_CHAR_EVT:
+ return "ESP_GATTC_GET_CHAR_EVT";
+ case ESP_GATTC_GET_DESCR_EVT:
+ return "ESP_GATTC_GET_DESCR_EVT";
+ case ESP_GATTC_GET_INCL_SRVC_EVT:
+ return "ESP_GATTC_GET_INCL_SRVC_EVT";
+ case ESP_GATTC_MULT_ADV_DATA_EVT:
+ return "ESP_GATTC_MULT_ADV_DATA_EVT";
+ case ESP_GATTC_MULT_ADV_DIS_EVT:
+ return "ESP_GATTC_MULT_ADV_DIS_EVT";
+ case ESP_GATTC_MULT_ADV_ENB_EVT:
+ return "ESP_GATTC_MULT_ADV_ENB_EVT";
+ case ESP_GATTC_MULT_ADV_UPD_EVT:
+ return "ESP_GATTC_MULT_ADV_UPD_EVT";
+ case ESP_GATTC_NOTIFY_EVT:
+ return "ESP_GATTC_NOTIFY_EVT";
+ case ESP_GATTC_OPEN_EVT:
+ return "ESP_GATTC_OPEN_EVT";
+ case ESP_GATTC_PREP_WRITE_EVT:
+ return "ESP_GATTC_PREP_WRITE_EVT";
+ case ESP_GATTC_READ_CHAR_EVT:
+ return "ESP_GATTC_READ_CHAR_EVT";
+ case ESP_GATTC_REG_EVT:
+ return "ESP_GATTC_REG_EVT";
+ case ESP_GATTC_REG_FOR_NOTIFY_EVT:
+ return "ESP_GATTC_REG_FOR_NOTIFY_EVT";
+ case ESP_GATTC_SCAN_FLT_CFG_EVT:
+ return "ESP_GATTC_SCAN_FLT_CFG_EVT";
+ case ESP_GATTC_SCAN_FLT_PARAM_EVT:
+ return "ESP_GATTC_SCAN_FLT_PARAM_EVT";
+ case ESP_GATTC_SCAN_FLT_STATUS_EVT:
+ return "ESP_GATTC_SCAN_FLT_STATUS_EVT";
+ case ESP_GATTC_SEARCH_CMPL_EVT:
+ return "ESP_GATTC_SEARCH_CMPL_EVT";
+ case ESP_GATTC_SEARCH_RES_EVT:
+ return "ESP_GATTC_SEARCH_RES_EVT";
+ case ESP_GATTC_SRVC_CHG_EVT:
+ return "ESP_GATTC_SRVC_CHG_EVT";
+ case ESP_GATTC_READ_DESCR_EVT:
+ return "ESP_GATTC_READ_DESCR_EVT";
+ case ESP_GATTC_UNREG_EVT:
+ return "ESP_GATTC_UNREG_EVT";
+ case ESP_GATTC_UNREG_FOR_NOTIFY_EVT:
+ return "ESP_GATTC_UNREG_FOR_NOTIFY_EVT";
+ case ESP_GATTC_WRITE_CHAR_EVT:
+ return "ESP_GATTC_WRITE_CHAR_EVT";
+ case ESP_GATTC_WRITE_DESCR_EVT:
+ return "ESP_GATTC_WRITE_DESCR_EVT";
+ default:
+ ESP_LOGW(LOG_TAG, "Unknown GATT Client event type: %d", eventType);
+ return "Unknown";
+ }
+} // gattClientEventTypeToString
+
+
+/**
+ * @brief Return a string representation of a GATT server event code.
+ * @param [in] eventType A GATT server event code.
+ * @return A string representation of the GATT server event code.
+ */
+std::string BLEUtils::gattServerEventTypeToString(esp_gatts_cb_event_t eventType) {
+ switch(eventType) {
+ case ESP_GATTS_REG_EVT:
+ return "ESP_GATTS_REG_EVT";
+ case ESP_GATTS_READ_EVT:
+ return "ESP_GATTS_READ_EVT";
+ case ESP_GATTS_WRITE_EVT:
+ return "ESP_GATTS_WRITE_EVT";
+ case ESP_GATTS_EXEC_WRITE_EVT:
+ return "ESP_GATTS_EXEC_WRITE_EVT";
+ case ESP_GATTS_MTU_EVT:
+ return "ESP_GATTS_MTU_EVT";
+ case ESP_GATTS_CONF_EVT:
+ return "ESP_GATTS_CONF_EVT";
+ case ESP_GATTS_UNREG_EVT:
+ return "ESP_GATTS_UNREG_EVT";
+ case ESP_GATTS_CREATE_EVT:
+ return "ESP_GATTS_CREATE_EVT";
+ case ESP_GATTS_ADD_INCL_SRVC_EVT:
+ return "ESP_GATTS_ADD_INCL_SRVC_EVT";
+ case ESP_GATTS_ADD_CHAR_EVT:
+ return "ESP_GATTS_ADD_CHAR_EVT";
+ case ESP_GATTS_ADD_CHAR_DESCR_EVT:
+ return "ESP_GATTS_ADD_CHAR_DESCR_EVT";
+ case ESP_GATTS_DELETE_EVT:
+ return "ESP_GATTS_DELETE_EVT";
+ case ESP_GATTS_START_EVT:
+ return "ESP_GATTS_START_EVT";
+ case ESP_GATTS_STOP_EVT:
+ return "ESP_GATTS_STOP_EVT";
+ case ESP_GATTS_CONNECT_EVT:
+ return "ESP_GATTS_CONNECT_EVT";
+ case ESP_GATTS_DISCONNECT_EVT:
+ return "ESP_GATTS_DISCONNECT_EVT";
+ case ESP_GATTS_OPEN_EVT:
+ return "ESP_GATTS_OPEN_EVT";
+ case ESP_GATTS_CANCEL_OPEN_EVT:
+ return "ESP_GATTS_CANCEL_OPEN_EVT";
+ case ESP_GATTS_CLOSE_EVT:
+ return "ESP_GATTS_CLOSE_EVT";
+ case ESP_GATTS_LISTEN_EVT:
+ return "ESP_GATTS_LISTEN_EVT";
+ case ESP_GATTS_CONGEST_EVT:
+ return "ESP_GATTS_CONGEST_EVT";
+ case ESP_GATTS_RESPONSE_EVT:
+ return "ESP_GATTS_RESPONSE_EVT";
+ case ESP_GATTS_CREAT_ATTR_TAB_EVT:
+ return "ESP_GATTS_CREAT_ATTR_TAB_EVT";
+ case ESP_GATTS_SET_ATTR_VAL_EVT:
+ return "ESP_GATTS_SET_ATTR_VAL_EVT";
+ }
+ return "Unknown";
+} // gattServerEventTypeToString
+
+
+
+/**
+ * @brief Convert a BLE device type to a string.
+ * @param [in] type The device type.
+ */
+const char* BLEUtils::devTypeToString(esp_bt_dev_type_t type) {
+ switch(type) {
+ case ESP_BT_DEVICE_TYPE_BREDR:
+ return "ESP_BT_DEVICE_TYPE_BREDR";
+ case ESP_BT_DEVICE_TYPE_BLE:
+ return "ESP_BT_DEVICE_TYPE_BLE";
+ case ESP_BT_DEVICE_TYPE_DUMO:
+ return "ESP_BT_DEVICE_TYPE_DUMO";
+ default:
+ return "Unknown";
+ }
+} // devTypeToString
+
+
+/**
+ * @brief Dump the GAP event to the log.
+ */
+void BLEUtils::dumpGapEvent(
+ esp_gap_ble_cb_event_t event,
+ esp_ble_gap_cb_param_t *param) {
+ ESP_LOGD(LOG_TAG, "Received a GAP event: %s", gapEventToString(event));
+ switch(event) {
+ //
+ // ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT
+ //
+ case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: {
+ ESP_LOGD(LOG_TAG, "[status: %d]", param->scan_rsp_data_cmpl.status);
+ break;
+ } // ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT
+
+
+ //
+ // ESP_GAP_BLE_ADV_START_COMPLETE_EVT
+ //
+ case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: {
+ ESP_LOGD(LOG_TAG, "[status: %d]", param->scan_start_cmpl.status);
+ break;
+ } // ESP_GAP_BLE_ADV_START_COMPLETE_EVT
+
+
+ //
+ // ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT
+ //
+ case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: {
+ ESP_LOGD(LOG_TAG, "[status: %d]", param->scan_stop_cmpl.status);
+ break;
+ } // ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT
+
+
+ //
+ // ESP_GAP_BLE_AUTH_CMPL_EVT
+ //
+ case ESP_GAP_BLE_AUTH_CMPL_EVT: {
+ ESP_LOGD(LOG_TAG, "[bd_addr: %s, key_present: %d, key: ***, key_type: %d, success: %d, fail_reason: %d, addr_type: ***, dev_type: %s]",
+ BLEAddress(param->ble_security.auth_cmpl.bd_addr).toString().c_str(),
+ param->ble_security.auth_cmpl.key_present,
+ param->ble_security.auth_cmpl.key_type,
+ param->ble_security.auth_cmpl.success,
+ param->ble_security.auth_cmpl.fail_reason,
+ BLEUtils::devTypeToString(param->ble_security.auth_cmpl.dev_type)
+ );
+ break;
+ } // ESP_GAP_BLE_AUTH_CMPL_EVT
+
+
+ //
+ // ESP_GAP_BLE_LOCAL_IR_EVT
+ //
+ case ESP_GAP_BLE_LOCAL_IR_EVT: {
+ break;
+ } // ESP_GAP_BLE_LOCAL_IR_EVT
+
+
+ //
+ // ESP_GAP_BLE_LOCAL_ER_EVT
+ //
+ case ESP_GAP_BLE_LOCAL_ER_EVT: {
+ break;
+ } // ESP_GAP_BLE_LOCAL_ER_EVT
+
+
+ //
+ // ESP_GAP_BLE_NC_REQ_EVT
+ //
+ case ESP_GAP_BLE_NC_REQ_EVT: {
+ ESP_LOGD(LOG_TAG, "[bd_addr: %s, passkey: %d]",
+ BLEAddress(param->ble_security.key_notif.bd_addr).toString().c_str(),
+ param->ble_security.key_notif.passkey);
+ break;
+ } // ESP_GAP_BLE_NC_REQ_EVT
+
+
+ //
+ // ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT
+ //
+ case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: {
+ ESP_LOGD(LOG_TAG, "[status: %d]", param->scan_param_cmpl.status);
+ break;
+ } // ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT
+
+ //
+ // ESP_GAP_BLE_SCAN_RESULT_EVT
+ //
+ // scan_rst:
+ // - search_evt
+ // - bda
+ // - dev_type
+ // - ble_addr_type
+ // - ble_evt_type
+ // - rssi
+ // - ble_adv
+ // - flag
+ // - num_resps
+ case ESP_GAP_BLE_SCAN_RESULT_EVT: {
+ switch(param->scan_rst.search_evt) {
+ case ESP_GAP_SEARCH_INQ_RES_EVT: {
+ ESP_LOGD(LOG_TAG, "search_evt: %s, bda: %s, dev_type: %s, ble_addr_type: %s, ble_evt_type: %s, rssi: %d, ble_adv: ??, flag: %d, num_resps: %d, adv_data_len: %d, scan_rsp_len: %d",
+ searchEventTypeToString(param->scan_rst.search_evt),
+ BLEAddress(param->scan_rst.bda).toString().c_str(),
+ devTypeToString(param->scan_rst.dev_type),
+ addressTypeToString(param->scan_rst.ble_addr_type),
+ eventTypeToString(param->scan_rst.ble_evt_type),
+ param->scan_rst.rssi,
+ param->scan_rst.flag,
+ param->scan_rst.num_resps,
+ param->scan_rst.adv_data_len,
+ param->scan_rst.scan_rsp_len
+ );
+ break;
+ } // ESP_GAP_SEARCH_INQ_RES_EVT
+
+ default: {
+ ESP_LOGD(LOG_TAG, "search_evt: %s",searchEventTypeToString(param->scan_rst.search_evt));
+ break;
+ }
+ }
+ break;
+ } // ESP_GAP_BLE_SCAN_RESULT_EVT
+
+
+ //
+ // ESP_GAP_BLE_SCAN_START_COMPLETE_EVT
+ //
+ case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: {
+ ESP_LOGD(LOG_TAG, "[status: %d]", param->scan_start_cmpl.status);
+ break;
+ } // ESP_GAP_BLE_SCAN_START_COMPLETE_EVT
+
+
+ //
+ // ESP_GAP_BLE_SEC_REQ_EVT
+ //
+ case ESP_GAP_BLE_SEC_REQ_EVT: {
+ ESP_LOGD(LOG_TAG, "[bd_addr: %s]", BLEAddress(param->ble_security.ble_req.bd_addr).toString().c_str());
+ break;
+ } // ESP_GAP_BLE_SEC_REQ_EVT
+
+
+ //
+ // ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT
+ //
+ case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: {
+ ESP_LOGD(LOG_TAG, "[status: %d]", param->scan_stop_cmpl.status);
+ break;
+ } // ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT
+
+ default: {
+ ESP_LOGD(LOG_TAG, "*** dumpGapEvent: Logger not coded ***");
+ break;
+ } // default
+ } // switch
+} // dumpGapEvent
+
+
+/**
+ * @brief Decode and dump a GATT client event
+ *
+ * @param [in] event The type of event received.
+ * @param [in] evtParam The data associated with the event.
+ */
+void BLEUtils::dumpGattClientEvent(
+ esp_gattc_cb_event_t event,
+ esp_gatt_if_t gattc_if,
+ esp_ble_gattc_cb_param_t *evtParam) {
+
+ //esp_ble_gattc_cb_param_t *evtParam = (esp_ble_gattc_cb_param_t *)param;
+ ESP_LOGD(LOG_TAG, "GATT Event: %s", BLEUtils::gattClientEventTypeToString(event).c_str());
+ switch(event) {
+ //
+ // ESP_GATTC_CLOSE_EVT
+ //
+ // close:
+ // - esp_gatt_status_t status
+ // - uint16_t conn_id
+ // - esp_bd_addr_t remote_bda
+ // - esp_gatt_conn_reason_t reason
+ //
+ case ESP_GATTC_CLOSE_EVT: {
+ ESP_LOGD(LOG_TAG, "[status: %s, reason:%s, conn_id: %d]",
+ BLEUtils::gattStatusToString(evtParam->close.status).c_str(),
+ BLEUtils::gattCloseReasonToString(evtParam->close.reason).c_str(),
+ evtParam->close.conn_id);
+ break;
+ }
+
+ //
+ // ESP_GATTC_CONNECT_EVT
+ //
+ // connect:
+ // - esp_gatt_status_t status
+ // - uint16_t conn_id
+ // - esp_bd_addr_t remote_bda
+ case ESP_GATTC_CONNECT_EVT: {
+ ESP_LOGD(LOG_TAG, "[staus: %s, conn_id: %d, remote_bda: %s]",
+ BLEUtils::gattStatusToString(evtParam->connect.status).c_str(),
+ evtParam->connect.conn_id,
+ BLEAddress(evtParam->connect.remote_bda).toString().c_str()
+ );
+ break;
+ }
+
+ //
+ // ESP_GATTC_DISCONNECT_EVT
+ //
+ // disconnect:
+ // - esp_gatt_status_t status
+ // - uint16_t conn_id
+ // - esp_bd_addr_t remote_bda
+ case ESP_GATTC_DISCONNECT_EVT: {
+ ESP_LOGD(LOG_TAG, "[staus: %s, conn_id: %d, remote_bda: %s]",
+ BLEUtils::gattStatusToString(evtParam->disconnect.status).c_str(),
+ evtParam->disconnect.conn_id,
+ BLEAddress(evtParam->disconnect.remote_bda).toString().c_str()
+ );
+ break;
+ } // ESP_GATTC_DISCONNECT_EVT
+
+ //
+ // ESP_GATTC_GET_CHAR_EVT
+ //
+ // get_char:
+ // - esp_gatt_status_t status
+ // - uin1t6_t conn_id
+ // - esp_gatt_srvc_id_t srvc_id
+ // - esp_gatt_id_t char_id
+ // - esp_gatt_char_prop_t char_prop
+ //
+ case ESP_GATTC_GET_CHAR_EVT: {
+
+ // If the status of the event shows that we have a value other than ESP_GATT_OK then the
+ // characteristic fields are not set to a usable value .. so don't try and log them.
+ if (evtParam->get_char.status == ESP_GATT_OK) {
+ std::string description = "Unknown";
+ if (evtParam->get_char.char_id.uuid.len == ESP_UUID_LEN_16) {
+ description = BLEUtils::gattCharacteristicUUIDToString(evtParam->get_char.char_id.uuid.uuid.uuid16);
+ }
+ ESP_LOGD(LOG_TAG, "[status: %s, conn_id: %d, srvc_id: %s, char_id: %s [description: %s]\nchar_prop: %s]",
+ BLEUtils::gattStatusToString(evtParam->get_char.status).c_str(),
+ evtParam->get_char.conn_id,
+ BLEUtils::gattServiceIdToString(evtParam->get_char.srvc_id).c_str(),
+ gattIdToString(evtParam->get_char.char_id).c_str(),
+ description.c_str(),
+ BLEUtils::characteristicPropertiesToString(evtParam->get_char.char_prop).c_str()
+ );
+ } else {
+ ESP_LOGD(LOG_TAG, "[status: %s, conn_id: %d, srvc_id: %s]",
+ BLEUtils::gattStatusToString(evtParam->get_char.status).c_str(),
+ evtParam->get_char.conn_id,
+ BLEUtils::gattServiceIdToString(evtParam->get_char.srvc_id).c_str()
+ );
+ }
+ break;
+ } // ESP_GATTC_GET_CHAR_EVT
+
+ //
+ // ESP_GATTC_NOTIFY_EVT
+ //
+ // notify
+ // uint16_t conn_id
+ // esp_bd_addr_t remote_bda
+ // esp_gatt_srvc_id_t srvc_id
+ // esp_gatt_id_t char_id
+ // esp_gatt_id_t descr_id
+ // uint16_t value_len
+ // uint8_t* value
+ // bool is_notify
+ //
+ case ESP_GATTC_NOTIFY_EVT: {
+ ESP_LOGD(LOG_TAG, "[conn_id: %d, remote_bda: %s, srvc_id: <%s>, char_id: <%s>, descr_id: <%s>, value_len: %d, is_notify: %d]",
+ evtParam->notify.conn_id,
+ BLEAddress(evtParam->notify.remote_bda).toString().c_str(),
+ BLEUtils::gattServiceIdToString(evtParam->notify.srvc_id).c_str(),
+ gattIdToString(evtParam->notify.char_id).c_str(),
+ gattIdToString(evtParam->notify.descr_id).c_str(),
+ evtParam->notify.value_len,
+ evtParam->notify.is_notify
+ );
+ break;
+ }
+
+ //
+ // ESP_GATTC_OPEN_EVT
+ //
+ // open:
+ // - esp_gatt_status_t status
+ // - uint16_t conn_id
+ // - esp_bd_addr_t remote_bda
+ // - uint16_t mtu
+ //
+ case ESP_GATTC_OPEN_EVT: {
+ ESP_LOGD(LOG_TAG, "[status: %s, conn_id: %d, remote_bda: %s, mtu: %d]",
+ BLEUtils::gattStatusToString(evtParam->open.status).c_str(),
+ evtParam->open.conn_id,
+ BLEAddress(evtParam->open.remote_bda).toString().c_str(),
+ evtParam->open.mtu);
+ break;
+ } // ESP_GATTC_OPEN_EVT
+
+
+ //
+ // ESP_GATTC_READ_CHAR_EVT
+ //
+ // Callback to indicate that requested data that we wanted to read is now available.
+ //
+ // read:
+ // esp_gatt_status_t status
+ // uint16_t conn_id
+ // esp_gatt_srvc_id_t srvc_id
+ // esp_gatt_id_t char_id
+ // esp_gatt_id_t descr_id
+ // uint8_t* value
+ // uint16_t value_type
+ // uint16_t value_len
+ case ESP_GATTC_READ_CHAR_EVT: {
+ ESP_LOGD(LOG_TAG, "[status: %s, conn_id: %d, srvc_id: <%s>, char_id: <%s>, descr_id: <%s>, value_type: 0x%x, value_len: %d]",
+ BLEUtils::gattStatusToString(evtParam->read.status).c_str(),
+ evtParam->read.conn_id,
+ BLEUtils::gattServiceIdToString(evtParam->read.srvc_id).c_str(),
+ gattIdToString(evtParam->read.char_id).c_str(),
+ gattIdToString(evtParam->read.descr_id).c_str(),
+ evtParam->read.value_type,
+ evtParam->read.value_len
+ );
+ if (evtParam->read.status == ESP_GATT_OK) {
+ GeneralUtils::hexDump(evtParam->read.value, evtParam->read.value_len);
+ /*
+ char *pHexData = BLEUtils::buildHexData(nullptr, evtParam->read.value, evtParam->read.value_len);
+ ESP_LOGD(LOG_TAG, "value: %s \"%s\"", pHexData, BLEUtils::buildPrintData(evtParam->read.value, evtParam->read.value_len).c_str());
+ free(pHexData);
+ */
+ }
+ break;
+ } // ESP_GATTC_READ_CHAR_EVT
+
+
+ //
+ // ESP_GATTC_REG_EVT
+ //
+ // reg:
+ // - esp_gatt_status_t status
+ // - uint16_t app_id
+ //
+ case ESP_GATTC_REG_EVT: {
+ ESP_LOGD(LOG_TAG, "[status: %s, app_id: 0x%x]",
+ BLEUtils::gattStatusToString(evtParam->reg.status).c_str(),
+ evtParam->reg.app_id);
+ break;
+ } // ESP_GATTC_REG_EVT
+
+
+ //
+ // ESP_GATTC_REG_FOR_NOTIFY_EVT
+ //
+ // reg_for_notify:
+ // - esp_gatt_status_t status
+ // - esp_gatt_srvc_id_t srvc_id
+ // - esp_gatt_id_t char_id
+ case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
+ ESP_LOGD(LOG_TAG, "[status: %s, srvc_id: <%s>, char_id: <%s>]",
+ BLEUtils::gattStatusToString(evtParam->reg_for_notify.status).c_str(),
+ BLEUtils::gattServiceIdToString(evtParam->reg_for_notify.srvc_id).c_str(),
+ gattIdToString(evtParam->reg_for_notify.char_id).c_str());
+ break;
+ } // ESP_GATTC_REG_FOR_NOTIFY_EVT
+
+
+ //
+ // ESP_GATTC_SEARCH_CMPL_EVT
+ //
+ // search_cmpl:
+ // - esp_gatt_status_t status
+ // - uint16_t conn_id
+ //
+ case ESP_GATTC_SEARCH_CMPL_EVT: {
+ ESP_LOGD(LOG_TAG, "[status: %s, conn_id: %d]",
+ BLEUtils::gattStatusToString(evtParam->search_cmpl.status).c_str(),
+ evtParam->search_cmpl.conn_id);
+ break;
+ } // ESP_GATTC_SEARCH_CMPL_EVT
+
+
+ //
+ // ESP_GATTC_SEARCH_RES_EVT
+ //
+ // search_res:
+ // - uint16_t conn_id
+ // - esp_gatt_srvc_id_t srvc_id
+ //
+ case ESP_GATTC_SEARCH_RES_EVT: {
+ std::string name = "";
+ if (evtParam->search_res.srvc_id.id.uuid.len == ESP_UUID_LEN_16) {
+ name = BLEUtils::gattServiceToString(evtParam->search_res.srvc_id.id.uuid.uuid.uuid16);
+ }
+ if (name.length() == 0) {
+ name = "<Unknown Service>";
+ }
+
+ ESP_LOGD(LOG_TAG, "[srvc_id: %s [%s], instanceId: 0x%.2x conn_id: %d]",
+ BLEUtils::gattServiceIdToString(evtParam->search_res.srvc_id).c_str(),
+ name.c_str(),
+ evtParam->search_res.srvc_id.id.inst_id,
+ evtParam->search_res.conn_id);
+ break;
+ } // ESP_GATTC_SEARCH_RES_EVT
+
+
+ //
+ // ESP_GATTC_WRITE_CHAR_EVT
+ //
+ // write:
+ // esp_gatt_status_t status
+ // uint16_t conn_id
+ // esp_gatt_srvc_id_t srvc_id
+ // esp_gatt_id_t char_id
+ // esp_gatt_id_t descr_id
+ case ESP_GATTC_WRITE_CHAR_EVT: {
+ ESP_LOGD(LOG_TAG, "[status: %s, conn_id: %d, srvc_id: <%s>, char_id: <%s>, descr_id: <%s>]",
+ BLEUtils::gattStatusToString(evtParam->write.status).c_str(),
+ evtParam->write.conn_id,
+ BLEUtils::gattServiceIdToString(evtParam->write.srvc_id).c_str(),
+ gattIdToString(evtParam->write.char_id).c_str(),
+ gattIdToString(evtParam->write.descr_id).c_str()
+ );
+ break;
+ }
+
+ default:
+ break;
+ }
+} // dumpGattClientEvent
+
+
+/**
+ * @brief Dump the details of a GATT server event.
+ * A GATT Server event is a callback received from the BLE subsystem when we are acting as a BLE
+ * server. The callback indicates the type of event in the `event` field. The `evtParam` is a
+ * union of structures where we can use the `event` to indicate which of the structures has been
+ * populated and hence is valid.
+ *
+ * @param [in] event The event type that was posted.
+ * @param [in] evtParam A union of structures only one of which is populated.
+ */
+void BLEUtils::dumpGattServerEvent(
+ esp_gatts_cb_event_t event,
+ esp_gatt_if_t gatts_if,
+ esp_ble_gatts_cb_param_t *evtParam) {
+ ESP_LOGD(LOG_TAG, "GATT ServerEvent: %s", BLEUtils::gattServerEventTypeToString(event).c_str());
+ switch(event) {
+
+ case ESP_GATTS_ADD_CHAR_DESCR_EVT: {
+ ESP_LOGD(LOG_TAG, "[status: %s, attr_handle: 0x%.2x, service_handle: 0x%.2x, char_uuid: %s]",
+ gattStatusToString(evtParam->add_char_descr.status).c_str(),
+ evtParam->add_char_descr.attr_handle,
+ evtParam->add_char_descr.service_handle,
+ BLEUUID(evtParam->add_char_descr.char_uuid).toString().c_str());
+ break;
+ } // ESP_GATTS_ADD_CHAR_DESCR_EVT
+
+ case ESP_GATTS_ADD_CHAR_EVT: {
+ ESP_LOGD(LOG_TAG, "[status: %s, attr_handle: 0x%.2x, service_handle: 0x%.2x, char_uuid: %s]",
+ gattStatusToString(evtParam->add_char.status).c_str(),
+ evtParam->add_char.attr_handle,
+ evtParam->add_char.service_handle,
+ BLEUUID(evtParam->add_char.char_uuid).toString().c_str());
+ break;
+ } // ESP_GATTS_ADD_CHAR_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: {
+ ESP_LOGD(LOG_TAG, "[status: %s, conn_id: 0x%.2x]",
+ gattStatusToString(evtParam->conf.status).c_str(),
+ evtParam->conf.conn_id);
+ break;
+ } // ESP_GATTS_CONF_EVT
+
+
+ case ESP_GATTS_CONGEST_EVT: {
+ ESP_LOGD(LOG_TAG, "[conn_id: %d, congested: %d]",
+ evtParam->congest.conn_id,
+ evtParam->congest.congested);
+ break;
+ } // ESP_GATTS_CONGEST_EVT
+
+ case ESP_GATTS_CONNECT_EVT: {
+ ESP_LOGD(LOG_TAG, "[conn_id: %d, remote_bda: %s, is_connected: %d]",
+ evtParam->connect.conn_id,
+ BLEAddress(evtParam->connect.remote_bda).toString().c_str(),
+ evtParam->connect.is_connected);
+ break;
+ } // ESP_GATTS_CONNECT_EVT
+
+ case ESP_GATTS_CREATE_EVT: {
+ ESP_LOGD(LOG_TAG, "[status: %s, service_handle: 0x%.2x, service_id: [%s]]",
+ gattStatusToString(evtParam->create.status).c_str(),
+ evtParam->create.service_handle,
+ gattServiceIdToString(evtParam->create.service_id).c_str());
+ break;
+ } // ESP_GATTS_CREATE_EVT
+
+ case ESP_GATTS_DISCONNECT_EVT: {
+ ESP_LOGD(LOG_TAG, "[conn_id: %d, remote_bda: %s, is_connected: %d]",
+ evtParam->connect.conn_id,
+ BLEAddress(evtParam->connect.remote_bda).toString().c_str(),
+ evtParam->connect.is_connected);
+ break;
+ } // ESP_GATTS_DISCONNECT_EVT
+
+
+ // ESP_GATTS_EXEC_WRITE_EVT
+ // 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: {
+ char* pWriteFlagText;
+ switch(evtParam->exec_write.exec_write_flag) {
+ case ESP_GATT_PREP_WRITE_EXEC: {
+ pWriteFlagText = (char*)"WRITE";
+ break;
+ }
+
+ case ESP_GATT_PREP_WRITE_CANCEL: {
+ pWriteFlagText = (char*)"CANCEL";
+ break;
+ }
+
+ default:
+ pWriteFlagText = (char*)"<Unknown>";
+ break;
+ }
+
+ ESP_LOGD(LOG_TAG, "[conn_id: %d, trans_id: %d, bda: %s, exec_write_flag: 0x%.2x=%s]",
+ evtParam->exec_write.conn_id,
+ evtParam->exec_write.trans_id,
+ BLEAddress(evtParam->exec_write.bda).toString().c_str(),
+ evtParam->exec_write.exec_write_flag,
+ pWriteFlagText);
+ break;
+ } // ESP_GATTS_DISCONNECT_EVT
+
+
+ case ESP_GATTS_MTU_EVT: {
+ ESP_LOGD(LOG_TAG, "[conn_id: %d, mtu: %d]",
+ evtParam->mtu.conn_id,
+ evtParam->mtu.mtu);
+ break;
+ } // ESP_GATTS_MTU_EVT
+
+ case ESP_GATTS_READ_EVT: {
+ ESP_LOGD(LOG_TAG, "[conn_id: %d, trans_id: %d, bda: %s, handle: 0x%.2x, is_long: %d, need_rsp:%d]",
+ evtParam->read.conn_id,
+ evtParam->read.trans_id,
+ BLEAddress(evtParam->read.bda).toString().c_str(),
+ evtParam->read.handle,
+ evtParam->read.is_long,
+ evtParam->read.need_rsp);
+ break;
+ } // ESP_GATTS_READ_EVT
+
+ case ESP_GATTS_RESPONSE_EVT: {
+ ESP_LOGD(LOG_TAG, "[status: %s, handle: 0x%.2x]",
+ gattStatusToString(evtParam->rsp.status).c_str(),
+ evtParam->rsp.handle);
+ break;
+ } // ESP_GATTS_RESPONSE_EVT
+
+ case ESP_GATTS_REG_EVT: {
+ ESP_LOGD(LOG_TAG, "[status: %s, app_id: %d]",
+ gattStatusToString(evtParam->reg.status).c_str(),
+ evtParam->reg.app_id);
+ break;
+ } // ESP_GATTS_REG_EVT
+
+
+ // ESP_GATTS_START_EVT
+ //
+ // start:
+ // - esp_gatt_status_t status
+ // - uint16_t service_handle
+ //
+ case ESP_GATTS_START_EVT: {
+ ESP_LOGD(LOG_TAG, "[status: %s, service_handle: 0x%.2x]",
+ gattStatusToString(evtParam->start.status).c_str(),
+ evtParam->start.service_handle);
+ break;
+ } // ESP_GATTS_START_EVT
+
+
+ // ESP_GATTS_WRITE_EVT
+ //
+ // write:
+ // - uint16_t conn_id – The connection id.
+ // - uint16_t trans_id – The transfer id.
+ // - esp_bd_addr_t bda – The address of the partner.
+ // - uint16_t handle – The attribute handle.
+ // - uint16_t offset – The offset of the currently received within the whole value.
+ // - bool need_rsp – Do we need a response?
+ // - bool is_prep – Is this a write prepare? If set, then this is to be considered part of the received value and not the whole value. A subsequent ESP_GATTS_EXEC_WRITE will mark the total.
+ // - uint16_t len – The length of the incoming value part.
+ // - uint8_t* value – The data for this value part.
+ //
+ case ESP_GATTS_WRITE_EVT: {
+ ESP_LOGD(LOG_TAG, "[conn_id: %d, trans_id: %d, bda: %s, handle: 0x%.2x, offset: %d, need_rsp: %d, is_prep: %d, len: %d]",
+ evtParam->write.conn_id,
+ evtParam->write.trans_id,
+ BLEAddress(evtParam->write.bda).toString().c_str(),
+ evtParam->write.handle,
+ evtParam->write.offset,
+ evtParam->write.need_rsp,
+ evtParam->write.is_prep,
+ evtParam->write.len);
+ char *pHex = buildHexData(nullptr, evtParam->write.value, evtParam->write.len);
+ ESP_LOGD(LOG_TAG, "[Data: %s]", pHex);
+ free(pHex);
+ break;
+ } // ESP_GATTS_WRITE_EVT
+
+ default:
+ ESP_LOGD(LOG_TAG, "dumpGattServerEvent: *** NOT CODED ***");
+ break;
+ }
+} // dumpGattServerEvent
+
+
+/**
+ * @brief Convert a BLE event type to a string.
+ * @param [in] eventType The event type.
+ * @return The event type as a string.
+ */
+const char* BLEUtils::eventTypeToString(esp_ble_evt_type_t eventType) {
+ switch(eventType) {
+ case ESP_BLE_EVT_CONN_ADV:
+ return "ESP_BLE_EVT_CONN_ADV";
+ case ESP_BLE_EVT_CONN_DIR_ADV:
+ return "ESP_BLE_EVT_CONN_DIR_ADV";
+ case ESP_BLE_EVT_DISC_ADV:
+ return "ESP_BLE_EVT_DISC_ADV";
+ case ESP_BLE_EVT_NON_CONN_ADV:
+ return "ESP_BLE_EVT_NON_CONN_ADV";
+ case ESP_BLE_EVT_SCAN_RSP:
+ return "ESP_BLE_EVT_SCAN_RSP";
+ default:
+ ESP_LOGD(LOG_TAG, "Unknown esp_ble_evt_type_t: %d", eventType);
+ return "*** Unknown ***";
+ }
+} // eventTypeToString
+
+
+
+/**
+ * @brief Convert a BT GAP event type to a string representation.
+ * @param [in] eventType The type of event.
+ * @return A string representation of the event type.
+ */
+const char* BLEUtils::gapEventToString(uint32_t eventType) {
+ switch(eventType) {
+ case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
+ return "ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT";
+ case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
+ return "ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT";
+ case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
+ return "ESP_GAP_BLE_ADV_START_COMPLETE_EVT";
+ case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
+ return "ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT";
+ case ESP_GAP_BLE_SCAN_RESULT_EVT:
+ return "ESP_GAP_BLE_SCAN_RESULT_EVT";
+ case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT:
+ return "ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT";
+ case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
+ return "ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT";
+ case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
+ return "ESP_GAP_BLE_SCAN_START_COMPLETE_EVT";
+ case ESP_GAP_BLE_AUTH_CMPL_EVT: /* Authentication complete indication. */
+ return "ESP_GAP_BLE_AUTH_CMPL_EVT";
+ case ESP_GAP_BLE_KEY_EVT: /* BLE key event for peer device keys */
+ return "ESP_GAP_BLE_KEY_EVT";
+ case ESP_GAP_BLE_SEC_REQ_EVT: /* BLE security request */
+ return "ESP_GAP_BLE_SEC_REQ_EVT";
+ case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: /* passkey notification event */
+ return "ESP_GAP_BLE_PASSKEY_NOTIF_EVT";
+ case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */
+ return "ESP_GAP_BLE_PASSKEY_REQ_EVT";
+ case ESP_GAP_BLE_OOB_REQ_EVT: /* OOB request event */
+ return "ESP_GAP_BLE_OOB_REQ_EVT";
+ case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */
+ return "ESP_GAP_BLE_LOCAL_IR_EVT";
+ case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */
+ return "ESP_GAP_BLE_LOCAL_ER_EVT";
+ case ESP_GAP_BLE_NC_REQ_EVT: /* Numeric Comparison request event */
+ return "ESP_GAP_BLE_NC_REQ_EVT";
+ case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: /*!< When stop adv complete, the event comes */
+ return "ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT";
+ case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
+ return "ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT";
+ default:
+ ESP_LOGD(LOG_TAG, "gapEventToString: Unknown event type 0x%x", eventType);
+ return "Unknown event type";
+ }
+} // gapEventToString
+
+
+std::string BLEUtils::gattCharacteristicUUIDToString(uint32_t characteristicUUID) {
+ characteristicMap_t *p = g_characteristicsMappings;
+ while (p->name.length() > 0) {
+ if (p->assignedNumber == characteristicUUID) {
+ return p->name;
+ }
+ p++;
+ }
+ return "Unknown";
+} // gattCharacteristicUUIDToString
+
+
+/**
+ * @brief Convert an esp_gatt_srvc_id_t to a string.
+ */
+std::string BLEUtils::gattServiceIdToString(esp_gatt_srvc_id_t srvcId) {
+ return gattIdToString(srvcId.id);
+} // gattServiceIdToString
+
+
+std::string BLEUtils::gattServiceToString(uint32_t serviceId) {
+ gattService_t* p = (gattService_t *)g_gattServices;
+ while (p->name.length() > 0) {
+ if (p->assignedNumber == serviceId) {
+ return p->name;
+ }
+ p++;
+ }
+ return "Unknown";
+} // gattServiceToString
+
+
+/**
+ * @brief Convert a GATT status to a string.
+ *
+ * @param [in] status The status to convert.
+ * @return A string representation of the status.
+ */
+std::string BLEUtils::gattStatusToString(esp_gatt_status_t status) {
+ switch(status) {
+ case ESP_GATT_OK:
+ return "ESP_GATT_OK";
+ case ESP_GATT_INVALID_HANDLE:
+ return "ESP_GATT_INVALID_HANDLE";
+ case ESP_GATT_READ_NOT_PERMIT:
+ return "ESP_GATT_READ_NOT_PERMIT";
+ case ESP_GATT_WRITE_NOT_PERMIT:
+ return "ESP_GATT_WRITE_NOT_PERMIT";
+ case ESP_GATT_INVALID_PDU:
+ return "ESP_GATT_INVALID_PDU";
+ case ESP_GATT_INSUF_AUTHENTICATION:
+ return "ESP_GATT_INSUF_AUTHENTICATION";
+ case ESP_GATT_REQ_NOT_SUPPORTED:
+ return "ESP_GATT_REQ_NOT_SUPPORTED";
+ case ESP_GATT_INVALID_OFFSET:
+ return "ESP_GATT_INVALID_OFFSET";
+ case ESP_GATT_INSUF_AUTHORIZATION:
+ return "ESP_GATT_INSUF_AUTHORIZATION";
+ case ESP_GATT_PREPARE_Q_FULL:
+ return "ESP_GATT_PREPARE_Q_FULL";
+ case ESP_GATT_NOT_FOUND:
+ return "ESP_GATT_NOT_FOUND";
+ case ESP_GATT_NOT_LONG:
+ return "ESP_GATT_NOT_LONG";
+ case ESP_GATT_INSUF_KEY_SIZE:
+ return "ESP_GATT_INSUF_KEY_SIZE";
+ case ESP_GATT_INVALID_ATTR_LEN:
+ return "ESP_GATT_INVALID_ATTR_LEN";
+ case ESP_GATT_ERR_UNLIKELY:
+ return "ESP_GATT_ERR_UNLIKELY";
+ case ESP_GATT_INSUF_ENCRYPTION:
+ return "ESP_GATT_INSUF_ENCRYPTION";
+ case ESP_GATT_UNSUPPORT_GRP_TYPE:
+ return "ESP_GATT_UNSUPPORT_GRP_TYPE";
+ case ESP_GATT_INSUF_RESOURCE:
+ return "ESP_GATT_INSUF_RESOURCE";
+ case ESP_GATT_NO_RESOURCES:
+ return "ESP_GATT_NO_RESOURCES";
+ case ESP_GATT_INTERNAL_ERROR:
+ return "ESP_GATT_INTERNAL_ERROR";
+ case ESP_GATT_WRONG_STATE:
+ return "ESP_GATT_WRONG_STATE";
+ case ESP_GATT_DB_FULL:
+ return "ESP_GATT_DB_FULL";
+ case ESP_GATT_BUSY:
+ return "ESP_GATT_BUSY";
+ case ESP_GATT_ERROR:
+ return "ESP_GATT_ERROR";
+ case ESP_GATT_CMD_STARTED:
+ return "ESP_GATT_CMD_STARTED";
+ case ESP_GATT_ILLEGAL_PARAMETER:
+ return "ESP_GATT_ILLEGAL_PARAMETER";
+ case ESP_GATT_PENDING:
+ return "ESP_GATT_PENDING";
+ case ESP_GATT_AUTH_FAIL:
+ return "ESP_GATT_AUTH_FAIL";
+ case ESP_GATT_MORE:
+ return "ESP_GATT_MORE";
+ case ESP_GATT_INVALID_CFG:
+ return "ESP_GATT_INVALID_CFG";
+ case ESP_GATT_SERVICE_STARTED:
+ return "ESP_GATT_SERVICE_STARTED";
+ case ESP_GATT_ENCRYPED_NO_MITM:
+ return "ESP_GATT_ENCRYPED_NO_MITM";
+ case ESP_GATT_NOT_ENCRYPTED:
+ return "ESP_GATT_NOT_ENCRYPTED";
+ case ESP_GATT_CONGESTED:
+ return "ESP_GATT_CONGESTED";
+ case ESP_GATT_DUP_REG:
+ return "ESP_GATT_DUP_REG";
+ case ESP_GATT_ALREADY_OPEN:
+ return "ESP_GATT_ALREADY_OPEN";
+ case ESP_GATT_CANCEL:
+ return "ESP_GATT_CANCEL";
+ case ESP_GATT_CCC_CFG_ERR:
+ return "ESP_GATT_CCC_CFG_ERR";
+ case ESP_GATT_PRC_IN_PROGRESS:
+ return "ESP_GATT_PRC_IN_PROGRESS";
+ default:
+ return "Unknown";
+ }
+} // gattStatusToString
+
+
+/**
+ * @brief convert a GAP search event to a string.
+ * @param [in] searchEvt
+ * @return The search event type as a string.
+ */
+const char* BLEUtils::searchEventTypeToString(esp_gap_search_evt_t searchEvt) {
+ switch(searchEvt) {
+ case ESP_GAP_SEARCH_INQ_RES_EVT:
+ return "ESP_GAP_SEARCH_INQ_RES_EVT";
+ case ESP_GAP_SEARCH_INQ_CMPL_EVT:
+ return "ESP_GAP_SEARCH_INQ_CMPL_EVT";
+ case ESP_GAP_SEARCH_DISC_RES_EVT:
+ return "ESP_GAP_SEARCH_DISC_RES_EVT";
+ case ESP_GAP_SEARCH_DISC_BLE_RES_EVT:
+ return "ESP_GAP_SEARCH_DISC_BLE_RES_EVT";
+ case ESP_GAP_SEARCH_DISC_CMPL_EVT:
+ return "ESP_GAP_SEARCH_DISC_CMPL_EVT";
+ case ESP_GAP_SEARCH_DI_DISC_CMPL_EVT:
+ return "ESP_GAP_SEARCH_DI_DISC_CMPL_EVT";
+ case ESP_GAP_SEARCH_SEARCH_CANCEL_CMPL_EVT:
+ return "ESP_GAP_SEARCH_SEARCH_CANCEL_CMPL_EVT";
+ default:
+ ESP_LOGD(LOG_TAG, "Unknown event type: 0x%x", searchEvt);
+ return "Unknown event type";
+ }
+} // searchEventTypeToString
+
+#endif // CONFIG_BT_ENABLED
diff --git a/src/BLEUtils.h b/src/BLEUtils.h
new file mode 100644
index 0000000..28c7a7e
--- /dev/null
+++ b/src/BLEUtils.h
@@ -0,0 +1,60 @@
+/*
+ * BLEUtils.h
+ *
+ * Created on: Mar 25, 2017
+ * Author: kolban
+ */
+
+#ifndef COMPONENTS_CPP_UTILS_BLEUTILS_H_
+#define COMPONENTS_CPP_UTILS_BLEUTILS_H_
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+#include <esp_gattc_api.h> // ESP32 BLE
+#include <esp_gatts_api.h> // ESP32 BLE
+#include <esp_gap_ble_api.h> // ESP32 BLE
+#include <string>
+#include "BLEClient.h"
+
+/**
+ * @brief A set of general %BLE utilities.
+ */
+class BLEUtils {
+public:
+ static const char* advTypeToString(uint8_t advType);
+ static esp_gatt_id_t buildGattId(esp_bt_uuid_t uuid, uint8_t inst_id=0);
+
+ static esp_gatt_srvc_id_t buildGattSrvcId(esp_gatt_id_t gattId, bool is_primary=true);
+ static std::string characteristicPropertiesToString(esp_gatt_char_prop_t prop);
+ static char* buildHexData(uint8_t *target, uint8_t *source, uint8_t length);
+ static BLEClient* findByConnId(uint16_t conn_id);
+ static BLEClient* findByAddress(BLEAddress address);
+ static std::string gattClientEventTypeToString(esp_gattc_cb_event_t eventType);
+ static std::string gattServerEventTypeToString(esp_gatts_cb_event_t eventType);
+ static std::string gattServiceIdToString(esp_gatt_srvc_id_t srvcId);
+ static std::string gattStatusToString(esp_gatt_status_t status);
+ static std::string gattServiceToString(uint32_t serviceId);
+ static std::string gattCloseReasonToString(esp_gatt_conn_reason_t reason);
+ static void registerByAddress(BLEAddress address, BLEClient* pDevice);
+ static void registerByConnId(uint16_t conn_id, BLEClient* pDevice);
+ static std::string gattCharacteristicUUIDToString(uint32_t characteristicUUID);
+ static std::string buildPrintData(uint8_t* source, size_t length);
+ static void dumpGattClientEvent(
+ esp_gattc_cb_event_t event,
+ esp_gatt_if_t gattc_if,
+ esp_ble_gattc_cb_param_t* evtParam);
+ static void dumpGattServerEvent(
+ esp_gatts_cb_event_t event,
+ esp_gatt_if_t gatts_if,
+ esp_ble_gatts_cb_param_t* evtParam);
+ static const char* devTypeToString(esp_bt_dev_type_t type);
+ static void dumpGapEvent(
+ esp_gap_ble_cb_event_t event,
+ esp_ble_gap_cb_param_t* param);
+ static const char* gapEventToString(uint32_t eventType);
+ static const char* searchEventTypeToString(esp_gap_search_evt_t searchEvt);
+ static const char* addressTypeToString(esp_ble_addr_type_t type);
+ static const char *eventTypeToString(esp_ble_evt_type_t eventType);
+};
+
+#endif // CONFIG_BT_ENABLED
+#endif /* COMPONENTS_CPP_UTILS_BLEUTILS_H_ */
diff --git a/src/BLEValue.cpp b/src/BLEValue.cpp
new file mode 100644
index 0000000..1989993
--- /dev/null
+++ b/src/BLEValue.cpp
@@ -0,0 +1,116 @@
+/*
+ * BLEValue.cpp
+ *
+ * Created on: Jul 17, 2017
+ * Author: kolban
+ */
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+
+#include <esp_log.h>
+
+#include "BLEValue.h"
+
+static const char* LOG_TAG="BLEValue";
+
+BLEValue::BLEValue() {
+ m_accumulation = "";
+ m_value = "";
+ m_readOffset = 0;
+} // BLEValue
+
+
+/**
+ * @brief Add a message part to the accumulation.
+ * The accumulation is a growing set of data that is added to until a commit or cancel.
+ * @param [in] part A message part being added.
+ */
+void BLEValue::addPart(std::string part) {
+ ESP_LOGD(LOG_TAG, ">> addPart: length=%d", part.length());
+ m_accumulation += part;
+} // addPart
+
+
+/**
+ * @brief Add a message part to the accumulation.
+ * The accumulation is a growing set of data that is added to until a commit or cancel.
+ * @param [in] pData A message part being added.
+ * @param [in] length The number of bytes being added.
+ */
+void BLEValue::addPart(uint8_t* pData, size_t length) {
+ ESP_LOGD(LOG_TAG, ">> addPart: length=%d", length);
+ m_accumulation += std::string((char *)pData, length);
+} // addPart
+
+
+/**
+ * @brief Cancel the current accumulation.
+ */
+void BLEValue::cancel() {
+ ESP_LOGD(LOG_TAG, ">> cancel");
+ m_accumulation = "";
+ m_readOffset = 0;
+} // cancel
+
+
+/**
+ * @brief Commit the current accumulation.
+ * When writing a value, we may find that we write it in "parts" meaning that the writes come in in pieces
+ * of the overall message. After the last part has been received, we may perform a commit which means that
+ * we now have the complete message and commit the change as a unit.
+ */
+void BLEValue::commit() {
+ ESP_LOGD(LOG_TAG, ">> commit");
+ // If there is nothing to commit, do nothing.
+ if (m_accumulation.length() == 0) {
+ return;
+ }
+ setValue(m_accumulation);
+ m_accumulation = "";
+ m_readOffset = 0;
+} // commit
+
+
+/**
+ * @brief Get the read offset.
+ * @return The read offset into the read.
+ */
+uint16_t BLEValue::getReadOffset() {
+ return m_readOffset;
+} // getReadOffset
+
+
+/**
+ * @brief Get the current value.
+ */
+std::string BLEValue::getValue() {
+ return m_value;
+} // getValue
+
+
+/**
+ * @brief Set the read offset
+ * @param [in] readOffset The offset into the read.
+ */
+void BLEValue::setReadOffset(uint16_t readOffset) {
+ m_readOffset = readOffset;
+} // setReadOffset
+
+
+/**
+ * @brief Set the current value.
+ */
+void BLEValue::setValue(std::string value) {
+ m_value = value;
+} // setValue
+
+
+/**
+ * @brief Set the current value.
+ * @param [in] pData The data for the current value.
+ * @param [in] The length of the new current value.
+ */
+void BLEValue::setValue(uint8_t* pData, size_t length) {
+ m_value = std::string((char*)pData, length);
+} // setValue
+#endif // CONFIG_BT_ENABLED
diff --git a/src/BLEValue.h b/src/BLEValue.h
new file mode 100644
index 0000000..a292c6e
--- /dev/null
+++ b/src/BLEValue.h
@@ -0,0 +1,36 @@
+/*
+ * BLEValue.h
+ *
+ * Created on: Jul 17, 2017
+ * Author: kolban
+ */
+
+#ifndef COMPONENTS_CPP_UTILS_BLEVALUE_H_
+#define COMPONENTS_CPP_UTILS_BLEVALUE_H_
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+#include <string>
+
+/**
+ * @brief The model of a %BLE value.
+ */
+class BLEValue {
+public:
+ BLEValue();
+ void addPart(std::string part);
+ void addPart(uint8_t* pData, size_t length);
+ void cancel();
+ void commit();
+ uint16_t getReadOffset();
+ std::string getValue();
+ void setReadOffset(uint16_t readOffset);
+ void setValue(std::string value);
+ void setValue(uint8_t* pData, size_t length);
+
+private:
+ std::string m_accumulation;
+ uint16_t m_readOffset;
+ std::string m_value;
+};
+#endif // CONFIG_BT_ENABLED
+#endif /* COMPONENTS_CPP_UTILS_BLEVALUE_H_ */
diff --git a/src/FreeRTOS.cpp b/src/FreeRTOS.cpp
new file mode 100644
index 0000000..663128d
--- /dev/null
+++ b/src/FreeRTOS.cpp
@@ -0,0 +1,165 @@
+/*
+ * FreeRTOS.cpp
+ *
+ * Created on: Feb 24, 2017
+ * Author: kolban
+ */
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <string>
+#include <sstream>
+#include <iomanip>
+#include "FreeRTOS.h"
+#include <esp_log.h>
+#include "sdkconfig.h"
+
+static const char* LOG_TAG = "FreeRTOS";
+
+/**
+ * Sleep for the specified number of milliseconds.
+ * @param[in] ms The period in milliseconds for which to sleep.
+ */
+void FreeRTOS::sleep(uint32_t ms) {
+ ::vTaskDelay(ms/portTICK_PERIOD_MS);
+} // sleep
+
+
+/**
+ * Start a new task.
+ * @param[in] task The function pointer to the function to be run in the task.
+ * @param[in] taskName A string identifier for the task.
+ * @param[in] param An optional parameter to be passed to the started task.
+ * @param[in] stackSize An optional paremeter supplying the size of the stack in which to run the task.
+ */
+void FreeRTOS::startTask(void task(void*), std::string taskName, void *param, int stackSize) {
+ ::xTaskCreate(task, taskName.data(), stackSize, param, 5, NULL);
+} // startTask
+
+
+/**
+ * Delete the task.
+ * @param[in] pTask An optional handle to the task to be deleted. If not supplied the calling task will be deleted.
+ */
+void FreeRTOS::deleteTask(TaskHandle_t pTask) {
+ ::vTaskDelete(pTask);
+} // deleteTask
+
+
+/**
+ * Get the time in milliseconds since the %FreeRTOS scheduler started.
+ * @return The time in milliseconds since the %FreeRTOS scheduler started.
+ */
+uint32_t FreeRTOS::getTimeSinceStart() {
+ return (uint32_t)(xTaskGetTickCount()*portTICK_PERIOD_MS);
+} // getTimeSinceStart
+
+/*
+ * public:
+ Semaphore(std::string = "<Unknown>");
+ ~Semaphore();
+ void give();
+ void take(std::string owner="<Unknown>");
+ void take(uint32_t timeoutMs, std::string owner="<Unknown>");
+ private:
+ SemaphoreHandle_t m_semaphore;
+ std::string m_name;
+ std::string m_owner;
+ };
+ *
+ */
+
+/**
+ * @brief Wait for a semaphore to be released by trying to take it and
+ * then releasing it again.
+ * @param [in] owner A debug tag.
+ * @return The value associated with the semaphore.
+ */
+uint32_t FreeRTOS::Semaphore::wait(std::string owner) {
+ ESP_LOGV(LOG_TAG, "Semaphore waiting: %s for %s", toString().c_str(), owner.c_str());
+ xSemaphoreTake(m_semaphore, portMAX_DELAY);
+ m_owner = owner;
+ xSemaphoreGive(m_semaphore);
+ ESP_LOGV(LOG_TAG, "Semaphore released: %s", toString().c_str());
+ m_owner = "<N/A>";
+ return m_value;
+} // wait
+
+FreeRTOS::Semaphore::Semaphore(std::string name) {
+ m_semaphore = xSemaphoreCreateMutex();
+ m_name = name;
+ m_owner = "<N/A>";
+ m_value = 0;
+}
+
+FreeRTOS::Semaphore::~Semaphore() {
+ vSemaphoreDelete(m_semaphore);
+}
+
+
+/**
+ * @brief Give a semaphore.
+ * The Semaphore is given.
+ */
+void FreeRTOS::Semaphore::give() {
+ xSemaphoreGive(m_semaphore);
+ ESP_LOGV(LOG_TAG, "Semaphore giving: %s", toString().c_str());
+ m_owner = "<N/A>";
+} // Semaphore::give
+
+
+/**
+ * @brief Give a semaphore.
+ * The Semaphore is given with an associated value.
+ * @param [in] value The value to associate with the semaphore.
+ */
+void FreeRTOS::Semaphore::give(uint32_t value) {
+ m_value = value;
+ give();
+}
+
+
+/**
+ * @brief Give a semaphore from an ISR.
+ */
+void FreeRTOS::Semaphore::giveFromISR() {
+ BaseType_t higherPriorityTaskWoken;
+ xSemaphoreGiveFromISR(m_semaphore, &higherPriorityTaskWoken);
+} // giveFromISR
+
+
+/**
+ * @brief Take a semaphore.
+ * Take a semaphore and wait indefinitely.
+ */
+void FreeRTOS::Semaphore::take(std::string owner)
+{
+
+ ESP_LOGV(LOG_TAG, "Semaphore taking: %s for %s", toString().c_str(), owner.c_str());
+ xSemaphoreTake(m_semaphore, portMAX_DELAY);
+ m_owner = owner;
+ ESP_LOGV(LOG_TAG, "Semaphore taken: %s", toString().c_str());
+} // Semaphore::take
+
+
+/**
+ * @brief Take a semaphore.
+ * Take a semaphore but return if we haven't obtained it in the given period of milliseconds.
+ * @param [in] timeoutMs Timeout in milliseconds.
+ */
+void FreeRTOS::Semaphore::take(uint32_t timeoutMs, std::string owner) {
+ ESP_LOGV(LOG_TAG, "Semaphore taking: %s for %s", toString().c_str(), owner.c_str());
+ m_owner = owner;
+ xSemaphoreTake(m_semaphore, timeoutMs/portTICK_PERIOD_MS);
+ ESP_LOGV(LOG_TAG, "Semaphore taken: %s", toString().c_str());
+} // Semaphore::take
+
+std::string FreeRTOS::Semaphore::toString() {
+ std::stringstream stringStream;
+ stringStream << "name: "<< m_name << " (0x" << std::hex << std::setfill('0') << (uint32_t)m_semaphore << "), owner: " << m_owner;
+ return stringStream.str();
+}
+
+void FreeRTOS::Semaphore::setName(std::string name) {
+ m_name = name;
+}
+
diff --git a/src/FreeRTOS.h b/src/FreeRTOS.h
new file mode 100644
index 0000000..320f4cc
--- /dev/null
+++ b/src/FreeRTOS.h
@@ -0,0 +1,49 @@
+/*
+ * FreeRTOS.h
+ *
+ * Created on: Feb 24, 2017
+ * Author: kolban
+ */
+
+#ifndef MAIN_FREERTOS_H_
+#define MAIN_FREERTOS_H_
+#include <stdint.h>
+#include <string>
+
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <freertos/semphr.h>
+
+
+/**
+ * @brief Interface to %FreeRTOS functions.
+ */
+class FreeRTOS {
+public:
+ static void sleep(uint32_t ms);
+ static void startTask(void task(void *), std::string taskName, void *param=nullptr, int stackSize = 2048);
+ static void deleteTask(TaskHandle_t pTask = nullptr);
+
+ static uint32_t getTimeSinceStart();
+
+ class Semaphore {
+ public:
+ Semaphore(std::string owner = "<Unknown>");
+ ~Semaphore();
+ void give();
+ void giveFromISR();
+ void give(uint32_t value);
+ void setName(std::string name);
+ void take(std::string owner="<Unknown>");
+ void take(uint32_t timeoutMs, std::string owner="<Unknown>");
+ uint32_t wait(std::string owner="<Unknown>");
+ std::string toString();
+ private:
+ SemaphoreHandle_t m_semaphore;
+ std::string m_name;
+ std::string m_owner;
+ uint32_t m_value;
+ };
+};
+
+#endif /* MAIN_FREERTOS_H_ */
diff --git a/src/GeneralUtils.cpp b/src/GeneralUtils.cpp
new file mode 100644
index 0000000..4237b14
--- /dev/null
+++ b/src/GeneralUtils.cpp
@@ -0,0 +1,388 @@
+/*
+ * GeneralUtils.cpp
+ *
+ * Created on: May 20, 2017
+ * Author: kolban
+ */
+
+#include "GeneralUtils.h"
+#include <esp_log.h>
+#include <string.h>
+#include <stdio.h>
+#include <string>
+#include <sstream>
+#include <iomanip>
+#include <FreeRTOS.h>
+#include <esp_err.h>
+#include <nvs.h>
+#include <esp_wifi.h>
+
+static const char* LOG_TAG = "GeneralUtils";
+
+static const char kBase64Alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789+/";
+
+GeneralUtils::GeneralUtils() {
+ // TODO Auto-generated constructor stub
+
+}
+
+GeneralUtils::~GeneralUtils() {
+ // TODO Auto-generated destructor stub
+}
+
+static int base64EncodedLength(size_t length) {
+ return (length + 2 - ((length + 2) % 3)) / 3 * 4;
+} // base64EncodedLength
+
+
+static int base64EncodedLength(const std::string &in) {
+ return base64EncodedLength(in.length());
+} // base64EncodedLength
+
+
+static void a3_to_a4(unsigned char * a4, unsigned char * a3) {
+ a4[0] = (a3[0] & 0xfc) >> 2;
+ a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4);
+ a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6);
+ a4[3] = (a3[2] & 0x3f);
+} // a3_to_a4
+
+
+static void a4_to_a3(unsigned char * a3, unsigned char * a4) {
+ a3[0] = (a4[0] << 2) + ((a4[1] & 0x30) >> 4);
+ a3[1] = ((a4[1] & 0xf) << 4) + ((a4[2] & 0x3c) >> 2);
+ a3[2] = ((a4[2] & 0x3) << 6) + a4[3];
+} // a4_to_a3
+
+
+/**
+ * @brief Encode a string into base 64.
+ * @param [in] in
+ * @param [out] out
+ */
+bool GeneralUtils::base64Encode(const std::string &in, std::string *out) {
+ int i = 0, j = 0;
+ size_t enc_len = 0;
+ unsigned char a3[3];
+ unsigned char a4[4];
+
+ out->resize(base64EncodedLength(in));
+
+ int input_len = in.size();
+ std::string::const_iterator input = in.begin();
+
+ while (input_len--) {
+ a3[i++] = *(input++);
+ if (i == 3) {
+ a3_to_a4(a4, a3);
+
+ for (i = 0; i < 4; i++) {
+ (*out)[enc_len++] = kBase64Alphabet[a4[i]];
+ }
+
+ i = 0;
+ }
+ }
+
+ if (i) {
+ for (j = i; j < 3; j++) {
+ a3[j] = '\0';
+ }
+
+ a3_to_a4(a4, a3);
+
+ for (j = 0; j < i + 1; j++) {
+ (*out)[enc_len++] = kBase64Alphabet[a4[j]];
+ }
+
+ while ((i++ < 3)) {
+ (*out)[enc_len++] = '=';
+ }
+ }
+
+ return (enc_len == out->size());
+} // base64Encode
+
+
+static int DecodedLength(const std::string &in) {
+ int numEq = 0;
+ int n = in.size();
+
+ for (std::string::const_reverse_iterator it = in.rbegin(); *it == '='; ++it) {
+ ++numEq;
+ }
+ return ((6 * n) / 8) - numEq;
+} // DecodedLength
+
+
+static unsigned char b64_lookup(unsigned char c) {
+ if(c >='A' && c <='Z') return c - 'A';
+ if(c >='a' && c <='z') return c - 71;
+ if(c >='0' && c <='9') return c + 4;
+ if(c == '+') return 62;
+ if(c == '/') return 63;
+ return 255;
+}; // b64_lookup
+
+
+/**
+ * @brief Decode a chunk of data that is base64 encoded.
+ * @param [in] in The string to be decoded.
+ * @param [out] out The resulting data.
+ */
+bool GeneralUtils::base64Decode(const std::string &in, std::string *out) {
+ int i = 0, j = 0;
+ size_t dec_len = 0;
+ unsigned char a3[3];
+ unsigned char a4[4];
+
+ int input_len = in.size();
+ std::string::const_iterator input = in.begin();
+
+ out->resize(DecodedLength(in));
+
+ while (input_len--) {
+ if (*input == '=') {
+ break;
+ }
+
+ a4[i++] = *(input++);
+ if (i == 4) {
+ for (i = 0; i <4; i++) {
+ a4[i] = b64_lookup(a4[i]);
+ }
+
+ a4_to_a3(a3,a4);
+
+ for (i = 0; i < 3; i++) {
+ (*out)[dec_len++] = a3[i];
+ }
+
+ i = 0;
+ }
+ }
+
+ if (i) {
+ for (j = i; j < 4; j++) {
+ a4[j] = '\0';
+ }
+
+ for (j = 0; j < 4; j++) {
+ a4[j] = b64_lookup(a4[j]);
+ }
+
+ a4_to_a3(a3,a4);
+
+ for (j = 0; j < i - 1; j++) {
+ (*out)[dec_len++] = a3[j];
+ }
+ }
+
+ return (dec_len == out->size());
+ } // base64Decode
+
+/*
+void GeneralUtils::hexDump(uint8_t* pData, uint32_t length) {
+ uint32_t index=0;
+ std::stringstream ascii;
+ std::stringstream hex;
+ char asciiBuf[80];
+ char hexBuf[80];
+ hex.str("");
+ ascii.str("");
+ while(index < length) {
+ hex << std::setfill('0') << std::setw(2) << std::hex << (int)pData[index] << ' ';
+ if (std::isprint(pData[index])) {
+ ascii << pData[index];
+ } else {
+ ascii << '.';
+ }
+ index++;
+ if (index % 16 == 0) {
+ strcpy(hexBuf, hex.str().c_str());
+ strcpy(asciiBuf, ascii.str().c_str());
+ ESP_LOGD(tag, "%s %s", hexBuf, asciiBuf);
+ hex.str("");
+ ascii.str("");
+ }
+ }
+ if (index %16 != 0) {
+ while(index % 16 != 0) {
+ hex << " ";
+ index++;
+ }
+ strcpy(hexBuf, hex.str().c_str());
+ strcpy(asciiBuf, ascii.str().c_str());
+ ESP_LOGD(tag, "%s %s", hexBuf, asciiBuf);
+ //ESP_LOGD(tag, "%s %s", hex.str().c_str(), ascii.str().c_str());
+ }
+ FreeRTOS::sleep(1000);
+}
+*/
+
+/*
+void GeneralUtils::hexDump(uint8_t* pData, uint32_t length) {
+ uint32_t index=0;
+ static std::stringstream ascii;
+ static std::stringstream hex;
+ hex.str("");
+ ascii.str("");
+ while(index < length) {
+ hex << std::setfill('0') << std::setw(2) << std::hex << (int)pData[index] << ' ';
+ if (std::isprint(pData[index])) {
+ ascii << pData[index];
+ } else {
+ ascii << '.';
+ }
+ index++;
+ if (index % 16 == 0) {
+ ESP_LOGD(tag, "%s %s", hex.str().c_str(), ascii.str().c_str());
+ hex.str("");
+ ascii.str("");
+ }
+ }
+ if (index %16 != 0) {
+ while(index % 16 != 0) {
+ hex << " ";
+ index++;
+ }
+ ESP_LOGD(tag, "%s %s", hex.str().c_str(), ascii.str().c_str());
+ }
+ FreeRTOS::sleep(1000);
+}
+*/
+
+/**
+ * @brief Dump a representation of binary data to the console.
+ *
+ * @param [in] pData Pointer to the start of data to be logged.
+ * @param [in] length Length of the data (in bytes) to be logged.
+ * @return N/A.
+ */
+void GeneralUtils::hexDump(uint8_t* pData, uint32_t length) {
+ char ascii[80];
+ char hex[80];
+ char tempBuf[80];
+ uint32_t lineNumber = 0;
+
+ ESP_LOGD(LOG_TAG, " 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ----------------");
+ strcpy(ascii, "");
+ strcpy(hex, "");
+ uint32_t index=0;
+ while(index < length) {
+ sprintf(tempBuf, "%.2x ", pData[index]);
+ strcat(hex, tempBuf);
+ if (isprint(pData[index])) {
+ sprintf(tempBuf, "%c", pData[index]);
+ } else {
+ sprintf(tempBuf, ".");
+ }
+ strcat(ascii, tempBuf);
+ index++;
+ if (index % 16 == 0) {
+ ESP_LOGD(LOG_TAG, "%.4x %s %s", lineNumber*16, hex, ascii);
+ strcpy(ascii, "");
+ strcpy(hex, "");
+ lineNumber++;
+ }
+ }
+ if (index %16 != 0) {
+ while(index % 16 != 0) {
+ strcat(hex, " ");
+ index++;
+ }
+ ESP_LOGD(LOG_TAG, "%.4x %s %s", lineNumber*16, hex, ascii);
+ }
+} // hexDump
+
+/**
+ * @brief Convert an IP address to string.
+ * @param ip The 4 byte IP address.
+ * @return A string representation of the IP address.
+ */
+std::string GeneralUtils::ipToString(uint8_t *ip) {
+ std::stringstream s;
+ s << (int)ip[0] << '.' << (int)ip[1] << '.' << (int)ip[2] << '.' << (int)ip[3];
+ return s.str();
+} // ipToString
+
+
+/**
+ * @brief Convert an ESP error code to a string.
+ * @param [in] errCode The errCode to be converted.
+ * @return A string representation of the error code.
+ */
+const char* GeneralUtils::errorToString(esp_err_t errCode) {
+ switch(errCode) {
+ case ESP_OK:
+ return "OK";
+ case ESP_FAIL:
+ return "Fail";
+ case ESP_ERR_NO_MEM:
+ return "No memory";
+ case ESP_ERR_INVALID_ARG:
+ return "Invalid argument";
+ case ESP_ERR_INVALID_SIZE:
+ return "Invalid state";
+ case ESP_ERR_INVALID_STATE:
+ return "Invalid state";
+ case ESP_ERR_NOT_FOUND:
+ return "Not found";
+ case ESP_ERR_NOT_SUPPORTED:
+ return "Not supported";
+ case ESP_ERR_TIMEOUT:
+ return "Timeout";
+ case ESP_ERR_NVS_NOT_INITIALIZED:
+ return "ESP_ERR_NVS_NOT_INITIALIZED";
+ case ESP_ERR_NVS_NOT_FOUND:
+ return "ESP_ERR_NVS_NOT_FOUND";
+ case ESP_ERR_NVS_TYPE_MISMATCH:
+ return "ESP_ERR_NVS_TYPE_MISMATCH";
+ case ESP_ERR_NVS_READ_ONLY:
+ return "ESP_ERR_NVS_READ_ONLY";
+ case ESP_ERR_NVS_NOT_ENOUGH_SPACE:
+ return "ESP_ERR_NVS_NOT_ENOUGH_SPACE";
+ case ESP_ERR_NVS_INVALID_NAME:
+ return "ESP_ERR_NVS_INVALID_NAME";
+ case ESP_ERR_NVS_INVALID_HANDLE:
+ return "ESP_ERR_NVS_INVALID_HANDLE";
+ case ESP_ERR_NVS_REMOVE_FAILED:
+ return "ESP_ERR_NVS_REMOVE_FAILED";
+ case ESP_ERR_NVS_KEY_TOO_LONG:
+ return "ESP_ERR_NVS_KEY_TOO_LONG";
+ case ESP_ERR_NVS_PAGE_FULL:
+ return "ESP_ERR_NVS_PAGE_FULL";
+ case ESP_ERR_NVS_INVALID_STATE:
+ return "ESP_ERR_NVS_INVALID_STATE";
+ case ESP_ERR_NVS_INVALID_LENGTH:
+ return "ESP_ERR_NVS_INVALID_LENGTH";
+ case ESP_ERR_WIFI_NOT_INIT:
+ return "ESP_ERR_WIFI_NOT_INIT";
+ //case ESP_ERR_WIFI_NOT_START:
+ // return "ESP_ERR_WIFI_NOT_START";
+ case ESP_ERR_WIFI_IF:
+ return "ESP_ERR_WIFI_IF";
+ case ESP_ERR_WIFI_MODE:
+ return "ESP_ERR_WIFI_MODE";
+ case ESP_ERR_WIFI_STATE:
+ return "ESP_ERR_WIFI_STATE";
+ case ESP_ERR_WIFI_CONN:
+ return "ESP_ERR_WIFI_CONN";
+ case ESP_ERR_WIFI_NVS:
+ return "ESP_ERR_WIFI_NVS";
+ case ESP_ERR_WIFI_MAC:
+ return "ESP_ERR_WIFI_MAC";
+ case ESP_ERR_WIFI_SSID:
+ return "ESP_ERR_WIFI_SSID";
+ case ESP_ERR_WIFI_PASSWORD:
+ return "ESP_ERR_WIFI_PASSWORD";
+ case ESP_ERR_WIFI_TIMEOUT:
+ return "ESP_ERR_WIFI_TIMEOUT";
+ case ESP_ERR_WIFI_WAKE_FAIL:
+ return "ESP_ERR_WIFI_WAKE_FAIL";
+ }
+ return "Unknown ESP_ERR error";
+} // errorToString
+
diff --git a/src/GeneralUtils.h b/src/GeneralUtils.h
new file mode 100644
index 0000000..152abca
--- /dev/null
+++ b/src/GeneralUtils.h
@@ -0,0 +1,28 @@
+/*
+ * GeneralUtils.h
+ *
+ * Created on: May 20, 2017
+ * Author: kolban
+ */
+
+#ifndef COMPONENTS_CPP_UTILS_GENERALUTILS_H_
+#define COMPONENTS_CPP_UTILS_GENERALUTILS_H_
+#include <stdint.h>
+#include <string>
+#include <esp_err.h>
+
+/**
+ * @brief General utilities.
+ */
+class GeneralUtils {
+public:
+ GeneralUtils();
+ virtual ~GeneralUtils();
+ static void hexDump(uint8_t *pData, uint32_t length);
+ static std::string ipToString(uint8_t *ip);
+ static bool base64Encode(const std::string &in, std::string *out);
+ static bool base64Decode(const std::string &in, std::string *out);
+ static const char *errorToString(esp_err_t errCode);
+};
+
+#endif /* COMPONENTS_CPP_UTILS_GENERALUTILS_H_ */