diff options
Diffstat (limited to 'ESP32_BLE_Arduino')
50 files changed, 8230 insertions, 0 deletions
diff --git a/ESP32_BLE_Arduino/examples/Arduino/BLE_client/BLE_client.ino b/ESP32_BLE_Arduino/examples/Arduino/BLE_client/BLE_client.ino new file mode 100644 index 0000000..3fc5efa --- /dev/null +++ b/ESP32_BLE_Arduino/examples/Arduino/BLE_client/BLE_client.ino @@ -0,0 +1,128 @@ +/** + * A BLE client example that is rich in capabilities. + */ + +#include "BLEDevice.h" +//#include "BLEScan.h" + +// The remote service we wish to connect to. +static BLEUUID serviceUUID("91bad492-b950-4226-aa2b-4ede9fa42f59"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("0d563a58-196a-48ce-ace2-dfec78acc814"); + +static BLEAddress *pServerAddress; +static boolean doConnect = false; +static boolean connected = false; +static BLERemoteCharacteristic* pRemoteCharacteristic; + +static void notifyCallback( + BLERemoteCharacteristic* pBLERemoteCharacteristic, + uint8_t* pData, + size_t length, + bool isNotify) { + Serial.print("Notify callback for characteristic "); + Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str()); + Serial.print(" of data length "); + Serial.println(length); +} + +void connectToServer(BLEAddress pAddress) { + Serial.print("Forming a connection to "); + Serial.println(pAddress.toString().c_str()); + + BLEClient* pClient = BLEDevice::createClient(); + Serial.println(" - Created client"); + + // Connect to the remove BLE Server. + pClient->connect(pAddress); + Serial.println(" - Connected to server"); + + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + Serial.print("Failed to find our service UUID: "); + Serial.println(serviceUUID.toString().c_str()); + return; + } + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + Serial.print("Failed to find our characteristic UUID: "); + Serial.println(charUUID.toString().c_str()); + return; + } + + // Read the value of the characteristic. + std::string value = pRemoteCharacteristic->readValue(); + Serial.print("The characteristic value was: "); + Serial.println(value.c_str()); + + pRemoteCharacteristic->registerForNotify(notifyCallback); +} +/** + * Scan for BLE servers and find the first one that advertises the service we are looking for. + */ +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + /** + * Called for each advertising BLE server. + */ + void onResult(BLEAdvertisedDevice advertisedDevice) { + Serial.print("BLE Advertised Device found: "); + Serial.println(advertisedDevice.toString().c_str()); + + // We have found a device, let us now see if it contains the service we are looking for. + if (advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(serviceUUID)) { + + // + Serial.print("Found our device! address: "); + advertisedDevice.getScan()->stop(); + + pServerAddress = new BLEAddress(advertisedDevice.getAddress()); + doConnect = true; + + } // Found our server + } // onResult +}; // MyAdvertisedDeviceCallbacks + + +void setup() { + Serial.begin(115200); + Serial.println("Starting Arduino BLE Client application..."); + BLEDevice::init(""); + + // Retrieve a Scanner and set the callback we want to use to be informed when we + // have detected a new device. Specify that we want active scanning and start the + // scan to run for 30 seconds. + BLEScan* pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); + pBLEScan->start(30); +} // End of setup. + + +// This is the Arduino main loop function. +void loop() { + + // If the flag "doConnect" is true then we have scanned for and found the desired + // BLE Server with which we wish to connect. Now we connect to it. Once we are + // connected we set the connected flag to be true. + if (doConnect == true) { + connectToServer(*pServerAddress); + doConnect = false; + connected = true; + } + + // If we are connected to a peer BLE Server, update the characteristic each time we are reached + // with the current time since boot. + if (connected) { + String newValue = "Time since boot: " + String(millis()/1000); + Serial.println("Setting new characteristic value to \"" + newValue + "\""); + + // Set the characteristic's value to be the array of bytes that is actually a string. + pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length()); + } + + delay(1000); // Delay a second between loops. +} // End of loop diff --git a/ESP32_BLE_Arduino/examples/Arduino/BLE_notify/BLE_notify.ino b/ESP32_BLE_Arduino/examples/Arduino/BLE_notify/BLE_notify.ino new file mode 100644 index 0000000..44506c2 --- /dev/null +++ b/ESP32_BLE_Arduino/examples/Arduino/BLE_notify/BLE_notify.ino @@ -0,0 +1,93 @@ +/* + Video: https://www.youtube.com/watch?v=oCMOYS71NIU + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp + Ported to Arduino ESP32 by Evandro Copercini + + Create a BLE server that, once we receive a connection, will send periodic notifications. + The service advertises itself as: 4fafc201-1fb5-459e-8fcc-c5c9c331914b + And has a characteristic of: beb5483e-36e1-4688-b7f5-ea07361b26a8 + + The design of creating the BLE server is: + 1. Create a BLE Server + 2. Create a BLE Service + 3. Create a BLE Characteristic on the Service + 4. Create a BLE Descriptor on the characteristic + 5. Start the service. + 6. Start advertising. + + A connect hander associated with the server starts a background task that performs notification + every couple of seconds. +*/ +#include <BLEDevice.h> +#include <BLEServer.h> +#include <BLEUtils.h> +#include <BLE2902.h> + +BLECharacteristic *pCharacteristic; +bool deviceConnected = false; +uint8_t value = 0; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" + + +class MyServerCallbacks: public BLEServerCallbacks { + void onConnect(BLEServer* pServer) { + deviceConnected = true; + }; + + void onDisconnect(BLEServer* pServer) { + deviceConnected = false; + } +}; + + + +void setup() { + Serial.begin(115200); + + // Create the BLE Device + BLEDevice::init("MyESP32"); + + // Create the BLE Server + BLEServer *pServer = new BLEServer(); + pServer->setCallbacks(new MyServerCallbacks()); + + // Create the BLE Service + BLEService *pService = pServer->createService(SERVICE_UUID); + + // Create a BLE Characteristic + pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_NOTIFY | + BLECharacteristic::PROPERTY_INDICATE + ); + + // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml + // Create a BLE Descriptor + pCharacteristic->addDescriptor(new BLE2902()); + + // Start the service + pService->start(); + + // Start advertising + pServer->getAdvertising()->start(); + Serial.println("Waiting a client connection to notify..."); +} + +void loop() { + + if (deviceConnected) { + Serial.printf("*** NOTIFY: %d ***\n", value); + pCharacteristic->setValue(&value, 1); + pCharacteristic->notify(); + //pCharacteristic->indicate(); + value++; + } + delay(2000); +}
\ No newline at end of file diff --git a/ESP32_BLE_Arduino/examples/Arduino/BLE_scan/BLE_scan.ino b/ESP32_BLE_Arduino/examples/Arduino/BLE_scan/BLE_scan.ino new file mode 100644 index 0000000..ef7d892 --- /dev/null +++ b/ESP32_BLE_Arduino/examples/Arduino/BLE_scan/BLE_scan.ino @@ -0,0 +1,36 @@ +/* + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleScan.cpp + Ported to Arduino ESP32 by Evandro Copercini +*/ + +#include <BLEDevice.h> +#include <BLEUtils.h> +#include <BLEScan.h> +#include <BLEAdvertisedDevice.h> + +int scanTime = 30; //In seconds + +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + void onResult(BLEAdvertisedDevice advertisedDevice) { + Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str()); + } +}; + +void setup() { + Serial.begin(115200); + Serial.println("Scanning..."); + + BLEDevice::init(""); + BLEScan* pBLEScan = BLEDevice::getScan(); //create new scan + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster + BLEScanResults foundDevices = pBLEScan->start(scanTime); + Serial.print("Devices found: "); + Serial.println(foundDevices.getCount()); + Serial.println("Scan done!"); +} + +void loop() { + // put your main code here, to run repeatedly: + delay(2000); +}
\ No newline at end of file diff --git a/ESP32_BLE_Arduino/examples/Arduino/BLE_server/BLE_server.ino b/ESP32_BLE_Arduino/examples/Arduino/BLE_server/BLE_server.ino new file mode 100644 index 0000000..45ebf99 --- /dev/null +++ b/ESP32_BLE_Arduino/examples/Arduino/BLE_server/BLE_server.ino @@ -0,0 +1,41 @@ +/* + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp + Ported to Arduino ESP32 by Evandro Copercini +*/ + +#include <BLEDevice.h> +#include <BLEUtils.h> +#include <BLEServer.h> + +BLEDevice ble; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" + +void setup() { + Serial.begin(115200); + Serial.println("Starting BLE work!"); + + ble.init("MyESP32"); + BLEServer *pServer = new BLEServer(); + BLEService *pService = pServer->createService(SERVICE_UUID); + BLECharacteristic *pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE + ); + + pCharacteristic->setValue("Hello World says Neil"); + pService->start(); + BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->start(); + Serial.println("Characteristic defined! Now you can read it in your phone!"); +} + +void loop() { + // put your main code here, to run repeatedly: + delay(2000); +}
\ No newline at end of file diff --git a/ESP32_BLE_Arduino/examples/Arduino/BLE_uart/BLE_uart.ino b/ESP32_BLE_Arduino/examples/Arduino/BLE_uart/BLE_uart.ino new file mode 100644 index 0000000..a8ab2d7 --- /dev/null +++ b/ESP32_BLE_Arduino/examples/Arduino/BLE_uart/BLE_uart.ino @@ -0,0 +1,111 @@ +/* + Video: https://www.youtube.com/watch?v=oCMOYS71NIU + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp + Ported to Arduino ESP32 by Evandro Copercini + + Create a BLE server that, once we receive a connection, will send periodic notifications. + The service advertises itself as: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E + Has a characteristic of: 6E400002-B5A3-F393-E0A9-E50E24DCCA9E - used for receiving data with "WRITE" + Has a characteristic of: 6E400003-B5A3-F393-E0A9-E50E24DCCA9E - used to send data with "NOTIFY" + + The design of creating the BLE server is: + 1. Create a BLE Server + 2. Create a BLE Service + 3. Create a BLE Characteristic on the Service + 4. Create a BLE Descriptor on the characteristic + 5. Start the service. + 6. Start advertising. + + In this example rxValue is the data received (only accessible inside that function). + And txValue is the data to be sent, in this example just a byte incremented every second. +*/ +#include <BLEDevice.h> +#include <BLEServer.h> +#include <BLEUtils.h> +#include <BLE2902.h> + +BLECharacteristic *pCharacteristic; +bool deviceConnected = false; +uint8_t txValue = 0; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID +#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" +#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" + + +class MyServerCallbacks: public BLEServerCallbacks { + void onConnect(BLEServer* pServer) { + deviceConnected = true; + }; + + void onDisconnect(BLEServer* pServer) { + deviceConnected = false; + } +}; + +class MyCallbacks: public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic *pCharacteristic) { + std::string rxValue = pCharacteristic->getValue(); + + if (rxValue.length() > 0) { + Serial.println("*********"); + Serial.print("Received Value: "); + for (int i = 0; i < rxValue.length(); i++) + Serial.print(rxValue[i]); + + Serial.println(); + Serial.println("*********"); + } + } +}; + + +void setup() { + Serial.begin(115200); + + // Create the BLE Device + BLEDevice::init("UART Service"); + + // Create the BLE Server + BLEServer *pServer = new BLEServer(); + pServer->setCallbacks(new MyServerCallbacks()); + + // Create the BLE Service + BLEService *pService = pServer->createService(SERVICE_UUID); + + // Create a BLE Characteristic + pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID_TX, + BLECharacteristic::PROPERTY_NOTIFY + ); + + pCharacteristic->addDescriptor(new BLE2902()); + + BLECharacteristic *pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID_RX, + BLECharacteristic::PROPERTY_WRITE + ); + + pCharacteristic->setCallbacks(new MyCallbacks()); + + // Start the service + pService->start(); + + // Start advertising + pServer->getAdvertising()->start(); + Serial.println("Waiting a client connection to notify..."); +} + +void loop() { + + if (deviceConnected) { + Serial.printf("*** Sent Value: %d ***\n", txValue); + pCharacteristic->setValue(&txValue, 1); + pCharacteristic->notify(); + txValue++; + } + delay(1000); +} diff --git a/ESP32_BLE_Arduino/examples/Arduino/BLE_write/BLE_write.ino b/ESP32_BLE_Arduino/examples/Arduino/BLE_write/BLE_write.ino new file mode 100644 index 0000000..ed5ebc6 --- /dev/null +++ b/ESP32_BLE_Arduino/examples/Arduino/BLE_write/BLE_write.ino @@ -0,0 +1,68 @@ +/* + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleWrite.cpp + Ported to Arduino ESP32 by Evandro Copercini +*/ + +#include <BLEDevice.h> +#include <BLEUtils.h> +#include <BLEServer.h> + +BLEDevice ble; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" + + +class MyCallbacks: public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic *pCharacteristic) { + std::string value = pCharacteristic->getValue(); + + if (value.length() > 0) { + Serial.println("*********"); + Serial.print("New value: "); + for (int i = 0; i < value.length(); i++) + Serial.print(value[i]); + + Serial.println(); + Serial.println("*********"); + } + } +}; + +void setup() { + Serial.begin(115200); + + Serial.println("1- Download and install an BLE scanner app in your phone"); + Serial.println("2- Scan for BLE devices in the app"); + Serial.println("3- Connect to MyESP32"); + Serial.println("4- Go to CUSTOM CHARACTERISTIC in CUSTOM SERVICE and write something"); + Serial.println("5- See the magic =)"); + + //ble.begin("MyESP32"); + ble.init("MyESP32"); + BLEServer *pServer = new BLEServer(); + + BLEService *pService = pServer->createService(SERVICE_UUID); + + BLECharacteristic *pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE + ); + + pCharacteristic->setCallbacks(new MyCallbacks()); + + pCharacteristic->setValue("Hello World"); + pService->start(); + + BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->start(); +} + +void loop() { + // put your main code here, to run repeatedly: + delay(2000); +}
\ No newline at end of file diff --git a/ESP32_BLE_Arduino/library.properties b/ESP32_BLE_Arduino/library.properties new file mode 100644 index 0000000..09a02ba --- /dev/null +++ b/ESP32_BLE_Arduino/library.properties @@ -0,0 +1,11 @@ +name=ESP32 BLE Arduino +version=0.1.0 +author=Neil Kolban <kolban1@kolban.com> +maintainer=Neil Kolban <kolban1@kolban.com> +sentence=BLE functions for ESP32 +paragraph=BLE functions for ESP32 +category=Communication +url=https://github.com/nkolban/ESP32_BLE_Arduino +architectures=esp32 +includes=BLE.h BLEUtils.h BLEScan.h BLEAdvertisedDevice.h +dot_a_linkage=true
\ No newline at end of file diff --git a/ESP32_BLE_Arduino/src/BLE2902.cpp b/ESP32_BLE_Arduino/src/BLE2902.cpp new file mode 100644 index 0000000..8984da8 --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLE2902.h b/ESP32_BLE_Arduino/src/BLE2902.h new file mode 100644 index 0000000..397360a --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLEAddress.cpp b/ESP32_BLE_Arduino/src/BLEAddress.cpp new file mode 100644 index 0000000..d2f7f8b --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLEAddress.h b/ESP32_BLE_Arduino/src/BLEAddress.h new file mode 100644 index 0000000..7eff4da --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLEAdvertisedDevice.cpp b/ESP32_BLE_Arduino/src/BLEAdvertisedDevice.cpp new file mode 100644 index 0000000..97ec1e3 --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLEAdvertisedDevice.h b/ESP32_BLE_Arduino/src/BLEAdvertisedDevice.h new file mode 100644 index 0000000..2fb2652 --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLEAdvertising.cpp b/ESP32_BLE_Arduino/src/BLEAdvertising.cpp new file mode 100644 index 0000000..ad076dd --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLEAdvertising.h b/ESP32_BLE_Arduino/src/BLEAdvertising.h new file mode 100644 index 0000000..2d0b51c --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLECharacteristic.cpp b/ESP32_BLE_Arduino/src/BLECharacteristic.cpp new file mode 100644 index 0000000..f5b8200 --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLECharacteristic.h b/ESP32_BLE_Arduino/src/BLECharacteristic.h new file mode 100644 index 0000000..b39c022 --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLECharacteristicCallbacks.cpp b/ESP32_BLE_Arduino/src/BLECharacteristicCallbacks.cpp new file mode 100644 index 0000000..b733865 --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLECharacteristicMap.cpp b/ESP32_BLE_Arduino/src/BLECharacteristicMap.cpp new file mode 100644 index 0000000..f475e83 --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLEClient.cpp b/ESP32_BLE_Arduino/src/BLEClient.cpp new file mode 100644 index 0000000..a5d60cf --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLEClient.h b/ESP32_BLE_Arduino/src/BLEClient.h new file mode 100644 index 0000000..898f98c --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLEDescriptor.cpp b/ESP32_BLE_Arduino/src/BLEDescriptor.cpp new file mode 100644 index 0000000..4a7fda6 --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLEDescriptor.h b/ESP32_BLE_Arduino/src/BLEDescriptor.h new file mode 100644 index 0000000..1d32d50 --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLEDescriptorMap.cpp b/ESP32_BLE_Arduino/src/BLEDescriptorMap.cpp new file mode 100644 index 0000000..b211652 --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLEDevice.cpp b/ESP32_BLE_Arduino/src/BLEDevice.cpp new file mode 100644 index 0000000..04cae14 --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLEDevice.h b/ESP32_BLE_Arduino/src/BLEDevice.h new file mode 100644 index 0000000..9d767c1 --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLERemoteCharacteristic.cpp b/ESP32_BLE_Arduino/src/BLERemoteCharacteristic.cpp new file mode 100644 index 0000000..5227bf6 --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLERemoteCharacteristic.h b/ESP32_BLE_Arduino/src/BLERemoteCharacteristic.h new file mode 100644 index 0000000..b5b22b6 --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLERemoteDescriptor.cpp b/ESP32_BLE_Arduino/src/BLERemoteDescriptor.cpp new file mode 100644 index 0000000..2be312a --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLERemoteDescriptor.h b/ESP32_BLE_Arduino/src/BLERemoteDescriptor.h new file mode 100644 index 0000000..a8d944d --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLERemoteService.cpp b/ESP32_BLE_Arduino/src/BLERemoteService.cpp new file mode 100644 index 0000000..a29a758 --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLERemoteService.h b/ESP32_BLE_Arduino/src/BLERemoteService.h new file mode 100644 index 0000000..4393fbc --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLEScan.cpp b/ESP32_BLE_Arduino/src/BLEScan.cpp new file mode 100644 index 0000000..9fdcffd --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLEScan.h b/ESP32_BLE_Arduino/src/BLEScan.h new file mode 100644 index 0000000..f9575ea --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLEServer.cpp b/ESP32_BLE_Arduino/src/BLEServer.cpp new file mode 100644 index 0000000..be9773d --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLEServer.h b/ESP32_BLE_Arduino/src/BLEServer.h new file mode 100644 index 0000000..c6307bc --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLEServerCallbacks.cpp b/ESP32_BLE_Arduino/src/BLEServerCallbacks.cpp new file mode 100644 index 0000000..8808720 --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLEService.cpp b/ESP32_BLE_Arduino/src/BLEService.cpp new file mode 100644 index 0000000..ec16db8 --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLEService.h b/ESP32_BLE_Arduino/src/BLEService.h new file mode 100644 index 0000000..86d0776 --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLEServiceMap.cpp b/ESP32_BLE_Arduino/src/BLEServiceMap.cpp new file mode 100644 index 0000000..8fdbd5a --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLEUUID.cpp b/ESP32_BLE_Arduino/src/BLEUUID.cpp new file mode 100644 index 0000000..9a4fe45 --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLEUUID.h b/ESP32_BLE_Arduino/src/BLEUUID.h new file mode 100644 index 0000000..c364796 --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLEUtils.cpp b/ESP32_BLE_Arduino/src/BLEUtils.cpp new file mode 100644 index 0000000..468a7e1 --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLEUtils.h b/ESP32_BLE_Arduino/src/BLEUtils.h new file mode 100644 index 0000000..28c7a7e --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLEValue.cpp b/ESP32_BLE_Arduino/src/BLEValue.cpp new file mode 100644 index 0000000..1989993 --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/BLEValue.h b/ESP32_BLE_Arduino/src/BLEValue.h new file mode 100644 index 0000000..a292c6e --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/FreeRTOS.cpp b/ESP32_BLE_Arduino/src/FreeRTOS.cpp new file mode 100644 index 0000000..663128d --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/FreeRTOS.h b/ESP32_BLE_Arduino/src/FreeRTOS.h new file mode 100644 index 0000000..320f4cc --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/GeneralUtils.cpp b/ESP32_BLE_Arduino/src/GeneralUtils.cpp new file mode 100644 index 0000000..4237b14 --- /dev/null +++ b/ESP32_BLE_Arduino/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/ESP32_BLE_Arduino/src/GeneralUtils.h b/ESP32_BLE_Arduino/src/GeneralUtils.h new file mode 100644 index 0000000..152abca --- /dev/null +++ b/ESP32_BLE_Arduino/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_ */ |