From 7f112f68771c09331bff7d5d1689e5a749667ba3 Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Mon, 12 May 2025 12:23:38 +0200 Subject: [PATCH 01/10] feat(NimBLE): Add support for NimBLE Co-authored-by: h2zero --- cores/esp32/esp32-hal-bt.c | 5 +- libraries/BLE/README.md | 8 +- .../BLE5_extended_scan/BLE5_extended_scan.ino | 6 +- .../BLE/examples/BLE5_extended_scan/ci.json | 3 +- .../BLE5_multi_advertising.ino | 4 +- .../examples/BLE5_multi_advertising/ci.json | 3 +- .../BLE5_periodic_advertising.ino | 4 +- .../BLE5_periodic_advertising/ci.json | 3 +- .../BLE5_periodic_sync/BLE5_periodic_sync.ino | 6 +- .../BLE/examples/BLE5_periodic_sync/ci.json | 3 +- libraries/BLE/library.properties | 2 +- libraries/BLE/src/BLE2901.cpp | 47 +- libraries/BLE/src/BLE2901.h | 39 +- libraries/BLE/src/BLE2902.cpp | 73 +- libraries/BLE/src/BLE2902.h | 32 +- libraries/BLE/src/BLE2904.cpp | 36 +- libraries/BLE/src/BLE2904.h | 39 +- libraries/BLE/src/BLEAddress.cpp | 184 ++- libraries/BLE/src/BLEAddress.h | 85 +- libraries/BLE/src/BLEAdvertisedDevice.cpp | 140 +- libraries/BLE/src/BLEAdvertisedDevice.h | 157 ++- libraries/BLE/src/BLEAdvertising.cpp | 914 +++++++++++-- libraries/BLE/src/BLEAdvertising.h | 210 ++- libraries/BLE/src/BLEBeacon.cpp | 47 +- libraries/BLE/src/BLEBeacon.h | 20 + libraries/BLE/src/BLECharacteristic.cpp | 1201 +++++++++++------ libraries/BLE/src/BLECharacteristic.h | 261 +++- libraries/BLE/src/BLECharacteristicMap.cpp | 76 +- libraries/BLE/src/BLEClient.cpp | 996 +++++++++++--- libraries/BLE/src/BLEClient.h | 190 ++- libraries/BLE/src/BLEConnInfo.h | 110 ++ libraries/BLE/src/BLEDescriptor.cpp | 297 ++-- libraries/BLE/src/BLEDescriptor.h | 118 +- libraries/BLE/src/BLEDescriptorMap.cpp | 101 +- libraries/BLE/src/BLEDevice.cpp | 866 ++++++++---- libraries/BLE/src/BLEDevice.h | 245 +++- libraries/BLE/src/BLEEddystoneTLM.cpp | 8 +- .../BLE/src/BLEEddystoneTLM.cppwithheadder | 202 --- libraries/BLE/src/BLEEddystoneTLM.h | 10 +- libraries/BLE/src/BLEEddystoneURL.cpp | 12 +- libraries/BLE/src/BLEEddystoneURL.h | 8 + libraries/BLE/src/BLEEddystoneURL.h.orig | 66 - libraries/BLE/src/BLEExceptions.cpp | 5 + libraries/BLE/src/BLEHIDDevice.cpp | 42 +- libraries/BLE/src/BLEHIDDevice.h | 8 +- libraries/BLE/src/BLERemoteCharacteristic.cpp | 1042 ++++++++++---- libraries/BLE/src/BLERemoteCharacteristic.h | 141 +- libraries/BLE/src/BLERemoteDescriptor.cpp | 378 +++++- libraries/BLE/src/BLERemoteDescriptor.h | 76 +- libraries/BLE/src/BLERemoteService.cpp | 393 ++++-- libraries/BLE/src/BLERemoteService.h | 88 +- libraries/BLE/src/BLEScan.cpp | 631 ++++++--- libraries/BLE/src/BLEScan.h | 147 +- libraries/BLE/src/BLESecurity.cpp | 141 +- libraries/BLE/src/BLESecurity.h | 144 +- libraries/BLE/src/BLEServer.cpp | 730 ++++++++-- libraries/BLE/src/BLEServer.h | 216 ++- libraries/BLE/src/BLEService.cpp | 428 ++++-- libraries/BLE/src/BLEService.h | 126 +- libraries/BLE/src/BLEServiceMap.cpp | 40 +- libraries/BLE/src/BLETypes.h | 57 + libraries/BLE/src/BLEUUID.cpp | 376 +++--- libraries/BLE/src/BLEUUID.h | 101 +- libraries/BLE/src/BLEUtils.cpp | 534 +++++++- libraries/BLE/src/BLEUtils.h | 73 +- libraries/BLE/src/BLEValue.cpp | 18 +- libraries/BLE/src/BLEValue.h | 25 +- 67 files changed, 9704 insertions(+), 3093 deletions(-) create mode 100644 libraries/BLE/src/BLEConnInfo.h delete mode 100644 libraries/BLE/src/BLEEddystoneTLM.cppwithheadder delete mode 100644 libraries/BLE/src/BLEEddystoneURL.h.orig create mode 100644 libraries/BLE/src/BLETypes.h diff --git a/cores/esp32/esp32-hal-bt.c b/cores/esp32/esp32-hal-bt.c index 5d512d448a3..a6dad2a7c17 100644 --- a/cores/esp32/esp32-hal-bt.c +++ b/cores/esp32/esp32-hal-bt.c @@ -15,6 +15,7 @@ #include "esp32-hal-bt.h" #if SOC_BT_SUPPORTED +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) #if defined(CONFIG_BT_BLUEDROID_ENABLED) && __has_include("esp_bt.h") #if CONFIG_IDF_TARGET_ESP32 @@ -116,7 +117,7 @@ bool btStop() { return false; } -#else // CONFIG_BT_ENABLED +#else // !defined(CONFIG_BLUEDROID_ENABLED) && !defined(CONFIG_NIMBLE_ENABLED) bool btStarted() { return false; } @@ -129,6 +130,6 @@ bool btStop() { return false; } -#endif /* CONFIG_BT_ENABLED */ +#endif /* !defined(CONFIG_BLUEDROID_ENABLED) && !defined(CONFIG_NIMBLE_ENABLED) */ #endif /* SOC_BT_SUPPORTED */ diff --git a/libraries/BLE/README.md b/libraries/BLE/README.md index eb70ee9ff00..759c8526f0f 100644 --- a/libraries/BLE/README.md +++ b/libraries/BLE/README.md @@ -1,8 +1,12 @@ # ESP32 BLE for Arduino The Arduino IDE provides an excellent library package manager where versions of libraries can be downloaded and installed. This Github project provides the repository for the ESP32 BLE support for Arduino. -The original source of the project, **which is not maintained anymore**, can be found here: https://github.com/nkolban/esp32-snippets +The original source of the Bluedroid project, **which is not maintained anymore**, can be found here: https://github.com/nkolban/esp32-snippets -Issues and questions should be raised here: https://github.com/espressif/arduino-esp32/issues
(please don't use https://github.com/nkolban/esp32-snippets/issues!) +Some parts of the NimBLE implementation are based on the work of h2zero, which can be found here: https://github.com/h2zero/NimBLE-Arduino + +Issues and questions should be raised here: https://github.com/espressif/arduino-esp32/issues
(please don't use https://github.com/nkolban/esp32-snippets/issues or https://github.com/h2zero/NimBLE-Arduino/issues!) Documentation for using the library can be found here: https://github.com/nkolban/esp32-snippets/tree/master/Documentation + +For a more customizable and feature-rich implementation of the NimBLE stack, you can use the [NimBLE-Arduino](https://github.com/h2zero/NimBLE-Arduino) library. diff --git a/libraries/BLE/examples/BLE5_extended_scan/BLE5_extended_scan.ino b/libraries/BLE/examples/BLE5_extended_scan/BLE5_extended_scan.ino index 42daff86835..e50d7339df6 100644 --- a/libraries/BLE/examples/BLE5_extended_scan/BLE5_extended_scan.ino +++ b/libraries/BLE/examples/BLE5_extended_scan/BLE5_extended_scan.ino @@ -7,8 +7,10 @@ author: chegewara */ -#ifndef SOC_BLE_50_SUPPORTED -#warning "This SoC does not support BLE5. Try using ESP32-C3, or ESP32-S3" +#ifndef CONFIG_BLUEDROID_ENABLED +#error "NimBLE does not support extended scan yet. Try using Bluedroid." +#elif !defined(SOC_BLE_50_SUPPORTED) +#error "This SoC does not support BLE5. Try using ESP32-C3, or ESP32-S3" #else #include diff --git a/libraries/BLE/examples/BLE5_extended_scan/ci.json b/libraries/BLE/examples/BLE5_extended_scan/ci.json index 184cc25a2b0..8e938055cf2 100644 --- a/libraries/BLE/examples/BLE5_extended_scan/ci.json +++ b/libraries/BLE/examples/BLE5_extended_scan/ci.json @@ -1,6 +1,7 @@ { "fqbn_append": "PartitionScheme=huge_app", "requires": [ - "CONFIG_SOC_BLE_50_SUPPORTED=y" + "CONFIG_SOC_BLE_50_SUPPORTED=y", + "CONFIG_BLUEDROID_ENABLED=y" ] } diff --git a/libraries/BLE/examples/BLE5_multi_advertising/BLE5_multi_advertising.ino b/libraries/BLE/examples/BLE5_multi_advertising/BLE5_multi_advertising.ino index c4d614786b0..ee25f8e95ef 100644 --- a/libraries/BLE/examples/BLE5_multi_advertising/BLE5_multi_advertising.ino +++ b/libraries/BLE/examples/BLE5_multi_advertising/BLE5_multi_advertising.ino @@ -6,7 +6,9 @@ author: chegewara */ -#ifndef CONFIG_BT_BLE_50_FEATURES_SUPPORTED +#ifndef CONFIG_BLUEDROID_ENABLED +#error "NimBLE does not support multi advertising yet. Try using Bluedroid." +#elif !defined(CONFIG_BT_BLE_50_FEATURES_SUPPORTED) #error "This SoC does not support BLE5. Try using ESP32-C3, or ESP32-S3" #else diff --git a/libraries/BLE/examples/BLE5_multi_advertising/ci.json b/libraries/BLE/examples/BLE5_multi_advertising/ci.json index 184cc25a2b0..8e938055cf2 100644 --- a/libraries/BLE/examples/BLE5_multi_advertising/ci.json +++ b/libraries/BLE/examples/BLE5_multi_advertising/ci.json @@ -1,6 +1,7 @@ { "fqbn_append": "PartitionScheme=huge_app", "requires": [ - "CONFIG_SOC_BLE_50_SUPPORTED=y" + "CONFIG_SOC_BLE_50_SUPPORTED=y", + "CONFIG_BLUEDROID_ENABLED=y" ] } diff --git a/libraries/BLE/examples/BLE5_periodic_advertising/BLE5_periodic_advertising.ino b/libraries/BLE/examples/BLE5_periodic_advertising/BLE5_periodic_advertising.ino index 0b9d4f87630..effb7efd241 100644 --- a/libraries/BLE/examples/BLE5_periodic_advertising/BLE5_periodic_advertising.ino +++ b/libraries/BLE/examples/BLE5_periodic_advertising/BLE5_periodic_advertising.ino @@ -5,7 +5,9 @@ author: chegewara */ -#ifndef CONFIG_BT_BLE_50_FEATURES_SUPPORTED +#ifndef CONFIG_BLUEDROID_ENABLED +#error "NimBLE does not support periodic advertising yet. Try using Bluedroid." +#elif !defined(CONFIG_BT_BLE_50_FEATURES_SUPPORTED) #error "This SoC does not support BLE5. Try using ESP32-C3, or ESP32-S3" #else #include diff --git a/libraries/BLE/examples/BLE5_periodic_advertising/ci.json b/libraries/BLE/examples/BLE5_periodic_advertising/ci.json index 184cc25a2b0..8e938055cf2 100644 --- a/libraries/BLE/examples/BLE5_periodic_advertising/ci.json +++ b/libraries/BLE/examples/BLE5_periodic_advertising/ci.json @@ -1,6 +1,7 @@ { "fqbn_append": "PartitionScheme=huge_app", "requires": [ - "CONFIG_SOC_BLE_50_SUPPORTED=y" + "CONFIG_SOC_BLE_50_SUPPORTED=y", + "CONFIG_BLUEDROID_ENABLED=y" ] } diff --git a/libraries/BLE/examples/BLE5_periodic_sync/BLE5_periodic_sync.ino b/libraries/BLE/examples/BLE5_periodic_sync/BLE5_periodic_sync.ino index 9e976e6ca6a..a93ebcefec3 100644 --- a/libraries/BLE/examples/BLE5_periodic_sync/BLE5_periodic_sync.ino +++ b/libraries/BLE/examples/BLE5_periodic_sync/BLE5_periodic_sync.ino @@ -7,8 +7,10 @@ author: chegewara */ -#ifndef SOC_BLE_50_SUPPORTED -#warning "This SoC does not support BLE5. Try using ESP32-C3, or ESP32-S3" +#ifndef CONFIG_BLUEDROID_ENABLED +#error "NimBLE does not support periodic sync yet. Try using Bluedroid." +#elif !defined(SOC_BLE_50_SUPPORTED) +#error "This SoC does not support BLE5. Try using ESP32-C3, or ESP32-S3" #else #include #include diff --git a/libraries/BLE/examples/BLE5_periodic_sync/ci.json b/libraries/BLE/examples/BLE5_periodic_sync/ci.json index 184cc25a2b0..8e938055cf2 100644 --- a/libraries/BLE/examples/BLE5_periodic_sync/ci.json +++ b/libraries/BLE/examples/BLE5_periodic_sync/ci.json @@ -1,6 +1,7 @@ { "fqbn_append": "PartitionScheme=huge_app", "requires": [ - "CONFIG_SOC_BLE_50_SUPPORTED=y" + "CONFIG_SOC_BLE_50_SUPPORTED=y", + "CONFIG_BLUEDROID_ENABLED=y" ] } diff --git a/libraries/BLE/library.properties b/libraries/BLE/library.properties index 7ef636223ec..fe39f5093c4 100644 --- a/libraries/BLE/library.properties +++ b/libraries/BLE/library.properties @@ -1,7 +1,7 @@ name=BLE version=3.2.0 author=Neil Kolban -maintainer=Dariusz Krempa +maintainer=lucasssvaz sentence=BLE functions for ESP32 paragraph=This library provides an implementation Bluetooth Low Energy support for the ESP32 using the Arduino platform. category=Communication diff --git a/libraries/BLE/src/BLE2901.cpp b/libraries/BLE/src/BLE2901.cpp index e929262b023..6b33ae566a9 100644 --- a/libraries/BLE/src/BLE2901.cpp +++ b/libraries/BLE/src/BLE2901.cpp @@ -1,29 +1,48 @@ /* - BLE2901.h + BLE2901.h - GATT Descriptor 0x2901 Characteristic User Description + GATT Descriptor 0x2901 Characteristic User Description - The value of this description is a user-readable string - describing the characteristic. + The value of this description is a user-readable string + describing the characteristic. - The Characteristic User Description descriptor - provides a textual user description for a characteristic - value. - If the Writable Auxiliary bit of the Characteristics - Properties is set then this descriptor is written. Only one - User Description descriptor exists in a characteristic - definition. + The Characteristic User Description descriptor + provides a textual user description for a characteristic + value. + If the Writable Auxiliary bit of the Characteristics + Properties is set then this descriptor is written. Only one + User Description descriptor exists in a characteristic + definition. */ #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes and definitions * + ***************************************************************************/ #include "BLE2901.h" -BLE2901::BLE2901() : BLEDescriptor(BLEUUID((uint16_t)0x2901)) {} // BLE2901 +#define BLE2901_UUID 0x2901 + +/*************************************************************************** + * NimBLE includes and definitions * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +#include "host/ble_att.h" +#define ESP_GATT_MAX_ATTR_LEN BLE_ATT_ATTR_MAX_LEN +#endif + +/*************************************************************************** + * Common functions * + ***************************************************************************/ + +BLE2901::BLE2901() : BLEDescriptor(BLEUUID((uint16_t)BLE2901_UUID)) {} /** * @brief Set the Characteristic User Description @@ -36,5 +55,5 @@ void BLE2901::setDescription(String userDesc) { setValue(userDesc); } -#endif +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ diff --git a/libraries/BLE/src/BLE2901.h b/libraries/BLE/src/BLE2901.h index f5ad7c94add..21e7cc9398c 100644 --- a/libraries/BLE/src/BLE2901.h +++ b/libraries/BLE/src/BLE2901.h @@ -1,37 +1,48 @@ /* - BLE2901.h + BLE2901.h - GATT Descriptor 0x2901 Characteristic User Description + GATT Descriptor 0x2901 Characteristic User Description - The value of this description is a user-readable string - describing the characteristic. - - The Characteristic User Description descriptor - provides a textual user description for a characteristic - value. - If the Writable Auxiliary bit of the Characteristics - Properties is set then this descriptor is written. Only one - User Description descriptor exists in a characteristic - definition. + The value of this description is a user-readable string + describing the characteristic. + The Characteristic User Description descriptor + provides a textual user description for a characteristic + value. + If the Writable Auxiliary bit of the Characteristics + Properties is set then this descriptor is written. Only one + User Description descriptor exists in a characteristic + definition. */ #ifndef COMPONENTS_CPP_UTILS_BLE2901_H_ #define COMPONENTS_CPP_UTILS_BLE2901_H_ + #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ #include "BLEDescriptor.h" +/** + * @brief GATT Descriptor 0x2901 Characteristic User Description + */ class BLE2901 : public BLEDescriptor { public: + /*************************************************************************** + * Common public functions * + ***************************************************************************/ + BLE2901(); void setDescription(String desc); }; // BLE2901 -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ #endif /* COMPONENTS_CPP_UTILS_BLE2901_H_ */ diff --git a/libraries/BLE/src/BLE2902.cpp b/libraries/BLE/src/BLE2902.cpp index 90cdf088ff2..3bac9281328 100644 --- a/libraries/BLE/src/BLE2902.cpp +++ b/libraries/BLE/src/BLE2902.cpp @@ -3,46 +3,86 @@ * * Created on: Jun 25, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ /* * See also: * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml */ + #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes and definitions * + ***************************************************************************/ #include "BLE2902.h" -BLE2902::BLE2902() : BLEDescriptor(BLEUUID((uint16_t)0x2902)) { +#define BLE2902_UUID 0x2902 + +/*************************************************************************** + * Common functions * + ***************************************************************************/ + +BLE2902::BLE2902() : BLEDescriptor(BLEUUID((uint16_t)BLE2902_UUID)) { +#if defined(CONFIG_BLUEDROID_ENABLED) uint8_t data[2] = {0, 0}; setValue(data, 2); -} // BLE2902 +#endif +} /** * @brief Get the notifications value. * @return The notifications value. True if notifications are enabled and false if not. */ bool BLE2902::getNotifications() { +#if defined(CONFIG_BLUEDROID_ENABLED) return (getValue()[0] & (1 << 0)) != 0; -} // getNotifications +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + if (m_pCharacteristic != nullptr) { + return (m_pCharacteristic->getProperties() & BLECharacteristic::PROPERTY_NOTIFY) != 0; + } else { + log_w("BLE2902::getNotifications() called on an uninitialized descriptor"); + return false; + } +#endif +} /** * @brief Get the indications value. * @return The indications value. True if indications are enabled and false if not. */ bool BLE2902::getIndications() { +#if defined(CONFIG_BLUEDROID_ENABLED) return (getValue()[0] & (1 << 1)) != 0; -} // getIndications +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + if (m_pCharacteristic != nullptr) { + return (m_pCharacteristic->getProperties() & BLECharacteristic::PROPERTY_INDICATE) != 0; + } else { + log_w("BLE2902::getIndications() called on an uninitialized descriptor"); + return false; + } +#endif +} /** * @brief Set the indications flag. * @param [in] flag The indications flag. */ void BLE2902::setIndications(bool flag) { +#if defined(CONFIG_BLUEDROID_ENABLED) uint8_t *pValue = getValue(); if (flag) { pValue[0] |= 1 << 1; @@ -50,13 +90,23 @@ void BLE2902::setIndications(bool flag) { pValue[0] &= ~(1 << 1); } setValue(pValue, 2); -} // setIndications +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + if (m_pCharacteristic != nullptr) { + m_pCharacteristic->setIndicateProperty(flag); + } else { + log_w("BLE2902::setIndications() called on an uninitialized descriptor"); + } +#endif +} /** * @brief Set the notifications flag. * @param [in] flag The notifications flag. */ void BLE2902::setNotifications(bool flag) { +#if defined(CONFIG_BLUEDROID_ENABLED) uint8_t *pValue = getValue(); if (flag) { pValue[0] |= 1 << 0; @@ -64,7 +114,16 @@ void BLE2902::setNotifications(bool flag) { pValue[0] &= ~(1 << 0); } setValue(pValue, 2); -} // setNotifications +#endif +#if defined(CONFIG_NIMBLE_ENABLED) + if (m_pCharacteristic != nullptr) { + m_pCharacteristic->setNotifyProperty(flag); + } else { + log_w("BLE2902::setNotifications() called on an uninitialized descriptor"); + } #endif +} + +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ diff --git a/libraries/BLE/src/BLE2902.h b/libraries/BLE/src/BLE2902.h index 74a477f3151..04da2462aa6 100644 --- a/libraries/BLE/src/BLE2902.h +++ b/libraries/BLE/src/BLE2902.h @@ -3,15 +3,24 @@ * * Created on: Jun 25, 2017 * Author: kolban + * + * Modified on: Feb 28, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ #ifndef COMPONENTS_CPP_UTILS_BLE2902_H_ #define COMPONENTS_CPP_UTILS_BLE2902_H_ + #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ #include "BLEDescriptor.h" @@ -23,16 +32,35 @@ * See also: * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml */ + +// Class declaration for Bluedroid +#if defined(CONFIG_BLUEDROID_ENABLED) class BLE2902 : public BLEDescriptor { +#endif + +// Class declaration for NimBLE (deprecated) +#if defined(CONFIG_NIMBLE_ENABLED) +class [[deprecated("NimBLE does not support manually adding 2902 descriptors as they \ +are automatically added when the characteristic has notifications or indications enabled. \ +Get/Set the notifications/indications properties of the characteristic instead. \ +This class will be removed in a future version.")]] BLE2902 : public BLEDescriptor { +#endif + public: + /*************************************************************************** + * Common public functions * + ***************************************************************************/ + BLE2902(); bool getNotifications(); bool getIndications(); void setNotifications(bool flag); void setIndications(bool flag); +private: + friend class BLECharacteristic; }; // BLE2902 -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ #endif /* COMPONENTS_CPP_UTILS_BLE2902_H_ */ diff --git a/libraries/BLE/src/BLE2904.cpp b/libraries/BLE/src/BLE2904.cpp index aeed11ebad1..b44d0941903 100644 --- a/libraries/BLE/src/BLE2904.cpp +++ b/libraries/BLE/src/BLE2904.cpp @@ -3,6 +3,10 @@ * * Created on: Dec 23, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ /* @@ -13,18 +17,30 @@ #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes and definitions * + ***************************************************************************/ #include "BLE2904.h" -BLE2904::BLE2904() : BLEDescriptor(BLEUUID((uint16_t)0x2904)) { +#define BLE2904_UUID 0x2904 +#define BLE2904_DEFAULT_NAMESPACE 1 // 1 = Bluetooth SIG Assigned Numbers +#define BLE2904_DEFAULT_UNIT 0x2700 // 0x2700 = Unitless + +/*************************************************************************** + * Common functions * + ***************************************************************************/ + +BLE2904::BLE2904() : BLEDescriptor(BLEUUID((uint16_t)BLE2904_UUID)) { m_data.m_format = 0; m_data.m_exponent = 0; - m_data.m_namespace = 1; // 1 = Bluetooth SIG Assigned Numbers - m_data.m_unit = 0; + m_data.m_namespace = BLE2904_DEFAULT_NAMESPACE; + m_data.m_unit = BLE2904_DEFAULT_UNIT; m_data.m_description = 0; setValue((uint8_t *)&m_data, sizeof(m_data)); -} // BLE2902 +} /** * @brief Set the description. @@ -40,7 +56,7 @@ void BLE2904::setDescription(uint16_t description) { void BLE2904::setExponent(int8_t exponent) { m_data.m_exponent = exponent; setValue((uint8_t *)&m_data, sizeof(m_data)); -} // setExponent +} /** * @brief Set the format. @@ -48,7 +64,7 @@ void BLE2904::setExponent(int8_t exponent) { void BLE2904::setFormat(uint8_t format) { m_data.m_format = format; setValue((uint8_t *)&m_data, sizeof(m_data)); -} // setFormat +} /** * @brief Set the namespace. @@ -56,7 +72,7 @@ void BLE2904::setFormat(uint8_t format) { void BLE2904::setNamespace(uint8_t namespace_value) { m_data.m_namespace = namespace_value; setValue((uint8_t *)&m_data, sizeof(m_data)); -} // setNamespace +} /** * @brief Set the units for this value. It should be one of the encoded values defined here: @@ -66,7 +82,7 @@ void BLE2904::setNamespace(uint8_t namespace_value) { void BLE2904::setUnit(uint16_t unit) { m_data.m_unit = unit; setValue((uint8_t *)&m_data, sizeof(m_data)); -} // setUnit +} -#endif +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ diff --git a/libraries/BLE/src/BLE2904.h b/libraries/BLE/src/BLE2904.h index 3ba66da0dc8..87e8c03c048 100644 --- a/libraries/BLE/src/BLE2904.h +++ b/libraries/BLE/src/BLE2904.h @@ -3,27 +3,43 @@ * * Created on: Dec 23, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ #ifndef COMPONENTS_CPP_UTILS_BLE2904_H_ #define COMPONENTS_CPP_UTILS_BLE2904_H_ + #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ #include "BLEDescriptor.h" +/*************************************************************************** + * Common types * + ***************************************************************************/ + struct BLE2904_Data { uint8_t m_format; int8_t m_exponent; uint16_t m_unit; // See https://www.bluetooth.com/specifications/assigned-numbers/units uint8_t m_namespace; uint16_t m_description; - } __attribute__((packed)); +/*************************************************************************** + * Common functions * + ***************************************************************************/ + /** * @brief Descriptor for Characteristic Presentation Format. * @@ -34,7 +50,10 @@ struct BLE2904_Data { */ class BLE2904 : public BLEDescriptor { public: - BLE2904(); + /*************************************************************************** + * Common public constants * + ***************************************************************************/ + static const uint8_t FORMAT_BOOLEAN = 1; static const uint8_t FORMAT_UINT2 = 2; static const uint8_t FORMAT_UINT4 = 3; @@ -62,7 +81,13 @@ class BLE2904 : public BLEDescriptor { static const uint8_t FORMAT_UTF8 = 25; static const uint8_t FORMAT_UTF16 = 26; static const uint8_t FORMAT_OPAQUE = 27; + static const uint8_t FORMAT_MEDASN1 = 28; + + /*************************************************************************** + * Common public functions * + ***************************************************************************/ + BLE2904(); void setDescription(uint16_t); void setExponent(int8_t exponent); void setFormat(uint8_t format); @@ -70,9 +95,15 @@ class BLE2904 : public BLEDescriptor { void setUnit(uint16_t unit); private: + friend class BLECharacteristic; + + /*************************************************************************** + * Common private properties * + ***************************************************************************/ + BLE2904_Data m_data; }; // BLE2904 -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ #endif /* COMPONENTS_CPP_UTILS_BLE2904_H_ */ diff --git a/libraries/BLE/src/BLEAddress.cpp b/libraries/BLE/src/BLEAddress.cpp index b91ef3cc4de..11c64850367 100644 --- a/libraries/BLE/src/BLEAddress.cpp +++ b/libraries/BLE/src/BLEAddress.cpp @@ -3,12 +3,21 @@ * * Created on: Jul 2, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ + #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ #include "BLEAddress.h" #include @@ -17,43 +26,33 @@ #include #include #include + #ifdef ARDUINO_ARCH_ESP32 #include "esp32-hal-log.h" #endif -/** - * @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 +/*************************************************************************** + * NimBLE includes * + ***************************************************************************/ -/** - * @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(String stringAddress) { - if (stringAddress.length() != 17) { - return; - } +#if defined(CONFIG_NIMBLE_ENABLED) +/************************************************* + * NOTE: NimBLE address bytes are in INVERSE ORDER! + * We will accommodate that fact in these methods. +*************************************************/ +#include +#endif - 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 +/*************************************************************************** + * Common functions * + ***************************************************************************/ + +BLEAddress::BLEAddress() { + memset(m_address, 0, ESP_BD_ADDR_LEN); +#if defined(CONFIG_NIMBLE_ENABLED) + m_addrType = 0; +#endif +} /** * @brief Determine if this address equals another. @@ -61,10 +60,20 @@ BLEAddress::BLEAddress(String stringAddress) { * @return True if the addresses are equal. */ bool BLEAddress::equals(BLEAddress otherAddress) { +#if defined(CONFIG_NIMBLE_ENABLED) + if (m_addrType != otherAddress.m_addrType) { + return false; + } +#endif return memcmp(otherAddress.getNative(), m_address, ESP_BD_ADDR_LEN) == 0; -} // equals +} bool BLEAddress::operator==(const BLEAddress &otherAddress) const { +#if defined(CONFIG_NIMBLE_ENABLED) + if (m_addrType != otherAddress.m_addrType) { + return false; + } +#endif return memcmp(otherAddress.m_address, m_address, ESP_BD_ADDR_LEN) == 0; } @@ -92,9 +101,9 @@ bool BLEAddress::operator>(const BLEAddress &otherAddress) const { * @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 +uint8_t *BLEAddress::getNative() { + return m_address; +} /** * @brief Convert a BLE address to a string. @@ -110,11 +119,112 @@ esp_bd_addr_t *BLEAddress::getNative() { String BLEAddress::toString() { auto size = 18; char *res = (char *)malloc(size); + +#if defined(CONFIG_BLUEDROID_ENABLED) snprintf(res, size, "%02x:%02x:%02x:%02x:%02x:%02x", m_address[0], m_address[1], m_address[2], m_address[3], m_address[4], m_address[5]); +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + snprintf(res, size, "%02x:%02x:%02x:%02x:%02x:%02x", m_address[5], m_address[4], m_address[3], m_address[2], m_address[1], m_address[0]); +#endif + String ret(res); free(res); return ret; -} // toString +} + +/*************************************************************************** + * Bluedroid functions * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + +/** + * @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); +} + +/** + * @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(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]); + + for (size_t index = 0; index < sizeof(m_address); index++) { + m_address[index] = (uint8_t)data[index]; + } +} + +#endif + +/*************************************************************************** + * NimBLE functions * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +/************************************************* + * NOTE: NimBLE address bytes are in INVERSE ORDER! + * We will accommodate that fact in these methods. +*************************************************/ + +BLEAddress::BLEAddress(uint8_t address[ESP_BD_ADDR_LEN], uint8_t type) { + std::reverse_copy(address, address + sizeof(m_address), m_address); + m_addrType = type; +} + +BLEAddress::BLEAddress(ble_addr_t address) { + memcpy(m_address, address.val, ESP_BD_ADDR_LEN); + m_addrType = address.type; +} + +uint8_t BLEAddress::getType() { + return m_addrType; +} + +/** + * @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. + * @param [in] type The address type. + */ +BLEAddress::BLEAddress(String stringAddress, uint8_t type) { + if (stringAddress.length() != 17) { + return; + } + + int data[6]; + m_addrType = type; + // NimBLE addresses are in INVERSE ORDER! + sscanf(stringAddress.c_str(), "%x:%x:%x:%x:%x:%x", &data[5], &data[4], &data[3], &data[2], &data[1], &data[0]); + + for (size_t index = 0; index < sizeof(m_address); index++) { + m_address[index] = (uint8_t)data[index]; + } +} #endif + +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ diff --git a/libraries/BLE/src/BLEAddress.h b/libraries/BLE/src/BLEAddress.h index f1c8aa9b632..71b3bdfce5f 100644 --- a/libraries/BLE/src/BLEAddress.h +++ b/libraries/BLE/src/BLEAddress.h @@ -3,19 +3,53 @@ * * Created on: Jul 2, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ #ifndef COMPONENTS_CPP_UTILS_BLEADDRESS_H_ #define COMPONENTS_CPP_UTILS_BLEADDRESS_H_ #include "soc/soc_caps.h" -#include "WString.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) -#include // ESP32 BLE +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ + +#include "WString.h" +#include #include +/*************************************************************************** + * Bluedroid includes * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) +#include +#endif + +/*************************************************************************** + * NimBLE includes and definitions * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +#include +#define ESP_BD_ADDR_LEN BLE_DEV_ADDR_LEN +#endif + +/*************************************************************************** + * NimBLE types * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +typedef uint8_t esp_bd_addr_t[ESP_BD_ADDR_LEN]; +#endif + /** * @brief A %BLE device address. * @@ -23,8 +57,11 @@ */ class BLEAddress { public: - BLEAddress(esp_bd_addr_t address); - BLEAddress(String stringAddress); + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ + + BLEAddress(); bool equals(BLEAddress otherAddress); bool operator==(const BLEAddress &otherAddress) const; bool operator!=(const BLEAddress &otherAddress) const; @@ -32,13 +69,45 @@ class BLEAddress { bool operator<=(const BLEAddress &otherAddress) const; bool operator>(const BLEAddress &otherAddress) const; bool operator>=(const BLEAddress &otherAddress) const; - esp_bd_addr_t *getNative(); + uint8_t *getNative(); String toString(); + /*************************************************************************** + * Bluedroid public declarations * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + BLEAddress(esp_bd_addr_t address); + BLEAddress(String stringAddress); +#endif + + /*************************************************************************** + * NimBLE public declarations * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + BLEAddress(ble_addr_t address); + BLEAddress(String stringAddress, uint8_t type = BLE_ADDR_PUBLIC); + BLEAddress(uint8_t address[ESP_BD_ADDR_LEN], uint8_t type = BLE_ADDR_PUBLIC); + uint8_t getType(); +#endif + private: - esp_bd_addr_t m_address; + /*************************************************************************** + * Common private properties * + ***************************************************************************/ + + uint8_t m_address[ESP_BD_ADDR_LEN]; + + /*************************************************************************** + * NimBLE private properties * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + uint8_t m_addrType; +#endif }; -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ #endif /* COMPONENTS_CPP_UTILS_BLEADDRESS_H_ */ diff --git a/libraries/BLE/src/BLEAdvertisedDevice.cpp b/libraries/BLE/src/BLEAdvertisedDevice.cpp index 8752d24a199..305794dbccc 100644 --- a/libraries/BLE/src/BLEAdvertisedDevice.cpp +++ b/libraries/BLE/src/BLEAdvertisedDevice.cpp @@ -4,22 +4,36 @@ * 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 + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ + #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ + + #include #include "BLEAdvertisedDevice.h" #include "BLEUtils.h" #include "esp32-hal-log.h" +/*************************************************************************** + * Common functions * + ***************************************************************************/ + BLEAdvertisedDevice::BLEAdvertisedDevice() { m_adFlag = 0; m_appearance = 0; @@ -32,6 +46,10 @@ BLEAdvertisedDevice::BLEAdvertisedDevice() { m_serviceDataUUIDs = {}; m_txPower = 0; m_pScan = nullptr; +#if defined(CONFIG_NIMBLE_ENABLED) + m_callbackSent = false; + m_advType = 0; +#endif m_haveAppearance = false; m_haveManufacturerData = false; @@ -281,71 +299,71 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t *payload, size_t total_len) free(pHex); switch (ad_type) { - case ESP_BLE_AD_TYPE_NAME_CMPL: - { // Adv Data Type: 0x09 + case 0x09: + { // Adv Data Type: ESP_BLE_AD_TYPE_NAME_CMPL setName(String(reinterpret_cast(payload), length)); break; - } // ESP_BLE_AD_TYPE_NAME_CMPL + } // 0x09 - case ESP_BLE_AD_TYPE_TX_PWR: - { // Adv Data Type: 0x0A + case 0x0A: + { // Adv Data Type: ESP_BLE_AD_TYPE_TX_PWR setTXPower(*payload); break; - } // ESP_BLE_AD_TYPE_TX_PWR + } // 0x0A - case ESP_BLE_AD_TYPE_APPEARANCE: - { // Adv Data Type: 0x19 + case 0x19: + { // Adv Data Type: ESP_BLE_AD_TYPE_APPEARANCE setAppearance(*reinterpret_cast(payload)); break; - } // ESP_BLE_AD_TYPE_APPEARANCE + } // 0x19 - case ESP_BLE_AD_TYPE_FLAG: - { // Adv Data Type: 0x01 + case 0x01: + { // Adv Data Type: ESP_BLE_AD_TYPE_FLAG setAdFlag(*payload); break; - } // ESP_BLE_AD_TYPE_FLAG + } // 0x01 - case ESP_BLE_AD_TYPE_16SRV_CMPL: - case ESP_BLE_AD_TYPE_16SRV_PART: - { // Adv Data Type: 0x02 + case 0x02: + case 0x03: + { // Adv Data Type: ESP_BLE_AD_TYPE_16SRV_PART/CMPL for (int var = 0; var < length / 2; ++var) { setServiceUUID(BLEUUID(*reinterpret_cast(payload + var * 2))); } break; - } // ESP_BLE_AD_TYPE_16SRV_PART + } // 0x02, 0x03 - case ESP_BLE_AD_TYPE_32SRV_CMPL: - case ESP_BLE_AD_TYPE_32SRV_PART: - { // Adv Data Type: 0x04 + case 0x04: + case 0x05: + { // Adv Data Type: ESP_BLE_AD_TYPE_32SRV_PART/CMPL for (int var = 0; var < length / 4; ++var) { setServiceUUID(BLEUUID(*reinterpret_cast(payload + var * 4))); } break; - } // ESP_BLE_AD_TYPE_32SRV_PART + } // 0x04, 0x05 - case ESP_BLE_AD_TYPE_128SRV_CMPL: - { // Adv Data Type: 0x07 + case 0x07: + { // Adv Data Type: ESP_BLE_AD_TYPE_128SRV_CMPL setServiceUUID(BLEUUID(payload, 16, false)); break; - } // ESP_BLE_AD_TYPE_128SRV_CMPL + } // 0x07 - case ESP_BLE_AD_TYPE_128SRV_PART: - { // Adv Data Type: 0x06 + case 0x06: + { // Adv Data Type: ESP_BLE_AD_TYPE_128SRV_PART setServiceUUID(BLEUUID(payload, 16, false)); break; - } // ESP_BLE_AD_TYPE_128SRV_PART + } // 0x06 // See CSS Part A 1.4 Manufacturer Specific Data - case ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE: + case 0xFF: { setManufacturerData(String(reinterpret_cast(payload), length)); break; - } // ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE + } // 0xFF - case ESP_BLE_AD_TYPE_SERVICE_DATA: - { // Adv Data Type: 0x16 (Service Data) - 2 byte UUID + case 0x16: + { // Adv Data Type: ESP_BLE_AD_TYPE_SERVICE_DATA - 2 byte UUID if (length < 2) { - log_e("Length too small for ESP_BLE_AD_TYPE_SERVICE_DATA"); + log_e("Length too small for SERVICE_DATA"); break; } uint16_t uuid = *(uint16_t *)payload; @@ -354,12 +372,12 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t *payload, size_t total_len) setServiceData(String(reinterpret_cast(payload + 2), length - 2)); } break; - } //ESP_BLE_AD_TYPE_SERVICE_DATA + } // 0x16 - case ESP_BLE_AD_TYPE_32SERVICE_DATA: - { // Adv Data Type: 0x20 (Service Data) - 4 byte UUID + case 0x20: + { // Adv Data Type: ESP_BLE_AD_TYPE_32SERVICE_DATA - 4 byte UUID if (length < 4) { - log_e("Length too small for ESP_BLE_AD_TYPE_32SERVICE_DATA"); + log_e("Length too small for 32SERVICE_DATA"); break; } uint32_t uuid = *(uint32_t *)payload; @@ -368,12 +386,12 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t *payload, size_t total_len) setServiceData(String(reinterpret_cast(payload + 4), length - 4)); } break; - } //ESP_BLE_AD_TYPE_32SERVICE_DATA + } // 0x20 - case ESP_BLE_AD_TYPE_128SERVICE_DATA: - { // Adv Data Type: 0x21 (Service Data) - 16 byte UUID + case 0x21: + { // Adv Data Type: ESP_BLE_AD_TYPE_128SERVICE_DATA - 16 byte UUID if (length < 16) { - log_e("Length too small for ESP_BLE_AD_TYPE_128SERVICE_DATA"); + log_e("Length too small for 128SERVICE_DATA"); break; } @@ -382,13 +400,13 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t *payload, size_t total_len) setServiceData(String(reinterpret_cast(payload + 16), length - 16)); } break; - } //ESP_BLE_AD_TYPE_32SERVICE_DATA + } // 0x21 default: { log_d("Unhandled type: adType: %d - 0x%.2x", ad_type, ad_type); break; - } + } // default } // switch payload += length; } // Length <> 0 @@ -405,9 +423,19 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t *payload, size_t total_len) * @param [in] payload The payload of the advertised device. * @param [in] total_len The length of payload */ -void BLEAdvertisedDevice::setPayload(uint8_t *payload, size_t total_len) { - m_payload = payload; - m_payloadLength = total_len; +void BLEAdvertisedDevice::setPayload(uint8_t *payload, size_t total_len, bool append) { + if (m_payload == nullptr || m_payloadLength == 0) { + return; + } + + if (append) { + m_payload = (uint8_t *)realloc(m_payload, m_payloadLength + total_len); + memcpy(m_payload + m_payloadLength, payload, total_len); + m_payloadLength += total_len; + } else { + m_payload = payload; + m_payloadLength = total_len; + } } // setPayload /** @@ -567,7 +595,7 @@ uint8_t *BLEAdvertisedDevice::getPayload() { return m_payload; } -esp_ble_addr_type_t BLEAdvertisedDevice::getAddressType() { +uint8_t BLEAdvertisedDevice::getAddressType() { return m_addressType; } @@ -587,7 +615,7 @@ ble_frame_type_t BLEAdvertisedDevice::getFrameType() { return BLE_UNKNOWN_FRAME; } -void BLEAdvertisedDevice::setAddressType(esp_ble_addr_type_t type) { +void BLEAdvertisedDevice::setAddressType(uint8_t type) { m_addressType = type; } @@ -595,5 +623,19 @@ size_t BLEAdvertisedDevice::getPayloadLength() { return m_payloadLength; } -#endif /* CONFIG_BLUEDROID_ENABLED */ +/*************************************************************************** + * NimBLE functions * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +void BLEAdvertisedDevice::setAdvType(uint8_t type) { + m_advType = type; +} + +uint8_t BLEAdvertisedDevice::getAdvType() { + return m_advType; +} +#endif /* CONFIG_NIMBLE_ENABLED */ + +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ diff --git a/libraries/BLE/src/BLEAdvertisedDevice.h b/libraries/BLE/src/BLEAdvertisedDevice.h index 700e5704034..16f047ae15a 100644 --- a/libraries/BLE/src/BLEAdvertisedDevice.h +++ b/libraries/BLE/src/BLEAdvertisedDevice.h @@ -3,6 +3,10 @@ * * Created on: Jul 3, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ #ifndef COMPONENTS_CPP_UTILS_BLEADVERTISEDDEVICE_H_ @@ -11,15 +15,38 @@ #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) -#include +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) -#include +/*************************************************************************** + * Common includes * + ***************************************************************************/ +#include #include "BLEAddress.h" #include "BLEScan.h" #include "BLEUUID.h" +/*************************************************************************** + * Bluedroid includes * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) +#include +#endif + +/*************************************************************************** + * NimBLE includes * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +#include +#include +#endif + +/*************************************************************************** + * Common types * + ***************************************************************************/ + typedef enum { BLE_UNKNOWN_FRAME, BLE_EDDYSTONE_UUID_FRAME, @@ -28,7 +55,12 @@ typedef enum { BLE_FRAME_MAX } ble_frame_type_t; +/*************************************************************************** + * Forward declarations * + ***************************************************************************/ + class BLEScan; + /** * @brief A representation of a %BLE advertised device found by a scan. * @@ -37,8 +69,11 @@ class BLEScan; */ class BLEAdvertisedDevice { public: - BLEAdvertisedDevice(); + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ + BLEAdvertisedDevice(); BLEAddress getAddress(); uint16_t getAppearance(); String getManufacturerData(); @@ -57,9 +92,9 @@ class BLEAdvertisedDevice { int8_t getTXPower(); uint8_t *getPayload(); size_t getPayloadLength(); - esp_ble_addr_type_t getAddressType(); + uint8_t getAddressType(); ble_frame_type_t getFrameType(); - void setAddressType(esp_ble_addr_type_t type); + void setAddressType(uint8_t type); bool isAdvertisingService(BLEUUID uuid); bool haveAppearance(); @@ -72,31 +107,27 @@ class BLEAdvertisedDevice { String toString(); + /*************************************************************************** + * NimBLE public declarations * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + void setAdvType(uint8_t type); + uint8_t getAdvType(); +#endif + private: friend class BLEScan; - void parseAdvertisement(uint8_t *payload, size_t total_len = 62); - void setPayload(uint8_t *payload, size_t total_len = 62); - void setAddress(BLEAddress address); - void setAdFlag(uint8_t adFlag); - void setAdvertizementResult(uint8_t *payload); - void setAppearance(uint16_t appearance); - void setManufacturerData(String manufacturerData); - void setName(String name); - void setRSSI(int rssi); - void setScan(BLEScan *pScan); - void setServiceData(String data); - void setServiceDataUUID(BLEUUID uuid); - void setServiceUUID(const char *serviceUUID); - void setServiceUUID(BLEUUID serviceUUID); - void setTXPower(int8_t txPower); + /*************************************************************************** + * Common private properties * + ***************************************************************************/ bool m_haveAppearance; bool m_haveManufacturerData; bool m_haveName; bool m_haveRSSI; bool m_haveTXPower; - BLEAddress m_address = BLEAddress((uint8_t *)"\0\0\0\0\0\0"); uint8_t m_adFlag; uint16_t m_appearance; @@ -111,7 +142,36 @@ class BLEAdvertisedDevice { std::vector m_serviceDataUUIDs; uint8_t *m_payload; size_t m_payloadLength = 0; - esp_ble_addr_type_t m_addressType; + uint8_t m_addressType; + + /*************************************************************************** + * NimBLE private properties * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + bool m_callbackSent; + uint8_t m_advType; +#endif + + /*************************************************************************** + * Common private declarations * + ***************************************************************************/ + + void parseAdvertisement(uint8_t *payload, size_t total_len = 62); + void setPayload(uint8_t *payload, size_t total_len = 62, bool append = false); + void setAddress(BLEAddress address); + void setAdFlag(uint8_t adFlag); + void setAdvertizementResult(uint8_t *payload); + void setAppearance(uint16_t appearance); + void setManufacturerData(String manufacturerData); + void setName(String name); + void setRSSI(int rssi); + void setScan(BLEScan *pScan); + void setServiceData(String data); + void setServiceDataUUID(BLEUUID uuid); + void setServiceUUID(const char *serviceUUID); + void setServiceUUID(BLEUUID serviceUUID); + void setTXPower(int8_t txPower); }; /** @@ -123,30 +183,55 @@ class BLEAdvertisedDevice { */ class BLEAdvertisedDeviceCallbacks { public: + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ + 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. - */ + * @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; }; -#ifdef SOC_BLE_50_SUPPORTED +#if defined(SOC_BLE_50_SUPPORTED) && defined(CONFIG_BLUEDROID_ENABLED) class BLEExtAdvertisingCallbacks { public: + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ + virtual ~BLEExtAdvertisingCallbacks() {} + + /*************************************************************************** + * Bluedroid public declarations * + ***************************************************************************/ + /** - * @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. - */ + * @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. + */ + +#if defined(CONFIG_BLUEDROID_ENABLED) virtual void onResult(esp_ble_gap_ext_adv_report_t report) = 0; +#endif + + /*************************************************************************** + * NimBLE public declarations * + ***************************************************************************/ + + // Extended advertising for NimBLE is not supported yet. +#if defined(CONFIG_NIMBLE_ENABLED) + virtual void onResult(struct ble_gap_ext_disc_desc report) = 0; +#endif }; -#endif // SOC_BLE_50_SUPPORTED +#endif // SOC_BLE_50_SUPPORTED && CONFIG_BLUEDROID_ENABLED -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ #endif /* COMPONENTS_CPP_UTILS_BLEADVERTISEDDEVICE_H_ */ diff --git a/libraries/BLE/src/BLEAdvertising.cpp b/libraries/BLE/src/BLEAdvertising.cpp index fe39a69c206..9bf3ffe3930 100644 --- a/libraries/BLE/src/BLEAdvertising.cpp +++ b/libraries/BLE/src/BLEAdvertising.cpp @@ -5,6 +5,10 @@ * Created on: Jun 21, 2017 * Author: kolban * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE + * * The ESP-IDF provides a framework for BLE advertising. It has determined that there are a common set * of properties that are advertised and has built a data structure that can be populated by the programmer. * This means that the programmer doesn't have to "mess with" the low level construction of a low level @@ -16,46 +20,32 @@ * set in the data will be advertised. * */ + #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ + #include "BLEAdvertising.h" #include #include "BLEUtils.h" #include "GeneralUtils.h" #include "esp32-hal-log.h" +/*************************************************************************** + * Common functions * + ***************************************************************************/ + /** * @brief Construct a default advertising object. - * */ -BLEAdvertising::BLEAdvertising() : m_scanRespData{} { - 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; - m_advParams.peer_addr_type = BLE_ADDR_TYPE_PUBLIC; - - m_customAdvData = false; // No custom advertising data - m_customScanResponseData = false; // No custom scan response data +BLEAdvertising::BLEAdvertising() { + reset(); } // BLEAdvertising /** @@ -64,6 +54,9 @@ BLEAdvertising::BLEAdvertising() : m_scanRespData{} { */ void BLEAdvertising::addServiceUUID(BLEUUID serviceUUID) { m_serviceUUIDs.push_back(serviceUUID); +#ifdef CONFIG_NIMBLE_ENABLED + m_advDataSet = false; +#endif } // addServiceUUID /** @@ -72,6 +65,9 @@ void BLEAdvertising::addServiceUUID(BLEUUID serviceUUID) { */ void BLEAdvertising::addServiceUUID(const char *serviceUUID) { addServiceUUID(BLEUUID(serviceUUID)); +#ifdef CONFIG_NIMBLE_ENABLED + m_advDataSet = false; +#endif } // addServiceUUID /** @@ -87,6 +83,9 @@ bool BLEAdvertising::removeServiceUUID(int index) { } m_serviceUUIDs.erase(m_serviceUUIDs.begin() + index); +#ifdef CONFIG_NIMBLE_ENABLED + m_advDataSet = false; +#endif return true; } @@ -120,34 +119,107 @@ bool BLEAdvertising::removeServiceUUID(const char *serviceUUID) { */ void BLEAdvertising::setAppearance(uint16_t appearance) { m_advData.appearance = appearance; +#ifdef CONFIG_NIMBLE_ENABLED + m_advData.appearance_is_present = 1; + m_advDataSet = false; +#endif } // setAppearance -void BLEAdvertising::setAdvertisementType(esp_ble_adv_type_t adv_type) { - m_advParams.adv_type = adv_type; -} // setAdvertisementType +void BLEAdvertising::setAdvertisementType(uint8_t adv_type) { +#ifdef CONFIG_BLUEDROID_ENABLED + m_advParams.adv_type = (esp_ble_adv_type_t)adv_type; +#endif -void BLEAdvertising::setAdvertisementChannelMap(esp_ble_adv_channel_t channel_map) { - m_advParams.channel_map = channel_map; -} // setAdvertisementChannelMap +#if defined(CONFIG_NIMBLE_ENABLED) + m_advParams.conn_mode = adv_type; +#endif +} // setAdvertisementType void BLEAdvertising::setMinInterval(uint16_t mininterval) { +#ifdef CONFIG_BLUEDROID_ENABLED m_advParams.adv_int_min = mininterval; +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + m_advParams.itvl_min = mininterval; +#endif } // setMinInterval void BLEAdvertising::setMaxInterval(uint16_t maxinterval) { +#ifdef CONFIG_BLUEDROID_ENABLED m_advParams.adv_int_max = maxinterval; +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + m_advParams.itvl_max = maxinterval; +#endif } // setMaxInterval void BLEAdvertising::setMinPreferred(uint16_t mininterval) { +#ifdef CONFIG_BLUEDROID_ENABLED m_advData.min_interval = mininterval; +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + // invalid parameters, set the slave interval to null + if (mininterval < 0x0006 || mininterval > 0x0C80) { + m_advData.slave_itvl_range = nullptr; + return; + } + + if (m_advData.slave_itvl_range == nullptr) { + m_advData.slave_itvl_range = m_slaveItvl; + } + + m_slaveItvl[0] = mininterval; + m_slaveItvl[1] = mininterval >> 8; + + uint16_t maxinterval = *(uint16_t *)(m_advData.slave_itvl_range + 2); + + // If mininterval is higher than the maxinterval make them the same + if (mininterval > maxinterval) { + m_slaveItvl[2] = m_slaveItvl[0]; + m_slaveItvl[3] = m_slaveItvl[1]; + } + + m_advDataSet = false; +#endif } // void BLEAdvertising::setMaxPreferred(uint16_t maxinterval) { +#ifdef CONFIG_BLUEDROID_ENABLED m_advData.max_interval = maxinterval; +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + // invalid parameters, set the slave interval to null + if (maxinterval < 0x0006 || maxinterval > 0x0C80) { + m_advData.slave_itvl_range = nullptr; + return; + } + if (m_advData.slave_itvl_range == nullptr) { + m_advData.slave_itvl_range = m_slaveItvl; + } + m_slaveItvl[2] = maxinterval; + m_slaveItvl[3] = maxinterval >> 8; + + uint16_t mininterval = *(uint16_t *)(m_advData.slave_itvl_range); + + // If mininterval is higher than the maxinterval make them the same + if (mininterval > maxinterval) { + m_slaveItvl[0] = m_slaveItvl[2]; + m_slaveItvl[1] = m_slaveItvl[3]; + } + + m_advDataSet = false; +#endif } // void BLEAdvertising::setScanResponse(bool set) { m_scanResp = set; +#ifdef CONFIG_NIMBLE_ENABLED + m_advDataSet = false; +#endif } /** @@ -158,22 +230,54 @@ void BLEAdvertising::setScanResponse(bool set) { void BLEAdvertising::setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly) { log_v(">> setScanFilter: scanRequestWhitelistOnly: %d, connectWhitelistOnly: %d", scanRequestWhitelistOnly, connectWhitelistOnly); if (!scanRequestWhitelistOnly && !connectWhitelistOnly) { + +#ifdef CONFIG_BLUEDROID_ENABLED m_advParams.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY; +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + m_advParams.filter_policy = BLE_HCI_ADV_FILT_NONE; +#endif + log_v("<< setScanFilter"); return; } if (scanRequestWhitelistOnly && !connectWhitelistOnly) { + +#ifdef CONFIG_BLUEDROID_ENABLED m_advParams.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_WLST_CON_ANY; +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + m_advParams.filter_policy = BLE_HCI_ADV_FILT_SCAN; +#endif + log_v("<< setScanFilter"); return; } if (!scanRequestWhitelistOnly && connectWhitelistOnly) { + +#ifdef CONFIG_BLUEDROID_ENABLED m_advParams.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_WLST; +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + m_advParams.filter_policy = BLE_HCI_ADV_FILT_CONN; +#endif + log_v("<< setScanFilter"); return; } if (scanRequestWhitelistOnly && connectWhitelistOnly) { + +#ifdef CONFIG_BLUEDROID_ENABLED m_advParams.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_WLST_CON_WLST; +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + m_advParams.filter_policy = BLE_HCI_ADV_FILT_BOTH; +#endif + log_v("<< setScanFilter"); return; } @@ -185,10 +289,21 @@ void BLEAdvertising::setScanFilter(bool scanRequestWhitelistOnly, bool connectWh */ bool BLEAdvertising::setAdvertisementData(BLEAdvertisementData &advertisementData) { log_v(">> setAdvertisementData"); + +#ifdef CONFIG_BLUEDROID_ENABLED esp_err_t errRc = ::esp_ble_gap_config_adv_data_raw((uint8_t *)advertisementData.getPayload().c_str(), advertisementData.getPayload().length()); if (errRc != ESP_OK) { log_e("esp_ble_gap_config_adv_data_raw: %d %s", errRc, GeneralUtils::errorToString(errRc)); } +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + esp_err_t errRc = ble_gap_adv_set_data((uint8_t *)advertisementData.getPayload().c_str(), advertisementData.getPayload().length()); + if (errRc != ESP_OK) { + log_e("ble_gap_adv_set_data: %d %s", errRc, BLEUtils::returnCodeToString(errRc)); + } +#endif + m_customAdvData = true; // Set the flag that indicates we are using custom advertising data. log_v("<< setAdvertisementData"); return ESP_OK == errRc; @@ -200,127 +315,25 @@ bool BLEAdvertising::setAdvertisementData(BLEAdvertisementData &advertisementDat */ bool BLEAdvertising::setScanResponseData(BLEAdvertisementData &advertisementData) { log_v(">> setScanResponseData"); + +#ifdef CONFIG_BLUEDROID_ENABLED esp_err_t errRc = ::esp_ble_gap_config_scan_rsp_data_raw((uint8_t *)advertisementData.getPayload().c_str(), advertisementData.getPayload().length()); if (errRc != ESP_OK) { log_e("esp_ble_gap_config_scan_rsp_data_raw: %d %s", errRc, GeneralUtils::errorToString(errRc)); } - m_customScanResponseData = true; // Set the flag that indicates we are using custom scan response data. - log_v("<< setScanResponseData"); - return ESP_OK == errRc; -} // setScanResponseData +#endif -/** - * @brief Start advertising. - * Start advertising. - * @return N/A. - */ -bool BLEAdvertising::start() { - log_v(">> start: customAdvData: %d, customScanResponseData: %d", m_customAdvData, m_customScanResponseData); - - // We have a vector of service UUIDs that we wish to advertise. In order to use the - // ESP-IDF framework, these must be supplied in a contiguous array of their 128bit (16 byte) - // representations. If we have 1 or more services to advertise then we allocate enough - // storage to host them and then copy them in one at a time into the contiguous storage. - int numServices = m_serviceUUIDs.size(); - if (numServices > 0) { - m_advData.service_uuid_len = 16 * numServices; - m_advData.p_service_uuid = (uint8_t *)malloc(m_advData.service_uuid_len); - if (!m_advData.p_service_uuid) { - log_e(">> start failed: out of memory"); - return false; - } - - uint8_t *p = m_advData.p_service_uuid; - for (int i = 0; i < numServices; i++) { - log_d("- advertising service: %s", m_serviceUUIDs[i].toString().c_str()); - BLEUUID serviceUUID128 = m_serviceUUIDs[i].to128(); - memcpy(p, serviceUUID128.getNative()->uuid.uuid128, 16); - p += 16; - } - } else { - m_advData.service_uuid_len = 0; - log_d("- no services advertised"); - } - - esp_err_t errRc; - - if (!m_customAdvData) { - // Set the configuration for advertising. - m_advData.set_scan_rsp = false; - m_advData.include_name = !m_scanResp; - m_advData.include_txpower = !m_scanResp; - errRc = ::esp_ble_gap_config_adv_data(&m_advData); - if (errRc != ESP_OK) { - log_e("<< esp_ble_gap_config_adv_data: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return false; - } - } - - if (!m_customScanResponseData && m_scanResp) { - // Set the configuration for scan response. - memcpy(&m_scanRespData, &m_advData, sizeof(esp_ble_adv_data_t)); // Copy the content of m_advData. - m_scanRespData.set_scan_rsp = true; // Define this struct as scan response data - m_scanRespData.include_name = true; // Caution: This may lead to a crash if the device name has more than 29 characters - m_scanRespData.include_txpower = true; - m_scanRespData.appearance = 0; // If defined the 'Appearance' attribute is already included in the advertising data - m_scanRespData.flag = 0; // 'Flags' attribute should no be included in the scan response - - errRc = ::esp_ble_gap_config_adv_data(&m_scanRespData); - if (errRc != ESP_OK) { - log_e("<< esp_ble_gap_config_adv_data (Scan response): rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return false; - } - } - - // If we had services to advertise then we previously allocated some storage for them. - // Here we release that storage. - free(m_advData.p_service_uuid); //TODO change this variable to local scope? - m_advData.p_service_uuid = nullptr; - - // Start advertising. - errRc = ::esp_ble_gap_start_advertising(&m_advParams); +#if defined(CONFIG_NIMBLE_ENABLED) + esp_err_t errRc = ble_gap_adv_rsp_set_data((uint8_t *)advertisementData.getPayload().c_str(), advertisementData.getPayload().length()); if (errRc != ESP_OK) { - log_e("<< esp_ble_gap_start_advertising: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - } else { - log_v("<< start"); + log_e("ble_gap_adv_rsp_set_data: %d %s", errRc, BLEUtils::returnCodeToString(errRc)); } - return ESP_OK == errRc; -} // start - -/** - * @brief Stop advertising. - * Stop advertising. - * @return N/A. - */ -bool BLEAdvertising::stop() { - log_v(">> stop"); - esp_err_t errRc = ::esp_ble_gap_stop_advertising(); - if (errRc != ESP_OK) { - log_e("esp_ble_gap_stop_advertising: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - } else { - log_v("<< stop"); - } - return ESP_OK == errRc; -} // stop - -/** - * @brief Set BLE address. - * @param [in] Bluetooth address. - * @param [in] Bluetooth address type. - * Set BLE address. - */ -bool BLEAdvertising::setDeviceAddress(esp_bd_addr_t addr, esp_ble_addr_type_t type) { - log_v(">> setPrivateAddress"); +#endif - m_advParams.own_addr_type = type; - esp_err_t errRc = esp_ble_gap_set_rand_addr((uint8_t *)addr); - if (errRc != ESP_OK) { - log_e("esp_ble_gap_set_rand_addr: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - } else { - log_v("<< setPrivateAddress"); - } + m_customScanResponseData = true; // Set the flag that indicates we are using custom scan response data. + log_v("<< setScanResponseData"); return ESP_OK == errRc; -} // setPrivateAddress +} // setScanResponseData /** * @brief Add data to the payload to be advertised. @@ -333,6 +346,13 @@ void BLEAdvertisementData::addData(String data) { m_payload.concat(data); } // addData +void BLEAdvertisementData::addData(char *data, size_t length) { + if ((m_payload.length() + length) > ESP_BLE_ADV_DATA_LEN_MAX) { + return; + } + m_payload.concat(String(data, length)); +} // addData + /** * @brief Set the appearance. * @param [in] appearance The appearance code value. @@ -359,7 +379,12 @@ void BLEAdvertisementData::setCompleteServices(BLEUUID uuid) { // [Len] [0x03] [LL] [HH] cdata[0] = 3; cdata[1] = ESP_BLE_AD_TYPE_16SRV_CMPL; // 0x03 +#if defined(CONFIG_BLUEDROID_ENABLED) addData(String(cdata, 2) + String((char *)&uuid.getNative()->uuid.uuid16, 2)); +#endif +#if defined(CONFIG_NIMBLE_ENABLED) + addData(String(cdata, 2) + String((char *)&uuid.getNative()->u16.value, 2)); +#endif break; } @@ -368,7 +393,12 @@ void BLEAdvertisementData::setCompleteServices(BLEUUID uuid) { // [Len] [0x05] [LL] [LL] [HH] [HH] cdata[0] = 5; cdata[1] = ESP_BLE_AD_TYPE_32SRV_CMPL; // 0x05 +#if defined(CONFIG_BLUEDROID_ENABLED) addData(String(cdata, 2) + String((char *)&uuid.getNative()->uuid.uuid32, 4)); +#endif +#if defined(CONFIG_NIMBLE_ENABLED) + addData(String(cdata, 2) + String((char *)&uuid.getNative()->u32.value, 4)); +#endif break; } @@ -377,7 +407,12 @@ void BLEAdvertisementData::setCompleteServices(BLEUUID uuid) { // [Len] [0x07] [0] [1] ... [15] cdata[0] = 17; cdata[1] = ESP_BLE_AD_TYPE_128SRV_CMPL; // 0x07 +#if defined(CONFIG_BLUEDROID_ENABLED) addData(String(cdata, 2) + String((char *)uuid.getNative()->uuid.uuid128, 16)); +#endif +#if defined(CONFIG_NIMBLE_ENABLED) + addData(String(cdata, 2) + String((char *)uuid.getNative()->u128.value, 16)); +#endif break; } @@ -442,7 +477,12 @@ void BLEAdvertisementData::setPartialServices(BLEUUID uuid) { // [Len] [0x02] [LL] [HH] cdata[0] = 3; cdata[1] = ESP_BLE_AD_TYPE_16SRV_PART; // 0x02 +#if defined(CONFIG_BLUEDROID_ENABLED) addData(String(cdata, 2) + String((char *)&uuid.getNative()->uuid.uuid16, 2)); +#endif +#if defined(CONFIG_NIMBLE_ENABLED) + addData(String(cdata, 2) + String((char *)&uuid.getNative()->u16.value, 2)); +#endif break; } @@ -451,7 +491,12 @@ void BLEAdvertisementData::setPartialServices(BLEUUID uuid) { // [Len] [0x04] [LL] [LL] [HH] [HH] cdata[0] = 5; cdata[1] = ESP_BLE_AD_TYPE_32SRV_PART; // 0x04 +#if defined(CONFIG_BLUEDROID_ENABLED) addData(String(cdata, 2) + String((char *)&uuid.getNative()->uuid.uuid32, 4)); +#endif +#if defined(CONFIG_NIMBLE_ENABLED) + addData(String(cdata, 2) + String((char *)&uuid.getNative()->u32.value, 4)); +#endif break; } @@ -460,7 +505,12 @@ void BLEAdvertisementData::setPartialServices(BLEUUID uuid) { // [Len] [0x06] [0] [1] ... [15] cdata[0] = 17; cdata[1] = ESP_BLE_AD_TYPE_128SRV_PART; // 0x06 +#if defined(CONFIG_BLUEDROID_ENABLED) addData(String(cdata, 2) + String((char *)&uuid.getNative()->uuid.uuid128, 16)); +#endif +#if defined(CONFIG_NIMBLE_ENABLED) + addData(String(cdata, 2) + String((char *)&uuid.getNative()->u128.value, 16)); +#endif break; } @@ -481,7 +531,12 @@ void BLEAdvertisementData::setServiceData(BLEUUID uuid, String data) { // [Len] [0x16] [UUID16] data cdata[0] = data.length() + 3; cdata[1] = ESP_BLE_AD_TYPE_SERVICE_DATA; // 0x16 +#if defined(CONFIG_BLUEDROID_ENABLED) addData(String(cdata, 2) + String((char *)&uuid.getNative()->uuid.uuid16, 2) + data); +#endif +#if defined(CONFIG_NIMBLE_ENABLED) + addData(String(cdata, 2) + String((char *)&uuid.getNative()->u16.value, 2) + data); +#endif break; } @@ -490,7 +545,12 @@ void BLEAdvertisementData::setServiceData(BLEUUID uuid, String data) { // [Len] [0x20] [UUID32] data cdata[0] = data.length() + 5; cdata[1] = ESP_BLE_AD_TYPE_32SERVICE_DATA; // 0x20 +#if defined(CONFIG_BLUEDROID_ENABLED) addData(String(cdata, 2) + String((char *)&uuid.getNative()->uuid.uuid32, 4) + data); +#endif +#if defined(CONFIG_NIMBLE_ENABLED) + addData(String(cdata, 2) + String((char *)&uuid.getNative()->u32.value, 4) + data); +#endif break; } @@ -499,7 +559,12 @@ void BLEAdvertisementData::setServiceData(BLEUUID uuid, String data) { // [Len] [0x21] [UUID128] data cdata[0] = data.length() + 17; cdata[1] = ESP_BLE_AD_TYPE_128SERVICE_DATA; // 0x21 +#if defined(CONFIG_BLUEDROID_ENABLED) addData(String(cdata, 2) + String((char *)&uuid.getNative()->uuid.uuid128, 16) + data); +#endif +#if defined(CONFIG_NIMBLE_ENABLED) + addData(String(cdata, 2) + String((char *)&uuid.getNative()->u128.value, 16) + data); +#endif break; } @@ -520,6 +585,33 @@ void BLEAdvertisementData::setShortName(String name) { log_d("BLEAdvertisementData", "<< setShortName"); } // setShortName +/** + * @brief Adds Tx power level to the advertisement data. + */ +void BLEAdvertisementData::addTxPower() { + char cdata[3]; + cdata[0] = 2; // length + cdata[1] = ESP_BLE_AD_TYPE_TX_PWR; + cdata[2] = BLEDevice::getPower(); + addData(cdata, 3); +} // addTxPower + +/** + * @brief Set the preferred connection interval parameters. + * @param [in] min The minimum interval desired. + * @param [in] max The maximum interval desired. + */ +void BLEAdvertisementData::setPreferredParams(uint16_t min, uint16_t max) { + char cdata[6]; + cdata[0] = 5; // length + cdata[1] = ESP_BLE_AD_TYPE_INT_RANGE; + cdata[2] = min; + cdata[3] = min >> 8; + cdata[4] = max; + cdata[5] = max >> 8; + addData(cdata, 6); +} // setPreferredParams + /** * @brief Retrieve the payload that is to be advertised. * @return The payload that is to be advertised. @@ -528,8 +620,146 @@ String BLEAdvertisementData::getPayload() { return m_payload; } // getPayload -void BLEAdvertising::handleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { +/*************************************************************************** + * Bluedroid functions * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + +void BLEAdvertising::reset() { + if (BLEDevice::getInitialized()) { + stop(); + } + + memset(&m_scanRespData, 0, sizeof(esp_ble_adv_data_t)); + memset(&m_advData, 0, sizeof(esp_ble_adv_data_t)); + memset(&m_advParams, 0, sizeof(esp_ble_adv_params_t)); + + 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; + m_advParams.peer_addr_type = BLE_ADDR_TYPE_PUBLIC; + m_customAdvData = false; // No custom advertising data + m_customScanResponseData = false; // No custom scan response data +} // BLEAdvertising + +void BLEAdvertising::setAdvertisementChannelMap(esp_ble_adv_channel_t channel_map) { + m_advParams.channel_map = channel_map; +} // setAdvertisementChannelMap + +/** + * @brief Start advertising. + * Start advertising. + * @return N/A. + */ +bool BLEAdvertising::start() { + log_v(">> start: customAdvData: %d, customScanResponseData: %d", m_customAdvData, m_customScanResponseData); + + // We have a vector of service UUIDs that we wish to advertise. In order to use the + // ESP-IDF framework, these must be supplied in a contiguous array of their 128bit (16 byte) + // representations. If we have 1 or more services to advertise then we allocate enough + // storage to host them and then copy them in one at a time into the contiguous storage. + int numServices = m_serviceUUIDs.size(); + if (numServices > 0) { + m_advData.service_uuid_len = 16 * numServices; + m_advData.p_service_uuid = (uint8_t *)malloc(m_advData.service_uuid_len); + if (!m_advData.p_service_uuid) { + log_e(">> start failed: out of memory"); + return false; + } + + uint8_t *p = m_advData.p_service_uuid; + for (int i = 0; i < numServices; i++) { + log_d("- advertising service: %s", m_serviceUUIDs[i].toString().c_str()); + BLEUUID serviceUUID128 = m_serviceUUIDs[i].to128(); + memcpy(p, serviceUUID128.getNative()->uuid.uuid128, 16); + p += 16; + } + } else { + m_advData.service_uuid_len = 0; + log_d("- no services advertised"); + } + + esp_err_t errRc; + + if (!m_customAdvData) { + // Set the configuration for advertising. + m_advData.set_scan_rsp = false; + m_advData.include_name = !m_scanResp; + m_advData.include_txpower = !m_scanResp; + errRc = ::esp_ble_gap_config_adv_data(&m_advData); + if (errRc != ESP_OK) { + log_e("<< esp_ble_gap_config_adv_data: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return false; + } + } + + if (!m_customScanResponseData && m_scanResp) { + // Set the configuration for scan response. + memcpy(&m_scanRespData, &m_advData, sizeof(esp_ble_adv_data_t)); // Copy the content of m_advData. + m_scanRespData.set_scan_rsp = true; // Define this struct as scan response data + m_scanRespData.include_name = true; // Caution: This may lead to a crash if the device name has more than 29 characters + m_scanRespData.include_txpower = true; + m_scanRespData.appearance = 0; // If defined the 'Appearance' attribute is already included in the advertising data + m_scanRespData.flag = 0; // 'Flags' attribute should no be included in the scan response + + errRc = ::esp_ble_gap_config_adv_data(&m_scanRespData); + if (errRc != ESP_OK) { + log_e("<< esp_ble_gap_config_adv_data (Scan response): rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return false; + } + } + + // If we had services to advertise then we previously allocated some storage for them. + // Here we release that storage. + free(m_advData.p_service_uuid); //TODO change this variable to local scope? + m_advData.p_service_uuid = nullptr; + + // Start advertising. + errRc = ::esp_ble_gap_start_advertising(&m_advParams); + if (errRc != ESP_OK) { + log_e("<< esp_ble_gap_start_advertising: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } else { + log_v("<< start"); + } + return ESP_OK == errRc; +} // start + +/** + * @brief Stop advertising. + * Stop advertising. + * @return N/A. + */ +bool BLEAdvertising::stop() { + log_v(">> stop"); + esp_err_t errRc = ::esp_ble_gap_stop_advertising(); + if (errRc != ESP_OK) { + log_e("esp_ble_gap_stop_advertising: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } else { + log_v("<< stop"); + } + return ESP_OK == errRc; +} // stop + +void BLEAdvertising::handleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { log_d("handleGAPEvent [event no: %d]", (int)event); switch (event) { @@ -558,7 +788,26 @@ void BLEAdvertising::handleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb } } -#ifdef SOC_BLE_50_SUPPORTED +/** + * @brief Set BLE address. + * @param [in] Bluetooth address. + * @param [in] Bluetooth address type. + * Set BLE address. + */ +bool BLEAdvertising::setDeviceAddress(esp_bd_addr_t addr, esp_ble_addr_type_t type) { + log_v(">> setPrivateAddress"); + + m_advParams.own_addr_type = type; + esp_err_t errRc = esp_ble_gap_set_rand_addr((uint8_t *)addr); + if (errRc != ESP_OK) { + log_e("esp_ble_gap_set_rand_addr: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } else { + log_v("<< setPrivateAddress"); + } + return ESP_OK == errRc; +} // setPrivateAddress + +#if defined(SOC_BLE_50_SUPPORTED) /** * @brief Creator @@ -803,7 +1052,376 @@ void BLEMultiAdvertising::setDuration(uint8_t instance, int duration, int max_ev ext_adv[instance] = {instance, duration, max_events}; } -#endif // SOC_BLE_50_SUPPORTED +#endif /* SOC_BLE_50_SUPPORTED */ #endif /* CONFIG_BLUEDROID_ENABLED */ + +/*************************************************************************** + * NimBLE functions * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + +void BLEAdvertising::reset() { + if (BLEDevice::getInitialized() && isAdvertising()) { + stop(); + } + memset(&m_advData, 0, sizeof m_advData); + memset(&m_scanData, 0, sizeof m_scanData); + memset(&m_advParams, 0, sizeof m_advParams); + memset(&m_slaveItvl, 0, sizeof m_slaveItvl); + const char *name = ble_svc_gap_device_name(); + + m_advData.name = (uint8_t *)name; + m_advData.name_len = strlen(name); + m_advData.name_is_complete = 1; + m_advData.tx_pwr_lvl = BLEDevice::getPower(); + m_advData.flags = (BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP); + m_advParams.conn_mode = BLE_GAP_CONN_MODE_UND; + m_advParams.disc_mode = BLE_GAP_DISC_MODE_GEN; + m_customAdvData = false; + m_customScanResponseData = false; + m_scanResp = true; + m_advDataSet = false; + // Set this to non-zero to prevent auto start if host reset before started by app. + m_duration = BLE_HS_FOREVER; + m_advCompCB = nullptr; +} // BLEAdvertising + +void BLEAdvertising::setName(String name) { + m_name = name; + m_advData.name = (uint8_t *)m_name.c_str(); + m_advData.name_len = m_name.length(); + m_advDataSet = false; +} + +/** + * @brief Add the transmission power level to the advertisement packet. + */ +void BLEAdvertising::addTxPower() { + m_advData.tx_pwr_lvl_is_present = 1; + m_advDataSet = false; +} // addTxPower + +/** + * @brief Handles the callback when advertising stops. + */ +void BLEAdvertising::advCompleteCB() { + if (m_advCompCB != nullptr) { + m_advCompCB(this); + } +} // advCompleteCB + +/** + * @brief Check if currently advertising. + * @return true if advertising is active. + */ +bool BLEAdvertising::isAdvertising() { + return ble_gap_adv_active(); +} // isAdvertising + +/* + * Host reset seems to clear advertising data, + * we need clear the flag so it reloads it. + */ +void BLEAdvertising::onHostSync() { + log_v("Host re-synced"); + + m_advDataSet = false; + // If we were advertising forever, restart it now + if (m_duration == 0) { + start(m_duration, m_advCompCB); + } else { + // Otherwise we should tell the app that advertising stopped. + advCompleteCB(); + } +} // onHostSync + +/** + * @brief Handler for gap events when not using peripheral role. + * @param [in] event the event data. + * @param [in] arg pointer to the advertising instance. + */ +int BLEAdvertising::handleGAPEvent(struct ble_gap_event *event, void *arg) { + BLEAdvertising *pAdv = (BLEAdvertising *)arg; + + if (event->type == BLE_GAP_EVENT_ADV_COMPLETE) { + switch (event->adv_complete.reason) { + // Don't call the callback if host reset, we want to + // preserve the active flag until re-sync to restart advertising. + case BLE_HS_ETIMEOUT_HCI: + case BLE_HS_EOS: + case BLE_HS_ECONTROLLER: + case BLE_HS_ENOTSYNCED: + log_v("host reset, rc=%d", event->adv_complete.reason); + BLEDevice::onReset(event->adv_complete.reason); + return 0; + default: break; + } + pAdv->advCompleteCB(); + } + return 0; +} + +/** + * @brief Start advertising. + * @param [in] duration The duration, in seconds, to advertise, 0 == advertise forever. + * @param [in] advCompleteCB A pointer to a callback to be invoked when advertising ends. + * @return True if advertising started successfully. + */ +bool BLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(BLEAdvertising *pAdv)) { + log_v(">> Advertising start: customAdvData: %d, customScanResponseData: %d", m_customAdvData, m_customScanResponseData); + + // If Host is not synced we cannot start advertising. + if (!BLEDevice::m_synced) { + log_e("Host reset, wait for sync."); + return false; + } + + BLEServer *pServer = BLEDevice::getServer(); + if (pServer != nullptr) { + if (!pServer->m_gattsStarted) { + pServer->start(); + } else if (pServer->getConnectedCount() >= CONFIG_BT_NIMBLE_MAX_CONNECTIONS) { + log_e("Max connections reached - not advertising"); + return false; + } + } + + // If already advertising just return + if (ble_gap_adv_active()) { + log_w("Advertising already active"); + return false; + } + + // Save the duration in case of host reset so we can restart with the same parameters + m_duration = duration; + + if (duration == 0) { + duration = BLE_HS_FOREVER; + } else { + duration = duration * 1000; // convert duration to milliseconds + } + + m_advCompCB = advCompleteCB; + + m_advParams.disc_mode = BLE_GAP_DISC_MODE_GEN; + m_advData.flags = (BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP); + if (m_advParams.conn_mode == BLE_GAP_CONN_MODE_NON) { + if (!m_scanResp) { + m_advParams.disc_mode = BLE_GAP_DISC_MODE_NON; + m_advData.flags = BLE_HS_ADV_F_BREDR_UNSUP; + } + } + + int rc = 0; + + if (!m_customAdvData && !m_advDataSet) { + //start with 3 bytes for the flags data + uint8_t payloadLen = (2 + 1); + if (m_advData.mfg_data_len > 0) { + payloadLen += (2 + m_advData.mfg_data_len); + } + + if (m_advData.svc_data_uuid16_len > 0) { + payloadLen += (2 + m_advData.svc_data_uuid16_len); + } + + if (m_advData.svc_data_uuid32_len > 0) { + payloadLen += (2 + m_advData.svc_data_uuid32_len); + } + + if (m_advData.svc_data_uuid128_len > 0) { + payloadLen += (2 + m_advData.svc_data_uuid128_len); + } + + if (m_advData.uri_len > 0) { + payloadLen += (2 + m_advData.uri_len); + } + + if (m_advData.appearance_is_present) { + payloadLen += (2 + BLE_HS_ADV_APPEARANCE_LEN); + } + + if (m_advData.tx_pwr_lvl_is_present) { + payloadLen += (2 + BLE_HS_ADV_TX_PWR_LVL_LEN); + } + + if (m_advData.slave_itvl_range != nullptr) { + payloadLen += (2 + BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN); + } + + for (auto &it : m_serviceUUIDs) { + if (it.getNative()->u.type == BLE_UUID_TYPE_16) { + int add = (m_advData.num_uuids16 > 0) ? 2 : 4; + if ((payloadLen + add) > BLE_HS_ADV_MAX_SZ) { + m_advData.uuids16_is_complete = 0; + continue; + } + payloadLen += add; + + if (nullptr == (m_advData.uuids16 = (ble_uuid16_t *)realloc((void *)m_advData.uuids16, (m_advData.num_uuids16 + 1) * sizeof(ble_uuid16_t)))) { + log_e("Error, no mem"); + abort(); + } + memcpy((void *)&m_advData.uuids16[m_advData.num_uuids16], &it.getNative()->u16, sizeof(ble_uuid16_t)); + m_advData.uuids16_is_complete = 1; + m_advData.num_uuids16++; + } + if (it.getNative()->u.type == BLE_UUID_TYPE_32) { + int add = (m_advData.num_uuids32 > 0) ? 4 : 6; + if ((payloadLen + add) > BLE_HS_ADV_MAX_SZ) { + m_advData.uuids32_is_complete = 0; + continue; + } + payloadLen += add; + + if (nullptr == (m_advData.uuids32 = (ble_uuid32_t *)realloc((void *)m_advData.uuids32, (m_advData.num_uuids32 + 1) * sizeof(ble_uuid32_t)))) { + log_e("Error, no mem"); + abort(); + } + memcpy((void *)&m_advData.uuids32[m_advData.num_uuids32], &it.getNative()->u32, sizeof(ble_uuid32_t)); + m_advData.uuids32_is_complete = 1; + m_advData.num_uuids32++; + } + if (it.getNative()->u.type == BLE_UUID_TYPE_128) { + int add = (m_advData.num_uuids128 > 0) ? 16 : 18; + if ((payloadLen + add) > BLE_HS_ADV_MAX_SZ) { + m_advData.uuids128_is_complete = 0; + continue; + } + payloadLen += add; + + if (nullptr == (m_advData.uuids128 = (ble_uuid128_t *)realloc((void *)m_advData.uuids128, (m_advData.num_uuids128 + 1) * sizeof(ble_uuid128_t)))) { + log_e("Error, no mem"); + abort(); + } + memcpy((void *)&m_advData.uuids128[m_advData.num_uuids128], &it.getNative()->u128, sizeof(ble_uuid128_t)); + m_advData.uuids128_is_complete = 1; + m_advData.num_uuids128++; + } + } + + // check if there is room for the name, if not put it in scan data + if ((payloadLen + (2 + m_advData.name_len)) > BLE_HS_ADV_MAX_SZ) { + if (m_scanResp && !m_customScanResponseData) { + m_scanData.name = m_advData.name; + m_scanData.name_len = m_advData.name_len; + if (m_scanData.name_len > BLE_HS_ADV_MAX_SZ - 2) { + m_scanData.name_len = BLE_HS_ADV_MAX_SZ - 2; + m_scanData.name_is_complete = 0; + } else { + m_scanData.name_is_complete = 1; + } + m_advData.name = nullptr; + m_advData.name_len = 0; + m_advData.name_is_complete = 0; + } else { + if (m_advData.tx_pwr_lvl_is_present) { + m_advData.tx_pwr_lvl_is_present = 0; + payloadLen -= (2 + 1); + } + // if not using scan response just cut the name down + // leaving 2 bytes for the data specifier. + if (m_advData.name_len > (BLE_HS_ADV_MAX_SZ - payloadLen - 2)) { + m_advData.name_len = (BLE_HS_ADV_MAX_SZ - payloadLen - 2); + m_advData.name_is_complete = 0; + } + } + } + + if (m_scanResp && !m_customScanResponseData) { + rc = ble_gap_adv_rsp_set_fields(&m_scanData); + switch (rc) { + case 0: break; + + case BLE_HS_EBUSY: log_e("Already advertising"); break; + + case BLE_HS_EMSGSIZE: log_e("Scan data too long"); break; + + default: log_e("Error setting scan response data; rc=%d, %s", rc, BLEUtils::returnCodeToString(rc)); break; + } + } + + if (rc == 0) { + rc = ble_gap_adv_set_fields(&m_advData); + switch (rc) { + case 0: break; + + case BLE_HS_EBUSY: log_e("Already advertising"); break; + + case BLE_HS_EMSGSIZE: log_e("Advertisement data too long"); break; + + default: log_e("Error setting advertisement data; rc=%d, %s", rc, BLEUtils::returnCodeToString(rc)); break; + } + } + + if (m_advData.num_uuids128 > 0) { + free((void *)m_advData.uuids128); + m_advData.uuids128 = nullptr; + m_advData.num_uuids128 = 0; + } + + if (m_advData.num_uuids32 > 0) { + free((void *)m_advData.uuids32); + m_advData.uuids32 = nullptr; + m_advData.num_uuids32 = 0; + } + + if (m_advData.num_uuids16 > 0) { + free((void *)m_advData.uuids16); + m_advData.uuids16 = nullptr; + m_advData.num_uuids16 = 0; + } + + if (rc != 0) { + return false; + } + + m_advDataSet = true; + } + + rc = ble_gap_adv_start( + BLEDevice::m_ownAddrType, NULL, duration, &m_advParams, (pServer != nullptr) ? BLEServer::handleGATTServerEvent : BLEAdvertising::handleGAPEvent, + (pServer != nullptr) ? (void *)pServer : (void *)this + ); + + switch (rc) { + case 0: break; + + case BLE_HS_EINVAL: log_e("Unable to advertise - Duration too long"); break; + + case BLE_HS_EPREEMPTED: log_e("Unable to advertise - busy"); break; + + case BLE_HS_ETIMEOUT_HCI: + case BLE_HS_EOS: + case BLE_HS_ECONTROLLER: + case BLE_HS_ENOTSYNCED: log_e("Unable to advertise - Host Reset"); break; + + default: log_e("Error enabling advertising; rc=%d, %s", rc, BLEUtils::returnCodeToString(rc)); break; + } + + log_d("<< Advertising start"); + return (rc == 0); +} // start + +/** + * @brief Stop advertising. + */ +bool BLEAdvertising::stop() { + log_d(">> stop"); + + int rc = ble_gap_adv_stop(); + if (rc != 0 && rc != BLE_HS_EALREADY) { + log_e("ble_gap_adv_stop rc=%d %s", rc, BLEUtils::returnCodeToString(rc)); + return false; + } + + log_d("<< stop"); + return true; +} // stop + +#endif /* CONFIG_NIMBLE_ENABLED */ + +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ diff --git a/libraries/BLE/src/BLEAdvertising.h b/libraries/BLE/src/BLEAdvertising.h index 1e573ac814f..cf68768ec91 100644 --- a/libraries/BLE/src/BLEAdvertising.h +++ b/libraries/BLE/src/BLEAdvertising.h @@ -3,6 +3,10 @@ * * Created on: Jun 21, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ #ifndef COMPONENTS_CPP_UTILS_BLEADVERTISING_H_ @@ -11,20 +15,97 @@ #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) -#include +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ + #include "BLEUUID.h" #include #include "RTOS.h" +#include "BLEUtils.h" + +/*************************************************************************** + * Bluedroid includes * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) +#include +#endif + +/*************************************************************************** + * NimBLE includes and definitions * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +#include +#include +#include + +#define ESP_BLE_ADV_DATA_LEN_MAX BLE_HS_ADV_MAX_SZ +#define ESP_BLE_ADV_FLAG_LIMIT_DISC (0x01 << 0) +#define ESP_BLE_ADV_FLAG_GEN_DISC (0x01 << 1) +#define ESP_BLE_ADV_FLAG_BREDR_NOT_SPT (0x01 << 2) +#define ESP_BLE_ADV_FLAG_DMT_CONTROLLER_SPT (0x01 << 3) +#define ESP_BLE_ADV_FLAG_DMT_HOST_SPT (0x01 << 4) +#define ESP_BLE_ADV_FLAG_NON_LIMIT_DISC (0x00) +#endif /* CONFIG_NIMBLE_ENABLED */ + +/*************************************************************************** + * NimBLE types * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +typedef enum { + ESP_BLE_AD_TYPE_FLAG = 0x01, + ESP_BLE_AD_TYPE_16SRV_PART = 0x02, + ESP_BLE_AD_TYPE_16SRV_CMPL = 0x03, + ESP_BLE_AD_TYPE_32SRV_PART = 0x04, + ESP_BLE_AD_TYPE_32SRV_CMPL = 0x05, + ESP_BLE_AD_TYPE_128SRV_PART = 0x06, + ESP_BLE_AD_TYPE_128SRV_CMPL = 0x07, + ESP_BLE_AD_TYPE_NAME_SHORT = 0x08, + ESP_BLE_AD_TYPE_NAME_CMPL = 0x09, + ESP_BLE_AD_TYPE_TX_PWR = 0x0A, + ESP_BLE_AD_TYPE_DEV_CLASS = 0x0D, + ESP_BLE_AD_TYPE_SM_TK = 0x10, + ESP_BLE_AD_TYPE_SM_OOB_FLAG = 0x11, + ESP_BLE_AD_TYPE_INT_RANGE = 0x12, + ESP_BLE_AD_TYPE_SOL_SRV_UUID = 0x14, + ESP_BLE_AD_TYPE_128SOL_SRV_UUID = 0x15, + ESP_BLE_AD_TYPE_SERVICE_DATA = 0x16, + ESP_BLE_AD_TYPE_PUBLIC_TARGET = 0x17, + ESP_BLE_AD_TYPE_RANDOM_TARGET = 0x18, + ESP_BLE_AD_TYPE_APPEARANCE = 0x19, + ESP_BLE_AD_TYPE_ADV_INT = 0x1A, + ESP_BLE_AD_TYPE_LE_DEV_ADDR = 0x1b, + ESP_BLE_AD_TYPE_LE_ROLE = 0x1c, + ESP_BLE_AD_TYPE_SPAIR_C256 = 0x1d, + ESP_BLE_AD_TYPE_SPAIR_R256 = 0x1e, + ESP_BLE_AD_TYPE_32SOL_SRV_UUID = 0x1f, + ESP_BLE_AD_TYPE_32SERVICE_DATA = 0x20, + ESP_BLE_AD_TYPE_128SERVICE_DATA = 0x21, + ESP_BLE_AD_TYPE_LE_SECURE_CONFIRM = 0x22, + ESP_BLE_AD_TYPE_LE_SECURE_RANDOM = 0x23, + ESP_BLE_AD_TYPE_URI = 0x24, + ESP_BLE_AD_TYPE_INDOOR_POSITION = 0x25, + ESP_BLE_AD_TYPE_TRANS_DISC_DATA = 0x26, + ESP_BLE_AD_TYPE_LE_SUPPORT_FEATURE = 0x27, + ESP_BLE_AD_TYPE_CHAN_MAP_UPDATE = 0x28, + ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE = 0xFF, +} esp_ble_adv_data_type; +#endif /** * @brief Advertisement data set by the programmer to be published by the %BLE server. */ class BLEAdvertisementData { - // Only a subset of the possible BLE architected advertisement fields are currently exposed. Others will - // be exposed on demand/request or as time permits. - // public: + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ + void setAppearance(uint16_t appearance); void setCompleteServices(BLEUUID uuid); void setFlags(uint8_t); @@ -33,85 +114,144 @@ class BLEAdvertisementData { void setPartialServices(BLEUUID uuid); void setServiceData(BLEUUID uuid, String data); void setShortName(String name); - void addData(String data); // Add data to the payload. - String getPayload(); // Retrieve the current advert payload. + void setPreferredParams(uint16_t min, uint16_t max); + void addTxPower(); + void addData(String data); + void addData(char *data, size_t length); + String getPayload(); private: friend class BLEAdvertising; - String m_payload; // The payload of the advertisement. -}; // BLEAdvertisementData + + /*************************************************************************** + * Common private declarations * + ***************************************************************************/ + + String m_payload; +}; /** * @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: + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ + BLEAdvertising(); void addServiceUUID(BLEUUID serviceUUID); void addServiceUUID(const char *serviceUUID); bool removeServiceUUID(int index); bool removeServiceUUID(BLEUUID serviceUUID); bool removeServiceUUID(const char *serviceUUID); - bool start(); bool stop(); + void reset(); void setAppearance(uint16_t appearance); - void setAdvertisementType(esp_ble_adv_type_t adv_type); - void setAdvertisementChannelMap(esp_ble_adv_channel_t channel_map); + void setAdvertisementType(uint8_t adv_type); void setMaxInterval(uint16_t maxinterval); void setMinInterval(uint16_t mininterval); bool setAdvertisementData(BLEAdvertisementData &advertisementData); void setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly); bool setScanResponseData(BLEAdvertisementData &advertisementData); - void setPrivateAddress(esp_ble_addr_type_t type = BLE_ADDR_TYPE_RANDOM); - bool setDeviceAddress(esp_bd_addr_t addr, esp_ble_addr_type_t type = BLE_ADDR_TYPE_RANDOM); - - void handleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); void setMinPreferred(uint16_t); void setMaxPreferred(uint16_t); void setScanResponse(bool); + /*************************************************************************** + * Bluedroid public declarations * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + void setPrivateAddress(esp_ble_addr_type_t type = BLE_ADDR_TYPE_RANDOM); + bool setDeviceAddress(esp_bd_addr_t addr, esp_ble_addr_type_t type = BLE_ADDR_TYPE_RANDOM); + void setAdvertisementChannelMap(esp_ble_adv_channel_t channel_map); + void handleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); + bool start(); +#endif + + /*************************************************************************** + * NimBLE public declarations * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + void setName(String name); + void addTxPower(); + void advCompleteCB(); + bool isAdvertising(); + void onHostSync(); + bool start(uint32_t duration = 0, void (*advCompleteCB)(BLEAdvertising *pAdv) = nullptr); + static int handleGAPEvent(ble_gap_event *event, void *arg); +#endif + private: - esp_ble_adv_data_t m_advData; - esp_ble_adv_data_t m_scanRespData; // Used for configuration of scan response data when m_scanResp is true - esp_ble_adv_params_t m_advParams; + /*************************************************************************** + * Common private properties * + ***************************************************************************/ + std::vector m_serviceUUIDs; - bool m_customAdvData = false; // Are we using custom advertising data? - bool m_customScanResponseData = false; // Are we using custom scan response data? + bool m_customAdvData = false; + bool m_customScanResponseData = false; FreeRTOS::Semaphore m_semaphoreSetAdv = FreeRTOS::Semaphore("startAdvert"); bool m_scanResp = true; + + /*************************************************************************** + * Bluedroid private properties * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + esp_ble_adv_data_t m_advData; + esp_ble_adv_data_t m_scanRespData; + esp_ble_adv_params_t m_advParams; +#endif + + /*************************************************************************** + * NimBLE private properties * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + ble_hs_adv_fields m_advData; + ble_hs_adv_fields m_scanData; + ble_gap_adv_params m_advParams; + bool m_advDataSet; + void (*m_advCompCB)(BLEAdvertising *pAdv); + uint8_t m_slaveItvl[4]; + uint32_t m_duration; + String m_name; +#endif }; -#ifdef SOC_BLE_50_SUPPORTED +/*************************************************************************** + * Bluedroid 5.0 specific classes * + ***************************************************************************/ +#if defined(SOC_BLE_50_SUPPORTED) && defined(CONFIG_BLUEDROID_ENABLED) class BLEMultiAdvertising { -private: - esp_ble_gap_ext_adv_params_t *params_arrays; - esp_ble_gap_ext_adv_t *ext_adv; - uint8_t count; - public: BLEMultiAdvertising(uint8_t num = 1); ~BLEMultiAdvertising() {} - bool setAdvertisingParams(uint8_t instance, const esp_ble_gap_ext_adv_params_t *params); bool setAdvertisingData(uint8_t instance, uint16_t length, const uint8_t *data); bool setScanRspData(uint8_t instance, uint16_t length, const uint8_t *data); bool start(); bool start(uint8_t num, uint8_t from); void setDuration(uint8_t instance, int duration = 0, int max_events = 0); - bool setInstanceAddress(uint8_t instance, esp_bd_addr_t rand_addr); + bool setInstanceAddress(uint8_t instance, uint8_t *rand_addr); bool stop(uint8_t num_adv, const uint8_t *ext_adv_inst); bool remove(uint8_t instance); bool clear(); - bool setPeriodicAdvertisingParams(uint8_t instance, const esp_ble_gap_periodic_adv_params_t *params); bool setPeriodicAdvertisingData(uint8_t instance, uint16_t length, const uint8_t *data); bool startPeriodicAdvertising(uint8_t instance); -}; + bool setAdvertisingParams(uint8_t instance, const esp_ble_gap_ext_adv_params_t *params); + bool setPeriodicAdvertisingParams(uint8_t instance, const esp_ble_gap_periodic_adv_params_t *params); -#endif // SOC_BLE_50_SUPPORTED +private: + esp_ble_gap_ext_adv_params_t *params_arrays; + esp_ble_gap_ext_adv_t *ext_adv; + uint8_t count; +}; +#endif -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ #endif /* COMPONENTS_CPP_UTILS_BLEADVERTISING_H_ */ diff --git a/libraries/BLE/src/BLEBeacon.cpp b/libraries/BLE/src/BLEBeacon.cpp index 43366a7b2d9..0a6c6b05258 100644 --- a/libraries/BLE/src/BLEBeacon.cpp +++ b/libraries/BLE/src/BLEBeacon.cpp @@ -3,17 +3,31 @@ * * Created on: Jan 4, 2018 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ + #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes and definitions * + ***************************************************************************/ + #include "BLEBeacon.h" #include "esp32-hal-log.h" #define ENDIAN_CHANGE_U16(x) ((((x) & 0xFF00) >> 8) + (((x) & 0xFF) << 8)) +/*************************************************************************** + * Common functions * + ***************************************************************************/ + BLEBeacon::BLEBeacon() { m_beaconData.manufacturerId = 0x4c00; m_beaconData.subType = 0x02; @@ -22,11 +36,11 @@ BLEBeacon::BLEBeacon() { m_beaconData.minor = 0; m_beaconData.signalPower = 0; memset(m_beaconData.proximityUUID, 0, sizeof(m_beaconData.proximityUUID)); -} // BLEBeacon +} String BLEBeacon::getData() { return String((char *)&m_beaconData, sizeof(m_beaconData)); -} // getData +} uint16_t BLEBeacon::getMajor() { return m_beaconData.major; @@ -48,37 +62,38 @@ int8_t BLEBeacon::getSignalPower() { return m_beaconData.signalPower; } -/** - * Set the raw data for the beacon record. - */ void BLEBeacon::setData(String data) { if (data.length() != sizeof(m_beaconData)) { log_e("Unable to set the data ... length passed in was %d and expected %d", data.length(), sizeof(m_beaconData)); return; } memcpy(&m_beaconData, data.c_str(), sizeof(m_beaconData)); -} // setData +} void BLEBeacon::setMajor(uint16_t major) { m_beaconData.major = ENDIAN_CHANGE_U16(major); -} // setMajor +} void BLEBeacon::setManufacturerId(uint16_t manufacturerId) { m_beaconData.manufacturerId = ENDIAN_CHANGE_U16(manufacturerId); -} // setManufacturerId +} void BLEBeacon::setMinor(uint16_t minor) { m_beaconData.minor = ENDIAN_CHANGE_U16(minor); -} // setMinior - -void BLEBeacon::setProximityUUID(BLEUUID uuid) { - uuid = uuid.to128(); - memcpy(m_beaconData.proximityUUID, uuid.getNative()->uuid.uuid128, 16); -} // setProximityUUID +} void BLEBeacon::setSignalPower(int8_t signalPower) { m_beaconData.signalPower = signalPower; -} // setSignalPower +} +void BLEBeacon::setProximityUUID(BLEUUID uuid) { + uuid = uuid.to128(); +#if defined(CONFIG_BLUEDROID_ENABLED) + memcpy(m_beaconData.proximityUUID, uuid.getNative()->uuid.uuid128, 16); +#elif defined(CONFIG_NIMBLE_ENABLED) + memcpy(m_beaconData.proximityUUID, uuid.getNative()->u128.value, 16); #endif +} + +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ diff --git a/libraries/BLE/src/BLEBeacon.h b/libraries/BLE/src/BLEBeacon.h index dcc41aafeb4..e0eaddda6e4 100644 --- a/libraries/BLE/src/BLEBeacon.h +++ b/libraries/BLE/src/BLEBeacon.h @@ -3,6 +3,10 @@ * * Created on: Jan 4, 2018 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ #ifndef COMPONENTS_CPP_UTILS_BLEBEACON_H_ @@ -10,6 +14,13 @@ #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED +#include "sdkconfig.h" +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ + #include "BLEUUID.h" /** * @brief Representation of a beacon. @@ -18,6 +29,10 @@ */ class BLEBeacon { private: + /*************************************************************************** + * Common types * + ***************************************************************************/ + struct { uint16_t manufacturerId; uint8_t subType; @@ -29,6 +44,10 @@ class BLEBeacon { } __attribute__((packed)) m_beaconData; public: + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ + BLEBeacon(); String getData(); uint16_t getMajor(); @@ -44,5 +63,6 @@ class BLEBeacon { void setSignalPower(int8_t signalPower); }; // BLEBeacon +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ #endif /* COMPONENTS_CPP_UTILS_BLEBEACON_H_ */ diff --git a/libraries/BLE/src/BLECharacteristic.cpp b/libraries/BLE/src/BLECharacteristic.cpp index b03d524a6a5..5411cd401ad 100644 --- a/libraries/BLE/src/BLECharacteristic.cpp +++ b/libraries/BLE/src/BLECharacteristic.cpp @@ -3,12 +3,22 @@ * * Created on: Jun 22, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ + #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ + #include #include #include @@ -23,10 +33,31 @@ #include "GeneralUtils.h" #include "esp32-hal-log.h" -#define NULL_HANDLE (0xffff) +/*************************************************************************** + * Common definitions * + ***************************************************************************/ + +#define NULL_HANDLE (0xffff) + +/*************************************************************************** + * NimBLE definitions * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +#define NIMBLE_SUB_NOTIFY 0x0001 +#define NIMBLE_SUB_INDICATE 0x0002 +#endif + +/*************************************************************************** + * Common global variables * + ***************************************************************************/ static BLECharacteristicCallbacks defaultCallback; //null-object-pattern +/*************************************************************************** + * Common functions * + ***************************************************************************/ + /** * @brief Construct a characteristic * @param [in] uuid - UUID (const char*) for the characteristic. @@ -42,15 +73,23 @@ BLECharacteristic::BLECharacteristic(const char *uuid, uint32_t properties) : BL BLECharacteristic::BLECharacteristic(BLEUUID uuid, uint32_t properties) { m_bleUUID = uuid; m_handle = NULL_HANDLE; - m_properties = (esp_gatt_char_prop_t)0; m_pCallbacks = &defaultCallback; - 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); +#ifdef CONFIG_BLUEDROID_ENABLED + m_properties = 0; + setBroadcastProperty((properties & BLECharacteristic::PROPERTY_BROADCAST) != 0); + setReadProperty((properties & BLECharacteristic::PROPERTY_READ) != 0); + setWriteProperty((properties & BLECharacteristic::PROPERTY_WRITE) != 0); + setNotifyProperty((properties & BLECharacteristic::PROPERTY_NOTIFY) != 0); + setIndicateProperty((properties & BLECharacteristic::PROPERTY_INDICATE) != 0); + setWriteNoResponseProperty((properties & BLECharacteristic::PROPERTY_WRITE_NR) != 0); +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + m_readMux = portMUX_INITIALIZER_UNLOCKED; + m_removed = 0; + m_properties = properties; +#endif } // BLECharacteristic /** @@ -66,51 +105,20 @@ BLECharacteristic::~BLECharacteristic() { * @return N/A. */ void BLECharacteristic::addDescriptor(BLEDescriptor *pDescriptor) { +#ifdef CONFIG_NIMBLE_ENABLED + if (pDescriptor->getUUID() == BLEUUID(uint16_t(0x2902))) { + log_i("NimBLE automatically creates the 0x2902 descriptor if a characteristic has a notification or indication property assigned to it.\n" + "You should check the characteristic properties for notification or indication rather than adding the descriptor manually.\n" + "This will be removed in a future version of the library."); + pDescriptor->executeCreate(this); + return; + } +#endif log_v(">> addDescriptor(): Adding %s to %s", pDescriptor->toString().c_str(), toString().c_str()); m_descriptorMap.setByUUID(pDescriptor->getUUID(), pDescriptor); log_v("<< 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) { - log_v(">> executeCreate()"); - - if (m_handle != NULL_HANDLE) { - log_e("Characteristic already has a handle."); - return; - } - - m_pService = pService; // Save the service to which this characteristic belongs. - - log_d("Registering characteristic (esp_ble_gatts_add_char): uuid: %s, service: %s", getUUID().toString().c_str(), m_pService->toString().c_str()); - - esp_attr_control_t control; - control.auto_rsp = ESP_GATT_RSP_BY_APP; - - m_semaphoreCreateEvt.take("executeCreate"); - esp_err_t errRc = ::esp_ble_gatts_add_char( - m_pService->getHandle(), getUUID().getNative(), static_cast(m_permissions), getProperties(), nullptr, - &control - ); // Whether to auto respond or not. - - if (errRc != ESP_OK) { - log_e("<< esp_ble_gatts_add_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - m_semaphoreCreateEvt.wait("executeCreate"); - - BLEDescriptor *pDescriptor = m_descriptorMap.getFirst(); - while (pDescriptor != nullptr) { - pDescriptor->executeCreate(this); - pDescriptor = m_descriptorMap.getNext(); - } // End while - - log_v("<< 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. @@ -137,8 +145,10 @@ uint16_t BLECharacteristic::getHandle() { return m_handle; } // getHandle -void BLECharacteristic::setAccessPermissions(esp_gatt_perm_t perm) { +void BLECharacteristic::setAccessPermissions(uint8_t perm) { +#ifdef CONFIG_BLUEDROID_ENABLED m_permissions = perm; +#endif } esp_gatt_char_prop_t BLECharacteristic::getProperties() { @@ -185,390 +195,76 @@ size_t BLECharacteristic::getLength() { } // getLength /** - * Handle a GATT server event. + * @brief Register a new characteristic with the ESP runtime. + * @param [in] pService The service with which to associate this characteristic. */ -void BLECharacteristic::handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { - log_v(">> handleGATTServerEvent: %s", BLEUtils::gattServerEventTypeToString(event).c_str()); +void BLECharacteristic::executeCreate(BLEService *pService) { + log_v(">> executeCreate()"); - switch (event) { - // Events handled: - // - // ESP_GATTS_ADD_CHAR_EVT - // ESP_GATTS_CONF_EVT - // ESP_GATTS_CONNECT_EVT - // ESP_GATTS_DISCONNECT_EVT - // ESP_GATTS_EXEC_WRITE_EVT - // ESP_GATTS_READ_EVT - // ESP_GATTS_WRITE_EVT + if (m_handle != NULL_HANDLE) { + log_e("Characteristic already has a handle."); + return; + } - // - // 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 - Either ESP_GATT_PREP_WRITE_EXEC or ESP_GATT_PREP_WRITE_CANCEL - // - case ESP_GATTS_EXEC_WRITE_EVT: - { - if (m_writeEvt) { - m_writeEvt = false; - if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) { - m_value.commit(); - // Invoke the onWrite callback handler. - m_pCallbacks->onWrite(this, param); - } 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) { - log_e("esp_ble_gatts_send_response: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - } - } - break; - } // ESP_GATTS_EXEC_WRITE_EVT + m_pService = pService; // Save the service to which this characteristic belongs. - // 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 (getHandle() == param->add_char.attr_handle) { - // we have created characteristic, now we can create descriptors - // BLEDescriptor* pDescriptor = m_descriptorMap.getFirst(); - // while (pDescriptor != nullptr) { - // pDescriptor->executeCreate(this); - // pDescriptor = m_descriptorMap.getNext(); - // } // End while - m_semaphoreCreateEvt.give(); - } - break; - } // ESP_GATTS_ADD_CHAR_EVT +#ifdef CONFIG_BLUEDROID_ENABLED + log_d("Registering characteristic (esp_ble_gatts_add_char): uuid: %s, service: %s", getUUID().toString().c_str(), m_pService->toString().c_str()); - // 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); - m_writeEvt = true; - } else { - setValue(param->write.value, param->write.len); - } + esp_attr_control_t control; + control.auto_rsp = ESP_GATT_RSP_BY_APP; - log_d(" - Response to write event: New value: handle: %.2x, uuid: %s", getHandle(), getUUID().toString().c_str()); + m_semaphoreCreateEvt.take("executeCreate"); + esp_err_t errRc = ::esp_ble_gatts_add_char( + m_pService->getHandle(), getUUID().getNative(), static_cast(m_permissions), getProperties(), nullptr, + &control + ); // Whether to auto respond or not. + + if (errRc != ESP_OK) { + log_e("<< esp_ble_gatts_add_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + m_semaphoreCreateEvt.wait("executeCreate"); -// The call to BLEUtils::buildHexData() doesn't output anything if the log level is not -// "DEBUG". As it is quite CPU intensive, it is much better to not call it if not needed. -#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG - char *pHexData = BLEUtils::buildHexData(nullptr, param->write.value, param->write.len); - log_d(" - Data: length: %d, data: %s", param->write.len, pHexData); - free(pHexData); #endif - if (param->write.need_rsp) { - esp_gatt_rsp_t rsp; + BLEDescriptor *pDescriptor = m_descriptorMap.getFirst(); + while (pDescriptor != nullptr) { + pDescriptor->executeCreate(this); + pDescriptor = m_descriptorMap.getNext(); + } // End while - 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); + log_v("<< executeCreate"); +} // executeCreate - 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) { - log_e("esp_ble_gatts_send_response: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - } - } // Response needed +/** + * @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() { - if (param->write.is_prep != true) { - // Invoke the onWrite callback handler. - m_pCallbacks->onWrite(this, param); - } - } // Match on handles. - break; - } // ESP_GATTS_WRITE_EVT + log_v(">> indicate: length: %d", m_value.getValue().length()); + notify(false); + log_v("<< indicate"); +} // indicate - // 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: - { - if (param->read.handle == m_handle) { - - // 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 units 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 that's 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. - // - - // get mtu for peer device that we are sending read request to - uint16_t maxOffset = getService()->getServer()->getPeerMTU(param->read.conn_id) - 1; - log_d("mtu value: %d", maxOffset); - if (param->read.need_rsp) { - log_d("Sending a response (esp_ble_gatts_send_response)"); - esp_gatt_rsp_t rsp; - - if (param->read.is_long) { - String value = m_value.getValue(); - - if (value.length() - m_value.getReadOffset() < maxOffset) { - // 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.c_str() + rsp.attr_value.offset, rsp.attr_value.len); - m_value.setReadOffset(0); - } else { - // There will be more to come. - rsp.attr_value.len = maxOffset; - rsp.attr_value.offset = m_value.getReadOffset(); - memcpy(rsp.attr_value.value, value.c_str() + rsp.attr_value.offset, rsp.attr_value.len); - m_value.setReadOffset(rsp.attr_value.offset + maxOffset); - } - } else { // read.is_long == false - - // If is.long is false then this is the first (or only) request to read data, so invoke the callback - // Invoke the read callback. - m_pCallbacks->onRead(this, param); - - String value = m_value.getValue(); - - if (value.length() + 1 > maxOffset) { - // Too big for a single shot entry. - m_value.setReadOffset(maxOffset); - rsp.attr_value.len = maxOffset; - rsp.attr_value.offset = 0; - memcpy(rsp.attr_value.value, value.c_str(), 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.c_str(), rsp.attr_value.len); - } - } - rsp.attr_value.handle = param->read.handle; - rsp.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE; - -// The call to BLEUtils::buildHexData() doesn't output anything if the log level is not -// "DEBUG". As it is quite CPU intensive, it is much better to not call it if not needed. -#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG - char *pHexData = BLEUtils::buildHexData(nullptr, rsp.attr_value.value, rsp.attr_value.len); - log_d(" - Data: length=%d, data=%s, offset=%d", rsp.attr_value.len, pHexData, rsp.attr_value.offset); - free(pHexData); -#endif - - 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) { - log_e("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: - { - // log_d("m_handle = %d, conf->handle = %d", m_handle, param->conf.handle); - if (param->conf.conn_id - == getService()->getServer()->getConnId()) { // && param->conf.handle == m_handle) // bug in esp-idf and not implemented in arduino yet - m_semaphoreConfEvt.give(param->conf.status); - } - break; - } - - case ESP_GATTS_CONNECT_EVT: - { - break; - } - - case ESP_GATTS_DISCONNECT_EVT: - { - m_semaphoreConfEvt.give(); - break; - } - - default: - { - break; - } // default - - } // switch event - - // Give each of the descriptors associated with this characteristic the opportunity to handle the - // event. - - m_descriptorMap.handleGATTServerEvent(event, gatts_if, param); - log_v("<< handleGATTServerEvent"); -} // 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() { - - log_v(">> indicate: length: %d", m_value.getValue().length()); - notify(false); - log_v("<< 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(bool is_notification) { - log_v(">> notify: length: %d", m_value.getValue().length()); - - assert(getService() != nullptr); - assert(getService()->getServer() != nullptr); - - m_pCallbacks->onNotify(this); // Invoke the notify callback. - - // GeneralUtils::hexDump() doesn't output anything if the log level is not - // "VERBOSE". Additionally, it is very CPU intensive, even when it doesn't - // output anything! So it is much better to *not* call it at all if not needed. - // In a simple program which calls BLECharacteristic::notify() every 50 ms, - // the performance gain of this little optimization is 37% in release mode - // (-O3) and 57% in debug mode. - // Of course, the "#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE" guard - // could also be put inside the GeneralUtils::hexDump() function itself. But - // it's better to put it here also, as it is clearer (indicating a verbose log - // thing) and it allows to remove the "m_value.getValue().c_str()" call, which - // is, in itself, quite CPU intensive. -#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE - GeneralUtils::hexDump((uint8_t *)m_value.getValue().c_str(), m_value.getValue().length()); -#endif - - if (getService()->getServer()->getConnectedCount() == 0) { - log_v("<< notify: No connected clients."); - m_pCallbacks->onStatus(this, BLECharacteristicCallbacks::Status::ERROR_NO_CLIENT, 0); - 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 (is_notification) { - if (p2902 != nullptr && !p2902->getNotifications()) { - log_v("<< notifications disabled; ignoring"); - m_pCallbacks->onStatus(this, BLECharacteristicCallbacks::Status::ERROR_NOTIFY_DISABLED, 0); // Invoke the notify callback. - return; - } - } else { - if (p2902 != nullptr && !p2902->getIndications()) { - log_v("<< indications disabled; ignoring"); - m_pCallbacks->onStatus(this, BLECharacteristicCallbacks::Status::ERROR_INDICATE_DISABLED, 0); // Invoke the notify callback. - return; - } - } - for (auto &myPair : getService()->getServer()->getPeerDevices(false)) { - uint16_t _mtu = (myPair.second.mtu); - if (m_value.getValue().length() > _mtu - 3) { - log_w("- Truncating to %d bytes (maximum notify size)", _mtu - 3); - } - - size_t length = m_value.getValue().length(); - if (!is_notification) { // is indication - m_semaphoreConfEvt.take("indicate"); - } - esp_err_t errRc = ::esp_ble_gatts_send_indicate( - getService()->getServer()->getGattsIf(), myPair.first, getHandle(), length, (uint8_t *)m_value.getValue().c_str(), !is_notification - ); // The need_confirm = false makes this a notify. - if (errRc != ESP_OK) { - log_e("<< esp_ble_gatts_send_ %s: rc=%d %s", is_notification ? "notify" : "indicate", errRc, GeneralUtils::errorToString(errRc)); - m_semaphoreConfEvt.give(); - m_pCallbacks->onStatus(this, BLECharacteristicCallbacks::Status::ERROR_GATT, errRc); // Invoke the notify callback. - return; - } - if (!is_notification) { // is indication - if (!m_semaphoreConfEvt.timedWait("indicate", indicationTimeout)) { - m_pCallbacks->onStatus(this, BLECharacteristicCallbacks::Status::ERROR_INDICATE_TIMEOUT, 0); // Invoke the notify callback. - } else { - auto code = (esp_gatt_status_t)m_semaphoreConfEvt.value(); - if (code == ESP_GATT_OK) { - m_pCallbacks->onStatus(this, BLECharacteristicCallbacks::Status::SUCCESS_INDICATE, code); // Invoke the notify callback. - } else { - m_pCallbacks->onStatus(this, BLECharacteristicCallbacks::Status::ERROR_INDICATE_FAILURE, code); - } - } - } else { - m_pCallbacks->onStatus(this, BLECharacteristicCallbacks::Status::SUCCESS_NOTIFY, 0); // Invoke the notify callback. - } - } - log_v("<< 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) { - //log_d("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 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) { + //log_d("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. @@ -595,9 +291,15 @@ void BLECharacteristic::setCallbacks(BLECharacteristicCallbacks *pCallbacks) { * @param [in] handle The handle associated with this characteristic. */ void BLECharacteristic::setHandle(uint16_t handle) { +#if defined(CONFIG_BLUEDROID_ENABLED) log_v(">> setHandle: handle=0x%.2x, characteristic uuid=%s", handle, getUUID().toString().c_str()); m_handle = handle; log_v("<< setHandle"); +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + log_w("NimBLE does not support manually setting the handle of a characteristic. Ignoring request."); +#endif } // setHandle /** @@ -767,19 +469,12 @@ String BLECharacteristic::toString() { BLECharacteristicCallbacks::~BLECharacteristicCallbacks() {} -void BLECharacteristicCallbacks::onRead(BLECharacteristic *pCharacteristic, esp_ble_gatts_cb_param_t *param) { - onRead(pCharacteristic); -} // onRead - +// Common callbacks void BLECharacteristicCallbacks::onRead(BLECharacteristic *pCharacteristic) { log_d(">> onRead: default"); log_d("<< onRead"); } // onRead -void BLECharacteristicCallbacks::onWrite(BLECharacteristic *pCharacteristic, esp_ble_gatts_cb_param_t *param) { - onWrite(pCharacteristic); -} // onWrite - void BLECharacteristicCallbacks::onWrite(BLECharacteristic *pCharacteristic) { log_d(">> onWrite: default"); log_d("<< onWrite"); @@ -795,5 +490,651 @@ void BLECharacteristicCallbacks::onStatus(BLECharacteristic *pCharacteristic, St log_d("<< onStatus"); } // onStatus -#endif /* CONFIG_BLUEDROID_ENABLED */ +/*************************************************************************** + * Bluedroid functions * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + +/** + * Handle a GATT server event. + */ +void BLECharacteristic::handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { + log_v(">> handleGATTServerEvent: %s", BLEUtils::gattServerEventTypeToString(event).c_str()); + + switch (event) { + // Events handled: + // + // ESP_GATTS_ADD_CHAR_EVT + // ESP_GATTS_CONF_EVT + // ESP_GATTS_CONNECT_EVT + // ESP_GATTS_DISCONNECT_EVT + // ESP_GATTS_EXEC_WRITE_EVT + // ESP_GATTS_READ_EVT + // ESP_GATTS_WRITE_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 - Either ESP_GATT_PREP_WRITE_EXEC or ESP_GATT_PREP_WRITE_CANCEL + // + case ESP_GATTS_EXEC_WRITE_EVT: + { + if (m_writeEvt) { + m_writeEvt = false; + if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) { + m_value.commit(); + // Invoke the onWrite callback handler. + m_pCallbacks->onWrite(this, param); + } 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) { + log_e("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 (getHandle() == param->add_char.attr_handle) { + // we have created characteristic, now we can create descriptors + // BLEDescriptor* pDescriptor = m_descriptorMap.getFirst(); + // while (pDescriptor != nullptr) { + // pDescriptor->executeCreate(this); + // pDescriptor = m_descriptorMap.getNext(); + // } // End while + 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); + m_writeEvt = true; + } else { + setValue(param->write.value, param->write.len); + } + + log_d(" - Response to write event: New value: handle: %.2x, uuid: %s", getHandle(), getUUID().toString().c_str()); + +// The call to BLEUtils::buildHexData() doesn't output anything if the log level is not +// "DEBUG". As it is quite CPU intensive, it is much better to not call it if not needed. +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG + char *pHexData = BLEUtils::buildHexData(nullptr, param->write.value, param->write.len); + log_d(" - Data: length: %d, data: %s", param->write.len, pHexData); + free(pHexData); +#endif + + 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) { + log_e("esp_ble_gatts_send_response: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + } // Response needed + + if (param->write.is_prep != true) { + // Invoke the onWrite callback handler. + m_pCallbacks->onWrite(this, param); + } + } // 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: + { + if (param->read.handle == m_handle) { + + // 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 units 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 that's 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. + // + + // get mtu for peer device that we are sending read request to + uint16_t maxOffset = getService()->getServer()->getPeerMTU(param->read.conn_id) - 1; + log_d("mtu value: %d", maxOffset); + if (param->read.need_rsp) { + log_d("Sending a response (esp_ble_gatts_send_response)"); + esp_gatt_rsp_t rsp; + + if (param->read.is_long) { + String value = m_value.getValue(); + + if (value.length() - m_value.getReadOffset() < maxOffset) { + // 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.c_str() + rsp.attr_value.offset, rsp.attr_value.len); + m_value.setReadOffset(0); + } else { + // There will be more to come. + rsp.attr_value.len = maxOffset; + rsp.attr_value.offset = m_value.getReadOffset(); + memcpy(rsp.attr_value.value, value.c_str() + rsp.attr_value.offset, rsp.attr_value.len); + m_value.setReadOffset(rsp.attr_value.offset + maxOffset); + } + } else { // read.is_long == false + + // If is.long is false then this is the first (or only) request to read data, so invoke the callback + // Invoke the read callback. + m_pCallbacks->onRead(this, param); + + String value = m_value.getValue(); + + if (value.length() + 1 > maxOffset) { + // Too big for a single shot entry. + m_value.setReadOffset(maxOffset); + rsp.attr_value.len = maxOffset; + rsp.attr_value.offset = 0; + memcpy(rsp.attr_value.value, value.c_str(), 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.c_str(), rsp.attr_value.len); + } + } + rsp.attr_value.handle = param->read.handle; + rsp.attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE; + +// The call to BLEUtils::buildHexData() doesn't output anything if the log level is not +// "DEBUG". As it is quite CPU intensive, it is much better to not call it if not needed. +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG + char *pHexData = BLEUtils::buildHexData(nullptr, rsp.attr_value.value, rsp.attr_value.len); + log_d(" - Data: length=%d, data=%s, offset=%d", rsp.attr_value.len, pHexData, rsp.attr_value.offset); + free(pHexData); +#endif + + 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) { + log_e("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: + { + // log_d("m_handle = %d, conf->handle = %d", m_handle, param->conf.handle); + if (param->conf.conn_id + == getService()->getServer()->getConnId()) { // && param->conf.handle == m_handle) // bug in esp-idf and not implemented in arduino yet + m_semaphoreConfEvt.give(param->conf.status); + } + break; + } + + case ESP_GATTS_CONNECT_EVT: + { + break; + } + + case ESP_GATTS_DISCONNECT_EVT: + { + m_semaphoreConfEvt.give(); + break; + } + + default: + { + break; + } // default + + } // switch event + + // Give each of the descriptors associated with this characteristic the opportunity to handle the + // event. + + m_descriptorMap.handleGATTServerEvent(event, gatts_if, param); + log_v("<< handleGATTServerEvent"); +} // handleGATTServerEvent + +/** + * @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(bool is_notification) { + log_v(">> notify: length: %d", m_value.getValue().length()); + + assert(getService() != nullptr); + assert(getService()->getServer() != nullptr); + + m_pCallbacks->onNotify(this); // Invoke the notify callback. + + // GeneralUtils::hexDump() doesn't output anything if the log level is not + // "VERBOSE". Additionally, it is very CPU intensive, even when it doesn't + // output anything! So it is much better to *not* call it at all if not needed. + // In a simple program which calls BLECharacteristic::notify() every 50 ms, + // the performance gain of this little optimization is 37% in release mode + // (-O3) and 57% in debug mode. + // Of course, the "#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE" guard + // could also be put inside the GeneralUtils::hexDump() function itself. But + // it's better to put it here also, as it is clearer (indicating a verbose log + // thing) and it allows to remove the "m_value.getValue().c_str()" call, which + // is, in itself, quite CPU intensive. +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE + GeneralUtils::hexDump((uint8_t *)m_value.getValue().c_str(), m_value.getValue().length()); +#endif + + if (getService()->getServer()->getConnectedCount() == 0) { + log_v("<< notify: No connected clients."); + m_pCallbacks->onStatus(this, BLECharacteristicCallbacks::Status::ERROR_NO_CLIENT, 0); + 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 (is_notification) { + if (p2902 != nullptr && !p2902->getNotifications()) { + log_v("<< notifications disabled; ignoring"); + m_pCallbacks->onStatus(this, BLECharacteristicCallbacks::Status::ERROR_NOTIFY_DISABLED, 0); // Invoke the notify callback. + return; + } + } else { + if (p2902 != nullptr && !p2902->getIndications()) { + log_v("<< indications disabled; ignoring"); + m_pCallbacks->onStatus(this, BLECharacteristicCallbacks::Status::ERROR_INDICATE_DISABLED, 0); // Invoke the notify callback. + return; + } + } + for (auto &myPair : getService()->getServer()->getPeerDevices(false)) { + uint16_t _mtu = (myPair.second.mtu); + if (m_value.getValue().length() > _mtu - 3) { + log_w("- Truncating to %d bytes (maximum notify size)", _mtu - 3); + } + + size_t length = m_value.getValue().length(); + if (!is_notification) { // is indication + m_semaphoreConfEvt.take("indicate"); + } + esp_err_t errRc = ::esp_ble_gatts_send_indicate( + getService()->getServer()->getGattsIf(), myPair.first, getHandle(), length, (uint8_t *)m_value.getValue().c_str(), !is_notification + ); // The need_confirm = false makes this a notify. + if (errRc != ESP_OK) { + log_e("<< esp_ble_gatts_send_ %s: rc=%d %s", is_notification ? "notify" : "indicate", errRc, GeneralUtils::errorToString(errRc)); + m_semaphoreConfEvt.give(); + m_pCallbacks->onStatus(this, BLECharacteristicCallbacks::Status::ERROR_GATT, errRc); // Invoke the notify callback. + return; + } + if (!is_notification) { // is indication + if (!m_semaphoreConfEvt.timedWait("indicate", indicationTimeout)) { + m_pCallbacks->onStatus(this, BLECharacteristicCallbacks::Status::ERROR_INDICATE_TIMEOUT, 0); // Invoke the notify callback. + } else { + auto code = (esp_gatt_status_t)m_semaphoreConfEvt.value(); + if (code == ESP_GATT_OK) { + m_pCallbacks->onStatus(this, BLECharacteristicCallbacks::Status::SUCCESS_INDICATE, code); // Invoke the notify callback. + } else { + m_pCallbacks->onStatus(this, BLECharacteristicCallbacks::Status::ERROR_INDICATE_FAILURE, code); + } + } + } else { + m_pCallbacks->onStatus(this, BLECharacteristicCallbacks::Status::SUCCESS_NOTIFY, 0); // Invoke the notify callback. + } + } + log_v("<< notify"); +} // Notify + +void BLECharacteristicCallbacks::onRead(BLECharacteristic *pCharacteristic, esp_ble_gatts_cb_param_t *param) { + onRead(pCharacteristic); +} // onRead + +void BLECharacteristicCallbacks::onWrite(BLECharacteristic *pCharacteristic, esp_ble_gatts_cb_param_t *param) { + onWrite(pCharacteristic); +} // onWrite + +#endif /* CONFIG_BLUEDROID_ENABLED */ + +/*************************************************************************** + * NimBLE functions * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + +int BLECharacteristic::handleGATTServerEvent(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) { + const ble_uuid_t *uuid; + int rc; + struct ble_gap_conn_desc desc; + BLECharacteristic *pCharacteristic = (BLECharacteristic *)arg; + + log_d("Characteristic %s %s event", pCharacteristic->getUUID().toString().c_str(), ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR ? "Read" : "Write"); + + uuid = ctxt->chr->uuid; + if (ble_uuid_cmp(uuid, &pCharacteristic->getUUID().getNative()->u) == 0) { + switch (ctxt->op) { + case BLE_GATT_ACCESS_OP_READ_CHR: + { + // If the packet header is only 8 bytes this is a follow up of a long read + // so we don't want to call the onRead() callback again. + if (ctxt->om->om_pkthdr_len > 8) { + rc = ble_gap_conn_find(conn_handle, &desc); + assert(rc == 0); + pCharacteristic->m_pCallbacks->onRead(pCharacteristic); + pCharacteristic->m_pCallbacks->onRead(pCharacteristic, &desc); + } + + portENTER_CRITICAL(&pCharacteristic->m_readMux); + rc = os_mbuf_append(ctxt->om, (uint8_t *)pCharacteristic->m_value.getValue().c_str(), pCharacteristic->m_value.getValue().length()); + portEXIT_CRITICAL(&pCharacteristic->m_readMux); + + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } + + case BLE_GATT_ACCESS_OP_WRITE_CHR: + { + if (ctxt->om->om_len > BLE_ATT_ATTR_MAX_LEN) { + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } + + uint8_t buf[BLE_ATT_ATTR_MAX_LEN]; + size_t len = ctxt->om->om_len; + memcpy(buf, ctxt->om->om_data, len); + + os_mbuf *next; + next = SLIST_NEXT(ctxt->om, om_next); + while (next != NULL) { + if ((len + next->om_len) > BLE_ATT_ATTR_MAX_LEN) { + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } + memcpy(&buf[len], next->om_data, next->om_len); + len += next->om_len; + next = SLIST_NEXT(next, om_next); + } + rc = ble_gap_conn_find(conn_handle, &desc); + assert(rc == 0); + pCharacteristic->setValue(buf, len); + pCharacteristic->m_pCallbacks->onWrite(pCharacteristic); + pCharacteristic->m_pCallbacks->onWrite(pCharacteristic, &desc); + + return 0; + } + + default: + break; + } + } + + return BLE_ATT_ERR_UNLIKELY; + + //m_descriptorMap.handleGATTServerEvent(conn_handle, attr_handle, ctxt, arg); +} + +/** + * @brief Set the subscribe status for this characteristic.\n + * This will maintain a vector of subscribed clients and their indicate/notify status. + */ +void BLECharacteristic::setSubscribe(struct ble_gap_event *event) { + ble_gap_conn_desc desc; + if(ble_gap_conn_find(event->subscribe.conn_handle, &desc) != 0) { + return; + } + + uint16_t subVal = 0; + if(event->subscribe.cur_notify > 0 && (m_properties & BLECharacteristic::PROPERTY_NOTIFY)) { + subVal |= NIMBLE_SUB_NOTIFY; + } + if(event->subscribe.cur_indicate && (m_properties & BLECharacteristic::PROPERTY_INDICATE)) { + subVal |= NIMBLE_SUB_INDICATE; + } + + log_i("New subscribe value for conn: %d val: %d", event->subscribe.conn_handle, subVal); + + if(!event->subscribe.cur_indicate && event->subscribe.prev_indicate) { + BLEDevice::getServer()->clearIndicateWait(event->subscribe.conn_handle); + } + + auto it = m_subscribedVec.begin(); + for(; it != m_subscribedVec.end(); ++it) { + if((*it).first == event->subscribe.conn_handle) { + break; + } + } + + if(subVal > 0) { + if(it == m_subscribedVec.end()) { + m_subscribedVec.push_back({event->subscribe.conn_handle, subVal}); + } else { + (*it).second = subVal; + } + } else if(it != m_subscribedVec.end()) { + m_subscribedVec.erase(it); + } + + m_pCallbacks->onSubscribe(this, &desc, subVal); +} + +/** + * @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(bool is_notification) { + log_v(">> notify: length: %d", m_value.getValue().length()); + + assert(getService() != nullptr); + assert(getService()->getServer() != nullptr); + + int rc = 0; + m_pCallbacks->onNotify(this); // Invoke the notify callback. + + // GeneralUtils::hexDump() doesn't output anything if the log level is not + // "VERBOSE". Additionally, it is very CPU intensive, even when it doesn't + // output anything! So it is much better to *not* call it at all if not needed. + // In a simple program which calls BLECharacteristic::notify() every 50 ms, + // the performance gain of this little optimization is 37% in release mode + // (-O3) and 57% in debug mode. + // Of course, the "#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE" guard + // could also be put inside the GeneralUtils::hexDump() function itself. But + // it's better to put it here also, as it is clearer (indicating a verbose log + // thing) and it allows to remove the "m_value.getValue().c_str()" call, which + // is, in itself, quite CPU intensive. +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE + GeneralUtils::hexDump((uint8_t *)m_value.getValue().c_str(), m_value.getValue().length()); +#endif + + if (getService()->getServer()->getConnectedCount() == 0) { + log_v("<< notify: No connected clients."); + m_pCallbacks->onStatus(this, BLECharacteristicCallbacks::Status::ERROR_NO_CLIENT, 0); + return; + } + + if (m_subscribedVec.size() == 0) { + log_v("<< notify: No clients subscribed."); + m_pCallbacks->onStatus(this, BLECharacteristicCallbacks::Status::ERROR_NO_SUBSCRIBER, 0); + return; + } + + if (is_notification) { + if (!(m_properties & BLECharacteristic::PROPERTY_NOTIFY)) { + log_v("<< notifications disabled; ignoring"); + m_pCallbacks->onStatus(this, BLECharacteristicCallbacks::Status::ERROR_NOTIFY_DISABLED, 0); // Invoke the notify callback. + return; + } + } else { + if (!(m_properties & BLECharacteristic::PROPERTY_INDICATE)) { + log_v("<< indications disabled; ignoring"); + m_pCallbacks->onStatus(this, BLECharacteristicCallbacks::Status::ERROR_INDICATE_DISABLED, 0); // Invoke the notify callback. + return; + } + } + + bool reqSec = (m_properties & BLE_GATT_CHR_F_READ_AUTHEN) || (m_properties & BLE_GATT_CHR_F_READ_AUTHOR) || (m_properties & BLE_GATT_CHR_F_READ_ENC); + + for (auto &myPair : m_subscribedVec) { + uint16_t _mtu = getService()->getServer()->getPeerMTU(myPair.first); + + // check if connected and subscribed + if (_mtu == 0 || myPair.second == 0) { + continue; + } + + if (reqSec) { + struct ble_gap_conn_desc desc; + rc = ble_gap_conn_find(myPair.first, &desc); + if (rc != 0 || !desc.sec_state.encrypted) { + continue; + } + } + + String value = getValue(); + size_t length = value.length(); + + if (length > _mtu - 3) { + log_w("- Truncating to %d bytes (maximum notify size)", _mtu - 3); + } + + if(is_notification && (!(myPair.second & NIMBLE_SUB_NOTIFY))) { + log_w("Sending notification to client subscribed to indications, sending indication instead"); + is_notification = false; + } + + if(!is_notification && (!(myPair.second & NIMBLE_SUB_INDICATE))) { + log_w("Sending indication to client subscribed to notification, sending notification instead"); + is_notification = true; + } + + if (!is_notification) { // is indication + m_semaphoreConfEvt.take("indicate"); + } + + // don't create the m_buf until we are sure to send the data or else + // we could be allocating a buffer that doesn't get released. + // We also must create it in each loop iteration because it is consumed with each host call. + os_mbuf *om = ble_hs_mbuf_from_flat((uint8_t *)value.c_str(), length); + + if (!is_notification && (m_properties & BLECharacteristic::PROPERTY_INDICATE)) { + if (!BLEDevice::getServer()->setIndicateWait(myPair.first)) { + log_e("prior Indication in progress"); + os_mbuf_free_chain(om); + return; + } + + rc = ble_gatts_indicate_custom(myPair.first, m_handle, om); + if (rc != 0) { + BLEDevice::getServer()->clearIndicateWait(myPair.first); + } + } else { + rc = ble_gatts_notify_custom(myPair.first, m_handle, om); + } + + if (rc != 0) { + log_e("<< ble_gatts_%s_custom: rc=%d %s", is_notification ? "notify" : "indicate", rc, GeneralUtils::errorToString(rc)); + m_semaphoreConfEvt.give(); + m_pCallbacks->onStatus(this, BLECharacteristicCallbacks::Status::ERROR_GATT, rc); // Invoke the notify callback. + return; + } + + if (!is_notification) { // is indication + if (!m_semaphoreConfEvt.timedWait("indicate", indicationTimeout)) { + m_pCallbacks->onStatus(this, BLECharacteristicCallbacks::Status::ERROR_INDICATE_TIMEOUT, 0); // Invoke the notify callback. + } else { + auto code = m_semaphoreConfEvt.value(); + if (code == ESP_OK) { + m_pCallbacks->onStatus(this, BLECharacteristicCallbacks::Status::SUCCESS_INDICATE, code); // Invoke the notify callback. + } else { + m_pCallbacks->onStatus(this, BLECharacteristicCallbacks::Status::ERROR_INDICATE_FAILURE, code); + } + } + } else { + m_pCallbacks->onStatus(this, BLECharacteristicCallbacks::Status::SUCCESS_NOTIFY, 0); // Invoke the notify callback. + } + } + log_v("<< notify"); +} // Notify + +void BLECharacteristicCallbacks::onRead(BLECharacteristic *pCharacteristic, ble_gap_conn_desc *desc) { + onRead(pCharacteristic); +} // onRead + +void BLECharacteristicCallbacks::onWrite(BLECharacteristic *pCharacteristic, ble_gap_conn_desc *desc) { + onWrite(pCharacteristic); +} // onWrite + +void BLECharacteristicCallbacks::onSubscribe(BLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc, uint16_t subValue) { + log_d(">> onSubscribe: default"); + log_d("<< onSubscribe"); +} // onSubscribe + +#endif /* CONFIG_NIMBLE_ENABLED */ + +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ diff --git a/libraries/BLE/src/BLECharacteristic.h b/libraries/BLE/src/BLECharacteristic.h index 29f105868fd..27df5a30c3e 100644 --- a/libraries/BLE/src/BLECharacteristic.h +++ b/libraries/BLE/src/BLECharacteristic.h @@ -3,6 +3,10 @@ * * Created on: Jun 22, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ #ifndef COMPONENTS_CPP_UTILS_BLECHARACTERISTIC_H_ @@ -11,15 +15,59 @@ #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ + #include #include #include "BLEUUID.h" -#include -#include #include "BLEDescriptor.h" #include "BLEValue.h" #include "RTOS.h" +#include "BLEUtils.h" + +/*************************************************************************** + * Bluedroid includes * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) +#include +#include +#endif + +/*************************************************************************** + * NimBLE includes and definitions * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +#include +#include +#include +#include "BLEConnInfo.h" +#define ESP_GATT_MAX_ATTR_LEN BLE_ATT_ATTR_MAX_LEN +#define ESP_GATT_CHAR_PROP_BIT_READ BLE_GATT_CHR_PROP_READ +#define ESP_GATT_CHAR_PROP_BIT_WRITE BLE_GATT_CHR_PROP_WRITE +#define ESP_GATT_CHAR_PROP_BIT_WRITE_NR BLE_GATT_CHR_PROP_WRITE_NO_RSP +#define ESP_GATT_CHAR_PROP_BIT_BROADCAST BLE_GATT_CHR_PROP_BROADCAST +#define ESP_GATT_CHAR_PROP_BIT_NOTIFY BLE_GATT_CHR_PROP_NOTIFY +#define ESP_GATT_CHAR_PROP_BIT_INDICATE BLE_GATT_CHR_PROP_INDICATE +#endif + +/*************************************************************************** + * NimBLE types * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +typedef uint16_t esp_gatt_char_prop_t; +typedef uint8_t esp_gatt_perm_t; +#endif + +/*************************************************************************** + * Forward declarations * + ***************************************************************************/ class BLEService; class BLEDescriptor; @@ -30,6 +78,10 @@ class BLECharacteristicCallbacks; */ class BLEDescriptorMap { public: + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ + void setByUUID(const char *uuid, BLEDescriptor *pDescriptor); void setByUUID(BLEUUID uuid, BLEDescriptor *pDescriptor); void setByHandle(uint16_t handle, BLEDescriptor *pDescriptor); @@ -37,11 +89,32 @@ class BLEDescriptorMap { BLEDescriptor *getByUUID(BLEUUID uuid); BLEDescriptor *getByHandle(uint16_t handle); 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(); + int getRegisteredDescriptorCount(); + void removeDescriptor(BLEDescriptor *pDescriptor); + + /*************************************************************************** + * Bluedroid public declarations * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); +#endif + + /*************************************************************************** + * NimBLE public declarations * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + void handleGATTServerEvent(uint16_t conn_handle, uint16_t attr_handle, ble_gatt_access_ctxt *ctxt, void *arg); +#endif private: + /*************************************************************************** + * Common private properties * + ***************************************************************************/ + std::map m_uuidMap; std::map m_handleMap; std::map::iterator m_iterator; @@ -55,6 +128,48 @@ class BLEDescriptorMap { */ class BLECharacteristic { public: + /*************************************************************************** + * Common properties * + ***************************************************************************/ + + static const uint32_t indicationTimeout = 1000; + + /*************************************************************************** + * Bluedroid public properties * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + 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; +#endif + + /*************************************************************************** + * NimBLE public properties * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + static const uint32_t PROPERTY_READ = BLE_GATT_CHR_F_READ; + static const uint32_t PROPERTY_READ_ENC = BLE_GATT_CHR_F_READ_ENC; + static const uint32_t PROPERTY_READ_AUTHEN = BLE_GATT_CHR_F_READ_AUTHEN; + static const uint32_t PROPERTY_READ_AUTHOR = BLE_GATT_CHR_F_READ_AUTHOR; + static const uint32_t PROPERTY_WRITE = BLE_GATT_CHR_F_WRITE; + static const uint32_t PROPERTY_WRITE_NR = BLE_GATT_CHR_F_WRITE_NO_RSP; + static const uint32_t PROPERTY_WRITE_ENC = BLE_GATT_CHR_F_WRITE_ENC; + static const uint32_t PROPERTY_WRITE_AUTHEN = BLE_GATT_CHR_F_WRITE_AUTHEN; + static const uint32_t PROPERTY_WRITE_AUTHOR = BLE_GATT_CHR_F_WRITE_AUTHOR; + static const uint32_t PROPERTY_BROADCAST = BLE_GATT_CHR_F_BROADCAST; + static const uint32_t PROPERTY_NOTIFY = BLE_GATT_CHR_F_NOTIFY; + static const uint32_t PROPERTY_INDICATE = BLE_GATT_CHR_F_INDICATE; +#endif + + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ + BLECharacteristic(const char *uuid, uint32_t properties = 0); BLECharacteristic(BLEUUID uuid, uint32_t properties = 0); virtual ~BLECharacteristic(); @@ -66,14 +181,9 @@ class BLECharacteristic { String getValue(); uint8_t *getData(); size_t getLength(); - void indicate(); void notify(bool is_notification = true); - 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(String value); void setValue(uint16_t &data16); @@ -81,20 +191,16 @@ class BLECharacteristic { void setValue(int &data32); void setValue(float &data32); void setValue(double &data64); - void setWriteProperty(bool value); - void setWriteNoResponseProperty(bool value); String toString(); uint16_t getHandle(); - void setAccessPermissions(esp_gatt_perm_t perm); - - 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; - - static const uint32_t indicationTimeout = 1000; + void setAccessPermissions(uint8_t perm); + esp_gatt_char_prop_t getProperties(); + void setReadProperty(bool value); + void setWriteProperty(bool value); + void setNotifyProperty(bool value); + void setBroadcastProperty(bool value); + void setIndicateProperty(bool value); + void setWriteNoResponseProperty(bool value); private: friend class BLEServer; @@ -102,6 +208,10 @@ class BLECharacteristic { friend class BLEDescriptor; friend class BLECharacteristicMap; + /*************************************************************************** + * Common private properties * + ***************************************************************************/ + BLEUUID m_bleUUID; BLEDescriptorMap m_descriptorMap; uint16_t m_handle; @@ -109,18 +219,53 @@ class BLECharacteristic { BLECharacteristicCallbacks *m_pCallbacks; BLEService *m_pService; BLEValue m_value; + bool m_writeEvt = false; + FreeRTOS::Semaphore m_semaphoreConfEvt = FreeRTOS::Semaphore("ConfEvt"); + FreeRTOS::Semaphore m_semaphoreSetValue = FreeRTOS::Semaphore("SetValue"); + + /*************************************************************************** + * Bluedroid private properties * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); esp_gatt_perm_t m_permissions = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; - bool m_writeEvt = false; // If we have started a long write, this tells the commit code that we were the target +#endif - void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); + /*************************************************************************** + * NimBLE private properties * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + portMUX_TYPE m_readMux; + uint8_t m_removed; + std::vector> m_subscribedVec; +#endif + + /*************************************************************************** + * Common private declarations * + ***************************************************************************/ void executeCreate(BLEService *pService); - 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"); - FreeRTOS::Semaphore m_semaphoreSetValue = FreeRTOS::Semaphore("SetValue"); + + /*************************************************************************** + * Bluedroid private declarations * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); +#endif + + /*************************************************************************** + * NimBLE private declarations * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + void setSubscribe(struct ble_gap_event *event); + static int handleGATTServerEvent(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg); +#endif }; // BLECharacteristic /** @@ -132,6 +277,10 @@ class BLECharacteristic { */ class BLECharacteristicCallbacks { public: + /*************************************************************************** + * Common public types * + ***************************************************************************/ + typedef enum { SUCCESS_INDICATE, SUCCESS_NOTIFY, @@ -139,51 +288,41 @@ class BLECharacteristicCallbacks { ERROR_NOTIFY_DISABLED, ERROR_GATT, ERROR_NO_CLIENT, + ERROR_NO_SUBSCRIBER, ERROR_INDICATE_TIMEOUT, ERROR_INDICATE_FAILURE } Status; - virtual ~BLECharacteristicCallbacks(); + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ - /** - * @brief Callback function to support a read request. - * @param [in] pCharacteristic The characteristic that is the source of the event. - * @param [in] param The BLE GATTS param. Use param->read. - */ - virtual void onRead(BLECharacteristic *pCharacteristic, esp_ble_gatts_cb_param_t *param); - /** - * @brief DEPRECATED! Callback function to support a read request. Called only if onRead(,) is not overridden - * @param [in] pCharacteristic The characteristic that is the source of the event. - */ + virtual ~BLECharacteristicCallbacks(); virtual void onRead(BLECharacteristic *pCharacteristic); + virtual void onWrite(BLECharacteristic *pCharacteristic); + virtual void onNotify(BLECharacteristic *pCharacteristic); + virtual void onStatus(BLECharacteristic *pCharacteristic, Status s, uint32_t code); - /** - * @brief Callback function to support a write request. - * @param [in] pCharacteristic The characteristic that is the source of the event. - * @param [in] param The BLE GATTS param. Use param->write. - */ + /*************************************************************************** + * Bluedroid public declarations * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + virtual void onRead(BLECharacteristic *pCharacteristic, esp_ble_gatts_cb_param_t *param); virtual void onWrite(BLECharacteristic *pCharacteristic, esp_ble_gatts_cb_param_t *param); - /** - * @brief DEPRECATED! Callback function to support a write request. Called only if onWrite(,) is not overridden. - * @param [in] pCharacteristic The characteristic that is the source of the event. - */ - virtual void onWrite(BLECharacteristic *pCharacteristic); +#endif - /** - * @brief Callback function to support a Notify request. - * @param [in] pCharacteristic The characteristic that is the source of the event. - */ - virtual void onNotify(BLECharacteristic *pCharacteristic); + /*************************************************************************** + * NimBLE public declarations * + ***************************************************************************/ - /** - * @brief Callback function to support a Notify/Indicate Status report. - * @param [in] pCharacteristic The characteristic that is the source of the event. - * @param [in] s Status of the notification/indication - * @param [in] code Additional code of underlying errors - */ - virtual void onStatus(BLECharacteristic *pCharacteristic, Status s, uint32_t code); +#if defined(CONFIG_NIMBLE_ENABLED) + virtual void onRead(BLECharacteristic *pCharacteristic, ble_gap_conn_desc *desc); + virtual void onWrite(BLECharacteristic *pCharacteristic, ble_gap_conn_desc *desc); + virtual void onSubscribe(BLECharacteristic *pCharacteristic, ble_gap_conn_desc *desc, uint16_t subValue); +#endif }; -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ #endif /* COMPONENTS_CPP_UTILS_BLECHARACTERISTIC_H_ */ diff --git a/libraries/BLE/src/BLECharacteristicMap.cpp b/libraries/BLE/src/BLECharacteristicMap.cpp index 6f2c0bb1154..ec3588bcde4 100644 --- a/libraries/BLE/src/BLECharacteristicMap.cpp +++ b/libraries/BLE/src/BLECharacteristicMap.cpp @@ -3,19 +3,34 @@ * * Created on: Jun 22, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ + #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ + #include #include #include "BLEService.h" +#include "BLEUtils.h" #ifdef ARDUINO_ARCH_ESP32 #include "esp32-hal-log.h" #endif +/*************************************************************************** + * Common functions * + ***************************************************************************/ + /** * @brief Return the characteristic by handle. * @param [in] handle The handle to look up the characteristic. @@ -77,17 +92,22 @@ BLECharacteristic *BLECharacteristicMap::getNext() { } // getNext /** - * @brief 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 + * @brief Get the number of registered characteristics. + * @return The number of registered characteristics. */ -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.first->handleGATTServerEvent(event, gatts_if, param); - } -} // handleGATTServerEvent +int BLECharacteristicMap::getRegisteredCharacteristicCount() { + return m_uuidMap.size(); +} // getRegisteredCharacteristicCount + +/** + * @brief Removes characteristic from maps. + * @param [in] characteristic The characteristic to remove. + * @return N/A. + */ +void BLECharacteristicMap::removeCharacteristic(BLECharacteristic *characteristic) { + m_handleMap.erase(characteristic->getHandle()); + m_uuidMap.erase(characteristic); +} // removeCharacteristic /** * @brief Set the characteristic by handle. @@ -130,5 +150,37 @@ String BLECharacteristicMap::toString() { return res; } // toString -#endif /* CONFIG_BLUEDROID_ENABLED */ +/*************************************************************************** + * Bluedroid functions * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) +/** + * @brief 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.first->handleGATTServerEvent(event, gatts_if, param); + } +} // handleGATTServerEvent +#endif // CONFIG_BLUEDROID_ENABLED + +/*************************************************************************** + * NimBLE functions * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +void BLECharacteristicMap::handleGATTServerEvent(uint16_t conn_handle, uint16_t attr_handle, ble_gatt_access_ctxt *ctxt, void *arg) { + // Invoke the handler for every Service we have. + for (auto &myPair : m_uuidMap) { + myPair.first->handleGATTServerEvent(conn_handle, attr_handle, ctxt, arg); + } +} +#endif // CONFIG_NIMBLE_ENABLED + +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ diff --git a/libraries/BLE/src/BLEClient.cpp b/libraries/BLE/src/BLEClient.cpp index 29fa0fbc140..83afd186fd7 100644 --- a/libraries/BLE/src/BLEClient.cpp +++ b/libraries/BLE/src/BLEClient.cpp @@ -3,17 +3,23 @@ * * Created on: Mar 22, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ + #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ + #include -#include -#include -#include -#include // ESP32 BLE #include "BLEClient.h" #include "BLEUtils.h" #include "BLEService.h" @@ -24,6 +30,39 @@ #include "BLEDevice.h" #include "esp32-hal-log.h" +/*************************************************************************** + * Bluedroid includes * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) +#include +#include +#include +#include +#endif + +/*************************************************************************** + * NimBLE includes * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +#include +#include +#include +#include +#include +#endif + +/*************************************************************************** + * Common global variables * + ***************************************************************************/ + +static BLEClientCallbacks defaultCallbacks; + +/*************************************************************************** + * Common functions * + ***************************************************************************/ + /* * Design * ------ @@ -46,11 +85,32 @@ */ BLEClient::BLEClient() { - m_pClientCallbacks = nullptr; + m_pClientCallbacks = &defaultCallbacks; m_conn_id = ESP_GATT_IF_NONE; - m_gattc_if = ESP_GATT_IF_NONE; m_haveServices = false; m_isConnected = false; // Initially, we are flagged as not connected. + +#if defined(CONFIG_BLUEDROID_ENABLED) + m_gattc_if = ESP_GATT_IF_NONE; +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + m_connectTimeout = 30000; + m_pTaskData = nullptr; + m_lastErr = 0; + + m_pConnParams.scan_itvl = 16; // Scan interval in 0.625ms units (NimBLE Default) + m_pConnParams.scan_window = 16; // Scan window in 0.625ms units (NimBLE Default) + m_pConnParams.itvl_min = BLE_GAP_INITIAL_CONN_ITVL_MIN; // min_int = 0x10*1.25ms = 20ms + m_pConnParams.itvl_max = BLE_GAP_INITIAL_CONN_ITVL_MAX; // max_int = 0x20*1.25ms = 40ms + m_pConnParams.latency = BLE_GAP_INITIAL_CONN_LATENCY; // number of packets allowed to skip (extends max interval) + m_pConnParams.supervision_timeout = BLE_GAP_INITIAL_SUPERVISION_TIMEOUT; // timeout = 400*10ms = 4000ms + m_pConnParams.min_ce_len = BLE_GAP_INITIAL_CONN_MIN_CE_LEN; // Minimum length of connection event in 0.625ms units + m_pConnParams.max_ce_len = BLE_GAP_INITIAL_CONN_MAX_CE_LEN; // Maximum length of connection event in 0.625ms units + + memset(&m_dcTimer, 0, sizeof(m_dcTimer)); + ble_npl_callout_init(&m_dcTimer, nimble_port_get_dflt_eventq(), BLEClient::dcTimerCb, this); +#endif } // BLEClient /** @@ -64,6 +124,10 @@ BLEClient::~BLEClient() { } m_servicesMap.clear(); m_servicesMapByInstID.clear(); + +#if defined(CONFIG_NIMBLE_ENABLED) + ble_npl_callout_deinit(&m_dcTimer); +#endif } // ~BLEClient /** @@ -86,7 +150,7 @@ void BLEClient::clearServices() { */ bool BLEClient::connect(BLEAdvertisedDevice *device) { BLEAddress address = device->getAddress(); - esp_ble_addr_type_t type = device->getAddressType(); + uint8_t type = device->getAddressType(); return connect(address, type); } @@ -95,10 +159,269 @@ bool BLEClient::connect(BLEAdvertisedDevice *device) { */ bool BLEClient::connectTimeout(BLEAdvertisedDevice *device, uint32_t timeoutMs) { BLEAddress address = device->getAddress(); - esp_ble_addr_type_t type = device->getAddressType(); + uint8_t type = device->getAddressType(); return connect(address, type, timeoutMs); } +esp_gatt_if_t BLEClient::getGattcIf() { +#if defined(CONFIG_BLUEDROID_ENABLED) + return m_gattc_if; +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + log_e("NimBLE does not support getGattcIf()"); + return ESP_GATT_IF_NONE; +#endif +} // getGattcIf + +/** + * @brief Initiate a secure connection (pair/bond) with the server.\n + * Called automatically when a characteristic or descriptor requires encryption or authentication to access it. + * @return True on success. + */ +bool BLEClient::secureConnection() { +#if defined(CONFIG_BLUEDROID_ENABLED) + log_i("secureConnection() does not need to be called for Bluedroid"); + return true; +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); + ble_task_data_t taskData = {this, cur_task, 0, nullptr}; + + int retryCount = 1; + + do { + m_pTaskData = &taskData; + + int rc = BLESecurity::startSecurity(m_conn_id); + if (rc != 0) { + m_lastErr = rc; + m_pTaskData = nullptr; + return false; + } + +#ifdef ulTaskNotifyValueClear + // Clear the task notification value to ensure we block + ulTaskNotifyValueClear(cur_task, ULONG_MAX); +#endif + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + } while (taskData.rc == (BLE_HS_ERR_HCI_BASE + BLE_ERR_PINKEY_MISSING) && retryCount--); + + if (taskData.rc != 0) { + m_lastErr = taskData.rc; + return false; + } + + return true; +#endif +} // secureConnection + +uint16_t BLEClient::getConnId() { + return m_conn_id; +} // getConnId + +/** + * @brief Retrieve the address of the peer. + * + * Returns the Bluetooth device address of the %BLE peer to which this client is connected. + */ +BLEAddress BLEClient::getPeerAddress() { + return m_peerAddress; +} // getAddress + +/** + * @brief Ask the BLE server for the RSSI value. + * @return The RSSI value. + */ +int BLEClient::getRssi() { + log_v(">> getRssi()"); + if (!isConnected()) { + log_v("<< getRssi(): Not connected"); + return 0; + } + +#if defined(CONFIG_BLUEDROID_ENABLED) + // We make the API call to read the RSSI value which is an asynchronous operation. We expect to receive + // an ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT to indicate completion. + // + m_semaphoreRssiCmplEvt.take("getRssi"); + esp_err_t rc = ::esp_ble_gap_read_rssi(getPeerAddress().getNative()); + if (rc != ESP_OK) { + log_e("<< getRssi: esp_ble_gap_read_rssi: rc=%d %s", rc, GeneralUtils::errorToString(rc)); + return 0; + } + int rssiValue = m_semaphoreRssiCmplEvt.wait("getRssi"); +#endif // CONFIG_BLUEDROID_ENABLED + +#if defined(CONFIG_NIMBLE_ENABLED) + int8_t rssiValue = 0; + int rc = ble_gap_conn_rssi(m_conn_id, &rssiValue); + if (rc != 0) { + log_e("<< getRssi: ble_gap_conn_rssi: rc=%d %s", rc, BLEUtils::returnCodeToString(rc)); + return 0; + } +#endif // CONFIG_BLUEDROID_ENABLED + log_v("<< getRssi(): %d", rssiValue); + return rssiValue; +} // getRssi + +/** + * @brief Get the service BLE Remote Service instance 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)); +} // getService + +/** + * @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. + * @throws BLEUuidNotFound + */ +BLERemoteService *BLEClient::getService(BLEUUID uuid) { + log_v(">> getService: uuid: %s", uuid.toString().c_str()); + // 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 uuidStr = uuid.toString().c_str(); + for (auto &myPair : m_servicesMap) { + if (myPair.first == uuidStr) { + log_v("<< getService: found the service with uuid: %s", uuid.toString().c_str()); + return myPair.second; + } + } // End of each of the services. + log_v("<< getService: not found"); + return nullptr; +} // getService + +/** + * @brief Get the value of a specific characteristic associated with a specific service. + * @param [in] serviceUUID The service that owns the characteristic. + * @param [in] characteristicUUID The characteristic whose value we wish to read. + * @throws BLEUuidNotFound + */ +String BLEClient::getValue(BLEUUID serviceUUID, BLEUUID characteristicUUID) { + log_v(">> getValue: serviceUUID: %s, characteristicUUID: %s", serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); + String ret = getService(serviceUUID)->getCharacteristic(characteristicUUID)->readValue(); + log_v("<> setValue: serviceUUID: %s, characteristicUUID: %s", serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); + getService(serviceUUID)->getCharacteristic(characteristicUUID)->writeValue(value); + log_v("<< setValue"); +} // setValue + +uint16_t BLEClient::getMTU() { +#ifdef CONFIG_BLUEDROID_ENABLED + return m_mtu; +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + return ble_att_mtu(m_conn_id); +#endif +} + +/** + @brief Set the local and remote MTU size. + Should be called once after client connects if MTU size needs to be changed. + @return bool indicating if MTU was successfully set locally and on remote. +*/ +bool BLEClient::setMTU(uint16_t mtu) { + log_v(">> setMTU: %d", mtu); + esp_err_t err = ESP_OK; + +#ifdef CONFIG_BLUEDROID_ENABLED + err = esp_ble_gatt_set_local_mtu(mtu); //First must set local MTU value. +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + err = ble_att_set_preferred_mtu(mtu); +#endif + + if (err == ESP_OK) { +#ifdef CONFIG_BLUEDROID_ENABLED + err = esp_ble_gattc_send_mtu_req(m_gattc_if, m_conn_id); //Once local is set successfully set remote size +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + err = ble_gattc_exchange_mtu(m_conn_id, nullptr, nullptr); +#endif + + if (err != ESP_OK) { + log_e("Error setting send MTU request MTU: %d err=%d", mtu, err); + return false; + } + } else { + log_e("can't set local mtu value: %d", mtu); + return false; + } + log_v("<< setMTU"); + + m_mtu = mtu; //successfully changed + + return true; +} + +/** + * @brief Return a string representation of this client. + * @return A string representation of this client. + */ +String BLEClient::toString() { + String res = "peer address: " + m_peerAddress.toString(); + res += "\nServices:\n"; + for (auto &myPair : m_servicesMap) { + res += myPair.second->toString() + "\n"; + // myPair.second is the value + } + return res; +} // toString + +void BLEClientCallbacks::onConnect(BLEClient *pClient) { + log_d("BLEClientCallbacks", "onConnect: default"); +} + +void BLEClientCallbacks::onDisconnect(BLEClient *pClient) { + log_d("BLEClientCallbacks", "onDisconnect: default"); +} + +/*************************************************************************** + * Bluedroid functions * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + /** * @brief Connect to the partner (BLE Server). * @param [in] address The address of the partner. @@ -106,7 +429,7 @@ bool BLEClient::connectTimeout(BLEAdvertisedDevice *device, uint32_t timeoutMs) * @param [in] timeoutMs The number of milliseconds to wait for the connection to complete. * @return True on success. */ -bool BLEClient::connect(BLEAddress address, esp_ble_addr_type_t type, uint32_t timeoutMs) { +bool BLEClient::connect(BLEAddress address, uint8_t type, uint32_t timeoutMs) { log_v(">> connect(%s)", address.toString().c_str()); // We need the connection handle that we get from registering the application. We register the app @@ -141,8 +464,8 @@ bool BLEClient::connect(BLEAddress address, esp_ble_addr_type_t type, uint32_t t m_semaphoreOpenEvt.take("connect"); errRc = ::esp_ble_gattc_open( m_gattc_if, - *getPeerAddress().getNative(), // address - type, // Note: This was added on 2018-04-03 when the latest ESP-IDF was detected to have changed the signature. + getPeerAddress().getNative(), // address + (esp_ble_addr_type_t)type, // Note: This was added on 2018-04-03 when the latest ESP-IDF was detected to have changed the signature. 1 // direct connection <-- maybe needs to be changed in case of direct indirect connection??? ); if (errRc != ESP_OK) { @@ -361,89 +684,11 @@ void BLEClient::gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t } // gattClientEventHandler -uint16_t BLEClient::getConnId() { - return m_conn_id; -} // getConnId - -esp_gatt_if_t BLEClient::getGattcIf() { - return m_gattc_if; -} // getGattcIf - /** - * @brief Retrieve the address of the peer. - * - * Returns the Bluetooth device address of the %BLE peer to which this client is connected. - */ -BLEAddress BLEClient::getPeerAddress() { - return m_peerAddress; -} // getAddress - -/** - * @brief Ask the BLE server for the RSSI value. - * @return The RSSI value. - */ -int BLEClient::getRssi() { - log_v(">> getRssi()"); - if (!isConnected()) { - log_v("<< getRssi(): Not connected"); - return 0; - } - // We make the API call to read the RSSI value which is an asynchronous operation. We expect to receive - // an ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT to indicate completion. - // - m_semaphoreRssiCmplEvt.take("getRssi"); - esp_err_t rc = ::esp_ble_gap_read_rssi(*getPeerAddress().getNative()); - if (rc != ESP_OK) { - log_e("<< getRssi: esp_ble_gap_read_rssi: rc=%d %s", rc, GeneralUtils::errorToString(rc)); - return 0; - } - int rssiValue = m_semaphoreRssiCmplEvt.wait("getRssi"); - log_v("<< getRssi(): %d", rssiValue); - return rssiValue; -} // getRssi - -/** - * @brief Get the service BLE Remote Service instance 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)); -} // getService - -/** - * @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. - * @throws BLEUuidNotFound - */ -BLERemoteService *BLEClient::getService(BLEUUID uuid) { - log_v(">> getService: uuid: %s", uuid.toString().c_str()); - // 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 uuidStr = uuid.toString().c_str(); - for (auto &myPair : m_servicesMap) { - if (myPair.first == uuidStr) { - log_v("<< getService: found the service with uuid: %s", uuid.toString().c_str()); - return myPair.second; - } - } // End of each of the services. - log_v("<< getService: not found"); - 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 + * @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 *BLEClient::getServices() { /* @@ -473,19 +718,6 @@ std::map *BLEClient::getServices() { return &m_servicesMap; } // getServices -/** - * @brief Get the value of a specific characteristic associated with a specific service. - * @param [in] serviceUUID The service that owns the characteristic. - * @param [in] characteristicUUID The characteristic whose value we wish to read. - * @throws BLEUuidNotFound - */ -String BLEClient::getValue(BLEUUID serviceUUID, BLEUUID characteristicUUID) { - log_v(">> getValue: serviceUUID: %s, characteristicUUID: %s", serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); - String ret = getService(serviceUUID)->getCharacteristic(characteristicUUID)->readValue(); - log_v("<> setValue: serviceUUID: %s, characteristicUUID: %s", serviceUUID.toString().c_str(), characteristicUUID.toString().c_str()); - getService(serviceUUID)->getCharacteristic(characteristicUUID)->writeValue(value); - log_v("<< setValue"); -} // setValue +bool BLEClient::connect(BLEAddress address, uint8_t type, uint32_t timeoutMs) { + log_v(">> connect(%s)", address.toString().c_str()); -uint16_t BLEClient::getMTU() { - return m_mtu; -} + if (!BLEDevice::m_synced) { + log_d("BLEClient", "Host reset, wait for sync."); + return false; + } -/** - @brief Set the local and remote MTU size. - Should be called once after client connects if MTU size needs to be changed. - @return bool indicating if MTU was successfully set locally and on remote. -*/ -bool BLEClient::setMTU(uint16_t mtu) { - esp_err_t err = esp_ble_gatt_set_local_mtu(mtu); //First must set local MTU value. - if (err == ESP_OK) { - err = esp_ble_gattc_send_mtu_req(m_gattc_if, m_conn_id); //Once local is set successfully set remote size - if (err != ESP_OK) { - log_e("Error setting send MTU request MTU: %d err=%d", mtu, err); - return false; + if (m_conn_id != BLE_HS_CONN_HANDLE_NONE || m_isConnected || m_pTaskData != nullptr) { + log_e("Client busy, connected to %s, id=%d", m_peerAddress.toString().c_str(), getConnId()); + return false; + } + + ble_addr_t peerAddr_t; + memcpy(&peerAddr_t.val, address.getNative(), 6); + peerAddr_t.type = address.getType(); + if (ble_gap_conn_find_by_addr(&peerAddr_t, NULL) == 0) { + log_e("A connection to %s already exists", address.toString().c_str()); + return false; + } + + if (address == BLEAddress("")) { + log_e("Invalid peer address (NULL)"); + return false; + } + + m_appId = BLEDevice::m_appId++; + m_peerAddress = address; + + TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); + ble_task_data_t taskData = {this, cur_task, 0, nullptr}; + m_pTaskData = &taskData; + int rc = 0; + + /* Try to connect the the advertiser. Allow 30 seconds (30000 ms) for + * timeout (default value of m_connectTimeout). + * Loop on BLE_HS_EBUSY if the scan hasn't stopped yet. + */ + do { + rc = ble_gap_connect(BLEDevice::m_ownAddrType, &peerAddr_t, m_connectTimeout, &m_pConnParams, BLEClient::handleGAPEvent, this); + switch (rc) { + case 0: break; + + case BLE_HS_EBUSY: + // Scan was still running, stop it and try again + if (!BLEDevice::getScan()->stop()) { + rc = BLE_HS_EUNKNOWN; + } + break; + + case BLE_HS_EDONE: + // A connection to this device already exists, do not connect twice. + log_e("Already connected to device; addr=%s", m_peerAddress.toString().c_str()); + break; + + case BLE_HS_EALREADY: + // Already attempting to connect to this device, cancel the previous + // attempt and report failure here so we don't get 2 connections. + log_e("Already attempting to connect to %s - canceling", m_peerAddress.toString().c_str()); + ble_gap_conn_cancel(); + break; + + default: log_e("Failed to connect to %s, rc=%d; %s", m_peerAddress.toString().c_str(), rc, BLEUtils::returnCodeToString(rc)); break; } - } else { - log_e("can't set local mtu value: %d", mtu); + + } while (rc == BLE_HS_EBUSY); + + m_lastErr = rc; + + if (rc != 0) { + m_pTaskData = nullptr; return false; } - log_v("<< setLocalMTU"); - m_mtu = mtu; //successfully changed +#ifdef ulTaskNotifyValueClear + // Clear the task notification value to ensure we block + ulTaskNotifyValueClear(cur_task, ULONG_MAX); +#endif - return true; + // Wait for the connect timeout time +1 second for the connection to complete + if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(m_connectTimeout + 1000)) == pdFALSE) { + m_pTaskData = nullptr; + // If a connection was made but no response from MTU exchange; disconnect + if (isConnected()) { + log_e("Connect timeout - no response"); + disconnect(); + } else { + // workaround; if the controller doesn't cancel the connection + // at the timeout, cancel it here. + log_e("Connect timeout - canceling"); + ble_gap_conn_cancel(); + } + + return false; + + } else if (taskData.rc != 0) { + m_lastErr = taskData.rc; + log_e("Connection failed; status=%d %s", taskData.rc, BLEUtils::returnCodeToString(taskData.rc)); + // If the failure was not a result of a disconnection + // make sure we disconnect now to avoid dangling connections + if (isConnected()) { + disconnect(); + } + return false; + } else { + log_i("Connection established"); + } + + m_isConnected = true; + m_pClientCallbacks->onConnect(this); + + log_i("<< connect()"); + + BLEDevice::addPeerDevice(this, true, m_appId); + // Check if still connected before returning + return isConnected(); } /** - * @brief Return a string representation of this client. - * @return A string representation of this client. + * @brief STATIC Callback for the service discovery API function.\n + * When a service is found or there is none left or there was an error + * the API will call this and report findings. */ -String BLEClient::toString() { - String res = "peer address: " + m_peerAddress.toString(); - res += "\nServices:\n"; - for (auto &myPair : m_servicesMap) { - res += myPair.second->toString() + "\n"; - // myPair.second is the value +int BLEClient::serviceDiscoveredCB(uint16_t conn_handle, const struct ble_gatt_error *error, const struct ble_gatt_svc *service, void *arg) +{ + log_d("Service Discovered >> status: %d handle: %d", error->status, (error->status == 0) ? service->start_handle : -1); + + ble_task_data_t *pTaskData = (ble_task_data_t*)arg; + BLEClient *client = (BLEClient*)pTaskData->pATT; + + // Make sure the service discovery is for this device + if(client->getConnId() != conn_handle){ + return 0; } - return res; -} // toString -#endif /* CONFIG_BLUEDROID_ENABLED */ + if(error->status == 0) { + // Found a service - add it to the vector + BLERemoteService* pRemoteService = new BLERemoteService(client, service); + client->m_servicesMap.insert(std::pair(pRemoteService->getUUID().toString().c_str(), pRemoteService)); + client->m_servicesMapByInstID.insert(std::pair(pRemoteService, service->start_handle)); + return 0; + } + + if(error->status == BLE_HS_EDONE) { + pTaskData->rc = 0; + } else { + log_e("serviceDiscoveredCB() rc=%d %s", error->status, BLEUtils::returnCodeToString(error->status)); + pTaskData->rc = error->status; + } + + xTaskNotifyGive(pTaskData->task); + + log_d("<< Service Discovered"); + return error->status; +} + +std::map *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. + */ + log_v(">> getServices"); + // TODO implement retrieving services from cache + m_semaphoreSearchCmplEvt.take("getServices"); + clearServices(); // Clear any services that may exist. + + int errRc = 0; + TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); + ble_task_data_t taskData = {this, cur_task, 0, nullptr}; + + errRc = ble_gattc_disc_all_svcs(m_conn_id, BLEClient::serviceDiscoveredCB, &taskData); + if (errRc != 0) { + log_e("ble_gattc_disc_all_svcs: rc=%d %s", errRc, BLEUtils::returnCodeToString(errRc)); + m_lastErr = errRc; + m_semaphoreSearchCmplEvt.give(); + return &m_servicesMap; + } + // If successful, remember that we now have services. + m_haveServices = m_servicesMap.size() > 0; + m_semaphoreSearchCmplEvt.give(); + log_v("<< getServices"); + return &m_servicesMap; +} // getServices + +int BLEClient::handleGAPEvent(struct ble_gap_event *event, void *arg) { + BLEClient *client = (BLEClient *)arg; + int rc; + + log_d("BLEClient", "Got Client event %s", BLEUtils::gapEventToString(event->type)); + + switch (event->type) { + case BLE_GAP_EVENT_DISCONNECT: + { + rc = event->disconnect.reason; + // If Host reset tell the device now before returning to prevent + // any errors caused by calling host functions before resyncing. + switch (rc) { + case BLE_HS_ECONTROLLER: + case BLE_HS_ETIMEOUT_HCI: + case BLE_HS_ENOTSYNCED: + case BLE_HS_EOS: + log_d("BLEClient", "Disconnect - host reset, rc=%d", rc); + BLEDevice::onReset(rc); + break; + default: + // Check that the event is for this client. + if (client->m_conn_id != event->disconnect.conn.conn_handle) { + return 0; + } + break; + } + + // Stop the disconnect timer since we are now disconnected. + ble_npl_callout_stop(&client->m_dcTimer); + + // Remove the device from ignore list so we will scan it again + // BLEDevice::removeIgnored(client->m_peerAddress); + + // No longer connected, clear the connection ID. + client->m_conn_id = BLE_HS_CONN_HANDLE_NONE; + + // If we received a connected event but did not get established (no PDU) + // then a disconnect event will be sent but we should not send it to the + // app for processing. Instead we will ensure the task is released + // and report the error. + if (!client->m_isConnected) { + break; + } + + log_i("BLEClient", "disconnect; reason=%d, %s", rc, BLEUtils::returnCodeToString(rc)); + + BLEDevice::removePeerDevice(client->m_appId, true); + client->m_isConnected = false; + if (client->m_pClientCallbacks != nullptr) { + client->m_pClientCallbacks->onDisconnect(client); + } + break; + } // BLE_GAP_EVENT_DISCONNECT + + case BLE_GAP_EVENT_CONNECT: + { + // If we aren't waiting for this connection response + // we should drop the connection immediately. + if (client->isConnected() || client->m_pTaskData == nullptr) { + ble_gap_terminate(event->connect.conn_handle, BLE_ERR_REM_USER_CONN_TERM); + return 0; + } + + rc = event->connect.status; + if (rc == 0) { + log_i("BLEClient", "Connected event"); + + client->m_conn_id = event->connect.conn_handle; + + rc = ble_gattc_exchange_mtu(client->m_conn_id, NULL, NULL); + if (rc != 0) { + log_e("BLEClient", "MTU exchange error; rc=%d %s", rc, BLEUtils::returnCodeToString(rc)); + break; + } + + // In the case of a multiconnecting device we ignore this device when + // scanning since we are already connected to it + // BLEDevice::addIgnored(client->m_peerAddress); + } else { + client->m_conn_id = BLE_HS_CONN_HANDLE_NONE; + break; + } + + return 0; + } // BLE_GAP_EVENT_CONNECT + + case BLE_GAP_EVENT_NOTIFY_RX: + { + if (client->m_conn_id != event->notify_rx.conn_handle) { + return 0; + } + + // If a notification comes before this flag is set we might + // access a vector while it is being cleared in connect() + if (!client->m_isConnected) { + return 0; + } + + log_d("BLEClient", "Notify received for handle: %d", event->notify_rx.attr_handle); + + for (auto &myPair : client->m_servicesMap) { + // Dont waste cycles searching services without this handle in its range + if (myPair.second->getEndHandle() < event->notify_rx.attr_handle) { + continue; + } + + auto cMap = &myPair.second->m_characteristicMap; + log_d("BLEClient", "checking service %s for handle: %d", myPair.second->getUUID().toString().c_str(), event->notify_rx.attr_handle); + + auto characteristic = cMap->cbegin(); + for (; characteristic != cMap->cend(); ++characteristic) { + if (characteristic->second->m_handle == event->notify_rx.attr_handle) { + break; + } + } + + if (characteristic != cMap->cend()) { + log_d("BLEClient", "Got Notification for characteristic %s", characteristic->second->toString().c_str()); + + characteristic->second->m_semaphoreReadCharEvt.take(); + characteristic->second->m_value = String((char *)event->notify_rx.om->om_data, event->notify_rx.om->om_len); + characteristic->second->m_semaphoreReadCharEvt.give(); + + if (characteristic->second->m_notifyCallback != nullptr) { + log_d("Invoking callback for notification on characteristic %s", characteristic->second->toString().c_str()); + characteristic->second->m_notifyCallback(characteristic->second, event->notify_rx.om->om_data, event->notify_rx.om->om_len, !event->notify_rx.indication); + } + break; + } + } + + return 0; + } // BLE_GAP_EVENT_NOTIFY_RX + + case BLE_GAP_EVENT_CONN_UPDATE_REQ: + case BLE_GAP_EVENT_L2CAP_UPDATE_REQ: + { + if (client->m_conn_id != event->conn_update_req.conn_handle) { + return 0; + } + log_d("Peer requesting to update connection parameters"); + log_d( + "MinInterval: %d, MaxInterval: %d, Latency: %d, Timeout: %d", event->conn_update_req.peer_params->itvl_min, + event->conn_update_req.peer_params->itvl_max, event->conn_update_req.peer_params->latency, event->conn_update_req.peer_params->supervision_timeout + ); + + rc = client->m_pClientCallbacks->onConnParamsUpdateRequest(client, event->conn_update_req.peer_params) ? 0 : BLE_ERR_CONN_PARMS; + + if (!rc && event->type == BLE_GAP_EVENT_CONN_UPDATE_REQ) { + event->conn_update_req.self_params->itvl_min = client->m_pConnParams.itvl_min; + event->conn_update_req.self_params->itvl_max = client->m_pConnParams.itvl_max; + event->conn_update_req.self_params->latency = client->m_pConnParams.latency; + event->conn_update_req.self_params->supervision_timeout = client->m_pConnParams.supervision_timeout; + } + + log_d("%s peer params", (rc == 0) ? "Accepted" : "Rejected"); + return rc; + } // BLE_GAP_EVENT_CONN_UPDATE_REQ, BLE_GAP_EVENT_L2CAP_UPDATE_REQ + + case BLE_GAP_EVENT_CONN_UPDATE: + { + if (client->m_conn_id != event->conn_update.conn_handle) { + return 0; + } + if (event->conn_update.status == 0) { + log_i("Connection parameters updated."); + } else { + log_e("Update connection parameters failed."); + } + return 0; + } // BLE_GAP_EVENT_CONN_UPDATE + + case BLE_GAP_EVENT_ENC_CHANGE: + { + if (client->m_conn_id != event->enc_change.conn_handle) { + return 0; + } + + if (event->enc_change.status == 0 || event->enc_change.status == (BLE_HS_ERR_HCI_BASE + BLE_ERR_PINKEY_MISSING)) { + struct ble_gap_conn_desc desc; + rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc); + assert(rc == 0); + + if (event->enc_change.status == (BLE_HS_ERR_HCI_BASE + BLE_ERR_PINKEY_MISSING)) { + // Key is missing, try deleting. + ble_store_util_delete_peer(&desc.peer_id_addr); + } else if (BLEDevice::m_securityCallbacks != nullptr) { + BLEDevice::m_securityCallbacks->onAuthenticationComplete(&desc); + } else { + client->m_pClientCallbacks->onAuthenticationComplete(&desc); + } + } + + rc = event->enc_change.status; + break; + } //BLE_GAP_EVENT_ENC_CHANGE + + case BLE_GAP_EVENT_MTU: + { + if (client->m_conn_id != event->mtu.conn_handle) { + return 0; + } + log_i("mtu update event; conn_handle=%d mtu=%d", event->mtu.conn_handle, event->mtu.value); + rc = 0; + break; + } // BLE_GAP_EVENT_MTU + + case BLE_GAP_EVENT_PASSKEY_ACTION: + { + struct ble_sm_io pkey = {0, 0}; + + if (client->m_conn_id != event->passkey.conn_handle) { + return 0; + } + + if (event->passkey.params.action == BLE_SM_IOACT_DISP) { + pkey.action = event->passkey.params.action; + pkey.passkey = BLESecurity::m_passkey; // This is the passkey to be entered on peer + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + log_d("ble_sm_inject_io result: %d", rc); + + } else if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) { + log_d("Passkey on device's display: %d", event->passkey.params.numcmp); + pkey.action = event->passkey.params.action; + // Compatibility only - Do not use, should be removed the in future + if (BLEDevice::m_securityCallbacks != nullptr) { + pkey.numcmp_accept = BLEDevice::m_securityCallbacks->onConfirmPIN(event->passkey.params.numcmp); + //////////////////////////////////////////////////// + } else { + pkey.numcmp_accept = client->m_pClientCallbacks->onConfirmPIN(event->passkey.params.numcmp); + } + + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + log_d("ble_sm_inject_io result: %d", rc); + + //TODO: Handle out of band pairing + } else if (event->passkey.params.action == BLE_SM_IOACT_OOB) { + static uint8_t tem_oob[16] = {0}; + pkey.action = event->passkey.params.action; + for (int i = 0; i < 16; i++) { + pkey.oob[i] = tem_oob[i]; + } + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + log_d("ble_sm_inject_io result: %d", rc); + //////// + } else if (event->passkey.params.action == BLE_SM_IOACT_INPUT) { + log_d("Enter the passkey"); + pkey.action = event->passkey.params.action; + + // Compatibility only - Do not use, should be removed the in future + if (BLEDevice::m_securityCallbacks != nullptr) { + pkey.passkey = BLEDevice::m_securityCallbacks->onPassKeyRequest(); + ///////////////////////////////////////////// + } else { + pkey.passkey = client->m_pClientCallbacks->onPassKeyRequest(); + } + + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + log_d("ble_sm_inject_io result: %d", rc); + + } else if (event->passkey.params.action == BLE_SM_IOACT_NONE) { + log_d("No passkey action required"); + } + + return 0; + } // BLE_GAP_EVENT_PASSKEY_ACTION + + default: + { + return 0; + } + } // Switch + + if (client->m_pTaskData != nullptr) { + client->m_pTaskData->rc = rc; + if (client->m_pTaskData->task) { + xTaskNotifyGive(client->m_pTaskData->task); + } + client->m_pTaskData = nullptr; + } + + return 0; +} // handleGAPEvent + +bool BLEClientCallbacks::onConnParamsUpdateRequest(BLEClient *pClient, const ble_gap_upd_params *params) { + log_d("BLEClientCallbacks", "onConnParamsUpdateRequest: default"); + return true; +} + +uint32_t BLEClientCallbacks::onPassKeyRequest(){ + log_d("onPassKeyRequest: default: 123456"); + return 123456; +} + +void BLEClientCallbacks::onAuthenticationComplete(ble_gap_conn_desc* desc){ + log_d("onAuthenticationComplete: default"); +} + +bool BLEClientCallbacks::onConfirmPIN(uint32_t pin){ + log_d("onConfirmPIN: default: true"); + return true; +} + +#endif // CONFIG_NIMBLE_ENABLED + +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ diff --git a/libraries/BLE/src/BLEClient.h b/libraries/BLE/src/BLEClient.h index ddb932fcd95..7f90de4c733 100644 --- a/libraries/BLE/src/BLEClient.h +++ b/libraries/BLE/src/BLEClient.h @@ -1,28 +1,66 @@ /* - * BLEDevice.h + * BLEClient.h * * Created on: Mar 22, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ -#ifndef MAIN_BLEDEVICE_H_ -#define MAIN_BLEDEVICE_H_ +#ifndef MAIN_BLECLIENT_H_ +#define MAIN_BLECLIENT_H_ #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ -#include #include #include #include -//#include "BLEExceptions.h" #include "BLERemoteService.h" #include "BLEService.h" #include "BLEAddress.h" #include "BLEAdvertisedDevice.h" +#include "BLEUtils.h" + +/*************************************************************************** + * Bluedroid includes * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) +#include +#endif + +/*************************************************************************** + * NimBLE includes and definitions * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +#include +#include +#include +#define ESP_GATT_IF_NONE BLE_HS_CONN_HANDLE_NONE +#endif + +/*************************************************************************** + * NimBLE types * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +typedef uint16_t esp_gatt_if_t; +#endif + +/*************************************************************************** + * Forward declarations * + ***************************************************************************/ class BLERemoteService; class BLEClientCallbacks; @@ -33,34 +71,57 @@ class BLEAdvertisedDevice; */ class BLEClient { public: + /*************************************************************************** + * Common public properties * + ***************************************************************************/ + + uint16_t m_appId; + + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ + BLEClient(); ~BLEClient(); - bool connect(BLEAdvertisedDevice *device); bool connectTimeout(BLEAdvertisedDevice *device, uint32_t timeoutMS = portMAX_DELAY); - bool connect(BLEAddress address, esp_ble_addr_type_t type = BLE_ADDR_TYPE_PUBLIC, uint32_t timeoutMS = portMAX_DELAY); // Connect to the remote BLE Server - void disconnect(); // Disconnect from the remote BLE Server - BLEAddress getPeerAddress(); // Get the address of the remote BLE Server - int getRssi(); // Get the RSSI of the remote BLE Server - std::map *getServices(); // Get a map of the services offered by the remote BLE Server - BLERemoteService *getService(const char *uuid); // Get a reference to a specified service offered by the remote BLE server. - BLERemoteService *getService(BLEUUID uuid); // Get a reference to a specified service offered by the remote BLE server. - String getValue(BLEUUID serviceUUID, BLEUUID characteristicUUID); // Get the value of a given characteristic at a given service. - - void handleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); - - bool isConnected(); // Return true if we are connected. - + bool connect(BLEAddress address, uint8_t type = 0, uint32_t timeoutMS = portMAX_DELAY); + bool secureConnection(); + void disconnect(); + BLEAddress getPeerAddress(); + int getRssi(); + std::map *getServices(); + BLERemoteService *getService(const char *uuid); + BLERemoteService *getService(BLEUUID uuid); + String getValue(BLEUUID serviceUUID, BLEUUID characteristicUUID); + bool isConnected(); void setClientCallbacks(BLEClientCallbacks *pClientCallbacks); - void setValue(BLEUUID serviceUUID, BLEUUID characteristicUUID, String value); // Set the value of a given characteristic at a given service. - - String toString(); // Return a string representation of this client. + void setValue(BLEUUID serviceUUID, BLEUUID characteristicUUID, String value); + String toString(); uint16_t getConnId(); esp_gatt_if_t getGattcIf(); uint16_t getMTU(); bool setMTU(uint16_t mtu); - uint16_t m_appId; + /*************************************************************************** + * Bluedroid public declarations * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + void handleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); +#endif + + /*************************************************************************** + * NimBLE public declarations * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + static int handleGAPEvent(struct ble_gap_event *event, void *arg); + static int serviceDiscoveredCB(uint16_t conn_handle, + const struct ble_gatt_error *error, + const struct ble_gatt_svc *service, + void *arg); +#endif private: friend class BLEDevice; @@ -68,15 +129,14 @@ class BLEClient { friend class BLERemoteCharacteristic; friend class BLERemoteDescriptor; - void gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); + /*************************************************************************** + * Common private properties * + ***************************************************************************/ - BLEAddress m_peerAddress = BLEAddress((uint8_t *)"\0\0\0\0\0\0"); // The BD address of the remote server. + 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; - bool m_haveServices = false; // Have we previously obtain the set of services from the remote server. - bool m_isConnected = false; // Are we currently connected. - + bool m_haveServices = false; + bool m_isConnected = false; BLEClientCallbacks *m_pClientCallbacks; FreeRTOS::Semaphore m_semaphoreRegEvt = FreeRTOS::Semaphore("RegEvt"); FreeRTOS::Semaphore m_semaphoreOpenEvt = FreeRTOS::Semaphore("OpenEvt"); @@ -84,20 +144,76 @@ class BLEClient { FreeRTOS::Semaphore m_semaphoreRssiCmplEvt = FreeRTOS::Semaphore("RssiCmplEvt"); std::map m_servicesMap; std::map m_servicesMapByInstID; - void clearServices(); // Clear any existing services. uint16_t m_mtu = 23; -}; // class BLEDevice + + /*************************************************************************** + * Bluedroid private properties * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + esp_gatt_if_t m_gattc_if; +#endif + + /*************************************************************************** + * NimBLE private properties * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + int m_lastErr; + int32_t m_connectTimeout; + ble_gap_conn_params m_pConnParams; + ble_task_data_t *m_pTaskData; + ble_npl_callout m_dcTimer; +#endif + + /*************************************************************************** + * Common private declarations * + ***************************************************************************/ + + void clearServices(); + + /*************************************************************************** + * Bluedroid private declarations * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + void gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); +#endif + + /*************************************************************************** + * NimBLE private declarations * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + static void dcTimerCb(ble_npl_event *event); +#endif +}; // class BLEClient /** * @brief Callbacks associated with a %BLE client. */ class BLEClientCallbacks { public: + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ + virtual ~BLEClientCallbacks(){}; - virtual void onConnect(BLEClient *pClient) = 0; - virtual void onDisconnect(BLEClient *pClient) = 0; + virtual void onConnect(BLEClient *pClient); + virtual void onDisconnect(BLEClient *pClient); + + /*************************************************************************** + * NimBLE public declarations * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + virtual bool onConnParamsUpdateRequest(BLEClient *pClient, const ble_gap_upd_params *params); + virtual uint32_t onPassKeyRequest(); + virtual void onAuthenticationComplete(ble_gap_conn_desc* desc); + virtual bool onConfirmPIN(uint32_t pin); +#endif }; -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ -#endif /* MAIN_BLEDEVICE_H_ */ +#endif /* MAIN_BLECLIENT_H_ */ diff --git a/libraries/BLE/src/BLEConnInfo.h b/libraries/BLE/src/BLEConnInfo.h new file mode 100644 index 00000000000..b81ab93c28f --- /dev/null +++ b/libraries/BLE/src/BLEConnInfo.h @@ -0,0 +1,110 @@ +/* + * Copyright 2020-2025 Ryan Powell and + * esp-nimble-cpp, NimBLE-Arduino contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BLECONNINFO_H_ +#define BLECONNINFO_H_ + +#if defined(CONFIG_NIMBLE_ENABLED) + +#include +#include "BLEAddress.h" + +/** + * @brief Connection information. + */ +class BLEConnInfo { +public: + /** @brief Gets the over-the-air address of the connected peer */ + BLEAddress getAddress() const { + return BLEAddress(m_desc.peer_ota_addr); + } + + /** @brief Gets the ID address of the connected peer */ + BLEAddress getIdAddress() const { + return BLEAddress(m_desc.peer_id_addr); + } + + /** @brief Gets the connection handle (also known as the connection id) of the connected peer */ + uint16_t getConnHandle() const { + return m_desc.conn_handle; + } + + /** @brief Gets the connection interval for this connection (in 1.25ms units) */ + uint16_t getConnInterval() const { + return m_desc.conn_itvl; + } + + /** @brief Gets the supervision timeout for this connection (in 10ms units) */ + uint16_t getConnTimeout() const { + return m_desc.supervision_timeout; + } + + /** @brief Gets the allowable latency for this connection (unit = number of intervals) */ + uint16_t getConnLatency() const { + return m_desc.conn_latency; + } + + /** @brief Gets the maximum transmission unit size for this connection (in bytes) */ + uint16_t getMTU() const { + return ble_att_mtu(m_desc.conn_handle); + } + + /** @brief Check if we are in the master role in this connection */ + bool isMaster() const { + return (m_desc.role == BLE_GAP_ROLE_MASTER); + } + + /** @brief Check if we are in the slave role in this connection */ + bool isSlave() const { + return (m_desc.role == BLE_GAP_ROLE_SLAVE); + } + + /** @brief Check if we are connected to a bonded peer */ + bool isBonded() const { + return (m_desc.sec_state.bonded == 1); + } + + /** @brief Check if the connection in encrypted */ + bool isEncrypted() const { + return (m_desc.sec_state.encrypted == 1); + } + + /** @brief Check if the the connection has been authenticated */ + bool isAuthenticated() const { + return (m_desc.sec_state.authenticated == 1); + } + + /** @brief Gets the key size used to encrypt the connection */ + uint8_t getSecKeySize() const { + return m_desc.sec_state.key_size; + } + +private: + friend class BLEServer; + friend class BLEClient; + friend class BLECharacteristic; + friend class BLEDescriptor; + + ble_gap_conn_desc m_desc{}; + BLEConnInfo(){}; + BLEConnInfo(ble_gap_conn_desc desc) { + m_desc = desc; + } +}; +#endif + +#endif // BLECONNINFO_H_ diff --git a/libraries/BLE/src/BLEDescriptor.cpp b/libraries/BLE/src/BLEDescriptor.cpp index 69a93e57201..51c77bb9b49 100644 --- a/libraries/BLE/src/BLEDescriptor.cpp +++ b/libraries/BLE/src/BLEDescriptor.cpp @@ -3,25 +3,50 @@ * * Created on: Jun 22, 2017 * Author: kolban + * + * Modified on: Apr 3, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ + #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ + #include #include #include #include #include "sdkconfig.h" #include +#include "BLE2904.h" #include "BLEService.h" #include "BLEDescriptor.h" #include "GeneralUtils.h" #include "esp32-hal-log.h" +/*************************************************************************** + * Common definitions * + ***************************************************************************/ + #define NULL_HANDLE (0xffff) +/*************************************************************************** + * Common global variables * + ***************************************************************************/ + +static BLEDescriptorCallbacks defaultCallbacks; + +/*************************************************************************** + * Common functions * + ***************************************************************************/ + /** * @brief BLEDescriptor constructor. */ @@ -32,13 +57,15 @@ BLEDescriptor::BLEDescriptor(const char *uuid, uint16_t len) : BLEDescriptor(BLE */ BLEDescriptor::BLEDescriptor(BLEUUID uuid, uint16_t max_len) { m_bleUUID = uuid; - m_value.attr_len = 0; // Initial length is 0. - m_value.attr_max_len = max_len; // Maximum length of the data. - m_handle = NULL_HANDLE; // Handle is initially unknown. - m_pCharacteristic = nullptr; // No initial characteristic. - m_pCallback = nullptr; // No initial callback. - + m_handle = NULL_HANDLE; // Handle is initially unknown. + m_pCharacteristic = nullptr; // No initial characteristic. + m_pCallback = nullptr; // No initial callback. + m_value.attr_len = 0; // Initial length is 0. + m_value.attr_max_len = max_len; // Maximum length of the data. m_value.attr_value = (uint8_t *)malloc(max_len); // Allocate storage for the value. +#if CONFIG_NIMBLE_ENABLED + m_removed = 0; +#endif } // BLEDescriptor /** @@ -62,6 +89,7 @@ void BLEDescriptor::executeCreate(BLECharacteristic *pCharacteristic) { m_pCharacteristic = pCharacteristic; // Save the characteristic associated with this service. +#if CONFIG_BLUEDROID_ENABLED esp_attr_control_t control; control.auto_rsp = ESP_GATT_AUTO_RSP; m_semaphoreCreateEvt.take("executeCreate"); @@ -73,6 +101,7 @@ void BLEDescriptor::executeCreate(BLECharacteristic *pCharacteristic) { } m_semaphoreCreateEvt.wait("executeCreate"); +#endif log_v("<< executeCreate"); } // executeCreate @@ -107,6 +136,118 @@ uint8_t *BLEDescriptor::getValue() { return m_value.attr_value; } // getValue +/** + * @brief Get the characteristic this descriptor belongs to. + * @return A pointer to the characteristic this descriptor belongs to. + */ +BLECharacteristic *BLEDescriptor::getCharacteristic() { + return m_pCharacteristic; +} // getCharacteristic + +/** + * @brief Set the callback handlers for this descriptor. + * @param [in] pCallbacks An instance of a callback structure used to define any callbacks for the descriptor. + */ +void BLEDescriptor::setCallbacks(BLEDescriptorCallbacks *pCallback) { + log_v(">> setCallbacks: 0x%x", (uint32_t)pCallback); + if (pCallback != nullptr) { + m_pCallback = pCallback; + } else { + m_pCallback = &defaultCallbacks; + } + log_v("<< setCallbacks"); +} // setCallbacks + +/** + * @brief Set the handle of this descriptor. + * Set the handle of this descriptor to be the supplied value. + * @param [in] handle The handle to be associated with this descriptor. + * @return N/A. + */ +void BLEDescriptor::setHandle(uint16_t handle) { +#if defined(CONFIG_BLUEDROID_ENABLED) + log_v(">> setHandle(0x%.2x): Setting descriptor handle to be 0x%.2x", handle, handle); + m_handle = handle; + log_v("<< setHandle()"); +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + log_w("NimBLE does not support manually setting the handle of a descriptor. Ignoring request."); +#endif +} // 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 > m_value.attr_max_len) { + log_e("Size %d too large, must be no bigger than %d", length, m_value.attr_max_len); + return; + } + + m_semaphoreSetValue.take(); + m_value.attr_len = length; + memcpy(m_value.attr_value, data, length); +#if CONFIG_BLUEDROID_ENABLED + if (m_handle != NULL_HANDLE) { + esp_ble_gatts_set_attr_value(m_handle, length, (const uint8_t *)data); + log_d("Set the value in the GATTS database using handle 0x%x", m_handle); + } +#endif + m_semaphoreSetValue.give(); +} // setValue + +/** + * @brief Set the value of the descriptor. + * @param [in] value The value of the descriptor in string form. + */ +void BLEDescriptor::setValue(String value) { + setValue((uint8_t *)value.c_str(), value.length()); +} // setValue + +void BLEDescriptor::setAccessPermissions(uint8_t perm) { + m_permissions = perm; +} + +/** + * @brief Return a string representation of the descriptor. + * @return A string representation of the descriptor. + */ +String BLEDescriptor::toString() { + char hex[5]; + snprintf(hex, sizeof(hex), "%04x", m_handle); + String res = "UUID: " + m_bleUUID.toString() + ", handle: 0x" + hex; + return res; +} // toString + +BLEDescriptorCallbacks::~BLEDescriptorCallbacks() {} + +/** + * @brief Callback function to support a read request. + * @param [in] pDescriptor The descriptor that is the source of the event. + */ +void BLEDescriptorCallbacks::onRead(BLEDescriptor *pDescriptor) { + log_d("BLEDescriptorCallbacks", ">> onRead: default"); + log_d("BLEDescriptorCallbacks", "<< onRead"); +} // onRead + +/** + * @brief Callback function to support a write request. + * @param [in] pDescriptor The descriptor that is the source of the event. + */ +void BLEDescriptorCallbacks::onWrite(BLEDescriptor *pDescriptor) { + log_d("BLEDescriptorCallbacks", ">> onWrite: default"); + log_d("BLEDescriptorCallbacks", "<< onWrite"); +} // onWrite + +/*************************************************************************** + * Bluedroid functions * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + /** * @brief Handle GATT server events for the descripttor. * @param [in] event @@ -185,88 +326,84 @@ void BLEDescriptor::handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_i } // switch event } // handleGATTServerEvent -/** - * @brief Set the callback handlers for this descriptor. - * @param [in] pCallbacks An instance of a callback structure used to define any callbacks for the descriptor. - */ -void BLEDescriptor::setCallbacks(BLEDescriptorCallbacks *pCallback) { - log_v(">> setCallbacks: 0x%x", (uint32_t)pCallback); - m_pCallback = pCallback; - log_v("<< setCallbacks"); -} // setCallbacks +#endif -/** - * @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) { - log_v(">> setHandle(0x%.2x): Setting descriptor handle to be 0x%.2x", handle, handle); - m_handle = handle; - log_v("<< setHandle()"); -} // setHandle +/*************************************************************************** + * NimBLE functions * + ***************************************************************************/ -/** - * @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) { - log_e("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); - if (m_handle != NULL_HANDLE) { - esp_ble_gatts_set_attr_value(m_handle, length, (const uint8_t *)data); - log_d("Set the value in the GATTS database using handle 0x%x", m_handle); - } -} // setValue +#if defined(CONFIG_NIMBLE_ENABLED) /** - * @brief Set the value of the descriptor. - * @param [in] value The value of the descriptor in string form. + * @brief Handle GATT server events for the descriptor. + * @param [in] conn_handle The connection handle. + * @param [in] attr_handle The attribute handle. + * @param [in] ctxt The GATT access context. + * @param [in] arg The argument. */ -void BLEDescriptor::setValue(String value) { - setValue((uint8_t *)value.c_str(), value.length()); -} // setValue +int BLEDescriptor::handleGATTServerEvent(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) { + const ble_uuid_t *uuid; + int rc; + struct ble_gap_conn_desc desc; + BLEDescriptor *pDescriptor = (BLEDescriptor *)arg; + + log_d("Descriptor %s %s event", pDescriptor->getUUID().toString().c_str(), ctxt->op == BLE_GATT_ACCESS_OP_READ_DSC ? "Read" : "Write"); + + uuid = ctxt->chr->uuid; + if (ble_uuid_cmp(uuid, &pDescriptor->getUUID().getNative()->u) == 0) { + switch (ctxt->op) { + case BLE_GATT_ACCESS_OP_READ_DSC: + { + rc = ble_gap_conn_find(conn_handle, &desc); + assert(rc == 0); + + // If the packet header is only 8 bytes this is a follow up of a long read + // so we don't want to call the onRead() callback again. + if (ctxt->om->om_pkthdr_len > 8 || pDescriptor->m_value.attr_len <= (ble_att_mtu(desc.conn_handle) - 3)) { + pDescriptor->m_pCallback->onRead(pDescriptor); + } -void BLEDescriptor::setAccessPermissions(esp_gatt_perm_t perm) { - m_permissions = perm; -} + ble_npl_hw_enter_critical(); + rc = os_mbuf_append(ctxt->om, pDescriptor->m_value.attr_value, pDescriptor->m_value.attr_len); + ble_npl_hw_exit_critical(0); + return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; + } -/** - * @brief Return a string representation of the descriptor. - * @return A string representation of the descriptor. - */ -String BLEDescriptor::toString() { - char hex[5]; - snprintf(hex, sizeof(hex), "%04x", m_handle); - String res = "UUID: " + m_bleUUID.toString() + ", handle: 0x" + hex; - return res; -} // toString + case BLE_GATT_ACCESS_OP_WRITE_DSC: + { + uint16_t att_max_len = pDescriptor->m_value.attr_max_len; -BLEDescriptorCallbacks::~BLEDescriptorCallbacks() {} + if (ctxt->om->om_len > att_max_len) { + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } -/** - * @brief Callback function to support a read request. - * @param [in] pDescriptor The descriptor that is the source of the event. - */ -void BLEDescriptorCallbacks::onRead(BLEDescriptor *pDescriptor) { - log_d("BLEDescriptorCallbacks", ">> onRead: default"); - log_d("BLEDescriptorCallbacks", "<< onRead"); -} // onRead + uint8_t buf[att_max_len]; + size_t len = ctxt->om->om_len; + memcpy(buf, ctxt->om->om_data, len); + os_mbuf *next; + next = SLIST_NEXT(ctxt->om, om_next); + while (next != NULL) { + if ((len + next->om_len) > att_max_len) { + return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } + memcpy(&buf[len], next->om_data, next->om_len); + len += next->om_len; + next = SLIST_NEXT(next, om_next); + } -/** - * @brief Callback function to support a write request. - * @param [in] pDescriptor The descriptor that is the source of the event. - */ -void BLEDescriptorCallbacks::onWrite(BLEDescriptor *pDescriptor) { - log_d("BLEDescriptorCallbacks", ">> onWrite: default"); - log_d("BLEDescriptorCallbacks", "<< onWrite"); -} // onWrite + pDescriptor->setValue(buf, len); + pDescriptor->m_pCallback->onWrite(pDescriptor); + return 0; + } + + default: break; + } + } + + return BLE_ATT_ERR_UNLIKELY; +} + +#endif -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ diff --git a/libraries/BLE/src/BLEDescriptor.h b/libraries/BLE/src/BLEDescriptor.h index e155a1f2971..58a56a0aa65 100644 --- a/libraries/BLE/src/BLEDescriptor.h +++ b/libraries/BLE/src/BLEDescriptor.h @@ -3,6 +3,10 @@ * * Created on: Jun 22, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ #ifndef COMPONENTS_CPP_UTILS_BLEDESCRIPTOR_H_ @@ -11,13 +15,50 @@ #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ + #include #include "BLEUUID.h" #include "BLECharacteristic.h" -#include #include "RTOS.h" +/*************************************************************************** + * Bluedroid includes * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) +#include +#endif + +/*************************************************************************** + * NimBLE includes * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +#include "host/ble_att.h" +#include "BLEConnInfo.h" +#endif + +/*************************************************************************** + * NimBLE types * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +typedef struct { + uint16_t attr_max_len; /*!< attribute max value length */ + uint16_t attr_len; /*!< attribute current value length */ + uint8_t *attr_value; /*!< the pointer to attribute value */ +} esp_attr_value_t; +#endif + +/*************************************************************************** + * Forward declarations * + ***************************************************************************/ + class BLEService; class BLECharacteristic; class BLEDescriptorCallbacks; @@ -27,33 +68,83 @@ class BLEDescriptorCallbacks; */ class BLEDescriptor { public: + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ + BLEDescriptor(const char *uuid, uint16_t max_len = 100); BLEDescriptor(BLEUUID uuid, uint16_t max_len = 100); virtual ~BLEDescriptor(); - uint16_t getHandle(); // Get the handle of the descriptor. - size_t getLength(); // Get the length of the value of the descriptor. - BLEUUID getUUID(); // Get the UUID of the descriptor. - uint8_t *getValue(); // Get a pointer to the value of the descriptor. - void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); + uint16_t getHandle(); // Get the handle of the descriptor. + size_t getLength(); // Get the length of the value of the descriptor. + BLEUUID getUUID(); // Get the UUID of the descriptor. + uint8_t *getValue(); // Get a pointer to the value of the descriptor. + BLECharacteristic *getCharacteristic(); // Get the characteristic that this descriptor belongs to. - void setAccessPermissions(esp_gatt_perm_t perm); // Set the permissions of the descriptor. + void setAccessPermissions(uint8_t perm); // Set the permissions of the descriptor. void setCallbacks(BLEDescriptorCallbacks *pCallbacks); // Set callbacks to be invoked for the descriptor. void setValue(uint8_t *data, size_t size); // Set the value of the descriptor as a pointer to data. void setValue(String value); // Set the value of the descriptor as a data buffer. String toString(); // Convert the descriptor to a string representation. + /*************************************************************************** + * Bluedroid public declarations * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); +#endif + + /*************************************************************************** + * NimBLE public declarations * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + static int handleGATTServerEvent(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg); +#endif + private: friend class BLEDescriptorMap; friend class BLECharacteristic; + friend class BLEService; + friend class BLE2901; + friend class BLE2902; + friend class BLE2904; + + /*************************************************************************** + * Common private properties * + ***************************************************************************/ + BLEUUID m_bleUUID; uint16_t m_handle; + esp_attr_value_t m_value; BLEDescriptorCallbacks *m_pCallback; BLECharacteristic *m_pCharacteristic; - esp_gatt_perm_t m_permissions = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; + FreeRTOS::Semaphore m_semaphoreSetValue = FreeRTOS::Semaphore("SetValue"); + + /*************************************************************************** + * Bluedroid private properties * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); - esp_attr_value_t m_value; + uint8_t m_permissions = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE; +#endif + + /*************************************************************************** + * NimBLE private properties * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + uint8_t m_permissions = HA_FLAG_PERM_RW; + uint8_t m_removed = 0; +#endif + + /*************************************************************************** + * Common private declarations * + ***************************************************************************/ void executeCreate(BLECharacteristic *pCharacteristic); void setHandle(uint16_t handle); @@ -68,11 +159,16 @@ class BLEDescriptor { */ class BLEDescriptorCallbacks { public: + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ + + BLEDescriptorCallbacks(); virtual ~BLEDescriptorCallbacks(); virtual void onRead(BLEDescriptor *pDescriptor); virtual void onWrite(BLEDescriptor *pDescriptor); }; -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ #endif /* COMPONENTS_CPP_UTILS_BLEDESCRIPTOR_H_ */ diff --git a/libraries/BLE/src/BLEDescriptorMap.cpp b/libraries/BLE/src/BLEDescriptorMap.cpp index 732fb62cdcf..42303932183 100644 --- a/libraries/BLE/src/BLEDescriptorMap.cpp +++ b/libraries/BLE/src/BLEDescriptorMap.cpp @@ -3,21 +3,50 @@ * * Created on: Jun 22, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ + #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ + #include #include #include "BLECharacteristic.h" #include "BLEDescriptor.h" -#include // ESP32 BLE #ifdef ARDUINO_ARCH_ESP32 #include "esp32-hal-log.h" #endif +/*************************************************************************** + * Bluedroid includes * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) +#include // ESP32 BLE +#endif + +/*************************************************************************** + * NimBLE includes * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +#include "host/ble_gatt.h" +#endif + +/*************************************************************************** + * Common functions * + ***************************************************************************/ + /** * @brief Return the descriptor by UUID. * @param [in] UUID The UUID to look up the descriptor. @@ -81,6 +110,24 @@ void BLEDescriptorMap::setByHandle(uint16_t handle, BLEDescriptor *pDescriptor) m_handleMap.insert(std::pair(handle, pDescriptor)); } // setByHandle +/** + * @brief Get the number of registered descriptors. + * @return The number of registered descriptors. + */ +int BLEDescriptorMap::getRegisteredDescriptorCount() { + return m_uuidMap.size(); +} + +/** + * @brief Remove a descriptor from the map. + * @param [in] pDescriptor The descriptor to remove. + * @return N/A. + */ +void BLEDescriptorMap::removeDescriptor(BLEDescriptor *pDescriptor) { + m_uuidMap.erase(pDescriptor); + m_handleMap.erase(pDescriptor->getHandle()); +} + /** * @brief Return a string representation of the descriptor map. * @return A string representation of the descriptor map. @@ -102,18 +149,6 @@ String BLEDescriptorMap::toString() { return res; } // toString -/** - * @brief 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.first->handleGATTServerEvent(event, gatts_if, param); - } -} // handleGATTServerEvent /** * @brief Get the first descriptor in the map. @@ -142,5 +177,41 @@ BLEDescriptor *BLEDescriptorMap::getNext() { return pRet; } // getNext -#endif /* CONFIG_BLUEDROID_ENABLED */ +/*************************************************************************** + * Bluedroid functions * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + +/** + * @brief 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.first->handleGATTServerEvent(event, gatts_if, param); + } +} // handleGATTServerEvent + +#endif + +/*************************************************************************** + * NimBLE functions * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + +void BLEDescriptorMap::handleGATTServerEvent(uint16_t conn_handle, uint16_t attr_handle, ble_gatt_access_ctxt *ctxt, void *arg) { + // Invoke the handler for every descriptor we have. + for (auto &myPair : m_uuidMap) { + myPair.first->handleGATTServerEvent(conn_handle, attr_handle, ctxt, arg); + } +} + +#endif + +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ diff --git a/libraries/BLE/src/BLEDevice.cpp b/libraries/BLE/src/BLEDevice.cpp index 186b36d6a33..f50126545c7 100644 --- a/libraries/BLE/src/BLEDevice.cpp +++ b/libraries/BLE/src/BLEDevice.cpp @@ -1,30 +1,35 @@ /* - * BLE.cpp + * BLEDevice.cpp * * Created on: Mar 16, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ + #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ + #include #include #include #include #include -#include // ESP32 BLE -#include // ESP32 BLE -#include // ESP32 BLE -#include // ESP32 BLE -#include // ESP32 BLE -#include // ESP32 BLE -#include // ESP32 BLE -#include // ESP32 ESP-IDF -#include // Part of C++ Standard library -#include // Part of C++ Standard library -#include // Part of C++ Standard library +#include + +#include +#include +#include +#include #include "BLEDevice.h" #include "BLEClient.h" @@ -37,34 +42,102 @@ #include "esp32-hal-log.h" +/*************************************************************************** + * Bluedroid includes * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) +#include +#include +#include +#include +#include +#include +#endif + +/*************************************************************************** + * NimBLE includes * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +#ifdef CONFIG_BT_NIMBLE_LEGACY_VHCI_ENABLE +#include +#endif +#include +#include +#include +#include +#include +#include "host/ble_hs_pvcy.h" +#include "host/util/util.h" +#include "services/gap/ble_svc_gap.h" +#include "services/gatt/ble_svc_gatt.h" +#endif + +/*************************************************************************** + * Common properties * + ***************************************************************************/ + /** * Singletons for the BLEDevice. */ BLEServer *BLEDevice::m_pServer = nullptr; BLEScan *BLEDevice::m_pScan = nullptr; BLEClient *BLEDevice::m_pClient = nullptr; -bool initialized = false; -esp_ble_sec_act_t BLEDevice::m_securityLevel = (esp_ble_sec_act_t)0; +static bool initialized = false; BLESecurityCallbacks *BLEDevice::m_securityCallbacks = nullptr; uint16_t BLEDevice::m_localMTU = 23; // not sure if this variable is useful BLEAdvertising *BLEDevice::m_bleAdvertising = nullptr; uint16_t BLEDevice::m_appId = 0; std::map BLEDevice::m_connectedClientsMap; gap_event_handler BLEDevice::m_customGapHandler = nullptr; + +/*************************************************************************** + * Bluedroid properties * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) +esp_ble_sec_act_t BLEDevice::m_securityLevel = (esp_ble_sec_act_t)0; gattc_event_handler BLEDevice::m_customGattcHandler = nullptr; gatts_event_handler BLEDevice::m_customGattsHandler = nullptr; +#endif + +/*************************************************************************** + * NimBLE properties * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +ble_gap_event_listener BLEDevice::m_listener; +BLEDeviceCallbacks BLEDevice::defaultDeviceCallbacks{}; +BLEDeviceCallbacks *BLEDevice::m_pDeviceCallbacks = &defaultDeviceCallbacks; +uint8_t BLEDevice::m_ownAddrType = BLE_OWN_ADDR_PUBLIC; +bool BLEDevice::m_synced = false; +#endif + +/*************************************************************************** + * Common functions * + ***************************************************************************/ /** * @brief Create a new instance of a client. * @return A new instance of the client. */ -/* STATIC */ BLEClient *BLEDevice::createClient() { +BLEClient *BLEDevice::createClient() { log_v(">> createClient"); -#ifndef CONFIG_GATTC_ENABLE // Check that BLE GATTC is enabled in make menuconfig - log_e("BLE GATTC is not enabled - CONFIG_GATTC_ENABLE not defined"); +#if !defined(CONFIG_GATTC_ENABLE) && !defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL) + log_e("BLE Client not enabled. Check CONFIG_GATTC_ENABLE for BlueDroid or CONFIG_BT_NIMBLE_ROLE_CENTRAL for NimBLE"); abort(); #endif // CONFIG_GATTC_ENABLE - m_pClient = new BLEClient(); + +#ifdef CONFIG_NIMBLE_ENABLED + if (m_connectedClientsMap.size() >= CONFIG_BT_NIMBLE_MAX_CONNECTIONS) { + log_e("Unable to create client. Max connections reached. Cur=%d Max=%d", m_connectedClientsMap.size(), CONFIG_BT_NIMBLE_MAX_CONNECTIONS); + m_pClient = nullptr; + } else +#endif + { + m_pClient = new BLEClient(); + } log_v("<< createClient"); return m_pClient; } // createClient @@ -73,200 +146,45 @@ gatts_event_handler BLEDevice::m_customGattsHandler = nullptr; * @brief Create a new instance of a server. * @return A new instance of the server. */ -/* STATIC */ BLEServer *BLEDevice::createServer() { +BLEServer *BLEDevice::createServer() { log_v(">> createServer"); -#ifndef CONFIG_GATTS_ENABLE // Check that BLE GATTS is enabled in make menuconfig - log_e("BLE GATTS is not enabled - CONFIG_GATTS_ENABLE not defined"); +#if !defined(CONFIG_GATTS_ENABLE) && !defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL) + log_e("BLE Server not enabled. Check CONFIG_GATTS_ENABLE for BlueDroid or CONFIG_BT_NIMBLE_ROLE_PERIPHERAL for NimBLE"); abort(); #endif // CONFIG_GATTS_ENABLE - m_pServer = new BLEServer(); - m_pServer->createApp(m_appId++); + + if (m_pServer == nullptr) { + m_pServer = new BLEServer(); + m_pServer->createApp(m_appId++); +#if defined(CONFIG_NIMBLE_ENABLED) + ble_gatts_reset(); + ble_svc_gap_init(); + ble_svc_gatt_init(); +#endif + } log_v("<< createServer"); return m_pServer; } // createServer -/** - * @brief Handle GATT server events. - * - * @param [in] event The event that has been newly received. - * @param [in] gatts_if The connection to the GATT interface. - * @param [in] param Parameters for the event. - */ -/* STATIC */ void BLEDevice::gattServerEventHandler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { - log_d("gattServerEventHandler [esp_gatt_if: %d] ... %s", gatts_if, BLEUtils::gattServerEventTypeToString(event).c_str()); - - BLEUtils::dumpGattServerEvent(event, gatts_if, param); - - switch (event) { - case ESP_GATTS_CONNECT_EVT: - { -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if (BLEDevice::m_securityLevel) { - esp_ble_set_encryption(param->connect.remote_bda, BLEDevice::m_securityLevel); - } -#endif // CONFIG_BLE_SMP_ENABLE - break; - } // ESP_GATTS_CONNECT_EVT - - default: - { - break; - } - } // switch - - if (BLEDevice::m_pServer != nullptr) { - BLEDevice::m_pServer->handleGATTServerEvent(event, gatts_if, param); - } - - if (m_customGattsHandler != nullptr) { - m_customGattsHandler(event, gatts_if, param); - } - -} // gattServerEventHandler - -/** - * @brief Handle GATT client events. - * - * Handler for the GATT client events. - * - * @param [in] event - * @param [in] gattc_if - * @param [in] param - */ -/* STATIC */ void BLEDevice::gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { - - log_d("gattClientEventHandler [esp_gatt_if: %d] ... %s", gattc_if, BLEUtils::gattClientEventTypeToString(event).c_str()); - BLEUtils::dumpGattClientEvent(event, gattc_if, param); - - switch (event) { - case ESP_GATTC_CONNECT_EVT: - { -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if (BLEDevice::m_securityLevel) { - esp_ble_set_encryption(param->connect.remote_bda, BLEDevice::m_securityLevel); - } -#endif // CONFIG_BLE_SMP_ENABLE - break; - } // ESP_GATTS_CONNECT_EVT - - default: break; - } // switch - for (auto &myPair : BLEDevice::getPeerDevices(true)) { - conn_status_t conn_status = (conn_status_t)myPair.second; - if (((BLEClient *)conn_status.peer_device)->getGattcIf() == gattc_if || ((BLEClient *)conn_status.peer_device)->getGattcIf() == ESP_GATT_IF_NONE - || gattc_if == ESP_GATT_IF_NONE) { - ((BLEClient *)conn_status.peer_device)->gattClientEventHandler(event, gattc_if, param); - } - } - - if (m_customGattcHandler != nullptr) { - m_customGattcHandler(event, gattc_if, param); - } - -} // gattClientEventHandler - -/** - * @brief Handle GAP events. - */ -/* STATIC */ 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_OOB_REQ_EVT: /* OOB request event */ log_i("ESP_GAP_BLE_OOB_REQ_EVT"); break; - case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */ log_i("ESP_GAP_BLE_LOCAL_IR_EVT"); break; - case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */ log_i("ESP_GAP_BLE_LOCAL_ER_EVT"); break; - case ESP_GAP_BLE_NC_REQ_EVT: /* NUMERIC CONFIRMATION */ log_i("ESP_GAP_BLE_NC_REQ_EVT"); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if (BLEDevice::m_securityCallbacks != nullptr) { - esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, BLEDevice::m_securityCallbacks->onConfirmPIN(param->ble_security.key_notif.passkey)); - } -#endif // CONFIG_BLE_SMP_ENABLE - break; - case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ - log_i("ESP_GAP_BLE_PASSKEY_REQ_EVT: "); - // esp_log_buffer_hex(m_remote_bda, sizeof(m_remote_bda)); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if (BLEDevice::m_securityCallbacks != nullptr) { - esp_ble_passkey_reply(param->ble_security.ble_req.bd_addr, true, BLEDevice::m_securityCallbacks->onPassKeyRequest()); - } -#endif // CONFIG_BLE_SMP_ENABLE - break; - /* - * TODO should we add white/black list comparison? - */ - case ESP_GAP_BLE_SEC_REQ_EVT: - /* send the positive(true) security response to the peer device to accept the security request. - If not accept the security request, should sent the security response with negative(false) accept value*/ - log_i("ESP_GAP_BLE_SEC_REQ_EVT"); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if (BLEDevice::m_securityCallbacks != nullptr) { - esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, BLEDevice::m_securityCallbacks->onSecurityRequest()); - } else { - esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); - } -#endif // CONFIG_BLE_SMP_ENABLE - break; - /* - * - */ - case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: //the app will receive this evt when the IO has Output capability and the peer device IO has Input capability. - //display the passkey number to the user to input it in the peer device within 30 seconds - log_i("ESP_GAP_BLE_PASSKEY_NOTIF_EVT"); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - log_i("passKey = %d", param->ble_security.key_notif.passkey); - if (BLEDevice::m_securityCallbacks != nullptr) { - BLEDevice::m_securityCallbacks->onPassKeyNotify(param->ble_security.key_notif.passkey); - } -#endif // CONFIG_BLE_SMP_ENABLE - break; - case ESP_GAP_BLE_KEY_EVT: - //shows the ble key type info share with peer device to the user. - log_d("ESP_GAP_BLE_KEY_EVT"); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - log_i("key type = %s", BLESecurity::esp_key_type_to_str(param->ble_security.ble_key.key_type)); -#endif // CONFIG_BLE_SMP_ENABLE - break; - case ESP_GAP_BLE_AUTH_CMPL_EVT: log_i("ESP_GAP_BLE_AUTH_CMPL_EVT"); -#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig - if (BLEDevice::m_securityCallbacks != nullptr) { - BLEDevice::m_securityCallbacks->onAuthenticationComplete(param->ble_security.auth_cmpl); - } -#endif // CONFIG_BLE_SMP_ENABLE - break; - default: - { - break; - } - } // switch - - if (BLEDevice::m_pClient != nullptr) { - BLEDevice::m_pClient->handleGAPEvent(event, param); - } - - if (BLEDevice::m_pScan != nullptr) { - BLEDevice::getScan()->handleGAPEvent(event, param); - } - - if (m_bleAdvertising != nullptr) { - BLEDevice::getAdvertising()->handleGAPEvent(event, param); - } - - if (m_customGapHandler != nullptr) { - BLEDevice::m_customGapHandler(event, param); - } - -} // gapEventHandler - /** * @brief Get the BLE device address. * @return The BLE device address. */ -/* STATIC*/ BLEAddress BLEDevice::getAddress() { +BLEAddress BLEDevice::getAddress() { +#if defined(CONFIG_BLUEDROID_ENABLED) const uint8_t *bdAddr = esp_bt_dev_get_address(); esp_bd_addr_t addr; memcpy(addr, bdAddr, sizeof(addr)); +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + ble_addr_t addr; + int ret = ble_hs_id_copy_addr(m_ownAddrType, addr.val, NULL); + if (ret != 0) { + log_e("No BLE address found. rc=%d", ret); + return BLEAddress(); + } +#endif return BLEAddress(addr); } // getAddress @@ -275,7 +193,7 @@ gatts_event_handler BLEDevice::m_customGattsHandler = nullptr; * @return The scanning object reference. This is a singleton object. The caller should not * try and release/delete it. */ -/* STATIC */ BLEScan *BLEDevice::getScan() { +BLEScan *BLEDevice::getScan() { //log_v(">> getScan"); if (m_pScan == nullptr) { m_pScan = new BLEScan(); @@ -285,13 +203,21 @@ gatts_event_handler BLEDevice::m_customGattsHandler = nullptr; return m_pScan; } // getScan +/** + * @brief Retrieve the server object + * @return The server object + */ +BLEServer *BLEDevice::getServer() { + return m_pServer; +} // getServer + /** * @brief Get the value of a characteristic of a service on a remote device. * @param [in] bdAddress * @param [in] serviceUUID * @param [in] characteristicUUID */ -/* STATIC */ String BLEDevice::getValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID) { +String BLEDevice::getValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID) { log_v( ">> getValue: bdAddress: %s, serviceUUID: %s, characteristicUUID: %s", bdAddress.toString().c_str(), serviceUUID.toString().c_str(), characteristicUUID.toString().c_str() @@ -308,18 +234,24 @@ gatts_event_handler BLEDevice::m_customGattsHandler = nullptr; * @brief Initialize the %BLE environment. * @param deviceName The device name of the device. */ -/* STATIC */ void BLEDevice::init(String deviceName) { +void BLEDevice::init(String deviceName) { if (!initialized) { - initialized = true; // Set the initialization flag to ensure we are only initialized once. - esp_err_t errRc = ESP_OK; -#ifdef ARDUINO_ARCH_ESP32 +#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_NIMBLE_ENABLED) if (!btStart()) { errRc = ESP_FAIL; return; } #else - errRc = ::nvs_flash_init(); + btStarted(); + errRc = nvs_flash_init(); + if (errRc == ESP_ERR_NVS_NO_FREE_PAGES || errRc == ESP_ERR_NVS_NEW_VERSION_FOUND) { + errRc = nvs_flash_erase(); + if (errRc == ESP_OK) { + errRc = nvs_flash_init(); + } + } + if (errRc != ESP_OK) { log_e("nvs_flash_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; @@ -328,6 +260,8 @@ gatts_event_handler BLEDevice::m_customGattsHandler = nullptr; #ifndef CONFIG_BT_CLASSIC_ENABLED esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); #endif + +#ifdef CONFIG_BLUEDROID_ENABLED esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); errRc = esp_bt_controller_init(&bt_cfg); if (errRc != ESP_OK) { @@ -347,7 +281,6 @@ gatts_event_handler BLEDevice::m_customGattsHandler = nullptr; log_e("esp_bt_controller_enable: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } -#endif #endif esp_bluedroid_status_t bt_state = esp_bluedroid_get_status(); @@ -402,7 +335,47 @@ gatts_event_handler BLEDevice::m_customGattsHandler = nullptr; log_e("esp_ble_gap_set_security_param: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; }; +#endif #endif // CONFIG_BLE_SMP_ENABLE + +#if defined(CONFIG_NIMBLE_ENABLED) + errRc = nimble_port_init(); + if (errRc != ESP_OK) { + log_e("nimble_port_init: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return; + } + + // Global struct ble_hs_cfg from nimble/host/ble_hs.h needs to be initialized + ble_hs_cfg.reset_cb = BLEDevice::onReset; + ble_hs_cfg.sync_cb = BLEDevice::onSync; + ble_hs_cfg.store_status_cb = [](struct ble_store_status_event *event, void *arg) { + return m_pDeviceCallbacks->onStoreStatus(event, arg); + }; + ble_hs_cfg.sm_io_cap = BLE_HS_IO_NO_INPUT_OUTPUT; + ble_hs_cfg.sm_bonding = 0; + ble_hs_cfg.sm_mitm = 0; + ble_hs_cfg.sm_sc = 1; + ble_hs_cfg.sm_our_key_dist = BLE_SM_PAIR_KEY_DIST_ENC; + ble_hs_cfg.sm_their_key_dist = BLE_SM_PAIR_KEY_DIST_ENC; +#if MYNEWT_VAL(BLE_LL_CFG_FEAT_LL_PRIVACY) + ble_hs_cfg.sm_our_key_dist |= BLE_SM_PAIR_KEY_DIST_ID; + ble_hs_cfg.sm_their_key_dist |= BLE_SM_PAIR_KEY_DIST_ID; +#endif + + errRc = ble_svc_gap_device_name_set(deviceName.c_str()); + if (errRc != ESP_OK) { + log_e("ble_svc_gap_device_name_set: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + } + + ble_store_config_init(); + nimble_port_freertos_init(BLEDevice::host_task); + + while (!m_synced) { + ble_npl_time_delay(1); + } +#endif +#endif // CONFIG_NIMBLE_ENABLED + initialized = true; // Set the initialization flag to ensure we are only initialized once. } vTaskDelay(200 / portTICK_PERIOD_MS); // Delay for 200 msecs as a workaround to an apparent Arduino environment issue. } // init @@ -435,7 +408,7 @@ gatts_event_handler BLEDevice::m_customGattsHandler = nullptr; * @param [in] powerType. * @param [in] powerLevel. */ -/* STATIC */ void BLEDevice::setPower(esp_power_level_t powerLevel, esp_ble_power_type_t powerType) { +void BLEDevice::setPower(esp_power_level_t powerLevel, esp_ble_power_type_t powerType) { log_v(">> setPower: %d (type: %d)", powerLevel, powerType); esp_err_t errRc = ::esp_ble_tx_power_set(powerType, powerLevel); if (errRc != ESP_OK) { @@ -444,13 +417,55 @@ gatts_event_handler BLEDevice::m_customGattsHandler = nullptr; log_v("<< setPower"); } // setPower +/** + * @brief Get the transmission power. + * @param [in] powerType The power level to set, can be one of: + * * ESP_BLE_PWR_TYPE_CONN_HDL0 = 0, For connection handle 0 + * * ESP_BLE_PWR_TYPE_CONN_HDL1 = 1, For connection handle 1 + * * ESP_BLE_PWR_TYPE_CONN_HDL2 = 2, For connection handle 2 + * * ESP_BLE_PWR_TYPE_CONN_HDL3 = 3, For connection handle 3 + * * ESP_BLE_PWR_TYPE_CONN_HDL4 = 4, For connection handle 4 + * * ESP_BLE_PWR_TYPE_CONN_HDL5 = 5, For connection handle 5 + * * ESP_BLE_PWR_TYPE_CONN_HDL6 = 6, For connection handle 6 + * * ESP_BLE_PWR_TYPE_CONN_HDL7 = 7, For connection handle 7 + * * ESP_BLE_PWR_TYPE_CONN_HDL8 = 8, For connection handle 8 + * * ESP_BLE_PWR_TYPE_ADV = 9, For advertising + * * ESP_BLE_PWR_TYPE_SCAN = 10, For scan + * * ESP_BLE_PWR_TYPE_DEFAULT = 11, For default, if not set other, it will use default value + * @return the power level currently used by the type specified. + */ + +int BLEDevice::getPower(esp_ble_power_type_t powerType) { + switch(esp_ble_tx_power_get(powerType)) { + case ESP_PWR_LVL_N12: + return -12; + case ESP_PWR_LVL_N9: + return -9; + case ESP_PWR_LVL_N6: + return -6; + case ESP_PWR_LVL_N3: + return -6; + case ESP_PWR_LVL_N0: + return 0; + case ESP_PWR_LVL_P3: + return 3; + case ESP_PWR_LVL_P6: + return 6; + case ESP_PWR_LVL_P9: + return 9; + default: + return -128; + } +} // getPower + + /** * @brief Set the value of a characteristic of a service on a remote device. * @param [in] bdAddress * @param [in] serviceUUID * @param [in] characteristicUUID */ -/* STATIC */ void BLEDevice::setValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID, String value) { +void BLEDevice::setValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID, String value) { log_v( ">> setValue: bdAddress: %s, serviceUUID: %s, characteristicUUID: %s", bdAddress.toString().c_str(), serviceUUID.toString().c_str(), characteristicUUID.toString().c_str() @@ -465,7 +480,7 @@ gatts_event_handler BLEDevice::m_customGattsHandler = nullptr; * @brief Return a string representation of the nature of this device. * @return A string representation of the nature of this device. */ -/* STATIC */ String BLEDevice::toString() { +String BLEDevice::toString() { String res = "BD Address: " + getAddress().toString(); return res; } // toString @@ -476,14 +491,27 @@ gatts_event_handler BLEDevice::m_customGattsHandler = nullptr; */ void BLEDevice::whiteListAdd(BLEAddress address) { log_v(">> whiteListAdd: %s", address.toString().c_str()); +#ifdef CONFIG_BLUEDROID_ENABLED #ifdef ESP_IDF_VERSION_MAJOR - esp_err_t errRc = esp_ble_gap_update_whitelist(true, *address.getNative(), BLE_WL_ADDR_TYPE_PUBLIC); // HACK!!! True to add an entry. + esp_err_t errRc = esp_ble_gap_update_whitelist(true, address.getNative(), BLE_WL_ADDR_TYPE_PUBLIC); // HACK!!! True to add an entry. #else - esp_err_t errRc = esp_ble_gap_update_whitelist(true, *address.getNative()); // True to add an entry. + esp_err_t errRc = esp_ble_gap_update_whitelist(true, address.getNative()); // True to add an entry. #endif if (errRc != ESP_OK) { log_e("esp_ble_gap_update_whitelist: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + if (!BLEDevice::onWhiteList(address)) { + m_whiteList.push_back(address); + int errRc = ble_gap_wl_set(reinterpret_cast(&m_whiteList[0]), m_whiteList.size()); + if (errRc != 0) { + log_e("Failed adding to whitelist rc=%d", errRc); + m_whiteList.pop_back(); + } + } +#endif log_v("<< whiteListAdd"); } // whiteListAdd @@ -493,28 +521,36 @@ void BLEDevice::whiteListAdd(BLEAddress address) { */ void BLEDevice::whiteListRemove(BLEAddress address) { log_v(">> whiteListRemove: %s", address.toString().c_str()); +#ifdef CONFIG_BLUEDROID_ENABLED #ifdef ESP_IDF_VERSION_MAJOR - esp_err_t errRc = esp_ble_gap_update_whitelist(false, *address.getNative(), BLE_WL_ADDR_TYPE_PUBLIC); // HACK!!! False to remove an entry. + esp_err_t errRc = esp_ble_gap_update_whitelist(false, address.getNative(), BLE_WL_ADDR_TYPE_PUBLIC); // HACK!!! False to remove an entry. #else - esp_err_t errRc = esp_ble_gap_update_whitelist(false, *address.getNative()); // False to remove an entry. + esp_err_t errRc = esp_ble_gap_update_whitelist(false, address.getNative()); // False to remove an entry. #endif if (errRc != ESP_OK) { log_e("esp_ble_gap_update_whitelist: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); } +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + for (auto it = m_whiteList.begin(); it < m_whiteList.end(); ++it) { + if (*it == address) { + m_whiteList.erase(it); + int errRc = ble_gap_wl_set(reinterpret_cast(&m_whiteList[0]), m_whiteList.size()); + if (errRc != 0) { + m_whiteList.push_back(address); + log_e("Failed removing from whitelist rc=%d", errRc); + } + std::vector(m_whiteList).swap(m_whiteList); + } + } +#endif log_v("<< whiteListRemove"); } // whiteListRemove -/* - * @brief Set encryption level that will be negotiated with peer device durng connection - * @param [in] level Requested encryption level - */ -void BLEDevice::setEncryptionLevel(esp_ble_sec_act_t level) { - BLEDevice::m_securityLevel = level; -} - /* * @brief Set callbacks that will be used to handle encryption negotiation events and authentication events - * @param [in] cllbacks Pointer to BLESecurityCallbacks class callback + * @param [in] callbacks Pointer to BLESecurityCallbacks class callback */ void BLEDevice::setSecurityCallbacks(BLESecurityCallbacks *callbacks) { BLEDevice::m_securityCallbacks = callbacks; @@ -526,11 +562,19 @@ void BLEDevice::setSecurityCallbacks(BLESecurityCallbacks *callbacks) { */ esp_err_t BLEDevice::setMTU(uint16_t mtu) { log_v(">> setLocalMTU: %d", mtu); + +#ifdef CONFIG_BLUEDROID_ENABLED esp_err_t err = esp_ble_gatt_set_local_mtu(mtu); +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + int err = ble_att_set_preferred_mtu(mtu); +#endif + if (err == ESP_OK) { m_localMTU = mtu; } else { - log_e("can't set local mtu value: %d", mtu); + log_e("can't set local mtu value: %d, rc=%d", mtu, err); } log_v("<< setLocalMTU"); return err; @@ -574,6 +618,10 @@ std::map BLEDevice::getPeerDevices(bool _client) { return m_connectedClientsMap; } +BLEClient *BLEDevice::getClientByID(uint16_t conn_id) { + return BLEDevice::getClientByGattIf(conn_id); +} + BLEClient *BLEDevice::getClientByGattIf(uint16_t conn_id) { return (BLEClient *)m_connectedClientsMap.find(conn_id)->second.peer_device; } @@ -619,20 +667,32 @@ void BLEDevice::removePeerDevice(uint16_t conn_id, bool _client) { * @brief de-Initialize the %BLE environment. * @param release_memory release the internal BT stack memory */ -/* STATIC */ void BLEDevice::deinit(bool release_memory) { +void BLEDevice::deinit(bool release_memory) { if (!initialized) { return; } +#ifdef CONFIG_BLUEDROID_ENABLED esp_bluedroid_disable(); esp_bluedroid_deinit(); +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + nimble_port_stop(); + nimble_port_deinit(); +#endif + esp_bt_controller_disable(); esp_bt_controller_deinit(); + #ifdef ARDUINO_ARCH_ESP32 if (release_memory) { - esp_bt_controller_mem_release(ESP_BT_MODE_BTDM - ); // <-- require tests because we released classic BT memory and this can cause crash (most likely not, esp-idf takes care of it) + // Require tests because we released classic BT memory and this can cause crash (most likely not, esp-idf takes care of it) + esp_bt_controller_mem_release(ESP_BT_MODE_BTDM); } else { +#ifdef CONFIG_NIMBLE_ENABLED + m_synced = false; +#endif initialized = false; } #endif @@ -640,8 +700,195 @@ void BLEDevice::removePeerDevice(uint16_t conn_id, bool _client) { void BLEDevice::setCustomGapHandler(gap_event_handler handler) { m_customGapHandler = handler; +#ifdef CONFIG_NIMBLE_ENABLED + int rc = ble_gap_event_listener_register(&m_listener, handler, NULL); + if (rc == BLE_HS_EALREADY) { + log_i("Already listening to GAP events."); + } else if (rc != 0) { + log_e("ble_gap_event_listener_register: rc=%d %s", rc, GeneralUtils::errorToString(rc)); + } +#endif } +/*************************************************************************** + * Bluedroid functions * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + +/** + * @brief Handle GATT server events. + * + * @param [in] event The event that has been newly received. + * @param [in] gatts_if The connection to the GATT interface. + * @param [in] param Parameters for the event. + */ +void BLEDevice::gattServerEventHandler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) { + log_d("gattServerEventHandler [esp_gatt_if: %d] ... %s", gatts_if, BLEUtils::gattServerEventTypeToString(event).c_str()); + + BLEUtils::dumpGattServerEvent(event, gatts_if, param); + + switch (event) { + case ESP_GATTS_CONNECT_EVT: + { +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if (BLEDevice::m_securityLevel) { + esp_ble_set_encryption(param->connect.remote_bda, BLEDevice::m_securityLevel); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + } // ESP_GATTS_CONNECT_EVT + + default: + { + break; + } + } // switch + + if (BLEDevice::m_pServer != nullptr) { + BLEDevice::m_pServer->handleGATTServerEvent(event, gatts_if, param); + } + + if (m_customGattsHandler != nullptr) { + m_customGattsHandler(event, gatts_if, param); + } + +} // gattServerEventHandler + +/** + * @brief Handle GATT client events. + * + * Handler for the GATT client events. + * + * @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) { + + log_d("gattClientEventHandler [esp_gatt_if: %d] ... %s", gattc_if, BLEUtils::gattClientEventTypeToString(event).c_str()); + BLEUtils::dumpGattClientEvent(event, gattc_if, param); + + switch (event) { + case ESP_GATTC_CONNECT_EVT: + { +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if (BLEDevice::m_securityLevel) { + esp_ble_set_encryption(param->connect.remote_bda, BLEDevice::m_securityLevel); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + } // ESP_GATTS_CONNECT_EVT + + default: break; + } // switch + for (auto &myPair : BLEDevice::getPeerDevices(true)) { + conn_status_t conn_status = (conn_status_t)myPair.second; + if (((BLEClient *)conn_status.peer_device)->getGattcIf() == gattc_if || ((BLEClient *)conn_status.peer_device)->getGattcIf() == ESP_GATT_IF_NONE + || gattc_if == ESP_GATT_IF_NONE) { + ((BLEClient *)conn_status.peer_device)->gattClientEventHandler(event, gattc_if, param); + } + } + + if (m_customGattcHandler != nullptr) { + m_customGattcHandler(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_OOB_REQ_EVT: /* OOB request event */ log_i("ESP_GAP_BLE_OOB_REQ_EVT"); break; + case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */ log_i("ESP_GAP_BLE_LOCAL_IR_EVT"); break; + case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */ log_i("ESP_GAP_BLE_LOCAL_ER_EVT"); break; + case ESP_GAP_BLE_NC_REQ_EVT: /* NUMERIC CONFIRMATION */ log_i("ESP_GAP_BLE_NC_REQ_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if (BLEDevice::m_securityCallbacks != nullptr) { + esp_ble_confirm_reply(param->ble_security.ble_req.bd_addr, BLEDevice::m_securityCallbacks->onConfirmPIN(param->ble_security.key_notif.passkey)); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ + log_i("ESP_GAP_BLE_PASSKEY_REQ_EVT: "); + // esp_log_buffer_hex(m_remote_bda, sizeof(m_remote_bda)); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if (BLEDevice::m_securityCallbacks != nullptr) { + esp_ble_passkey_reply(param->ble_security.ble_req.bd_addr, true, BLEDevice::m_securityCallbacks->onPassKeyRequest()); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + /* + * TODO should we add white/black list comparison? + */ + case ESP_GAP_BLE_SEC_REQ_EVT: + /* send the positive(true) security response to the peer device to accept the security request. + If not accept the security request, should sent the security response with negative(false) accept value*/ + log_i("ESP_GAP_BLE_SEC_REQ_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if (BLEDevice::m_securityCallbacks != nullptr) { + esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, BLEDevice::m_securityCallbacks->onSecurityRequest()); + } else { + esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + /* + * + */ + case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: //the app will receive this evt when the IO has Output capability and the peer device IO has Input capability. + //display the passkey number to the user to input it in the peer device within 30 seconds + log_i("ESP_GAP_BLE_PASSKEY_NOTIF_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + log_i("passKey = %d", param->ble_security.key_notif.passkey); + if (BLEDevice::m_securityCallbacks != nullptr) { + BLEDevice::m_securityCallbacks->onPassKeyNotify(param->ble_security.key_notif.passkey); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + case ESP_GAP_BLE_KEY_EVT: + //shows the ble key type info share with peer device to the user. + log_d("ESP_GAP_BLE_KEY_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + log_i("key type = %s", BLESecurity::esp_key_type_to_str(param->ble_security.ble_key.key_type)); +#endif // CONFIG_BLE_SMP_ENABLE + break; + case ESP_GAP_BLE_AUTH_CMPL_EVT: log_i("ESP_GAP_BLE_AUTH_CMPL_EVT"); +#ifdef CONFIG_BLE_SMP_ENABLE // Check that BLE SMP (security) is configured in make menuconfig + if (BLEDevice::m_securityCallbacks != nullptr) { + BLEDevice::m_securityCallbacks->onAuthenticationComplete(param->ble_security.auth_cmpl); + } +#endif // CONFIG_BLE_SMP_ENABLE + break; + default: + { + break; + } + } // switch + + if (BLEDevice::m_pClient != nullptr) { + BLEDevice::m_pClient->handleGAPEvent(event, param); + } + + if (BLEDevice::m_pScan != nullptr) { + BLEDevice::getScan()->handleGAPEvent(event, param); + } + + if (m_bleAdvertising != nullptr) { + BLEDevice::getAdvertising()->handleGAPEvent(event, param); + } + + if (m_customGapHandler != nullptr) { + BLEDevice::m_customGapHandler(event, param); + } + +} // gapEventHandler void BLEDevice::setCustomGattcHandler(gattc_event_handler handler) { m_customGattcHandler = handler; } @@ -650,5 +897,168 @@ void BLEDevice::setCustomGattsHandler(gatts_event_handler handler) { m_customGattsHandler = handler; } -#endif /* CONFIG_BLUEDROID_ENABLED */ +/* + * @brief Set encryption level that will be negotiated with peer device durng connection + * @param [in] level Requested encryption level + */ +void BLEDevice::setEncryptionLevel(esp_ble_sec_act_t level) { + BLEDevice::m_securityLevel = level; +} + +#endif + +/*************************************************************************** + * NimBLE functions * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + +/** + * @brief Checks if a peer device is whitelisted. + * @param [in] address The address to check for in the whitelist. + * @returns True if the address is in the whitelist. + */ +bool BLEDevice::onWhiteList(BLEAddress &address) { + for (auto &addr : m_whiteList) { + if (addr == address) { + return true; + } + } + return false; +} + +void BLEDevice::host_task(void *param) { + nimble_port_run(); + nimble_port_freertos_deinit(); +} + +void BLEDevice::onReset(int reason) { + if (!m_synced) { + return; + } + + m_synced = false; + + log_i("onReset, reason=%d, %s", reason, BLEUtils::returnCodeToString(reason)); + + if (initialized) { + if (m_pScan != nullptr) { + m_pScan->onHostReset(); + } + } +} + +void BLEDevice::onSync() { + log_d("onSync"); + + if (m_synced) { + log_d("onSync: already synced"); + return; + } + + int rc = ble_hs_util_ensure_addr(0); + if (rc == 0) { + rc = ble_hs_util_ensure_addr(1); + } + + if (rc != 0) { + log_e("onSync: failed to ensure BLE address. rc=%d", rc); + return; + } + + rc = ble_hs_id_copy_addr(BLE_OWN_ADDR_PUBLIC, NULL, NULL); + if (rc != 0) { + log_d("onSync: no public address available"); + m_ownAddrType = BLE_OWN_ADDR_RANDOM; + } + + // Occasionally triggers exception without. + vTaskDelay(1 / portTICK_PERIOD_MS); + + m_synced = true; + + if (initialized) { + if (m_pScan != nullptr) { + m_pScan->onHostSync(); + } + if (m_bleAdvertising != nullptr) { + m_bleAdvertising->onHostSync(); + } + } +} + +void BLEDevice::setDeviceCallbacks(BLEDeviceCallbacks *cb) { + if (cb == nullptr) { + m_pDeviceCallbacks = &defaultDeviceCallbacks; + } else { + m_pDeviceCallbacks = cb; + } +} + +/** + * @brief Sets the address type to use. + * @param [in] type Bluetooth Device address type. + * The available types are defined as: + * * 0x00: BLE_OWN_ADDR_PUBLIC - Public address; Uses the hardware static address. + * * 0x01: BLE_OWN_ADDR_RANDOM - Random static address; Uses the hardware or generated random static address. + * * 0x02: BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT - Resolvable private address, defaults to public if no RPA available. + * * 0x03: BLE_OWN_ADDR_RPA_RANDOM_DEFAULT - Resolvable private address, defaults to random static if no RPA available. + */ +bool BLEDevice::setOwnAddrType(uint8_t type) { + int rc = ble_hs_id_copy_addr(type & 1, NULL, NULL); // Odd values are random + if (rc != 0) { + log_e("Unable to set address type %d, rc=%d", type, rc); + return false; + } + + m_ownAddrType = type; + + if (type == BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT || type == BLE_OWN_ADDR_RPA_RANDOM_DEFAULT) { +#ifdef CONFIG_IDF_TARGET_ESP32 + // esp32 controller does not support RPA so we must use the random static for calls to the stack + // the host will take care of the random private address generation/setting. + m_ownAddrType = BLE_OWN_ADDR_RANDOM; + rc = ble_hs_pvcy_rpa_config(NIMBLE_HOST_ENABLE_RPA); +#endif + } else { +#ifdef CONFIG_IDF_TARGET_ESP32 + rc = ble_hs_pvcy_rpa_config(NIMBLE_HOST_DISABLE_PRIVACY); +#endif + } + return rc == 0; +} // setOwnAddrType + +/** + * @brief Set the device address to use. + * @param [in] addr The address to set. + * @return True if the address was set successfully. + * @details To use the address generated the address type must be set to random with `setOwnAddrType`. + */ +bool BLEDevice::setOwnAddr(BLEAddress &addr) { + return setOwnAddr(addr.getNative()); +} // setOwnAddr + +/** + * @brief Set the device address to use. + * @param [in] addr The address to set. + * @return True if the address was set successfully. + * @details To use the address generated the address type must be set to random with `setOwnAddrType`. + */ +bool BLEDevice::setOwnAddr(uint8_t *addr) { + int rc = ble_hs_id_set_rnd(addr); + if (rc != 0) { + log_e("Failed to set address, rc=%d", rc); + return false; + } + return true; +} // setOwnAddr + +int BLEDeviceCallbacks::onStoreStatus(struct ble_store_status_event *event, void *arg) { + log_d("onStoreStatus: default"); + return ble_store_util_status_rr(event, arg); +} + +#endif + +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ diff --git a/libraries/BLE/src/BLEDevice.h b/libraries/BLE/src/BLEDevice.h index 01bf143c101..718b9d2f6d0 100644 --- a/libraries/BLE/src/BLEDevice.h +++ b/libraries/BLE/src/BLEDevice.h @@ -3,6 +3,10 @@ * * Created on: Mar 16, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ #ifndef MAIN_BLEDevice_H_ @@ -11,88 +15,251 @@ #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) -#include // ESP32 BLE -#include // ESP32 BLE -#include // Part of C++ STL +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ + +#include #include #include - #include "BLEServer.h" #include "BLEClient.h" #include "BLEUtils.h" #include "BLEScan.h" +#include "BLEAdvertising.h" +#include "BLESecurity.h" #include "BLEAddress.h" +#include "BLETypes.h" -/** - * @brief BLE functions. - */ +/*************************************************************************** + * Bluedroid includes * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) +#include +#include +#include +#endif + +/*************************************************************************** + * NimBLE includes and definitions * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +#include +#define ESP_GATT_IF_NONE BLE_HS_CONN_HANDLE_NONE + +// NimBLE configuration compatibility macros +#if defined(CONFIG_SCAN_DUPLICATE_BY_DEVICE_ADDR) && !defined(CONFIG_BTDM_SCAN_DUPL_TYPE_DEVICE) +#define CONFIG_BTDM_SCAN_DUPL_TYPE_DEVICE CONFIG_SCAN_DUPLICATE_BY_DEVICE_ADDR +#endif + +#if defined(CONFIG_SCAN_DUPLICATE_BY_ADV_DATA) && !defined(CONFIG_BTDM_SCAN_DUPL_TYPE_DATA) +#define CONFIG_BTDM_SCAN_DUPL_TYPE_DATA CONFIG_SCAN_DUPLICATE_BY_ADV_DATA +#endif + +#if defined(CONFIG_SCAN_DUPLICATE_BY_ADV_DATA_AND_DEVICE_ADDR) && !defined(CONFIG_BTDM_SCAN_DUPL_TYPE_DATA_DEVICE) +#define CONFIG_BTDM_SCAN_DUPL_TYPE_DATA_DEVICE CONFIG_SCAN_DUPLICATE_BY_ADV_DATA_AND_DEVICE_ADDR +#endif + +#if defined(CONFIG_SCAN_DUPLICATE_TYPE) && !defined(CONFIG_BTDM_SCAN_DUPL_TYPE) +#define CONFIG_BTDM_SCAN_DUPL_TYPE CONFIG_SCAN_DUPLICATE_TYPE +#endif + +#if defined(CONFIG_BT_CTRL_SCAN_DUPL_TYPE) && !defined(CONFIG_BTDM_SCAN_DUPL_TYPE) +#define CONFIG_BTDM_SCAN_DUPL_TYPE CONFIG_BT_CTRL_SCAN_DUPL_TYPE +#endif + +#if defined(CONFIG_BT_LE_SCAN_DUPL_TYPE) && !defined(CONFIG_BTDM_SCAN_DUPL_TYPE) +#define CONFIG_BTDM_SCAN_DUPL_TYPE CONFIG_BT_LE_SCAN_DUPL_TYPE +#endif + +#if defined(CONFIG_DUPLICATE_SCAN_CACHE_SIZE) && !defined(CONFIG_BTDM_SCAN_DUPL_CACHE_SIZE) +#define CONFIG_BTDM_SCAN_DUPL_CACHE_SIZE CONFIG_DUPLICATE_SCAN_CACHE_SIZE +#endif + +#if defined(CONFIG_BT_CTRL_SCAN_DUPL_CACHE_SIZE) && !defined(CONFIG_BTDM_SCAN_DUPL_CACHE_SIZE) +#define CONFIG_BTDM_SCAN_DUPL_CACHE_SIZE CONFIG_BT_CTRL_SCAN_DUPL_CACHE_SIZE +#endif + +#if defined(CONFIG_BT_LE_LL_DUP_SCAN_LIST_COUNT) && !defined(CONFIG_BTDM_SCAN_DUPL_CACHE_SIZE) +#define CONFIG_BTDM_SCAN_DUPL_CACHE_SIZE CONFIG_BT_LE_LL_DUP_SCAN_LIST_COUNT +#endif + +#if defined(CONFIG_NIMBLE_MAX_CONNECTIONS) && !defined(CONFIG_BT_NIMBLE_MAX_CONNECTIONS) +#define CONFIG_BT_NIMBLE_MAX_CONNECTIONS CONFIG_NIMBLE_MAX_CONNECTIONS +#endif + +#endif + +/*************************************************************************** + * Forward declarations * + ***************************************************************************/ + +class BLEAddress; +class BLEDeviceCallbacks; +class BLESecurityCallbacks; +class BLEServer; +class BLEScan; +class BLEAdvertising; +class BLEClient; +class BLESecurity; + +/*************************************************************************** + * Bluedroid type definitions * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) typedef void (*gap_event_handler)(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); typedef void (*gattc_event_handler)(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); typedef void (*gatts_event_handler)(esp_gatts_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gatts_cb_param_t *param); +#endif + +/*************************************************************************** + * NimBLE type definitions and externals * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +extern "C" void ble_store_config_init(void); +typedef int (*gap_event_handler)(struct ble_gap_event *event, void *param); +#endif class BLEDevice { public: - static BLEClient *createClient(); // Create a new BLE client. - static BLEServer *createServer(); // Create a new BLE server. - static BLEAddress getAddress(); // Retrieve our own local BD address. - static BLEScan *getScan(); // Get the scan object - static String getValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID); // Get the value of a characteristic of a service on a server. - static void init(String deviceName); // Initialize the local BLE environment. - static void setPower(esp_power_level_t powerLevel, esp_ble_power_type_t powerType = ESP_BLE_PWR_TYPE_DEFAULT); // Set our power level. - static void setValue( - BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID, String value - ); // Set the value of a characteristic on a service on a server. - static String toString(); // Return a string representation of our device. - static void whiteListAdd(BLEAddress address); // Add an entry to the BLE white list. - static void whiteListRemove(BLEAddress address); // Remove an entry from the BLE white list. - static void setEncryptionLevel(esp_ble_sec_act_t level); + /*************************************************************************** + * Common public properties * + ***************************************************************************/ + + static uint16_t m_appId; + static uint16_t m_localMTU; + static gap_event_handler m_customGapHandler; + + /*************************************************************************** + * Bluedroid public properties * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + static esp_ble_sec_act_t m_securityLevel; + static gattc_event_handler m_customGattcHandler; + static gatts_event_handler m_customGattsHandler; +#endif + + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ + + static BLEClient *createClient(); + static BLEServer *createServer(); + static BLEAddress getAddress(); + static BLEServer *getServer(); + static BLEScan *getScan(); + static String getValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID); + static void init(String deviceName); + static void setPower(esp_power_level_t powerLevel, esp_ble_power_type_t powerType = ESP_BLE_PWR_TYPE_DEFAULT); + static int getPower(esp_ble_power_type_t powerType=ESP_BLE_PWR_TYPE_DEFAULT); + static void setValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID characteristicUUID, String value); + static String toString(); + static void whiteListAdd(BLEAddress address); + static void whiteListRemove(BLEAddress address); static void setSecurityCallbacks(BLESecurityCallbacks *pCallbacks); static esp_err_t setMTU(uint16_t mtu); static uint16_t getMTU(); - static bool getInitialized(); // Returns the state of the device, is it initialized or not? - /* move advertising to BLEDevice for saving ram and flash in beacons */ + static bool getInitialized(); static BLEAdvertising *getAdvertising(); static void startAdvertising(); static void stopAdvertising(); - static uint16_t m_appId; - /* multi connect */ static std::map getPeerDevices(bool client); static void addPeerDevice(void *peer, bool is_client, uint16_t conn_id); static void updatePeerDevice(void *peer, bool _client, uint16_t conn_id); static void removePeerDevice(uint16_t conn_id, bool client); + static BLEClient *getClientByID(uint16_t conn_id); static BLEClient *getClientByGattIf(uint16_t conn_id); static void setCustomGapHandler(gap_event_handler handler); + static void deinit(bool release_memory = false); + + /*************************************************************************** + * Bluedroid public declarations * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + static void setEncryptionLevel(esp_ble_sec_act_t level); static void setCustomGattcHandler(gattc_event_handler handler); static void setCustomGattsHandler(gatts_event_handler handler); - static void deinit(bool release_memory = false); - static uint16_t m_localMTU; - static esp_ble_sec_act_t m_securityLevel; +#endif + + /*************************************************************************** + * NimBLE public declarations * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + static void onReset(int reason); + static void onSync(void); + static void host_task(void *param); + static bool setOwnAddrType(uint8_t type); + static bool setOwnAddr(BLEAddress &addr); + static bool setOwnAddr(uint8_t *addr); + static void setDeviceCallbacks(BLEDeviceCallbacks *cb); + static bool onWhiteList(BLEAddress &address); +#endif private: + friend class BLEClient; + friend class BLEScan; + friend class BLEServer; + friend class BLECharacteristic; + friend class BLEAdvertising; + + /*************************************************************************** + * Common private properties * + ***************************************************************************/ + static BLEServer *m_pServer; static BLEScan *m_pScan; static BLEClient *m_pClient; static BLESecurityCallbacks *m_securityCallbacks; static BLEAdvertising *m_bleAdvertising; - static esp_gatt_if_t getGattcIF(); static std::map m_connectedClientsMap; static portMUX_TYPE mux; - static void gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); + /*************************************************************************** + * NimBLE private properties * + ***************************************************************************/ - static void gattServerEventHandler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); +#if defined(CONFIG_NIMBLE_ENABLED) + static uint8_t m_ownAddrType; + static bool m_synced; + static std::vector m_whiteList; + static BLEDeviceCallbacks defaultDeviceCallbacks; + static BLEDeviceCallbacks *m_pDeviceCallbacks; + static ble_gap_event_listener m_listener; +#endif + /*************************************************************************** + * Bluedroid private declarations * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + 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); +#endif +}; // class BLE -public: - /* custom gap and gatt handlers for flexibility */ - static gap_event_handler m_customGapHandler; - static gattc_event_handler m_customGattcHandler; - static gatts_event_handler m_customGattsHandler; +/*************************************************************************** + * NimBLE specific classes * + ***************************************************************************/ -}; // class BLE +#if defined(CONFIG_NIMBLE_ENABLED) +class BLEDeviceCallbacks { +public: + virtual ~BLEDeviceCallbacks(){}; + virtual int onStoreStatus(struct ble_store_status_event *event, void *arg); +}; +#endif -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ #endif /* MAIN_BLEDevice_H_ */ diff --git a/libraries/BLE/src/BLEEddystoneTLM.cpp b/libraries/BLE/src/BLEEddystoneTLM.cpp index 1a301f09011..ca0fb41b1a2 100644 --- a/libraries/BLE/src/BLEEddystoneTLM.cpp +++ b/libraries/BLE/src/BLEEddystoneTLM.cpp @@ -8,12 +8,16 @@ * Fix time stamp (0.1 second resolution) * Fixes based on EddystoneTLM frame specification https://github.com/google/eddystone/blob/master/eddystone-tlm/tlm-plain.md * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on pcbreflux's and h2zero's work) + * Description: Added support for NimBLE */ + #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) #include #include #include "esp32-hal-log.h" @@ -176,5 +180,5 @@ void BLEEddystoneTLM::setTime(uint32_t tmil) { m_eddystoneData.tmil = tmil; } // setTime -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ diff --git a/libraries/BLE/src/BLEEddystoneTLM.cppwithheadder b/libraries/BLE/src/BLEEddystoneTLM.cppwithheadder deleted file mode 100644 index 07002dbab1f..00000000000 --- a/libraries/BLE/src/BLEEddystoneTLM.cppwithheadder +++ /dev/null @@ -1,202 +0,0 @@ -/* - * BLEEddystoneTLM.cpp - * - * Created on: Mar 12, 2018 - * Author: pcbreflux - * Edited on: Mar 20, 2020 by beegee-tokyo - * Fix temperature value (8.8 fixed format) - * Fix time stamp (0.1 second resolution) - * Fixes based on EddystoneTLM frame specification https://github.com/google/eddystone/blob/master/eddystone-tlm/tlm-plain.md - * - */ -#include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) -#include -#include -#include "esp32-hal-log.h" -#include "BLEEddystoneTLM.h" - -static const char LOG_TAG[] = "BLEEddystoneTLM"; - -BLEEddystoneTLM::BLEEddystoneTLM() { - m_eddystoneData.frameType = EDDYSTONE_TLM_FRAME_TYPE; - m_eddystoneData.version = 0; - m_eddystoneData.volt = 3300; // 3300mV = 3.3V - m_eddystoneData.temp = (uint16_t) ((float) 23.00)/256; - m_eddystoneData.advCount = 0; - m_eddystoneData.tmil = 0; - _initHeadder(); -} // BLEEddystoneTLM - -BLEEddystoneTLM::BLEEddystoneTLM(BLEAdvertisedDevice *advertisedDevice){ - char* payload = (char*)advertisedDevice->getPayload(); - for(int i = 0; i < advertisedDevice->getPayloadLength(); ++i){ - if(payload[i] == 0x16 && advertisedDevice->getPayloadLength() >= i+2+sizeof(m_eddystoneData) && payload[i+1] == 0xAA && payload[i+2] == 0xFE && payload[i+3] == 0x20){ - log_d("Eddystone TLM data frame starting at byte [%d]", i+3); - setData(std::string(payload+i+3, sizeof(m_eddystoneData))); - break; - } - } - _initHeadder(); -} - -String BLEEddystoneTLM::getData() { - return String((char*) &m_eddystoneData, sizeof(m_eddystoneData)); -} // getData - -BLEUUID BLEEddystoneTLM::getUUID() { - return beaconUUID; -} // getUUID - -uint8_t BLEEddystoneTLM::getVersion() { - return m_eddystoneData.version; -} // getVersion - -uint16_t BLEEddystoneTLM::getVolt() { - return ENDIAN_CHANGE_U16(m_eddystoneData.volt); -} // getVolt - -float BLEEddystoneTLM::getTemp() { - return EDDYSTONE_TEMP_U16_TO_FLOAT(m_eddystoneData.temp); -} // getTemp - -uint16_t BLEEddystoneTLM::getRawTemp() { - return ENDIAN_CHANGE_U16(m_eddystoneData.temp); -} // getRawTemp - -uint32_t BLEEddystoneTLM::getCount() { - return ENDIAN_CHANGE_U32(m_eddystoneData.advCount); -} // getCount - -uint32_t BLEEddystoneTLM::getTime() { - return (ENDIAN_CHANGE_U32(m_eddystoneData.tmil)) / 10; -} // getTime - -String BLEEddystoneTLM::getFrame(){ - String frame(BLEHeadder); - frame += String((char*) &m_eddystoneData, sizeof(m_eddystoneData)); - log_d("Compiled frame of length %d Bytes", frame.length()); - for(int i = 0; i < frame.length(); ++i){ - log_d("[%d]=0x%02X",i, frame[i]); - } - return frame; -} // getServiceData - -String BLEEddystoneTLM::toString() { - String out = ""; - uint32_t rawsec = ENDIAN_CHANGE_U32(m_eddystoneData.tmil); - char val[12]; - - out += "Version "; // + std::string(m_eddystoneData.version); - snprintf(val, sizeof(val), "%d", m_eddystoneData.version); - out += val; - out += "\n"; - out += "Battery Voltage "; // + ENDIAN_CHANGE_U16(m_eddystoneData.volt); - snprintf(val, sizeof(val), "%d", ENDIAN_CHANGE_U16(m_eddystoneData.volt)); - out += val; - out += " mV\n"; - - out += "Temperature "; - snprintf(val, sizeof(val), "%.2f", ((int16_t)ENDIAN_CHANGE_U16(m_eddystoneData.temp)) / 256.0f); - out += val; - out += " C\n"; - - out += "Adv. Count "; - snprintf(val, sizeof(val), "%d", ENDIAN_CHANGE_U32(m_eddystoneData.advCount)); - out += val; - out += "\n"; - - out += "Time in seconds "; - snprintf(val, sizeof(val), "%d", rawsec/10); - out += val; - out += "\n"; - - out += "Time "; - - snprintf(val, sizeof(val), "%04d", rawsec / 864000); - out += val; - out += "."; - - snprintf(val, sizeof(val), "%02d", (rawsec / 36000) % 24); - out += val; - out += ":"; - - snprintf(val, sizeof(val), "%02d", (rawsec / 600) % 60); - out += val; - out += ":"; - - snprintf(val, sizeof(val), "%02d", (rawsec / 10) % 60); - out += val; - out += "\n"; - - return out; -} // toString - -/** - * Set the raw data for the beacon record. - * Example: - * uint8_t *payload = advertisedDevice.getPayload(); - * eddystoneTLM.setData(std::string((char*)payload+22, advertisedDevice.getPayloadLength() - 22)); - * Note: the offset 22 works for current implementation of example BLE_EddystoneTLM Beacon.ino, however - * the position is not static and it is programmers responsibility to align the data. - * Data frame: - * | Field || Len | Type | UUID | EddyStone TLM | - * | Offset || 0 | 1 | 2 | 4 | - * | Len || 1 B | 1 B | 2 B | 14 B | - * | Data || ?? | ?? | 0xAA | 0xFE | ??? | - * - * EddyStone TLM frame: - * | Field || Type | Version | Batt mV | Beacon temp | Cnt since boot | Time since boot | - * | Offset || 0 | 1 | 2 | 4 | 6 | 10 | - * | Len || 1 B | 1 B | 2 B | 2 B | 4 B | 4 B | - * | Data || 0x20 | ?? | ?? | ?? | ?? | ?? | | | | | | | | | - */ -void BLEEddystoneTLM::setData(std::string data) { - if (data.length() != sizeof(m_eddystoneData)) { - log_e("Unable to set the data ... length passed in was %d and expected %d", data.length(), sizeof(m_eddystoneData)); - return; - } - memcpy(&m_eddystoneData, data.data(), data.length()); -} // setData - -void BLEEddystoneTLM::setUUID(BLEUUID l_uuid) { - beaconUUID = l_uuid; -} // setUUID - -void BLEEddystoneTLM::setVersion(uint8_t version) { - m_eddystoneData.version = version; -} // setVersion - -// Set voltage in ESP32 native Big endian and convert it to little endian used for BLE Frame -void BLEEddystoneTLM::setVolt(uint16_t volt) { - m_eddystoneData.volt = ENDIAN_CHANGE_U16(volt); -} // setVolt - -void BLEEddystoneTLM::setTemp(float temp) { - m_eddystoneData.temp = EDDYSTONE_TEMP_FLOAT_TO_U16(temp); -} // setTemp - -void BLEEddystoneTLM::setCount(uint32_t advCount) { - m_eddystoneData.advCount = advCount; -} // setCount - -void BLEEddystoneTLM::setTime(uint32_t tmil) { - m_eddystoneData.tmil = tmil; -} // setTime - -void BLEEddystoneTLM::_initHeadder(){ - BLEHeadder[0] = 0x02; // Len - BLEHeadder[1] = 0x01; // Type Flags - BLEHeadder[2] = 0x06; // GENERAL_DISC_MODE 0x02 | BR_EDR_NOT_SUPPORTED 0x04 - BLEHeadder[3] = 0x03; // Len - BLEHeadder[4] = 0x03; // Type 16-Bit UUID - BLEHeadder[5] = 0xAA; // Eddystone UUID 2 -> 0xFEAA LSB - BLEHeadder[6] = 0xFE; // Eddystone UUID 1 MSB - BLEHeadder[7] = 0x11; // Length of TLM Beacon Data is constant 17 B (not counting the length field itself) - BLEHeadder[8] = 0x16; // Type Service Data - BLEHeadder[9] = 0xAA; // Eddystone UUID 2 -> 0xFEAA LSB - BLEHeadder[10] = 0xFE; // Eddystone UUID 1 MSB - BLEHeadder[11] = 0x20; // Eddystone Frame Type - TLM -} - -#endif diff --git a/libraries/BLE/src/BLEEddystoneTLM.h b/libraries/BLE/src/BLEEddystoneTLM.h index 3981af4a4a9..17915a670da 100644 --- a/libraries/BLE/src/BLEEddystoneTLM.h +++ b/libraries/BLE/src/BLEEddystoneTLM.h @@ -1,8 +1,12 @@ /* - * BLEEddystoneTLM.cpp + * BLEEddystoneTLM.h * * Created on: Mar 12, 2018 * Author: pcbreflux + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on pcbreflux's and h2zero's work) + * Description: Added support for NimBLE */ #ifndef _BLEEddystoneTLM_H_ @@ -10,6 +14,9 @@ #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED +#include "sdkconfig.h" +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + #include "BLEUUID.h" #include @@ -57,5 +64,6 @@ class BLEEddystoneTLM { } __attribute__((packed)) m_eddystoneData; }; // BLEEddystoneTLM +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ #endif /* _BLEEddystoneTLM_H_ */ diff --git a/libraries/BLE/src/BLEEddystoneURL.cpp b/libraries/BLE/src/BLEEddystoneURL.cpp index ddee8af0b30..495671ca49b 100644 --- a/libraries/BLE/src/BLEEddystoneURL.cpp +++ b/libraries/BLE/src/BLEEddystoneURL.cpp @@ -3,14 +3,20 @@ * * Created on: Mar 12, 2018 * Author: pcbreflux + * * Upgraded on: Feb 20, 2023 * By: Tomas Pilny + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on pcbreflux's and h2zero's work) + * Description: Added support for NimBLE */ + #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) #include #include "esp32-hal-log.h" #include "BLEEddystoneURL.h" @@ -158,7 +164,11 @@ void BLEEddystoneURL::setData(String data) { } // setData void BLEEddystoneURL::setUUID(BLEUUID l_uuid) { +#if defined(CONFIG_BLUEDROID_ENABLED) uint16_t beaconUUID = l_uuid.getNative()->uuid.uuid16; +#elif defined(CONFIG_NIMBLE_ENABLED) + uint16_t beaconUUID = l_uuid.getNative()->u16.value; +#endif BLEHeadder[10] = beaconUUID >> 8; BLEHeadder[9] = beaconUUID & 0x00FF; } // setUUID diff --git a/libraries/BLE/src/BLEEddystoneURL.h b/libraries/BLE/src/BLEEddystoneURL.h index 92668eb6855..9ed89a23694 100644 --- a/libraries/BLE/src/BLEEddystoneURL.h +++ b/libraries/BLE/src/BLEEddystoneURL.h @@ -3,9 +3,13 @@ * * Created on: Mar 12, 2018 * Author: pcbreflux + * * Upgraded on: Feb 20, 2023 * By: Tomas Pilny * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on pcbreflux's and h2zero's work) + * Description: Added support for NimBLE */ #ifndef _BLEEddystoneURL_H_ @@ -13,6 +17,9 @@ #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED +#include "sdkconfig.h" +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + #include "BLEUUID.h" #include #include "esp_bt.h" @@ -57,5 +64,6 @@ class BLEEddystoneURL { char BLEHeadder[12]; }; // BLEEddystoneURL +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ #endif /* _BLEEddystoneURL_H_ */ diff --git a/libraries/BLE/src/BLEEddystoneURL.h.orig b/libraries/BLE/src/BLEEddystoneURL.h.orig deleted file mode 100644 index 57722d0b769..00000000000 --- a/libraries/BLE/src/BLEEddystoneURL.h.orig +++ /dev/null @@ -1,66 +0,0 @@ -/* - * BLEEddystoneURL.cpp - * - * Created on: Mar 12, 2018 - * Author: pcbreflux - * Upgraded on: Feb 17, 2023 - * By: Tomas Pilny - * - */ - -#ifndef _BLEEddystoneURL_H_ -#define _BLEEddystoneURL_H_ -#include "BLEUUID.h" -#include -#include - -#define EDDYSTONE_URL_FRAME_TYPE 0x10 - -extern String EDDYSTONE_URL_PREFIX[]; -extern String EDDYSTONE_URL_SUFFIX[]; - -/** - * @brief Representation of a beacon. - * See: - * * https://github.com/google/eddystone - */ -class BLEEddystoneURL { -public: - BLEEddystoneURL(); - BLEEddystoneURL(BLEAdvertisedDevice *advertisedDevice); - std::string getData(); - String getFrame(); - BLEUUID getUUID(); - int8_t getPower(); - std::string getURL(); - String getPrefix(); - String getSuffix(); - std::string getDecodedURL(); - void setData(std::string data); - void setUUID(BLEUUID l_uuid); - void setPower(int8_t advertisedTxPower); - void setURL(std::string url); - int setSmartURL(String url); - -private: -<<<<<<< Updated upstream - uint16_t beaconUUID; - uint8_t lengthURL; - struct { - uint8_t frameType; - int8_t advertisedTxPower; - uint8_t url[18]; // 18 bytes: 1 byte for URL scheme + up to 17 bytes of URL - } __attribute__((packed)) m_eddystoneData; - -======= - uint8_t lengthURL; // Describes length of URL part including prefix and suffix - max 18 B (excluding TX power, frame type and preceding header) - struct { - int8_t advertisedTxPower; - uint8_t url[18]; // Byte [0] is for prefix. Last byte **can** contain suffix - } __attribute__((packed)) m_eddystoneData; - void _initHeadder(); - char BLEHeadder[12]; ->>>>>>> Stashed changes -}; // BLEEddystoneURL - -#endif /* _BLEEddystoneURL_H_ */ diff --git a/libraries/BLE/src/BLEExceptions.cpp b/libraries/BLE/src/BLEExceptions.cpp index 4e6c31fca22..b88ea337493 100644 --- a/libraries/BLE/src/BLEExceptions.cpp +++ b/libraries/BLE/src/BLEExceptions.cpp @@ -5,4 +5,9 @@ * Author: kolban */ +#include "soc/soc_caps.h" +#if SOC_BLE_SUPPORTED + //#include "BLEExceptions.h" + +#endif /* SOC_BLE_SUPPORTED */ diff --git a/libraries/BLE/src/BLEHIDDevice.cpp b/libraries/BLE/src/BLEHIDDevice.cpp index 0873aa1049f..1623c13430b 100644 --- a/libraries/BLE/src/BLEHIDDevice.cpp +++ b/libraries/BLE/src/BLEHIDDevice.cpp @@ -3,16 +3,41 @@ * * Created on: Jan 03, 2018 * Author: chegewara + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ + #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ #include "BLEHIDDevice.h" #include "BLE2904.h" +/*************************************************************************** + * NimBLE includes and definitions * + ***************************************************************************/ + +#ifdef CONFIG_NIMBLE_ENABLED +#include +#define ESP_GATT_PERM_READ BLE_ATT_F_READ +#define ESP_GATT_PERM_WRITE BLE_ATT_F_WRITE +#define ESP_GATT_PERM_READ_ENCRYPTED BLE_ATT_F_READ_ENC +#define ESP_GATT_PERM_WRITE_ENCRYPTED BLE_ATT_F_WRITE_ENC +#endif + +/*************************************************************************** + * Common functions * + ***************************************************************************/ + BLEHIDDevice::BLEHIDDevice(BLEServer *server) { /* * Here we create mandatory services described in bluetooth specification @@ -45,10 +70,12 @@ BLEHIDDevice::BLEHIDDevice(BLEServer *server) { m_batteryLevelCharacteristic = m_batteryService->createCharacteristic((uint16_t)0x2a19, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); m_batteryLevelCharacteristic->addDescriptor(batteryLevelDescriptor); +#if CONFIG_BLUEDROID_ENABLED BLE2902 *batLevelIndicator = new BLE2902(); // Battery Level Notification is ON by default, making it work always on BLE Pairing and Bonding batLevelIndicator->setNotifications(true); m_batteryLevelCharacteristic->addDescriptor(batLevelIndicator); +#endif /* * This value is setup here because its default value in most usage cases, its very rare to use boot mode @@ -117,16 +144,19 @@ BLECharacteristic *BLEHIDDevice::inputReport(uint8_t reportID) { BLECharacteristic *inputReportCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a4d, BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); BLEDescriptor *inputReportDescriptor = new BLEDescriptor(BLEUUID((uint16_t)0x2908)); - BLE2902 *p2902 = new BLE2902(); inputReportCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); inputReportDescriptor->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); - p2902->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); uint8_t desc1_val[] = {reportID, 0x01}; inputReportDescriptor->setValue((uint8_t *)desc1_val, 2); - inputReportCharacteristic->addDescriptor(p2902); inputReportCharacteristic->addDescriptor(inputReportDescriptor); +#if CONFIG_BLUEDROID_ENABLED + BLE2902 *p2902 = new BLE2902(); + p2902->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + inputReportCharacteristic->addDescriptor(p2902); +#endif + return inputReportCharacteristic; } @@ -175,7 +205,9 @@ BLECharacteristic *BLEHIDDevice::featureReport(uint8_t reportID) { */ BLECharacteristic *BLEHIDDevice::bootInput() { BLECharacteristic *bootInputCharacteristic = m_hidService->createCharacteristic((uint16_t)0x2a22, BLECharacteristic::PROPERTY_NOTIFY); +#if CONFIG_BLUEDROID_ENABLED bootInputCharacteristic->addDescriptor(new BLE2902()); +#endif return bootInputCharacteristic; } @@ -252,5 +284,5 @@ BLEService *BLEHIDDevice::batteryService() { return m_batteryService; } -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ diff --git a/libraries/BLE/src/BLEHIDDevice.h b/libraries/BLE/src/BLEHIDDevice.h index a92a23c21d5..9dde9452c12 100644 --- a/libraries/BLE/src/BLEHIDDevice.h +++ b/libraries/BLE/src/BLEHIDDevice.h @@ -3,6 +3,10 @@ * * Created on: Jan 03, 2018 * Author: chegewara + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ #ifndef _BLEHIDDEVICE_H_ @@ -12,7 +16,7 @@ #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) #include "BLECharacteristic.h" #include "BLEService.h" @@ -75,6 +79,6 @@ class BLEHIDDevice { BLECharacteristic *m_batteryLevelCharacteristic; //0x2a19 }; -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ #endif /* _BLEHIDDEVICE_H_ */ diff --git a/libraries/BLE/src/BLERemoteCharacteristic.cpp b/libraries/BLE/src/BLERemoteCharacteristic.cpp index 60d5108c1fc..7b1a24ad790 100644 --- a/libraries/BLE/src/BLERemoteCharacteristic.cpp +++ b/libraries/BLE/src/BLERemoteCharacteristic.cpp @@ -3,46 +3,55 @@ * * Created on: Jul 8, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ -#include "BLERemoteCharacteristic.h" - #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ -#include #include #include +#include "WString.h" //#include "BLEExceptions.h" #include "BLEUtils.h" #include "GeneralUtils.h" +#include "BLERemoteCharacteristic.h" #include "BLERemoteDescriptor.h" #include "esp32-hal-log.h" -/** - * @brief Constructor. - * @param [in] handle The BLE server side handle of this characteristic. - * @param [in] uuid The UUID of this characteristic. - * @param [in] charProp The properties of this characteristic. - * @param [in] pRemoteService A reference to the remote service to which this remote characteristic pertains. - */ -BLERemoteCharacteristic::BLERemoteCharacteristic(uint16_t handle, BLEUUID uuid, esp_gatt_char_prop_t charProp, BLERemoteService *pRemoteService) { - log_v(">> BLERemoteCharacteristic: handle: %d 0x%d, uuid: %s", handle, handle, uuid.toString().c_str()); - m_handle = handle; - m_uuid = uuid; - m_charProp = charProp; - m_pRemoteService = pRemoteService; - m_notifyCallback = nullptr; - m_rawData = nullptr; - m_auth = ESP_GATT_AUTH_REQ_NONE; +/*************************************************************************** + * Bluedroid includes * + ***************************************************************************/ - retrieveDescriptors(); // Get the descriptors for this characteristic - log_v("<< BLERemoteCharacteristic"); -} // BLERemoteCharacteristic +#if defined(CONFIG_BLUEDROID_ENABLED) +#include +#endif + +/*************************************************************************** + * NimBLE includes * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +#include +#include +#include +#include +#endif + +/*************************************************************************** + * Common functions * + ***************************************************************************/ /** *@brief Destructor. @@ -100,211 +109,6 @@ bool BLERemoteCharacteristic::canWriteNoResponse() { return (m_charProp & ESP_GATT_CHAR_PROP_BIT_WRITE_NR) != 0; } // canWriteNoResponse -/* -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_NOTIFY_EVT - // - // notify - // - uint16_t conn_id - The connection identifier of the server. - // - esp_bd_addr_t remote_bda - The device address of the BLE server. - // - uint16_t handle - The handle of the characteristic for which the event is being received. - // - uint16_t value_len - The length of the received data. - // - uint8_t* value - The received data. - // - bool is_notify - True if this is a notify, false if it is an indicate. - // - // We have received a notification event which means that the server wishes us to know about a notification - // piece of data. What we must now do is find the characteristic with the associated handle and then - // invoke its notification callback (if it has one). - case ESP_GATTC_NOTIFY_EVT: - { - if (evtParam->notify.handle != getHandle()) { - break; - } - if (m_notifyCallback != nullptr) { - log_d("Invoking callback for notification on characteristic %s", toString().c_str()); - m_notifyCallback(this, evtParam->notify.value, evtParam->notify.value_len, evtParam->notify.is_notify); - } // End we have a callback function ... - break; - } // ESP_GATTC_NOTIFY_EVT - - // 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 - // - uint16_t handle - // - uint8_t* value - // - uint16_t value_len - case ESP_GATTC_READ_CHAR_EVT: - { - // If this event is not for us, then nothing further to do. - if (evtParam->read.handle != getHandle()) { - break; - } - - // At this point, we have determined that the event is for us, so now we save the value - // and unlock the semaphore to ensure that the requester of the data can continue. - if (evtParam->read.status == ESP_GATT_OK) { - m_value = String((char *)evtParam->read.value, evtParam->read.value_len); - if (m_rawData != nullptr) { - free(m_rawData); - } - m_rawData = (uint8_t *)calloc(evtParam->read.value_len, sizeof(uint8_t)); - memcpy(m_rawData, 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 - // - uint16_t handle - case ESP_GATTC_REG_FOR_NOTIFY_EVT: - { - // If the request is not for this BLERemoteCharacteristic then move on to the next. - if (evtParam->reg_for_notify.handle != getHandle()) { - break; - } - - // We have processed the notify registration and can unlock the semaphore. - m_semaphoreRegForNotifyEvt.give(); - break; - } // ESP_GATTC_REG_FOR_NOTIFY_EVT - - // ESP_GATTC_UNREG_FOR_NOTIFY_EVT - // - // unreg_for_notify: - // - esp_gatt_status_t status - // - uint16_t handle - case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: - { - if (evtParam->unreg_for_notify.handle != getHandle()) { - break; - } - // We have processed the notify un-registration and can unlock the semaphore. - m_semaphoreRegForNotifyEvt.give(); - break; - } // ESP_GATTC_UNREG_FOR_NOTIFY_EVT: - - // ESP_GATTC_WRITE_CHAR_EVT - // - // write: - // - esp_gatt_status_t status - // - uint16_t conn_id - // - uint16_t handle - case ESP_GATTC_WRITE_CHAR_EVT: - { - // Determine if this event is for us and, if not, pass onwards. - if (evtParam->write.handle != getHandle()) { - break; - } - - // There is nothing further we need to do here. This is merely an indication - // that the write has completed and we can unlock the caller. - m_semaphoreWriteCharEvt.give(); - break; - } // ESP_GATTC_WRITE_CHAR_EVT - - case ESP_GATTC_READ_DESCR_EVT: - case ESP_GATTC_WRITE_DESCR_EVT: - for (auto &myPair : m_descriptorMap) { - myPair.second->gattClientEventHandler(event, gattc_if, evtParam); - } - break; - - case ESP_GATTC_DISCONNECT_EVT: - // Cleanup semaphores to avoid deadlocks. - m_semaphoreReadCharEvt.give(1); - m_semaphoreWriteCharEvt.give(1); - break; - - default: break; - } // End switch -}; // gattClientEventHandler - -/** - * @brief Populate the descriptors (if any) for this characteristic. - */ -void BLERemoteCharacteristic::retrieveDescriptors() { - log_v(">> retrieveDescriptors() for characteristic: %s", getUUID().toString().c_str()); - - removeDescriptors(); // Remove any existing descriptors. - - // Loop over each of the descriptors within the service associated with this characteristic. - // For each descriptor we find, create a BLERemoteDescriptor instance. - uint16_t offset = 0; - esp_gattc_descr_elem_t result; - while (true) { - uint16_t count = 10; - esp_gatt_status_t status = ::esp_ble_gattc_get_all_descr( - getRemoteService()->getClient()->getGattcIf(), getRemoteService()->getClient()->getConnId(), getHandle(), &result, &count, offset - ); - - if (status == ESP_GATT_INVALID_OFFSET) { // We have reached the end of the entries. - break; - } - - if (status != ESP_GATT_OK) { - log_e("esp_ble_gattc_get_all_descr: %s", BLEUtils::gattStatusToString(status).c_str()); - break; - } - - if (count == 0) { - break; - } - - log_d("Found a descriptor: Handle: %d, UUID: %s", result.handle, BLEUUID(result.uuid).toString().c_str()); - - // We now have a new characteristic ... let us add that to our set of known characteristics - BLERemoteDescriptor *pNewRemoteDescriptor = new BLERemoteDescriptor(result.handle, BLEUUID(result.uuid), this); - - m_descriptorMap.insert(std::pair(pNewRemoteDescriptor->getUUID().toString().c_str(), pNewRemoteDescriptor)); - - offset++; - } // while true - //m_haveCharacteristics = true; // Remember that we have received the characteristics. - log_v("<< retrieveDescriptors(): Found %d descriptors.", offset); -} // getDescriptors - /** * @brief Retrieve the map of descriptors keyed by UUID. */ @@ -404,44 +208,6 @@ float BLERemoteCharacteristic::readFloat() { return 0.0; } // readFloat -/** - * @brief Read the value of the remote characteristic. - * @return The value of the remote characteristic. - */ -String BLERemoteCharacteristic::readValue() { - log_v(">> readValue(): uuid: %s, handle: %d 0x%.2x", getUUID().toString().c_str(), getHandle(), getHandle()); - - // Check to see that we are connected. - if (!getRemoteService()->getClient()->isConnected()) { - log_e("Disconnected"); - return String(); - } - - m_semaphoreReadCharEvt.take("readValue"); - - // Ask the BLE subsystem to retrieve the value for the remote hosted characteristic. - // This is an asynchronous request which means that we must block waiting for the response - // to become available. - esp_err_t errRc = ::esp_ble_gattc_read_char( - m_pRemoteService->getClient()->getGattcIf(), - m_pRemoteService->getClient()->getConnId(), // The connection ID to the BLE server - getHandle(), // The handle of this characteristic - m_auth - ); // Security - - if (errRc != ESP_OK) { - log_e("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 String found - // in m_value will contain our data. - m_semaphoreReadCharEvt.wait("readValue"); - - log_v("<< readValue(): length: %d", m_value.length()); - return m_value; -} // readValue - /** * @brief Register for notifications. * @param [in] notifyCallback A callback to be invoked for a notification. If NULL is provided then we are @@ -451,13 +217,14 @@ String BLERemoteCharacteristic::readValue() { void BLERemoteCharacteristic::registerForNotify(notify_callback notifyCallback, bool notifications, bool descriptorRequiresRegistration) { log_v(">> registerForNotify(): %s", toString().c_str()); +#if defined(CONFIG_BLUEDROID_ENABLED) m_notifyCallback = notifyCallback; // Save the notification callback. m_semaphoreRegForNotifyEvt.take("registerForNotify"); if (notifyCallback != nullptr) { // If we have a callback function, then this is a registration. esp_err_t errRc = ::esp_ble_gattc_register_for_notify( - m_pRemoteService->getClient()->getGattcIf(), *m_pRemoteService->getClient()->getPeerAddress().getNative(), getHandle() + m_pRemoteService->getClient()->getGattcIf(), m_pRemoteService->getClient()->getPeerAddress().getNative(), getHandle() ); if (errRc != ESP_OK) { @@ -475,7 +242,7 @@ void BLERemoteCharacteristic::registerForNotify(notify_callback notifyCallback, } // End Register else { // If we weren't passed a callback function, then this is an unregistration. esp_err_t errRc = ::esp_ble_gattc_unregister_for_notify( - m_pRemoteService->getClient()->getGattcIf(), *m_pRemoteService->getClient()->getPeerAddress().getNative(), getHandle() + m_pRemoteService->getClient()->getGattcIf(), m_pRemoteService->getClient()->getPeerAddress().getNative(), getHandle() ); if (errRc != ESP_OK) { @@ -491,8 +258,22 @@ void BLERemoteCharacteristic::registerForNotify(notify_callback notifyCallback, m_semaphoreRegForNotifyEvt.wait("registerForNotify"); - log_v("<< registerForNotify()"); -} // registerForNotify +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + bool success; + if (notifyCallback != nullptr) { + success = subscribe(notifications, notifyCallback, descriptorRequiresRegistration); + } else { + success = unsubscribe(descriptorRequiresRegistration); + } + + if (!success) { + log_e("Failed to subscribe/unsubscribe for notify"); + } +#endif + log_v("<< registerForNotify()"); +} // registerForNotify /** * @brief Delete the descriptors in the descriptor map. @@ -532,8 +313,8 @@ String BLERemoteCharacteristic::toString() { * @param [in] response Do we expect a response? * @return N/A. */ -void BLERemoteCharacteristic::writeValue(String newValue, bool response) { - writeValue((uint8_t *)newValue.c_str(), newValue.length(), response); +bool BLERemoteCharacteristic::writeValue(String newValue, bool response) { + return writeValue((uint8_t *)newValue.c_str(), newValue.length(), response); } // writeValue /** @@ -544,58 +325,745 @@ void BLERemoteCharacteristic::writeValue(String newValue, bool response) { * @param [in] response Whether we require a response from the write. * @return N/A. */ -void BLERemoteCharacteristic::writeValue(uint8_t newValue, bool response) { - writeValue(&newValue, 1, response); +bool BLERemoteCharacteristic::writeValue(uint8_t newValue, bool response) { + return writeValue(&newValue, 1, response); } // writeValue +/** + * @brief Read raw data from remote characteristic as hex bytes + * @return return pointer data read + */ +uint8_t *BLERemoteCharacteristic::readRawData() { + return m_rawData; +} + +/** + * @brief Set authentication request type for characteristic + * @param [in] auth Authentication request type. + */ +void BLERemoteCharacteristic::setAuth(uint8_t auth) { + m_auth = auth; +} + +/*************************************************************************** + * Bluedroid functions * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + +/** + * @brief Constructor. + * @param [in] handle The BLE server side handle of this characteristic. + * @param [in] uuid The UUID of this characteristic. + * @param [in] charProp The properties of this characteristic. + * @param [in] pRemoteService A reference to the remote service to which this remote characteristic pertains. + */ +BLERemoteCharacteristic::BLERemoteCharacteristic(uint16_t handle, BLEUUID uuid, esp_gatt_char_prop_t charProp, BLERemoteService *pRemoteService) { + log_v(">> BLERemoteCharacteristic: handle: %d 0x%d, uuid: %s", handle, handle, uuid.toString().c_str()); + m_handle = handle; + m_uuid = uuid; + m_charProp = charProp; + m_pRemoteService = pRemoteService; + m_notifyCallback = nullptr; + m_rawData = nullptr; + m_auth = ESP_GATT_AUTH_REQ_NONE; + + retrieveDescriptors(); // Get the descriptors for this characteristic + log_v("<< BLERemoteCharacteristic"); +} // BLERemoteCharacteristic + +/** + * @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_NOTIFY_EVT + // + // notify + // - uint16_t conn_id - The connection identifier of the server. + // - esp_bd_addr_t remote_bda - The device address of the BLE server. + // - uint16_t handle - The handle of the characteristic for which the event is being received. + // - uint16_t value_len - The length of the received data. + // - uint8_t* value - The received data. + // - bool is_notify - True if this is a notify, false if it is an indicate. + // + // We have received a notification event which means that the server wishes us to know about a notification + // piece of data. What we must now do is find the characteristic with the associated handle and then + // invoke its notification callback (if it has one). + case ESP_GATTC_NOTIFY_EVT: + { + if (evtParam->notify.handle != getHandle()) { + break; + } + if (m_notifyCallback != nullptr) { + log_d("Invoking callback for notification on characteristic %s", toString().c_str()); + m_notifyCallback(this, evtParam->notify.value, evtParam->notify.value_len, evtParam->notify.is_notify); + } // End we have a callback function ... + break; + } // ESP_GATTC_NOTIFY_EVT + + // 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 + // - uint16_t handle + // - uint8_t* value + // - uint16_t value_len + case ESP_GATTC_READ_CHAR_EVT: + { + // If this event is not for us, then nothing further to do. + if (evtParam->read.handle != getHandle()) { + break; + } + + // At this point, we have determined that the event is for us, so now we save the value + // and unlock the semaphore to ensure that the requester of the data can continue. + if (evtParam->read.status == ESP_GATT_OK) { + m_value = String((char *)evtParam->read.value, evtParam->read.value_len); + if (m_rawData != nullptr) { + free(m_rawData); + } + m_rawData = (uint8_t *)calloc(evtParam->read.value_len, sizeof(uint8_t)); + memcpy(m_rawData, 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 + // - uint16_t handle + case ESP_GATTC_REG_FOR_NOTIFY_EVT: + { + // If the request is not for this BLERemoteCharacteristic then move on to the next. + if (evtParam->reg_for_notify.handle != getHandle()) { + break; + } + + // We have processed the notify registration and can unlock the semaphore. + m_semaphoreRegForNotifyEvt.give(); + break; + } // ESP_GATTC_REG_FOR_NOTIFY_EVT + + // ESP_GATTC_UNREG_FOR_NOTIFY_EVT + // + // unreg_for_notify: + // - esp_gatt_status_t status + // - uint16_t handle + case ESP_GATTC_UNREG_FOR_NOTIFY_EVT: + { + if (evtParam->unreg_for_notify.handle != getHandle()) { + break; + } + // We have processed the notify un-registration and can unlock the semaphore. + m_semaphoreRegForNotifyEvt.give(); + break; + } // ESP_GATTC_UNREG_FOR_NOTIFY_EVT: + + // ESP_GATTC_WRITE_CHAR_EVT + // + // write: + // - esp_gatt_status_t status + // - uint16_t conn_id + // - uint16_t handle + case ESP_GATTC_WRITE_CHAR_EVT: + { + // Determine if this event is for us and, if not, pass onwards. + if (evtParam->write.handle != getHandle()) { + break; + } + + // There is nothing further we need to do here. This is merely an indication + // that the write has completed and we can unlock the caller. + m_semaphoreWriteCharEvt.give(); + break; + } // ESP_GATTC_WRITE_CHAR_EVT + + case ESP_GATTC_READ_DESCR_EVT: + case ESP_GATTC_WRITE_DESCR_EVT: + for (auto &myPair : m_descriptorMap) { + myPair.second->gattClientEventHandler(event, gattc_if, evtParam); + } + break; + + case ESP_GATTC_DISCONNECT_EVT: + // Cleanup semaphores to avoid deadlocks. + m_semaphoreReadCharEvt.give(1); + m_semaphoreWriteCharEvt.give(1); + break; + + default: break; + } // End switch +}; // gattClientEventHandler + +/** + * @brief Populate the descriptors (if any) for this characteristic. + */ +void BLERemoteCharacteristic::retrieveDescriptors() { + log_v(">> retrieveDescriptors() for characteristic: %s", getUUID().toString().c_str()); + + removeDescriptors(); // Remove any existing descriptors. + + // Loop over each of the descriptors within the service associated with this characteristic. + // For each descriptor we find, create a BLERemoteDescriptor instance. + uint16_t offset = 0; + esp_gattc_descr_elem_t result; + while (true) { + uint16_t count = 10; + esp_gatt_status_t status = ::esp_ble_gattc_get_all_descr( + getRemoteService()->getClient()->getGattcIf(), getRemoteService()->getClient()->getConnId(), getHandle(), &result, &count, offset + ); + + if (status == ESP_GATT_INVALID_OFFSET) { // We have reached the end of the entries. + break; + } + + if (status != ESP_GATT_OK) { + log_e("esp_ble_gattc_get_all_descr: %s", BLEUtils::gattStatusToString(status).c_str()); + break; + } + + if (count == 0) { + break; + } + + log_d("Found a descriptor: Handle: %d, UUID: %s", result.handle, BLEUUID(result.uuid).toString().c_str()); + + // We now have a new characteristic ... let us add that to our set of known characteristics + BLERemoteDescriptor *pNewRemoteDescriptor = new BLERemoteDescriptor(result.handle, BLEUUID(result.uuid), this); + + m_descriptorMap.insert(std::pair(pNewRemoteDescriptor->getUUID().toString().c_str(), pNewRemoteDescriptor)); + + offset++; + } // while true + //m_haveCharacteristics = true; // Remember that we have received the characteristics. + log_v("<< retrieveDescriptors(): Found %d descriptors.", offset); +} // getDescriptors + +/** + * @brief Read the value of the remote characteristic. + * @return The value of the remote characteristic. + */ +String BLERemoteCharacteristic::readValue() { + log_v(">> readValue(): uuid: %s, handle: %d 0x%.2x", getUUID().toString().c_str(), getHandle(), getHandle()); + + // Check to see that we are connected. + if (!getRemoteService()->getClient()->isConnected()) { + log_e("Disconnected"); + return String(); + } + + m_semaphoreReadCharEvt.take("readValue"); + + // Ask the BLE subsystem to retrieve the value for the remote hosted characteristic. + // This is an asynchronous request which means that we must block waiting for the response + // to become available. + esp_err_t errRc = ::esp_ble_gattc_read_char( + m_pRemoteService->getClient()->getGattcIf(), + m_pRemoteService->getClient()->getConnId(), // The connection ID to the BLE server + getHandle(), // The handle of this characteristic + (esp_gatt_auth_req_t)m_auth + ); // Security + + if (errRc != ESP_OK) { + log_e("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 String found + // in m_value will contain our data. + m_semaphoreReadCharEvt.wait("readValue"); + + log_v("<< readValue(): length: %d", m_value.length()); + return m_value; +} // readValue + /** * @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. + * @return True if successful */ -void BLERemoteCharacteristic::writeValue(uint8_t *data, size_t length, bool response) { +bool BLERemoteCharacteristic::writeValue(uint8_t *data, size_t length, bool response) { // writeValue(String((char*)data, length), response); log_v(">> writeValue(), length: %d", length); // Check to see that we are connected. if (!getRemoteService()->getClient()->isConnected()) { log_e("Disconnected"); - return; + return false; } m_semaphoreWriteCharEvt.take("writeValue"); // Invoke the ESP-IDF API to perform the write. esp_err_t errRc = ::esp_ble_gattc_write_char( m_pRemoteService->getClient()->getGattcIf(), m_pRemoteService->getClient()->getConnId(), getHandle(), length, data, - response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, m_auth + response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, (esp_gatt_auth_req_t)m_auth ); if (errRc != ESP_OK) { log_e("esp_ble_gattc_write_char: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; + return false; } m_semaphoreWriteCharEvt.wait("writeValue"); log_v("<< writeValue"); + return true; } // writeValue +#endif + +/*************************************************************************** + * NimBLE functions * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + /** - * @brief Read raw data from remote characteristic as hex bytes - * @return return pointer data read + * @brief Constructor. + * @param [in] reference to the service this characteristic belongs to. + * @param [in] ble_gatt_chr struct defined as: + * struct ble_gatt_chr { + * uint16_t def_handle; + * uint16_t val_handle; + * uint8_t properties; + * ble_uuid_any_t uuid; + * }; */ -uint8_t *BLERemoteCharacteristic::readRawData() { - return m_rawData; +BLERemoteCharacteristic::BLERemoteCharacteristic(BLERemoteService *pRemoteService, const struct ble_gatt_chr *chr) { + log_v(">> BLERemoteCharacteristic()"); + switch (chr->uuid.u.type) { + case BLE_UUID_TYPE_16: m_uuid = BLEUUID(chr->uuid.u16.value); break; + case BLE_UUID_TYPE_32: m_uuid = BLEUUID(chr->uuid.u32.value); break; + case BLE_UUID_TYPE_128: m_uuid = BLEUUID(const_cast(&chr->uuid.u128)); break; + default: break; + } + + m_handle = chr->val_handle; + m_defHandle = chr->def_handle; + m_endHandle = 0; + m_charProp = chr->properties; + m_pRemoteService = pRemoteService; + m_notifyCallback = nullptr; + m_rawData = nullptr; + m_auth = 0; + + retrieveDescriptors(); // Get the descriptors for this characteristic + + log_v("<< BLERemoteCharacteristic(): %s", m_uuid.toString().c_str()); +} // BLERemoteCharacteristic + +/** + * @brief Callback used by the API when a descriptor is discovered or search complete. + */ +int BLERemoteCharacteristic::descriptorDiscCB( + uint16_t conn_handle, const struct ble_gatt_error *error, uint16_t chr_val_handle, const struct ble_gatt_dsc *dsc, void *arg +) { + int rc = error->status; + log_d("Descriptor Discovered >> status: %d handle: %d", rc, (rc == 0) ? dsc->handle : -1); + + desc_filter_t *filter = (desc_filter_t *)arg; + const BLEUUID *uuid_filter = filter->uuid; + ble_task_data_t *pTaskData = (ble_task_data_t *)filter->task_data; + BLERemoteCharacteristic *characteristic = (BLERemoteCharacteristic *)pTaskData->pATT; + + if (characteristic->getRemoteService()->getClient()->getConnId() != conn_handle) { + return 0; + } + + switch (rc) { + case 0: + { + if (uuid_filter != nullptr) { + if (ble_uuid_cmp(&uuid_filter->getNative()->u, &dsc->uuid.u) != 0) { + return 0; + } else { + rc = BLE_HS_EDONE; + } + } + + BLERemoteDescriptor *pNewRemoteDescriptor = new BLERemoteDescriptor(characteristic, dsc); + characteristic->m_descriptorMap.insert( + std::pair(pNewRemoteDescriptor->getUUID().toString().c_str(), pNewRemoteDescriptor) + ); + break; + } + default: break; + } + + /* If rc == BLE_HS_EDONE, resume the task with a success error code and stop the discovery process. + * Else if rc == 0, just return 0 to continue the discovery until we get BLE_HS_EDONE. + * If we get any other error code tell the application to abort by returning non-zero in the rc. + */ + if (rc == BLE_HS_EDONE) { + pTaskData->rc = 0; + xTaskNotifyGive(pTaskData->task); + } else if (rc != 0) { + // Error; abort discovery. + pTaskData->rc = rc; + xTaskNotifyGive(pTaskData->task); + } + + log_d("<< Descriptor Discovered. status: %d", pTaskData->rc); + return rc; } /** - * @brief Set authentication request type for characteristic - * @param [in] auth Authentication request type. + * @brief callback from NimBLE when the next characteristic of the service is discovered. */ -void BLERemoteCharacteristic::setAuth(esp_gatt_auth_req_t auth) { - m_auth = auth; +int BLERemoteCharacteristic::nextCharCB(uint16_t conn_handle, const struct ble_gatt_error *error, const struct ble_gatt_chr *chr, void *arg) { + int rc = error->status; + log_d("Next Characteristic >> status: %d handle: %d", rc, (rc == 0) ? chr->val_handle : -1); + + ble_task_data_t *pTaskData = (ble_task_data_t *)arg; + BLERemoteCharacteristic *pChar = (BLERemoteCharacteristic *)pTaskData->pATT; + + if (pChar->getRemoteService()->getClient()->getConnId() != conn_handle) { + return 0; + } + + if (rc == 0) { + pChar->m_endHandle = chr->def_handle - 1; + rc = BLE_HS_EDONE; + } else if (rc == BLE_HS_EDONE) { + pChar->m_endHandle = pChar->getRemoteService()->getEndHandle(); + } else { + pTaskData->rc = rc; + } + + xTaskNotifyGive(pTaskData->task); + return rc; +} + +/** + * @brief Callback for characteristic read operation. + * @return success == 0 or error code. + */ +int BLERemoteCharacteristic::onReadCB(uint16_t conn_handle, const struct ble_gatt_error *error, struct ble_gatt_attr *attr, void *arg) { + ble_task_data_t *pTaskData = (ble_task_data_t *)arg; + BLERemoteCharacteristic *characteristic = (BLERemoteCharacteristic *)pTaskData->pATT; + uint16_t conn_id = characteristic->getRemoteService()->getClient()->getConnId(); + + if (conn_id != conn_handle) { + return 0; + } + + log_i("Read complete; status=%d conn_handle=%d", error->status, conn_handle); + + String *strBuf = (String *)pTaskData->buf; + int rc = error->status; + + if (rc == 0) { + if (attr) { + uint32_t data_len = OS_MBUF_PKTLEN(attr->om); + if (((*strBuf).length() + data_len) > BLE_ATT_ATTR_MAX_LEN) { + rc = BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } else { + log_i("Got %d bytes", data_len); + (*strBuf) += String((char *)attr->om->om_data, data_len); + return 0; + } + } + } + + pTaskData->rc = rc; + xTaskNotifyGive(pTaskData->task); + + return rc; +} + +/** + * @brief Callback for characteristic write operation. + * @return success == 0 or error code. + */ +int BLERemoteCharacteristic::onWriteCB(uint16_t conn_handle, const struct ble_gatt_error *error, struct ble_gatt_attr *attr, void *arg) { + ble_task_data_t *pTaskData = (ble_task_data_t *)arg; + BLERemoteCharacteristic *characteristic = (BLERemoteCharacteristic *)pTaskData->pATT; + + if (characteristic->getRemoteService()->getClient()->getConnId() != conn_handle) { + return 0; + } + + log_i("Write complete; status=%d conn_handle=%d", error->status, conn_handle); + + pTaskData->rc = error->status; + xTaskNotifyGive(pTaskData->task); + + return 0; } -#endif /* CONFIG_BLUEDROID_ENABLED */ +/** + * @brief Populate the descriptors (if any) for this characteristic. + * @param [in] the end handle of the characteristic, or the service, whichever comes first. + */ +bool BLERemoteCharacteristic::retrieveDescriptors(const BLEUUID *uuid_filter) { + log_d(">> retrieveDescriptors() for characteristic: %s", getUUID().toString().c_str()); + + // If this is the last handle then there are no descriptors + if (m_handle == getRemoteService()->getEndHandle()) { + return true; + } + + int rc = 0; + TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); + ble_task_data_t taskData = {this, cur_task, 0, nullptr}; + + // If we don't know the end handle of this characteristic retrieve the next one in the service + // The end handle is the next characteristic definition handle -1. + if (m_endHandle == 0) { + rc = ble_gattc_disc_all_chrs( + getRemoteService()->getClient()->getConnId(), m_handle, getRemoteService()->getEndHandle(), BLERemoteCharacteristic::nextCharCB, &taskData + ); + if (rc != 0) { + log_e("Error getting end handle rc=%d", rc); + return false; + } + +#ifdef ulTaskNotifyValueClear + // Clear the task notification value to ensure we block + ulTaskNotifyValueClear(cur_task, ULONG_MAX); +#endif + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + if (taskData.rc != 0) { + log_e("Could not retrieve end handle rc=%d", taskData.rc); + return false; + } + } + + if (m_handle == m_endHandle) { + return true; + } + + desc_filter_t filter = {uuid_filter, &taskData}; + + rc = ble_gattc_disc_all_dscs(getRemoteService()->getClient()->getConnId(), m_handle, m_endHandle, BLERemoteCharacteristic::descriptorDiscCB, &filter); + + if (rc != 0) { + log_e("ble_gattc_disc_all_dscs: rc=%d %s", rc, BLEUtils::returnCodeToString(rc)); + return false; + } + +#ifdef ulTaskNotifyValueClear + // Clear the task notification value to ensure we block + ulTaskNotifyValueClear(cur_task, ULONG_MAX); +#endif + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + if (taskData.rc != 0) { + log_e("Failed to retrieve descriptors; startHandle:%d endHandle:%d taskData.rc=%d", m_handle, m_endHandle, taskData.rc); + } + + log_d("<< retrieveDescriptors(): Found %d descriptors.", m_descriptorMap.size()); + return (taskData.rc == 0); +} // retrieveDescriptors + +/** + * @brief Read the value of the remote characteristic. + * @return The value of the remote characteristic. + */ +String BLERemoteCharacteristic::readValue() { + log_d(">> readValue(): uuid: %s, handle: %d 0x%.2x", getUUID().toString().c_str(), getHandle(), getHandle()); + + BLEClient *pClient = getRemoteService()->getClient(); + String value; + + if (!pClient->isConnected()) { + log_e("Disconnected"); + return value; + } + + int rc = 0; + int retryCount = 1; + TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); + ble_task_data_t taskData = {this, cur_task, 0, &value}; + + do { + rc = ble_gattc_read_long(pClient->getConnId(), m_handle, 0, BLERemoteCharacteristic::onReadCB, &taskData); + if (rc != 0) { + log_e("Error: Failed to read characteristic; rc=%d, %s", rc, BLEUtils::returnCodeToString(rc)); + return value; + } + +#ifdef ulTaskNotifyValueClear + // Clear the task notification value to ensure we block + ulTaskNotifyValueClear(cur_task, ULONG_MAX); +#endif + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + rc = taskData.rc; + + switch (rc) { + case 0: + case BLE_HS_EDONE: rc = 0; break; + // Characteristic is not long-readable, return with what we have. + case BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_LONG): + log_i("Attribute not long"); + rc = 0; + break; + case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN): + case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHOR): + case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_ENC): + if (retryCount && pClient->secureConnection()) { + break; + } + /* Else falls through. */ + default: log_e("<< readValue rc=%d", rc); return value; + } + } while (rc != 0 && retryCount--); + + m_semaphoreReadCharEvt.take("readValue"); + m_value = value; + m_rawData = (uint8_t *)calloc(value.length(), sizeof(uint8_t)); + for (size_t i = 0; i < value.length(); i++) { + m_rawData[i] = value[i]; + } + m_semaphoreReadCharEvt.give(); + + log_d("<< readValue length: %d rc=%d", value.length(), rc); + return value; +} // readValue + +/** + * @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. + * @return false if not connected or cant perform write for some reason. + */ +bool BLERemoteCharacteristic::writeValue(uint8_t *data, size_t length, bool response) { + log_d(">> writeValue(), length: %d", length); + + BLEClient *pClient = getRemoteService()->getClient(); + + if (!pClient->isConnected()) { + log_e("Disconnected"); + return false; + } + + int rc = 0; + int retryCount = 1; + uint16_t mtu = ble_att_mtu(pClient->getConnId()) - 3; + + // Check if the data length is longer than we can write in one connection event. + // If so we must do a long write which requires a response. + if (length <= mtu && !response) { + rc = ble_gattc_write_no_rsp_flat(pClient->getConnId(), m_handle, data, length); + return (rc == 0); + } + + TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); + ble_task_data_t taskData = {this, cur_task, 0, nullptr}; + + do { + if (length > mtu) { + log_i("long write %d bytes", length); + os_mbuf *om = ble_hs_mbuf_from_flat(data, length); + rc = ble_gattc_write_long(pClient->getConnId(), m_handle, 0, om, BLERemoteCharacteristic::onWriteCB, &taskData); + } else { + rc = ble_gattc_write_flat(pClient->getConnId(), m_handle, data, length, BLERemoteCharacteristic::onWriteCB, &taskData); + } + if (rc != 0) { + log_e("Error: Failed to write characteristic; rc=%d", rc); + return false; + } + +#ifdef ulTaskNotifyValueClear + // Clear the task notification value to ensure we block + ulTaskNotifyValueClear(cur_task, ULONG_MAX); +#endif + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + rc = taskData.rc; + + switch (rc) { + case 0: + case BLE_HS_EDONE: rc = 0; break; + case BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_LONG): + log_e("Long write not supported by peer; Truncating length to %d", mtu); + retryCount++; + length = mtu; + break; + + case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN): + case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHOR): + case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_ENC): + if (retryCount && pClient->secureConnection()) { + break; + } + /* Else falls through. */ + default: log_e("<< writeValue, rc: %d", rc); return false; + } + } while (rc != 0 && retryCount--); + + log_d("<< writeValue, rc: %d", rc); + return (rc == 0); +} // writeValue + +/** + * @brief Subscribe or unsubscribe for notifications or indications. + * @param [in] val 0x00 to unsubscribe, 0x01 for notifications, 0x02 for indications. + * @param [in] notifyCallback A callback to be invoked for a notification. + * @param [in] response If write response required set this to true. + * If NULL is provided then no callback is performed. + * @return false if writing to the descriptor failed. + */ +bool BLERemoteCharacteristic::setNotify(uint16_t val, notify_callback notifyCallback, bool response) { + log_v(">> setNotify(): %s, %02x", toString().c_str(), val); + + m_notifyCallback = notifyCallback; + + BLERemoteDescriptor *desc = getDescriptor(BLEUUID((uint16_t)0x2902)); + if (desc == nullptr) { + log_w("<< setNotify(): Callback set, CCCD not found"); + return true; + } + + log_d("<< setNotify()"); + + response = true; // Always write with response as per Bluetooth core specification. + return desc->writeValue((uint8_t *)&val, 2, response); +} // setNotify + +/** + * @brief Subscribe for notifications or indications. + * @param [in] notifications If true, subscribe for notifications, false subscribe for indications. + * @param [in] notifyCallback A callback to be invoked for a notification. + * @param [in] response If true, require a write response from the descriptor write operation. + * If NULL is provided then no callback is performed. + * @return false if writing to the descriptor failed. + */ +bool BLERemoteCharacteristic::subscribe(bool notifications, notify_callback notifyCallback, bool response) { + if (notifications) { + return setNotify(0x01, notifyCallback, response); + } else { + return setNotify(0x02, notifyCallback, response); + } +} // subscribe + +/** + * @brief Unsubscribe for notifications or indications. + * @param [in] response bool if true, require a write response from the descriptor write operation. + * @return false if writing to the descriptor failed. + */ +bool BLERemoteCharacteristic::unsubscribe(bool response) { + return setNotify(0x00, nullptr, response); +} // unsubscribe + +#endif + +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ diff --git a/libraries/BLE/src/BLERemoteCharacteristic.h b/libraries/BLE/src/BLERemoteCharacteristic.h index dc63a3bc1a6..e846888d54a 100644 --- a/libraries/BLE/src/BLERemoteCharacteristic.h +++ b/libraries/BLE/src/BLERemoteCharacteristic.h @@ -3,6 +3,10 @@ * * Created on: Jul 8, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ #ifndef COMPONENTS_CPP_UTILS_BLEREMOTECHARACTERISTIC_H_ @@ -11,27 +15,80 @@ #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) -#include +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) -#include +/*************************************************************************** + * Common includes * + ***************************************************************************/ +#include #include "BLERemoteService.h" #include "BLERemoteDescriptor.h" #include "BLEUUID.h" #include "RTOS.h" +#include "BLEUtils.h" + +/*************************************************************************** + * Bluedroid includes * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) +#include +#endif + +/*************************************************************************** + * NimBLE includes and definitions * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +#include +#include +#include + +#define ESP_GATT_MAX_ATTR_LEN BLE_ATT_ATTR_MAX_LEN +#define ESP_GATT_CHAR_PROP_BIT_READ BLE_GATT_CHR_PROP_READ +#define ESP_GATT_CHAR_PROP_BIT_WRITE BLE_GATT_CHR_PROP_WRITE +#define ESP_GATT_CHAR_PROP_BIT_WRITE_NR BLE_GATT_CHR_PROP_WRITE_NO_RSP +#define ESP_GATT_CHAR_PROP_BIT_BROADCAST BLE_GATT_CHR_PROP_BROADCAST +#define ESP_GATT_CHAR_PROP_BIT_NOTIFY BLE_GATT_CHR_PROP_NOTIFY +#define ESP_GATT_CHAR_PROP_BIT_INDICATE BLE_GATT_CHR_PROP_INDICATE + +#endif + +/*************************************************************************** + * Common types * + ***************************************************************************/ + +typedef std::function notify_callback; + +/*************************************************************************** + * NimBLE types * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +typedef struct { + const BLEUUID *uuid; + void *task_data; +} desc_filter_t; +#endif + +/*************************************************************************** + * Forward declarations * + ***************************************************************************/ class BLERemoteService; class BLERemoteDescriptor; -typedef std::function notify_callback; + /** * @brief A model of a remote %BLE characteristic. */ class BLERemoteCharacteristic { public: - ~BLERemoteCharacteristic(); + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ - // Public member functions + ~BLERemoteCharacteristic(); bool canBroadcast(); bool canIndicate(); bool canNotify(); @@ -49,29 +106,34 @@ class BLERemoteCharacteristic { uint32_t readUInt32(); float readFloat(); void registerForNotify(notify_callback _callback, bool notifications = true, bool descriptorRequiresRegistration = true); - void writeValue(uint8_t *data, size_t length, bool response = false); - void writeValue(String newValue, bool response = false); - void writeValue(uint8_t newValue, bool response = false); + bool writeValue(uint8_t *data, size_t length, bool response = false); + bool writeValue(String newValue, bool response = false); + bool writeValue(uint8_t newValue, bool response = false); String toString(); uint8_t *readRawData(); - void setAuth(esp_gatt_auth_req_t auth); + void setAuth(uint8_t auth); + + /*************************************************************************** + * NimBLE public declarations * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + bool subscribe(bool notifications = true, notify_callback notifyCallback = nullptr, bool response = true); + bool unsubscribe(bool response = true); +#endif private: - BLERemoteCharacteristic(uint16_t handle, BLEUUID uuid, esp_gatt_char_prop_t charProp, BLERemoteService *pRemoteService); friend class BLEClient; friend class BLERemoteService; friend class BLERemoteDescriptor; - // Private member functions - void gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *evtParam); - - void removeDescriptors(); - void retrieveDescriptors(); + /*************************************************************************** + * Common private properties * + ***************************************************************************/ - // Private properties BLEUUID m_uuid; - esp_gatt_char_prop_t m_charProp; - esp_gatt_auth_req_t m_auth; + uint8_t m_charProp; + uint8_t m_auth; uint16_t m_handle; BLERemoteService *m_pRemoteService; FreeRTOS::Semaphore m_semaphoreReadCharEvt = FreeRTOS::Semaphore("ReadCharEvt"); @@ -83,8 +145,47 @@ class BLERemoteCharacteristic { // We maintain a map of descriptors owned by this characteristic keyed by a string representation of the UUID. std::map m_descriptorMap; + + /*************************************************************************** + * NimBLE private properties * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + uint16_t m_defHandle; + uint16_t m_endHandle; +#endif + + /*************************************************************************** + * Common private declarations * + ***************************************************************************/ + + void removeDescriptors(); + + /*************************************************************************** + * Bluedroid private declarations * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + BLERemoteCharacteristic(uint16_t handle, BLEUUID uuid, uint8_t charProp, BLERemoteService *pRemoteService); + void retrieveDescriptors(); + void gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *evtParam); +#endif + + /*************************************************************************** + * NimBLE private declarations * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + BLERemoteCharacteristic(BLERemoteService *pRemoteservice, const struct ble_gatt_chr *chr); + bool setNotify(uint16_t val, notify_callback notifyCallback = nullptr, bool response = true); + bool retrieveDescriptors(const BLEUUID *uuid_filter = nullptr); + static int onReadCB(uint16_t conn_handle, const struct ble_gatt_error *error, struct ble_gatt_attr *attr, void *arg); + static int onWriteCB(uint16_t conn_handle, const struct ble_gatt_error *error, struct ble_gatt_attr *attr, void *arg); + static int descriptorDiscCB(uint16_t conn_handle, const struct ble_gatt_error *error, uint16_t chr_val_handle, const struct ble_gatt_dsc *dsc, void *arg); + static int nextCharCB(uint16_t conn_handle, const struct ble_gatt_error *error, const struct ble_gatt_chr *chr, void *arg); +#endif }; // BLERemoteCharacteristic -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ #endif /* COMPONENTS_CPP_UTILS_BLEREMOTECHARACTERISTIC_H_ */ diff --git a/libraries/BLE/src/BLERemoteDescriptor.cpp b/libraries/BLE/src/BLERemoteDescriptor.cpp index b6d654cf9ec..4545111ba5a 100644 --- a/libraries/BLE/src/BLERemoteDescriptor.cpp +++ b/libraries/BLE/src/BLERemoteDescriptor.cpp @@ -3,23 +3,48 @@ * * Created on: Jul 8, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ + #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ + +#include "WString.h" #include #include "BLERemoteDescriptor.h" #include "GeneralUtils.h" #include "esp32-hal-log.h" -BLERemoteDescriptor::BLERemoteDescriptor(uint16_t handle, BLEUUID uuid, BLERemoteCharacteristic *pRemoteCharacteristic) { +/*************************************************************************** + * NimBLE includes * + ***************************************************************************/ +#if defined(CONFIG_NIMBLE_ENABLED) +#include +#include +#include +#include +#endif + +/*************************************************************************** + * Common functions * + ***************************************************************************/ + +BLERemoteDescriptor::BLERemoteDescriptor(uint16_t handle, BLEUUID uuid, BLERemoteCharacteristic *pRemoteCharacteristic) { m_handle = handle; m_uuid = uuid; m_pRemoteCharacteristic = pRemoteCharacteristic; - m_auth = ESP_GATT_AUTH_REQ_NONE; + m_auth = 0; } /** @@ -46,6 +71,75 @@ BLEUUID BLERemoteDescriptor::getUUID() { return m_uuid; } // getUUID +uint8_t BLERemoteDescriptor::readUInt8() { + String value = readValue(); + if (value.length() >= 1) { + return (uint8_t)value[0]; + } + return 0; +} // readUInt8 + +uint16_t BLERemoteDescriptor::readUInt16() { + String value = readValue(); + if (value.length() >= 2) { + return *(uint16_t *)value.c_str(); + } + return 0; +} // readUInt16 + +uint32_t BLERemoteDescriptor::readUInt32() { + String value = readValue(); + if (value.length() >= 4) { + return *(uint32_t *)value.c_str(); + } + return 0; +} // readUInt32 + +/** + * @brief Return a string representation of this BLE Remote Descriptor. + * @return A string representation of this BLE Remote Descriptor. + */ +String BLERemoteDescriptor::toString() { + char val[6]; + snprintf(val, sizeof(val), "%d", getHandle()); + String res = "handle: "; + res += val; + res += ", uuid: " + getUUID().toString(); + return res; +} // toString + +/** + * @brief Write data represented as a string to the BLE Remote Descriptor. + * @param [in] newValue The data to send to the remote descriptor. + * @param [in] response True if we expect a response. + */ +bool BLERemoteDescriptor::writeValue(String newValue, bool response) { + return writeValue((uint8_t *)newValue.c_str(), newValue.length(), response); +} // writeValue + +/** + * @brief Write a byte value to the Descriptor. + * @param [in] The single byte to write. + * @param [in] True if we expect a response. + */ +bool BLERemoteDescriptor::writeValue(uint8_t newValue, bool response) { + return writeValue(&newValue, 1, response); +} // writeValue + +/** + * @brief Set authentication request type for characteristic + * @param [in] auth Authentication request type. + */ +void BLERemoteDescriptor::setAuth(uint8_t auth) { + m_auth = auth; +} + +/*************************************************************************** + * Bluedroid functions * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + void BLERemoteDescriptor::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_DESCR_EVT @@ -99,7 +193,7 @@ String BLERemoteDescriptor::readValue() { m_pRemoteCharacteristic->getRemoteService()->getClient()->getGattcIf(), m_pRemoteCharacteristic->getRemoteService()->getClient()->getConnId(), // The connection ID to the BLE server getHandle(), // The handle of this characteristic - m_auth + (esp_gatt_auth_req_t)m_auth ); // Security if (errRc != ESP_OK) { @@ -115,55 +209,19 @@ String BLERemoteDescriptor::readValue() { return m_value; } // readValue -uint8_t BLERemoteDescriptor::readUInt8() { - String value = readValue(); - if (value.length() >= 1) { - return (uint8_t)value[0]; - } - return 0; -} // readUInt8 - -uint16_t BLERemoteDescriptor::readUInt16() { - String value = readValue(); - if (value.length() >= 2) { - return *(uint16_t *)value.c_str(); - } - return 0; -} // readUInt16 - -uint32_t BLERemoteDescriptor::readUInt32() { - String value = readValue(); - if (value.length() >= 4) { - return *(uint32_t *)value.c_str(); - } - return 0; -} // readUInt32 - -/** - * @brief Return a string representation of this BLE Remote Descriptor. - * @return A string representation of this BLE Remote Descriptor. - */ -String BLERemoteDescriptor::toString() { - char val[6]; - snprintf(val, sizeof(val), "%d", getHandle()); - String res = "handle: "; - res += val; - res += ", uuid: " + getUUID().toString(); - return res; -} // toString - /** * @brief Write data to the BLE Remote Descriptor. * @param [in] data The data to send to the remote descriptor. * @param [in] length The length of the data to send. * @param [in] response True if we expect a response. + * @return True if successful */ -void BLERemoteDescriptor::writeValue(uint8_t *data, size_t length, bool response) { +bool BLERemoteDescriptor::writeValue(uint8_t *data, size_t length, bool response) { log_v(">> writeValue: %s", toString().c_str()); // Check to see that we are connected. if (!getRemoteCharacteristic()->getRemoteService()->getClient()->isConnected()) { log_e("Disconnected"); - return; + return false; } m_semaphoreWriteDescrEvt.take("writeValue"); @@ -172,7 +230,8 @@ void BLERemoteDescriptor::writeValue(uint8_t *data, size_t length, bool response m_pRemoteCharacteristic->getRemoteService()->getClient()->getGattcIf(), m_pRemoteCharacteristic->getRemoteService()->getClient()->getConnId(), getHandle(), length, // Data length data, // Data - response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, m_auth + response ? ESP_GATT_WRITE_TYPE_RSP : ESP_GATT_WRITE_TYPE_NO_RSP, + (esp_gatt_auth_req_t)m_auth ); if (errRc != ESP_OK) { log_e("esp_ble_gattc_write_char_descr: %d", errRc); @@ -180,33 +239,230 @@ void BLERemoteDescriptor::writeValue(uint8_t *data, size_t length, bool response m_semaphoreWriteDescrEvt.wait("writeValue"); log_v("<< writeValue"); + return (errRc == ESP_OK); } // writeValue +#endif + +/*************************************************************************** + * NimBLE functions * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + /** - * @brief Write data represented as a string to the BLE Remote Descriptor. - * @param [in] newValue The data to send to the remote descriptor. - * @param [in] response True if we expect a response. + * @brief Remote descriptor constructor. + * @param [in] pRemoteCharacteristic A pointer to the Characteristic that this belongs to. + * @param [in] dsc A pointer to the struct that contains the descriptor information. */ -void BLERemoteDescriptor::writeValue(String newValue, bool response) { - writeValue((uint8_t *)newValue.c_str(), newValue.length(), response); -} // writeValue +BLERemoteDescriptor::BLERemoteDescriptor(BLERemoteCharacteristic *pRemoteCharacteristic, const struct ble_gatt_dsc *dsc) { + log_d(">> BLERemoteDescriptor()"); + switch (dsc->uuid.u.type) { + case BLE_UUID_TYPE_16: m_uuid = BLEUUID(dsc->uuid.u16.value); break; + case BLE_UUID_TYPE_32: m_uuid = BLEUUID(dsc->uuid.u32.value); break; + case BLE_UUID_TYPE_128: m_uuid = BLEUUID(const_cast(&dsc->uuid.u128)); break; + default: break; + } + + m_handle = dsc->handle; + m_pRemoteCharacteristic = pRemoteCharacteristic; + m_auth = 0; + + log_d("<< BLERemoteDescriptor(): %s", m_uuid.toString().c_str()); +} /** - * @brief Write a byte value to the Descriptor. - * @param [in] The single byte to write. - * @param [in] True if we expect a response. + * @brief Read the value of the remote descriptor. + * @return The value of the remote descriptor. */ -void BLERemoteDescriptor::writeValue(uint8_t newValue, bool response) { - writeValue(&newValue, 1, response); -} // writeValue +String BLERemoteDescriptor::readValue() { + log_d(">> Descriptor readValue: %s", toString().c_str()); + + BLEClient *pClient = getRemoteCharacteristic()->getRemoteService()->getClient(); + String value; + + if (!pClient->isConnected()) { + log_e("Disconnected"); + return value; + } + + int rc = 0; + int retryCount = 1; + TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); + ble_task_data_t taskData = {this, cur_task, 0, &value}; + + do { + rc = ble_gattc_read_long(pClient->getConnId(), m_handle, 0, BLERemoteDescriptor::onReadCB, &taskData); + if (rc != 0) { + log_e("Error: Failed to read descriptor; rc=%d, %s", rc, BLEUtils::returnCodeToString(rc)); + return value; + } + +#ifdef ulTaskNotifyValueClear + // Clear the task notification value to ensure we block + ulTaskNotifyValueClear(cur_task, ULONG_MAX); +#endif + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + rc = taskData.rc; + + switch (rc) { + case 0: + case BLE_HS_EDONE: rc = 0; break; + // Descriptor is not long-readable, return with what we have. + case BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_LONG): + log_i("Attribute not long"); + rc = 0; + break; + case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN): + case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHOR): + case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_ENC): + if (retryCount && pClient->secureConnection()) { + break; + } + /* Else falls through. */ + default: return value; + } + } while (rc != 0 && retryCount--); + + log_d("<< Descriptor readValue(): length: %d rc=%d", value.length(), rc); + return value; +} // readValue /** - * @brief Set authentication request type for characteristic - * @param [in] auth Authentication request type. + * @brief Callback for Descriptor read operation. + * @return success == 0 or error code. */ -void BLERemoteDescriptor::setAuth(esp_gatt_auth_req_t auth) { - m_auth = auth; +int BLERemoteDescriptor::onReadCB(uint16_t conn_handle, const struct ble_gatt_error *error, struct ble_gatt_attr *attr, void *arg) { + ble_task_data_t *pTaskData = (ble_task_data_t *)arg; + BLERemoteDescriptor *desc = (BLERemoteDescriptor *)pTaskData->pATT; + uint16_t conn_id = desc->getRemoteCharacteristic()->getRemoteService()->getClient()->getConnId(); + + if (conn_id != conn_handle) { + return 0; + } + + log_d("Read complete; status=%d conn_handle=%d", error->status, conn_handle); + + String *strBuf = (String *)pTaskData->buf; + int rc = error->status; + + if (rc == 0) { + if (attr) { + uint32_t data_len = OS_MBUF_PKTLEN(attr->om); + if (((*strBuf).length() + data_len) > BLE_ATT_ATTR_MAX_LEN) { + rc = BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; + } else { + log_d("Got %d bytes", data_len); + (*strBuf) += String((char *)attr->om->om_data, data_len); + return 0; + } + } + } + + pTaskData->rc = rc; + xTaskNotifyGive(pTaskData->task); + + return rc; } -#endif /* CONFIG_BLUEDROID_ENABLED */ +/** + * @brief Callback for descriptor write operation. + * @return success == 0 or error code. + */ +int BLERemoteDescriptor::onWriteCB(uint16_t conn_handle, const struct ble_gatt_error *error, struct ble_gatt_attr *attr, void *arg) { + ble_task_data_t *pTaskData = (ble_task_data_t *)arg; + BLERemoteDescriptor *descriptor = (BLERemoteDescriptor *)pTaskData->pATT; + + if (descriptor->getRemoteCharacteristic()->getRemoteService()->getClient()->getConnId() != conn_handle) { + return 0; + } + + log_i("Write complete; status=%d conn_handle=%d", error->status, conn_handle); + + pTaskData->rc = error->status; + xTaskNotifyGive(pTaskData->task); + + return 0; +} + +/** + * @brief Write data to the BLE Remote Descriptor. + * @param [in] data The data to send to the remote descriptor. + * @param [in] length The length of the data to send. + * @param [in] response True if we expect a write response. + * @return True if successful + */ +bool BLERemoteDescriptor::writeValue(uint8_t *data, size_t length, bool response) { + log_d(">> Descriptor writeValue: %s", toString().c_str()); + + BLEClient *pClient = getRemoteCharacteristic()->getRemoteService()->getClient(); + + // Check to see that we are connected. + if (!pClient->isConnected()) { + log_e("Disconnected"); + return false; + } + + int rc = 0; + int retryCount = 1; + uint16_t mtu = ble_att_mtu(pClient->getConnId()) - 3; + + // Check if the data length is longer than we can write in 1 connection event. + // If so we must do a long write which requires a response. + if (length <= mtu && !response) { + rc = ble_gattc_write_no_rsp_flat(pClient->getConnId(), m_handle, data, length); + return (rc == 0); + } + + TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); + ble_task_data_t taskData = {this, cur_task, 0, nullptr}; + + do { + if (length > mtu) { + log_i("long write %d bytes", length); + os_mbuf *om = ble_hs_mbuf_from_flat(data, length); + rc = ble_gattc_write_long(pClient->getConnId(), m_handle, 0, om, BLERemoteDescriptor::onWriteCB, &taskData); + } else { + rc = ble_gattc_write_flat(pClient->getConnId(), m_handle, data, length, BLERemoteDescriptor::onWriteCB, &taskData); + } + + if (rc != 0) { + log_e("Error: Failed to write descriptor; rc=%d", rc); + return false; + } + +#ifdef ulTaskNotifyValueClear + // Clear the task notification value to ensure we block + ulTaskNotifyValueClear(cur_task, ULONG_MAX); +#endif + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + rc = taskData.rc; + + switch (rc) { + case 0: + case BLE_HS_EDONE: rc = 0; break; + case BLE_HS_ATT_ERR(BLE_ATT_ERR_ATTR_NOT_LONG): + log_e("Long write not supported by peer; Truncating length to %d", mtu); + retryCount++; + length = mtu; + break; + + case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN): + case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHOR): + case BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_ENC): + if (retryCount && pClient->secureConnection()) { + break; + } + /* Else falls through. */ + default: return false; + } + } while (rc != 0 && retryCount--); + + log_d("<< Descriptor writeValue, rc: %d", rc); + return (rc == 0); +} // writeValue + +#endif + +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ diff --git a/libraries/BLE/src/BLERemoteDescriptor.h b/libraries/BLE/src/BLERemoteDescriptor.h index 94b11f1490a..afe113df551 100644 --- a/libraries/BLE/src/BLERemoteDescriptor.h +++ b/libraries/BLE/src/BLERemoteDescriptor.h @@ -3,6 +3,10 @@ * * Created on: Jul 8, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ #ifndef COMPONENTS_CPP_UTILS_BLEREMOTEDESCRIPTOR_H_ @@ -11,21 +15,41 @@ #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) -#include +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) -#include +/*************************************************************************** + * Common includes * + ***************************************************************************/ +#include #include "BLERemoteCharacteristic.h" #include "BLEUUID.h" #include "RTOS.h" +#include "BLEUtils.h" + +/*************************************************************************** + * Bluedroid includes * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) +#include +#endif + +/*************************************************************************** + * Forward declarations * + ***************************************************************************/ class BLERemoteCharacteristic; + /** * @brief A model of remote %BLE descriptor. */ class BLERemoteDescriptor { public: + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ + uint16_t getHandle(); BLERemoteCharacteristic *getRemoteCharacteristic(); BLEUUID getUUID(); @@ -34,24 +58,50 @@ class BLERemoteDescriptor { uint16_t readUInt16(void); uint32_t readUInt32(void); String toString(void); - void writeValue(uint8_t *data, size_t length, bool response = false); - void writeValue(String newValue, bool response = false); - void writeValue(uint8_t newValue, bool response = false); - void setAuth(esp_gatt_auth_req_t auth); + bool writeValue(uint8_t *data, size_t length, bool response = false); + bool writeValue(String newValue, bool response = false); + bool writeValue(uint8_t newValue, bool response = false); + void setAuth(uint8_t auth); + + /*************************************************************************** + * Bluedroid public declarations * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) void gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *evtParam); +#endif private: friend class BLERemoteCharacteristic; - BLERemoteDescriptor(uint16_t handle, BLEUUID uuid, BLERemoteCharacteristic *pRemoteCharacteristic); - uint16_t m_handle; // Server handle of this descriptor. - BLEUUID m_uuid; // UUID of this descriptor. - String m_value; // Last received value of the descriptor. + + /*************************************************************************** + * Common private properties * + ***************************************************************************/ + uint16_t m_handle; // Server handle of this descriptor. + BLEUUID m_uuid; // UUID of this descriptor. + String m_value; // Last received value of the descriptor. + uint8_t m_auth; BLERemoteCharacteristic *m_pRemoteCharacteristic; // Reference to the Remote characteristic of which this descriptor is associated. FreeRTOS::Semaphore m_semaphoreReadDescrEvt = FreeRTOS::Semaphore("ReadDescrEvt"); FreeRTOS::Semaphore m_semaphoreWriteDescrEvt = FreeRTOS::Semaphore("WriteDescrEvt"); - esp_gatt_auth_req_t m_auth; + + /*************************************************************************** + * Common private declarations * + ***************************************************************************/ + + BLERemoteDescriptor(uint16_t handle, BLEUUID uuid, BLERemoteCharacteristic *pRemoteCharacteristic); + + /*************************************************************************** + * NimBLE private declarations * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + BLERemoteDescriptor(BLERemoteCharacteristic *pRemoteCharacteristic, const struct ble_gatt_dsc *dsc); + static int onWriteCB(uint16_t conn_handle, const struct ble_gatt_error *error, struct ble_gatt_attr *attr, void *arg); + static int onReadCB(uint16_t conn_handle, const struct ble_gatt_error *error, struct ble_gatt_attr *attr, void *arg); +#endif }; -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ #endif /* COMPONENTS_CPP_UTILS_BLEREMOTEDESCRIPTOR_H_ */ diff --git a/libraries/BLE/src/BLERemoteService.cpp b/libraries/BLE/src/BLERemoteService.cpp index e4cc31dbb33..accde423627 100644 --- a/libraries/BLE/src/BLERemoteService.cpp +++ b/libraries/BLE/src/BLERemoteService.cpp @@ -3,12 +3,21 @@ * * Created on: Jul 8, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ + #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ #include #include "BLERemoteService.h" @@ -19,95 +28,14 @@ #pragma GCC diagnostic warning "-Wunused-but-set-parameter" -BLERemoteService::BLERemoteService(esp_gatt_id_t srvcId, BLEClient *pClient, uint16_t startHandle, uint16_t endHandle) { - - log_v(">> BLERemoteService()"); - m_srvcId = srvcId; - m_pClient = pClient; - m_uuid = BLEUUID(m_srvcId); - m_haveCharacteristics = false; - m_startHandle = startHandle; - m_endHandle = endHandle; - - log_v("<< BLERemoteService()"); -} +/*************************************************************************** + * Common functions * + ***************************************************************************/ 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( - BLEUUID(evtParam->get_char.char_id.uuid).toString().c_str(), - 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) { - log_e("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_characteristicMapByHandle) { - myPair.second->gattClientEventHandler(event, gattc_if, evtParam); - } -} // gattClientEventHandler - /** * @brief Get the remote characteristic object for the characteristic UUID. * @param [in] uuid Remote characteristic uuid. @@ -144,52 +72,6 @@ BLERemoteCharacteristic *BLERemoteService::getCharacteristic(BLEUUID uuid) { return nullptr; } // getCharacteristic -/** - * @brief Retrieve all the characteristics for this service. - * This function will not return until we have all the characteristics. - * @return N/A - */ -void BLERemoteService::retrieveCharacteristics() { - log_v(">> getCharacteristics() for service: %s", getUUID().toString().c_str()); - - removeCharacteristics(); // Forget any previous characteristics. - - uint16_t offset = 0; - esp_gattc_char_elem_t result; - while (true) { - uint16_t count = 1; // only room for 1 result allocated, so go one by one - esp_gatt_status_t status = - ::esp_ble_gattc_get_all_char(getClient()->getGattcIf(), getClient()->getConnId(), m_startHandle, m_endHandle, &result, &count, offset); - - if (status == ESP_GATT_INVALID_OFFSET) { // We have reached the end of the entries. - break; - } - - if (status != ESP_GATT_OK) { // If we got an error, end. - log_e("esp_ble_gattc_get_all_char: %s", BLEUtils::gattStatusToString(status).c_str()); - break; - } - - if (count == 0) { // If we failed to get any new records, end. - break; - } - - log_d("Found a characteristic: Handle: %d, UUID: %s", result.char_handle, BLEUUID(result.uuid).toString().c_str()); - - // We now have a new characteristic ... let us add that to our set of known characteristics - BLERemoteCharacteristic *pNewRemoteCharacteristic = new BLERemoteCharacteristic(result.char_handle, BLEUUID(result.uuid), result.properties, this); - - m_characteristicMap.insert( - std::pair(pNewRemoteCharacteristic->getUUID().toString().c_str(), pNewRemoteCharacteristic) - ); - m_characteristicMapByHandle.insert(std::pair(result.char_handle, pNewRemoteCharacteristic)); - offset++; // Increment our count of number of descriptors found. - } // Loop forever (until we break inside the loop). - - m_haveCharacteristics = true; // Remember that we have received the characteristics. - log_v("<< getCharacteristics()"); -} // getCharacteristics - /** * @brief Retrieve a map of all the characteristics of this service. * @return A map of all the characteristics of this service. @@ -248,10 +130,6 @@ uint16_t BLERemoteService::getEndHandle() { return m_endHandle; } // getEndHandle -esp_gatt_id_t *BLERemoteService::getSrvcId() { - return &m_srvcId; -} // getSrvcId - uint16_t BLERemoteService::getStartHandle() { return m_startHandle; } // getStartHandle @@ -330,5 +208,248 @@ String BLERemoteService::toString() { return res; } // toString -#endif /* CONFIG_BLUEDROID_ENABLED */ +/*************************************************************************** + * Bluedroid functions * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + +BLERemoteService::BLERemoteService(esp_gatt_id_t srvcId, BLEClient *pClient, uint16_t startHandle, uint16_t endHandle) { + log_v(">> BLERemoteService()"); + m_srvcId = srvcId; + m_pClient = pClient; + m_uuid = BLEUUID(m_srvcId); + m_haveCharacteristics = false; + m_startHandle = startHandle; + m_endHandle = endHandle; + + log_v("<< BLERemoteService()"); +} + +esp_gatt_id_t *BLERemoteService::getSrvcId() { + return &m_srvcId; +} // getSrvcId + +/** + * @brief Retrieve all the characteristics for this service. + * This function will not return until we have all the characteristics. + * @return N/A + */ +void BLERemoteService::retrieveCharacteristics() { + log_v(">> getCharacteristics() for service: %s", getUUID().toString().c_str()); + + removeCharacteristics(); // Forget any previous characteristics. + + uint16_t offset = 0; + esp_gattc_char_elem_t result; + while (true) { + uint16_t count = 1; // only room for 1 result allocated, so go one by one + esp_gatt_status_t status = + ::esp_ble_gattc_get_all_char(getClient()->getGattcIf(), getClient()->getConnId(), m_startHandle, m_endHandle, &result, &count, offset); + + if (status == ESP_GATT_INVALID_OFFSET) { // We have reached the end of the entries. + break; + } + + if (status != ESP_GATT_OK) { // If we got an error, end. + log_e("esp_ble_gattc_get_all_char: %s", BLEUtils::gattStatusToString(status).c_str()); + break; + } + + if (count == 0) { // If we failed to get any new records, end. + break; + } + + log_d("Found a characteristic: Handle: %d, UUID: %s", result.char_handle, BLEUUID(result.uuid).toString().c_str()); + + // We now have a new characteristic ... let us add that to our set of known characteristics + BLERemoteCharacteristic *pNewRemoteCharacteristic = new BLERemoteCharacteristic(result.char_handle, BLEUUID(result.uuid), result.properties, this); + + m_characteristicMap.insert( + std::pair(pNewRemoteCharacteristic->getUUID().toString().c_str(), pNewRemoteCharacteristic) + ); + m_characteristicMapByHandle.insert(std::pair(result.char_handle, pNewRemoteCharacteristic)); + offset++; // Increment our count of number of descriptors found. + } // Loop forever (until we break inside the loop). + + m_haveCharacteristics = true; // Remember that we have received the characteristics. + log_v("<< getCharacteristics()"); +} // getCharacteristics + +/** + * @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( + BLEUUID(evtParam->get_char.char_id.uuid).toString().c_str(), + 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) { + log_e("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_characteristicMapByHandle) { + myPair.second->gattClientEventHandler(event, gattc_if, evtParam); + } +} // gattClientEventHandler + +#endif + +/*************************************************************************** + * NimBLE functions * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + +BLERemoteService::BLERemoteService(BLEClient *pClient, const struct ble_gatt_svc *service) { + log_v(">> BLERemoteService()"); + m_pClient = pClient; + switch (service->uuid.u.type) { + case BLE_UUID_TYPE_16: m_uuid = BLEUUID(service->uuid.u16.value); break; + case BLE_UUID_TYPE_32: m_uuid = BLEUUID(service->uuid.u32.value); break; + case BLE_UUID_TYPE_128: m_uuid = BLEUUID(const_cast(&service->uuid.u128)); break; + default: break; + } + m_startHandle = service->start_handle; + m_endHandle = service->end_handle; + log_v("<< BLERemoteService(): %s", m_uuid.toString().c_str()); +} + +/** + * @brief Retrieve all the characteristics for this service. + * This function will not return until we have all the characteristics. + */ +void BLERemoteService::retrieveCharacteristics(BLEUUID *uuid_filter) { + log_v(">> retrieveCharacteristics() for service: %s", getUUID().toString().c_str()); + + int rc = 0; + TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); + ble_task_data_t taskData = {this, cur_task, 0, nullptr}; + + if (uuid_filter == nullptr) { + rc = ble_gattc_disc_all_chrs(m_pClient->getConnId(), m_startHandle, m_endHandle, BLERemoteService::characteristicDiscCB, &taskData); + } else { + rc = ble_gattc_disc_chrs_by_uuid( + m_pClient->getConnId(), m_startHandle, m_endHandle, &uuid_filter->getNative()->u, BLERemoteService::characteristicDiscCB, &taskData + ); + } + + if (rc != 0) { + log_e("ble_gattc_disc_all_chrs: rc=%d %s", rc, BLEUtils::returnCodeToString(rc)); + return; + } + +#ifdef ulTaskNotifyValueClear + // Clear the task notification value to ensure we block + ulTaskNotifyValueClear(cur_task, ULONG_MAX); +#endif + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + if (taskData.rc == 0) { + if (uuid_filter == nullptr) { + if (m_characteristicMap.size() > 1) { + for (auto it = m_characteristicMap.begin(); it != m_characteristicMap.end(); ++it) { + auto nx = std::next(it, 1); + if (nx == m_characteristicMap.end()) { + break; + } + it->second->m_endHandle = nx->second->m_defHandle - 1; + } + } + + if (m_characteristicMap.size() > 0) { + std::prev(m_characteristicMap.end())->second->m_endHandle = getEndHandle(); + } + } + + log_v("<< retrieveCharacteristics()"); + } else { + log_e("Could not retrieve characteristics"); + } +} // retrieveCharacteristics + +/** + * @brief Callback for characteristic discovery. + * @return success == 0 or error code. + */ +int BLERemoteService::characteristicDiscCB(uint16_t conn_handle, const struct ble_gatt_error *error, const struct ble_gatt_chr *chr, void *arg) { + log_d("Characteristic Discovered >> status: %d handle: %d", error->status, (error->status == 0) ? chr->val_handle : -1); + + ble_task_data_t *pTaskData = (ble_task_data_t *)arg; + BLERemoteService *service = (BLERemoteService *)pTaskData->pATT; + + // Make sure the discovery is for this device + if (service->getClient()->getConnId() != conn_handle) { + return 0; + } + + if (error->status == 0) { + // Found a service - add it to the vector + BLERemoteCharacteristic *pRemoteCharacteristic = new BLERemoteCharacteristic(service, chr); + service->m_characteristicMap.insert( + std::pair(pRemoteCharacteristic->getUUID().toString().c_str(), pRemoteCharacteristic) + ); + service->m_characteristicMapByHandle.insert(std::pair(chr->val_handle, pRemoteCharacteristic)); + return 0; + } + + if (error->status == BLE_HS_EDONE) { + pTaskData->rc = 0; + } else { + log_e("characteristicDiscCB() rc=%d %s", error->status, BLEUtils::returnCodeToString(error->status)); + pTaskData->rc = error->status; + } + + xTaskNotifyGive(pTaskData->task); + + log_d("<< Characteristic Discovered"); + return error->status; +} + +#endif + +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ diff --git a/libraries/BLE/src/BLERemoteService.h b/libraries/BLE/src/BLERemoteService.h index 49845a0a1e8..5db4622f445 100644 --- a/libraries/BLE/src/BLERemoteService.h +++ b/libraries/BLE/src/BLERemoteService.h @@ -3,6 +3,10 @@ * * Created on: Jul 8, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ #ifndef COMPONENTS_CPP_UTILS_BLEREMOTESERVICE_H_ @@ -11,7 +15,11 @@ #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ #include @@ -19,6 +27,19 @@ #include "BLERemoteCharacteristic.h" #include "BLEUUID.h" #include "RTOS.h" +#include "BLEUtils.h" + +/*************************************************************************** + * NimBLE includes * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +#include +#endif + +/*************************************************************************** + * Forward declarations * + ***************************************************************************/ class BLEClient; class BLERemoteCharacteristic; @@ -28,9 +49,11 @@ class BLERemoteCharacteristic; */ class BLERemoteService { public: - virtual ~BLERemoteService(); + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ - // Public methods + virtual ~BLERemoteService(); BLERemoteCharacteristic *getCharacteristic(const char *uuid); // Get the specified characteristic reference. BLERemoteCharacteristic *getCharacteristic(BLEUUID uuid); // Get the specified characteristic reference. BLERemoteCharacteristic *getCharacteristic(uint16_t uuid); // Get the specified characteristic reference. @@ -46,24 +69,12 @@ class BLERemoteService { String toString(void); private: - // Private constructor ... never meant to be created by a user application. - BLERemoteService(esp_gatt_id_t srvcId, BLEClient *pClient, uint16_t startHandle, uint16_t endHandle); - - // Friends friend class BLEClient; friend class BLERemoteCharacteristic; - // Private methods - void retrieveCharacteristics(void); // Retrieve the characteristics from the BLE Server. - esp_gatt_id_t *getSrvcId(void); - uint16_t getStartHandle(); // Get the start handle for this service. - uint16_t getEndHandle(); // Get the end handle for this service. - - void gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *evtParam); - - void removeCharacteristics(); - - // Properties + /*************************************************************************** + * Common private properties * + ***************************************************************************/ // We maintain a map of characteristics owned by this service keyed by a string representation of the UUID. std::map m_characteristicMap; @@ -74,12 +85,49 @@ class BLERemoteService { bool m_haveCharacteristics; // Have we previously obtained the characteristics. BLEClient *m_pClient; FreeRTOS::Semaphore m_semaphoreGetCharEvt = FreeRTOS::Semaphore("GetCharEvt"); - esp_gatt_id_t m_srvcId; BLEUUID m_uuid; // The UUID of this service. uint16_t m_startHandle; // The starting handle of this service. uint16_t m_endHandle; // The ending handle of this service. + + /*************************************************************************** + * Bluedroid private properties * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + esp_gatt_id_t m_srvcId; +#endif + + /*************************************************************************** + * Common private declarations * + ***************************************************************************/ + + uint16_t getStartHandle(); // Get the start handle for this service. + uint16_t getEndHandle(); // Get the end handle for this service. + void removeCharacteristics(); + + /*************************************************************************** + * Bluedroid private declarations * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + // Private constructor ... never meant to be created by a user application. + BLERemoteService(esp_gatt_id_t srvcId, BLEClient *pClient, uint16_t startHandle, uint16_t endHandle); + esp_gatt_id_t *getSrvcId(void); + void retrieveCharacteristics(void); + void gattClientEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *evtParam); +#endif + + /*************************************************************************** + * NimBLE private declarations * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + BLERemoteService(BLEClient *pClient, const struct ble_gatt_svc *service); + void retrieveCharacteristics(BLEUUID *uuid_filter = nullptr); + static int characteristicDiscCB(uint16_t conn_handle, const struct ble_gatt_error *error, const struct ble_gatt_chr *chr, void *arg); +#endif }; // BLERemoteService -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ #endif /* COMPONENTS_CPP_UTILS_BLEREMOTESERVICE_H_ */ diff --git a/libraries/BLE/src/BLEScan.cpp b/libraries/BLE/src/BLEScan.cpp index 0a99b46c61d..73890c39c27 100644 --- a/libraries/BLE/src/BLEScan.cpp +++ b/libraries/BLE/src/BLEScan.cpp @@ -6,12 +6,21 @@ * * Update: April, 2021 * add BLE5 support + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ + #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ #include @@ -23,12 +32,17 @@ #include "GeneralUtils.h" #include "esp32-hal-log.h" +/*************************************************************************** + * Common functions * + ***************************************************************************/ + /** - * Constructor + * @brief Constructor */ BLEScan::BLEScan() { memset(&m_scan_params, 0, sizeof(m_scan_params)); // Initialize all params - m_scan_params.scan_type = BLE_SCAN_TYPE_PASSIVE; // Default is a passive scan. +#if defined(CONFIG_BLUEDROID_ENABLED) + 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; m_scan_params.scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE; @@ -38,15 +52,203 @@ BLEScan::BLEScan() { m_shouldParse = true; setInterval(100); setWindow(100); +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + m_scan_params.filter_policy = BLE_HCI_SCAN_FILT_NO_WL; + m_scan_params.passive = 1; // If set, don’t send scan requests to advertisers (i.e., don’t request additional advertising data). + m_scan_params.limited = 0; // If set, only discover devices in limited discoverable mode. + m_scan_params.filter_duplicates = 0; // If set, the controller ignores all but the first advertisement from each device. + m_pAdvertisedDeviceCallbacks = nullptr; + m_ignoreResults = false; + m_pTaskData = nullptr; + m_duration = BLE_HS_FOREVER; // make sure this is non-zero in the event of a host reset + m_maxResults = 0xFF; + m_stopped = true; + m_wantDuplicates = false; + m_shouldParse = true; + // This is defined as the time interval from when the Controller started its last LE scan until it begins the subsequent LE scan. (units=0.625 msec) + setInterval(100); + // The duration of the LE scan. LE_Scan_Window shall be less than or equal to LE_Scan_Interval (units=0.625 msec) + setWindow(100); +#endif } // BLEScan +/** + * @brief Scan destructor, release any allocated resources. + */ +BLEScan::~BLEScan() { + clearResults(); +} + +/** + * @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 defined(CONFIG_BLUEDROID_ENABLED) + if (active) { + m_scan_params.scan_type = BLE_SCAN_TYPE_ACTIVE; + } else { + m_scan_params.scan_type = BLE_SCAN_TYPE_PASSIVE; + } +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + m_scan_params.passive = !active; +#endif +} // setActiveScan + +/** + * @brief Set the call backs to be invoked. + * @param [in] pAdvertisedDeviceCallbacks Call backs to be invoked. + * @param [in] wantDuplicates True if we wish to be called back with duplicates. Default is false. + * @param [in] shouldParse True if we wish to parse advertised package or raw payload. Default is true. + */ +void BLEScan::setAdvertisedDeviceCallbacks(BLEAdvertisedDeviceCallbacks *pAdvertisedDeviceCallbacks, bool wantDuplicates, bool shouldParse) { + m_wantDuplicates = wantDuplicates; +#if defined(CONFIG_NIMBLE_ENABLED) + setDuplicateFilter(!wantDuplicates); +#endif + m_pAdvertisedDeviceCallbacks = pAdvertisedDeviceCallbacks; + m_shouldParse = shouldParse; +} // setAdvertisedDeviceCallbacks + +/** + * @brief Set the interval to scan. + * @param [in] The interval in msecs. + */ +void BLEScan::setInterval(uint16_t intervalMSecs) { +#if defined(CONFIG_BLUEDROID_ENABLED) + m_scan_params.scan_interval = intervalMSecs / 0.625; +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + m_scan_params.itvl = intervalMSecs / 0.625; +#endif +} // setInterval + +/** + * @brief Set the window to actively scan. + * @param [in] windowMSecs How long to actively scan. + */ +void BLEScan::setWindow(uint16_t windowMSecs) { +#if defined(CONFIG_BLUEDROID_ENABLED) + m_scan_params.scan_window = windowMSecs / 0.625; +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + m_scan_params.window = windowMSecs / 0.625; +#endif +} // setWindow + +// delete peer device from cache after disconnecting, it is required in case we are connecting to devices with not public address +void BLEScan::erase(BLEAddress address) { + log_i("erase device: %s", address.toString().c_str()); + BLEAdvertisedDevice *advertisedDevice = m_scanResults.m_vectorAdvertisedDevices.find(address.toString().c_str())->second; + m_scanResults.m_vectorAdvertisedDevices.erase(address.toString().c_str()); + delete advertisedDevice; +} + +/** + * @brief Dump the scan results to the log. + */ +void BLEScanResults::dump() { + log_v(">> Dump scan results:"); + for (int i = 0; i < getCount(); i++) { + log_d("- %s", getDevice(i).toString().c_str()); + } +} // dump + +/** + * @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) { + uint32_t x = 0; + BLEAdvertisedDevice dev = *m_vectorAdvertisedDevices.begin()->second; + for (auto it = m_vectorAdvertisedDevices.begin(); it != m_vectorAdvertisedDevices.end(); it++) { + dev = *it->second; + if (x == i) { + break; + } + x++; + } + return dev; +} + +BLEScanResults *BLEScan::getResults() { + return &m_scanResults; +} + +void BLEScan::clearResults() { + for (auto _dev : m_scanResults.m_vectorAdvertisedDevices) { + delete _dev.second; + } + m_scanResults.m_vectorAdvertisedDevices.clear(); +} + +/*************************************************************************** + * Bluedroid functions * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + +#if defined(SOC_BLE_50_SUPPORTED) +void BLEScan::setExtendedScanCallback(BLEExtAdvertisingCallbacks *cb) { + m_pExtendedScanCb = cb; +} + +/** +* @brief This function is used to enable scanning. +* +* @param[in] duration : Scan duration +* @param[in] period : Time interval from when the Controller started its last Scan Duration until it begins the subsequent Scan Duration. +* +* @return - ESP_OK : success +* - other : failed +* +*/ +esp_err_t BLEScan::startExtScan(uint32_t duration, uint16_t period) { + esp_err_t rc = esp_ble_gap_start_ext_scan(duration, period); + if (rc) { + log_e("extended scan start failed: %d", rc); + } + return rc; +} + +esp_err_t BLEScan::stopExtScan() { + esp_err_t rc = esp_ble_gap_stop_ext_scan(); + if (rc) { + log_e("extended scan stop failed: %d", rc); + } + return rc; +} + +void BLEScan::setPeriodicScanCallback(BLEPeriodicScanCallbacks *cb) { + m_pPeriodicScanCb = cb; +} + +#endif // SOC_BLE_50_SUPPORTED + /** * @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::handleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { - switch (event) { // --------------------------- @@ -245,126 +447,6 @@ void BLEScan::handleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_ } // End switch } // gapEventHandler -/** - * @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. - * @param [in] wantDuplicates True if we wish to be called back with duplicates. Default is false. - * @param [in] shouldParse True if we wish to parse advertised package or raw payload. Default is true. - */ -void BLEScan::setAdvertisedDeviceCallbacks(BLEAdvertisedDeviceCallbacks *pAdvertisedDeviceCallbacks, bool wantDuplicates, bool shouldParse) { - m_wantDuplicates = wantDuplicates; - m_pAdvertisedDeviceCallbacks = pAdvertisedDeviceCallbacks; - m_shouldParse = shouldParse; -} // setAdvertisedDeviceCallbacks - -#ifdef SOC_BLE_50_SUPPORTED - -void BLEScan::setExtendedScanCallback(BLEExtAdvertisingCallbacks *cb) { - m_pExtendedScanCb = cb; -} - -/** -* @brief This function is used to set the extended scan parameters to be used on the advertising channels. -* -* -* @return - ESP_OK : success -* - other : failed -* -*/ -esp_err_t BLEScan::setExtScanParams() { - esp_ble_ext_scan_params_t ext_scan_params = { - .own_addr_type = BLE_ADDR_TYPE_PUBLIC, - .filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, - .scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE, - .cfg_mask = ESP_BLE_GAP_EXT_SCAN_CFG_UNCODE_MASK | ESP_BLE_GAP_EXT_SCAN_CFG_CODE_MASK, - .uncoded_cfg = {BLE_SCAN_TYPE_ACTIVE, 40, 40}, - .coded_cfg = {BLE_SCAN_TYPE_ACTIVE, 40, 40}, - }; - - esp_err_t rc = esp_ble_gap_set_ext_scan_params(&ext_scan_params); - if (rc) { - log_e("set extend scan params error, error code = %x", rc); - } - return rc; -} - -/** -* @brief This function is used to set the extended scan parameters to be used on the advertising channels. -* -* @param[in] params : scan parameters -* -* @return - ESP_OK : success -* - other : failed -* -*/ -esp_err_t BLEScan::setExtScanParams(esp_ble_ext_scan_params_t *ext_scan_params) { - esp_err_t rc = esp_ble_gap_set_ext_scan_params(ext_scan_params); - if (rc) { - log_e("set extend scan params error, error code = %x", rc); - } - return rc; -} - -/** -* @brief This function is used to enable scanning. -* -* @param[in] duration : Scan duration -* @param[in] period : Time interval from when the Controller started its last Scan Duration until it begins the subsequent Scan Duration. -* -* @return - ESP_OK : success -* - other : failed -* -*/ -esp_err_t BLEScan::startExtScan(uint32_t duration, uint16_t period) { - esp_err_t rc = esp_ble_gap_start_ext_scan(duration, period); - if (rc) { - log_e("extended scan start failed: %d", rc); - } - return rc; -} - -esp_err_t BLEScan::stopExtScan() { - esp_err_t rc; - rc = esp_ble_gap_stop_ext_scan(); - - return rc; -} - -void BLEScan::setPeriodicScanCallback(BLEPeriodicScanCallbacks *cb) { - m_pPeriodicScanCb = cb; -} - -#endif // SOC_BLE_50_SUPPORTED -/** - * @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. @@ -425,7 +507,7 @@ BLEScanResults *BLEScan::start(uint32_t duration, bool is_continue) { * @brief Stop an in progress scan. * @return N/A. */ -void BLEScan::stop() { +bool BLEScan::stop() { log_v(">> stop()"); esp_err_t errRc = ::esp_ble_gap_stop_scanning(); @@ -435,67 +517,288 @@ void BLEScan::stop() { if (errRc != ESP_OK) { log_e("esp_ble_gap_stop_scanning: err: %d, text: %s", errRc, GeneralUtils::errorToString(errRc)); - return; + return false; } log_v("<< stop()"); + return true; } // stop -// delete peer device from cache after disconnecting, it is required in case we are connecting to devices with not public address -void BLEScan::erase(BLEAddress address) { - log_i("erase device: %s", address.toString().c_str()); - BLEAdvertisedDevice *advertisedDevice = m_scanResults.m_vectorAdvertisedDevices.find(address.toString().c_str())->second; - m_scanResults.m_vectorAdvertisedDevices.erase(address.toString().c_str()); - delete advertisedDevice; +#endif // CONFIG_BLUEDROID_ENABLED + +/*************************************************************************** + * NimBLE functions * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + +/** + * @brief Set whether or not the BLE controller should only report results + * from devices it has not already seen. + * @param [in] enabled If true, scanned devices will only be reported once. + * @details The controller has a limited buffer and will start reporting + * duplicate devices once the limit is reached. + */ +void BLEScan::setDuplicateFilter(bool enabled) { + m_scan_params.filter_duplicates = enabled; +} // setDuplicateFilter + +/** + * @brief Get the status of the scanner. + * @return true if scanning or scan starting. + */ +bool BLEScan::isScanning() { + return ble_gap_disc_active(); } /** - * @brief Dump the scan results to the log. + * @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 BLEScanResults::dump() { - log_v(">> Dump scan results:"); - for (int i = 0; i < getCount(); i++) { - log_d("- %s", getDevice(i).toString().c_str()); +int BLEScan::handleGAPEvent(ble_gap_event *event, void *arg) { + BLEScan *pScan = (BLEScan *)arg; + + switch (event->type) { + case BLE_GAP_EVENT_DISC: + { + if (pScan->m_ignoreResults) { + log_i("Scan op in progress - ignoring results"); + return 0; + } + + BLEAddress advertisedAddress(event->disc.addr); + + /* // Examine our list of ignored addresses and stop processing if we don't want to see it or are already connected + if (BLEDevice::isIgnored(advertisedAddress)) { + log_i("Ignoring device: address: %s", advertisedAddress.toString().c_str()); + return 0; + } */ + + BLEAdvertisedDevice *advertisedDevice = nullptr; + + // If we've seen this device before get a pointer to it from the vector + for (auto &it : pScan->m_scanResults.m_vectorAdvertisedDevices) { + if (it.second->getAddress() == advertisedAddress) { + advertisedDevice = it.second; + break; + } + } + + // If we haven't seen this device before; create a new instance and insert it in the vector. + // Otherwise just update the relevant parameters of the already known device. + if (advertisedDevice == nullptr && event->disc.event_type != BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP) { + // Check if we have reach the scan results limit, ignore this one if so. + // We still need to store each device when maxResults is 0 to be able to append the scan results + if (pScan->m_maxResults > 0 && pScan->m_maxResults < 0xFF && (pScan->m_scanResults.m_vectorAdvertisedDevices.size() >= pScan->m_maxResults)) { + return 0; + } + advertisedDevice = new BLEAdvertisedDevice(); + advertisedDevice->setAddress(advertisedAddress); + advertisedDevice->setAdvType(event->disc.event_type); + pScan->m_scanResults.m_vectorAdvertisedDevices.insert(std::pair(advertisedAddress.toString().c_str(), advertisedDevice)); + log_i("New advertiser: %s", advertisedAddress.toString().c_str()); + } else if (advertisedDevice != nullptr) { + log_i("Updated advertiser: %s", advertisedAddress.toString().c_str()); + } else { + // Scan response from unknown device + return 0; + } + + advertisedDevice->setRSSI(event->disc.rssi); + advertisedDevice->setPayload((uint8_t *)event->disc.data, event->disc.length_data, event->disc.event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP); + + if (pScan->m_pAdvertisedDeviceCallbacks) { + // If not active scanning or scan response is not available + // report the result to the callback now. + if (pScan->m_scan_params.passive + || (advertisedDevice->getAdvType() != BLE_HCI_ADV_TYPE_ADV_IND && advertisedDevice->getAdvType() != BLE_HCI_ADV_TYPE_ADV_SCAN_IND)) { + advertisedDevice->m_callbackSent = true; + pScan->m_pAdvertisedDeviceCallbacks->onResult(*advertisedDevice); + + // Otherwise, wait for the scan response so we can report the complete data. + } else if (event->disc.event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP) { + advertisedDevice->m_callbackSent = true; + pScan->m_pAdvertisedDeviceCallbacks->onResult(*advertisedDevice); + } + // If not storing results and we have invoked the callback, delete the device. + if (pScan->m_maxResults == 0 && advertisedDevice->m_callbackSent) { + pScan->erase(advertisedAddress); + } + } + + return 0; + } + + case BLE_GAP_EVENT_DISC_COMPLETE: + { + log_d("discovery complete; reason=%d", event->disc_complete.reason); + + // If a device advertised with scan response available and it was not received + // the callback would not have been invoked, so do it here. + if (pScan->m_pAdvertisedDeviceCallbacks) { + for (auto &it : pScan->m_scanResults.m_vectorAdvertisedDevices) { + if (!it.second->m_callbackSent) { + pScan->m_pAdvertisedDeviceCallbacks->onResult(*(it.second)); + } + } + } + + if (pScan->m_maxResults == 0) { + pScan->clearResults(); + } + + if (pScan->m_scanCompleteCB != nullptr) { + pScan->m_scanCompleteCB(pScan->m_scanResults); + } + + if (pScan->m_pTaskData != nullptr) { + pScan->m_pTaskData->rc = event->disc_complete.reason; + xTaskNotifyGive(pScan->m_pTaskData->task); + } + + return 0; + } + + default: return 0; } -} // dump +} // gapEventHandler /** - * @brief Return the count of devices found in the last scan. - * @return The number of devices found in the last scan. + * @brief Start scanning. + * @param [in] duration The duration in seconds for which to scan. + * @param [in] scanCompleteCB A function to be called when scanning has completed. + * @param [in] is_continue Set to true to save previous scan results, false to clear them. + * @return True if scan started or false if there was an error. */ -int BLEScanResults::getCount() { - return m_vectorAdvertisedDevices.size(); -} // getCount +bool BLEScan::start(uint32_t duration, void (*scanCompleteCB)(BLEScanResults), bool is_continue) { + log_d(">> start(duration=%d)", duration); + + // Save the callback to be invoked when the scan completes. + m_scanCompleteCB = scanCompleteCB; + // Save the duration in the case that the host is reset so we can reuse it. + m_duration = duration; + + // If 0 duration specified then we assume a continuous scan is desired. + if (duration == 0) { + duration = BLE_HS_FOREVER; + } else { + // convert duration to milliseconds + duration = duration * 1000; + } + + // Set the flag to ignore the results while we are deleting the vector + if (!is_continue) { + m_ignoreResults = true; + } + + int rc = ble_gap_disc(BLEDevice::m_ownAddrType, duration, &m_scan_params, BLEScan::handleGAPEvent, this); + + switch (rc) { + case 0: + if (!is_continue) { + clearResults(); + } + break; + + case BLE_HS_EALREADY: + break; + + case BLE_HS_EBUSY: log_e("Unable to scan - connection in progress."); break; + + case BLE_HS_ETIMEOUT_HCI: + case BLE_HS_EOS: + case BLE_HS_ECONTROLLER: + case BLE_HS_ENOTSYNCED: log_e("Unable to scan - Host Reset"); break; + + default: log_e("Error initiating GAP discovery procedure; rc=%d, %s", rc, BLEUtils::returnCodeToString(rc)); break; + } + + m_ignoreResults = false; + log_d("<< start()"); + + if (rc != 0 && rc != BLE_HS_EALREADY) { + return false; + } + return true; +} // start /** - * @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. + * @brief Start scanning and block until scanning has been completed. + * @param [in] duration The duration in seconds for which to scan. + * @param [in] is_continue Set to true to save previous scan results, false to clear them. + * @return The BLEScanResults. */ -BLEAdvertisedDevice BLEScanResults::getDevice(uint32_t i) { - uint32_t x = 0; - BLEAdvertisedDevice dev = *m_vectorAdvertisedDevices.begin()->second; - for (auto it = m_vectorAdvertisedDevices.begin(); it != m_vectorAdvertisedDevices.end(); it++) { - dev = *it->second; - if (x == i) { - break; - } - x++; +BLEScanResults *BLEScan::start(uint32_t duration, bool is_continue) { + if (duration == 0) { + log_w("Blocking scan called with duration = forever"); } - return dev; -} -BLEScanResults *BLEScan::getResults() { + TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); + ble_task_data_t taskData = {nullptr, cur_task, 0, nullptr}; + m_pTaskData = &taskData; + + if (start(duration, nullptr, is_continue)) { +#ifdef ulTaskNotifyValueClear + // Clear the task notification value to ensure we block + ulTaskNotifyValueClear(cur_task, ULONG_MAX); +#endif + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + } + + m_pTaskData = nullptr; return &m_scanResults; +} // start + +/** + * @brief Stop an in progress scan. + * @return True if successful. + */ +bool BLEScan::stop() { + log_d(">> stop()"); + + int rc = ble_gap_disc_cancel(); + if (rc != 0 && rc != BLE_HS_EALREADY) { + log_e("Failed to cancel scan; rc=%d", rc); + return false; + } + + if (m_maxResults == 0) { + clearResults(); + } + + if (rc != BLE_HS_EALREADY && m_scanCompleteCB != nullptr) { + m_scanCompleteCB(m_scanResults); + } + + if (m_pTaskData != nullptr) { + xTaskNotifyGive(m_pTaskData->task); + } + + log_d("<< stop()"); + return true; +} // stop + +/** + * @brief Called when host reset, we set a flag to stop scanning until synced. + */ +void BLEScan::onHostReset() { + m_ignoreResults = true; } -void BLEScan::clearResults() { - for (auto _dev : m_scanResults.m_vectorAdvertisedDevices) { - delete _dev.second; +/** + * @brief If the host reset and re-synced this is called. + * If the application was scanning indefinitely with a callback, restart it. + */ +void BLEScan::onHostSync() { + m_ignoreResults = false; + + if (m_duration == 0 && m_pAdvertisedDeviceCallbacks != nullptr) { + start(m_duration, m_scanCompleteCB); } - m_scanResults.m_vectorAdvertisedDevices.clear(); } -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif // CONFIG_NIMBLE_ENABLED + +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ diff --git a/libraries/BLE/src/BLEScan.h b/libraries/BLE/src/BLEScan.h index 080e3b803b2..900a2d59093 100644 --- a/libraries/BLE/src/BLEScan.h +++ b/libraries/BLE/src/BLEScan.h @@ -3,6 +3,10 @@ * * Created on: Jul 1, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ #ifndef COMPONENTS_CPP_UTILS_BLESCAN_H_ @@ -11,15 +15,38 @@ #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) -#include +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ -// #include #include #include "BLEAdvertisedDevice.h" #include "BLEClient.h" +#include "BLETypes.h" #include "RTOS.h" +/*************************************************************************** + * Bluedroid includes * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) +#include +#endif + +/*************************************************************************** + * NimBLE includes * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +#include "host/ble_gap.h" +#endif + +/*************************************************************************** + * Forward declarations * + ***************************************************************************/ + class BLEAdvertisedDevice; class BLEAdvertisedDeviceCallbacks; class BLEExtAdvertisingCallbacks; @@ -27,6 +54,11 @@ class BLEClient; class BLEScan; class BLEPeriodicScanCallbacks; +/*************************************************************************** + * Bluedroid type definitions * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) struct esp_ble_periodic_adv_sync_estab_param_t { uint8_t status; /*!< periodic advertising sync status */ uint16_t sync_handle; /*!< periodic advertising sync handle */ @@ -37,6 +69,7 @@ struct esp_ble_periodic_adv_sync_estab_param_t { uint16_t period_adv_interval; /*!< periodic advertising interval */ uint8_t adv_clk_accuracy; /*!< periodic advertising clock accuracy */ }; +#endif /** * @brief The result of having performed a scan. @@ -47,12 +80,21 @@ struct esp_ble_periodic_adv_sync_estab_param_t { */ class BLEScanResults { public: + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ + void dump(); int getCount(); BLEAdvertisedDevice getDevice(uint32_t i); private: friend BLEScan; + + /*************************************************************************** + * Common private properties * + ***************************************************************************/ + std::map m_vectorAdvertisedDevices; }; @@ -63,37 +105,52 @@ class BLEScanResults { */ class BLEScan { public: + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ + + ~BLEScan(); void setActiveScan(bool active); void setAdvertisedDeviceCallbacks(BLEAdvertisedDeviceCallbacks *pAdvertisedDeviceCallbacks, bool wantDuplicates = false, bool shouldParse = true); void setInterval(uint16_t intervalMSecs); void setWindow(uint16_t windowMSecs); bool start(uint32_t duration, void (*scanCompleteCB)(BLEScanResults), bool is_continue = false); BLEScanResults *start(uint32_t duration, bool is_continue = false); - void stop(); + bool stop(); void erase(BLEAddress address); BLEScanResults *getResults(); void clearResults(); -#ifdef SOC_BLE_50_SUPPORTED + /*************************************************************************** + * Bluedroid public declarations * + ***************************************************************************/ + +#if defined(SOC_BLE_50_SUPPORTED) && defined(CONFIG_BLUEDROID_ENABLED) void setExtendedScanCallback(BLEExtAdvertisingCallbacks *cb); void setPeriodicScanCallback(BLEPeriodicScanCallbacks *cb); - esp_err_t stopExtScan(); esp_err_t setExtScanParams(); - esp_err_t setExtScanParams(esp_ble_ext_scan_params_t *ext_scan_params); esp_err_t startExtScan(uint32_t duration, uint16_t period); - -private: - BLEExtAdvertisingCallbacks *m_pExtendedScanCb = nullptr; - BLEPeriodicScanCallbacks *m_pPeriodicScanCb = nullptr; + esp_err_t setExtScanParams(esp_ble_ext_scan_params_t *ext_scan_params); #endif // SOC_BLE_50_SUPPORTED + /*************************************************************************** + * NimBLE public declarations * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + void setDuplicateFilter(bool enabled); + bool isScanning(); + void clearDuplicateCache(); +#endif + private: - BLEScan(); // One doesn't create a new instance instead one asks the BLEDevice for the singleton. friend class BLEDevice; - void handleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); - esp_ble_scan_params_t m_scan_params; + /*************************************************************************** + * Common private properties * + ***************************************************************************/ + BLEAdvertisedDeviceCallbacks *m_pAdvertisedDeviceCallbacks = nullptr; bool m_stopped = true; bool m_shouldParse = true; @@ -101,21 +158,79 @@ class BLEScan { BLEScanResults m_scanResults; bool m_wantDuplicates; void (*m_scanCompleteCB)(BLEScanResults scanResults); + + /*************************************************************************** + * Bluedroid private properties * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + esp_ble_scan_params_t m_scan_params; +#if defined(SOC_BLE_50_SUPPORTED) + BLEExtAdvertisingCallbacks *m_pExtendedScanCb = nullptr; + BLEPeriodicScanCallbacks *m_pPeriodicScanCb = nullptr; +#endif // SOC_BLE_50_SUPPORTED +#endif + + /*************************************************************************** + * NimBLE private properties * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + uint32_t m_duration; + ble_gap_disc_params m_scan_params; + bool m_ignoreResults; + ble_task_data_t *m_pTaskData; + uint8_t m_maxResults; +#endif + + /*************************************************************************** + * Common private definitions * + ***************************************************************************/ + + BLEScan(); // One doesn't create a new instance instead one asks the BLEDevice for the singleton. + + /*************************************************************************** + * Bluedroid private definitions * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + void handleGAPEvent(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); +#endif + + /*************************************************************************** + * NimBLE private definitions * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + void onHostReset(); + void onHostSync(); + static int handleGAPEvent(ble_gap_event *event, void *arg); +#endif }; // BLEScan class BLEPeriodicScanCallbacks { public: + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ + virtual ~BLEPeriodicScanCallbacks() {} + virtual void onLostSync(uint16_t sync_handle) {} + /*************************************************************************** + * Bluedroid public declarations * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) virtual void onCreateSync(esp_bt_status_t status) {} virtual void onCancelSync(esp_bt_status_t status) {} virtual void onTerminateSync(esp_bt_status_t status) {} - virtual void onLostSync(uint16_t sync_handle) {} virtual void onSync(esp_ble_periodic_adv_sync_estab_param_t) {} virtual void onReport(esp_ble_gap_periodic_adv_report_t params) {} virtual void onStop(esp_bt_status_t status) {} +#endif }; -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ #endif /* COMPONENTS_CPP_UTILS_BLESCAN_H_ */ diff --git a/libraries/BLE/src/BLESecurity.cpp b/libraries/BLE/src/BLESecurity.cpp index 34fc3e69e9e..87aa8cd0d77 100644 --- a/libraries/BLE/src/BLESecurity.cpp +++ b/libraries/BLE/src/BLESecurity.cpp @@ -3,77 +3,115 @@ * * Created on: Dec 17, 2017 * Author: chegewara + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on chegewara's and h2zero's work) + * Description: Added support for NimBLE */ #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED -#include "BLESecurity.h" #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ + +#include "BLESecurity.h" +#include "BLEUtils.h" + +/*************************************************************************** + * NimBLE includes * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +#include +#endif + +/*************************************************************************** + * Common properties * + ***************************************************************************/ + +uint32_t BLESecurity::m_passkey = BLE_SM_DEFAULT_PASSKEY; + +/*************************************************************************** + * Common functions * + ***************************************************************************/ BLESecurity::BLESecurity() {} BLESecurity::~BLESecurity() {} -/* - * @brief Set requested authentication mode - */ -void BLESecurity::setAuthenticationMode(esp_ble_auth_req_t auth_req) { + +void BLESecurity::setAuthenticationMode(uint8_t auth_req) { m_authReq = auth_req; - esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &m_authReq, sizeof(uint8_t)); // <--- setup requested authentication mode +#if defined(CONFIG_BLUEDROID_ENABLED) + esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &m_authReq, sizeof(uint8_t)); +#elif defined(CONFIG_NIMBLE_ENABLED) + BLESecurity::setAuthenticationMode( + (auth_req & BLE_SM_PAIR_AUTHREQ_BOND) != 0, + (auth_req & BLE_SM_PAIR_AUTHREQ_MITM) != 0, + (auth_req & BLE_SM_PAIR_AUTHREQ_SC) != 0 + ); +#endif } -/** - * @brief Set our device IO capability to let end user perform authorization - * either by displaying or entering generated 6-digits pin code - */ -void BLESecurity::setCapability(esp_ble_io_cap_t iocap) { +void BLESecurity::setCapability(uint8_t iocap) { m_iocap = iocap; +#if defined(CONFIG_BLUEDROID_ENABLED) esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t)); -} // setCapability +#elif defined(CONFIG_NIMBLE_ENABLED) + ble_hs_cfg.sm_io_cap = iocap; +#endif +} -/** - * @brief Init encryption key by server - * @param key_size is value between 7 and 16 - */ void BLESecurity::setInitEncryptionKey(uint8_t init_key) { m_initKey = init_key; +#if defined(CONFIG_BLUEDROID_ENABLED) esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &m_initKey, sizeof(uint8_t)); -} // setInitEncryptionKey +#elif defined(CONFIG_NIMBLE_ENABLED) + ble_hs_cfg.sm_our_key_dist = init_key; +#endif +} -/** - * @brief Init encryption key by client - * @param key_size is value between 7 and 16 - */ void BLESecurity::setRespEncryptionKey(uint8_t resp_key) { m_respKey = resp_key; +#if defined(CONFIG_BLUEDROID_ENABLED) esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &m_respKey, sizeof(uint8_t)); -} // setRespEncryptionKey +#elif defined(CONFIG_NIMBLE_ENABLED) + ble_hs_cfg.sm_their_key_dist = resp_key; +#endif +} -/** - * - * - */ void BLESecurity::setKeySize(uint8_t key_size) { +#if defined(CONFIG_BLUEDROID_ENABLED) m_keySize = key_size; esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &m_keySize, sizeof(uint8_t)); -} //setKeySize +#endif +} -/** - * Setup for static PIN connection, call it first and then call setAuthenticationMode eventually to change it - */ void BLESecurity::setStaticPIN(uint32_t pin) { - uint32_t passkey = pin; - esp_ble_gap_set_security_param(ESP_BLE_SM_SET_STATIC_PASSKEY, &passkey, sizeof(uint32_t)); + m_passkey = pin; +#if defined(CONFIG_BLUEDROID_ENABLED) + esp_ble_gap_set_security_param(ESP_BLE_SM_SET_STATIC_PASSKEY, &m_passkey, sizeof(uint32_t)); setCapability(ESP_IO_CAP_OUT); setKeySize(); setAuthenticationMode(ESP_LE_AUTH_REQ_SC_ONLY); setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); +#elif defined(CONFIG_NIMBLE_ENABLED) + setCapability(BLE_HS_IO_DISPLAY_ONLY); + setKeySize(); + setAuthenticationMode(false, false, true); // No bonding, no MITM, secure connection only + setInitEncryptionKey(BLE_HS_KEY_DIST_ENC_KEY | BLE_HS_KEY_DIST_ID_KEY); +#endif } -/** - * @brief Debug function to display what keys are exchanged by peers - */ +/*************************************************************************** + * Bluedroid functions * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) char *BLESecurity::esp_key_type_to_str(esp_ble_key_type_t key_type) { char *key_str = nullptr; switch (key_type) { @@ -89,7 +127,34 @@ char *BLESecurity::esp_key_type_to_str(esp_ble_key_type_t key_type) { default: key_str = (char *)"INVALID BLE KEY TYPE"; break; } return key_str; -} // esp_key_type_to_str +} +#endif + +/*************************************************************************** + * NimBLE functions * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +void BLESecurity::setAuthenticationMode(bool bonding, bool mitm, bool sc) { + m_authReq = bonding ? BLE_SM_PAIR_AUTHREQ_BOND : 0; + m_authReq |= mitm ? BLE_SM_PAIR_AUTHREQ_MITM : 0; + m_authReq |= sc ? BLE_SM_PAIR_AUTHREQ_SC : 0; + ble_hs_cfg.sm_bonding = bonding; + ble_hs_cfg.sm_mitm = mitm; + ble_hs_cfg.sm_sc = sc; +} + +bool BLESecurity::startSecurity(uint16_t connHandle, int *rcPtr) { + int rc = ble_gap_security_initiate(connHandle); + if (rc != 0) { + log_e("ble_gap_security_initiate: rc=%d %s", rc, BLEUtils::returnCodeToString(rc)); + } + if (rcPtr) { + *rcPtr = rc; + } + return rc == 0 || rc == BLE_HS_EALREADY; +} +#endif -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ diff --git a/libraries/BLE/src/BLESecurity.h b/libraries/BLE/src/BLESecurity.h index 2e3a44b42d1..535220aeb6c 100644 --- a/libraries/BLE/src/BLESecurity.h +++ b/libraries/BLE/src/BLESecurity.h @@ -3,74 +3,148 @@ * * Created on: Dec 17, 2017 * Author: chegewara + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on chegewara's and h2zero's work) + * Description: Added support for NimBLE */ #ifndef COMPONENTS_CPP_UTILS_BLESECURITY_H_ #define COMPONENTS_CPP_UTILS_BLESECURITY_H_ + #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ + +#include "WString.h" +/*************************************************************************** + * Bluedroid includes * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) #include +#endif + +/*************************************************************************** + * NimBLE includes * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +#include +#endif +/*************************************************************************** + * Common definitions * + ***************************************************************************/ + +#define BLE_SM_DEFAULT_PASSKEY 123456 + +/*************************************************************************** + * Forward declarations * + ***************************************************************************/ + +class BLEServer; +class BLEClient; + +/** + * @brief Security management class + */ class BLESecurity { public: + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ + BLESecurity(); virtual ~BLESecurity(); - void setAuthenticationMode(esp_ble_auth_req_t auth_req); - void setCapability(esp_ble_io_cap_t iocap); - void setInitEncryptionKey(uint8_t init_key); - void setRespEncryptionKey(uint8_t resp_key); - void setKeySize(uint8_t key_size = 16); - void setStaticPIN(uint32_t pin); + static void setAuthenticationMode(uint8_t auth_req); + static void setCapability(uint8_t iocap); + static void setInitEncryptionKey(uint8_t init_key); + static void setRespEncryptionKey(uint8_t resp_key); + static void setKeySize(uint8_t key_size = 16); + static void setStaticPIN(uint32_t pin); + + /*************************************************************************** + * Bluedroid public declarations * + ***************************************************************************/ + + #if defined(CONFIG_BLUEDROID_ENABLED) static char *esp_key_type_to_str(esp_ble_key_type_t key_type); + #endif + + /*************************************************************************** + * NimBLE public declarations * + ***************************************************************************/ + + #if defined(CONFIG_NIMBLE_ENABLED) + static void setAuthenticationMode(bool bonding, bool mitm, bool sc); + static bool startSecurity(uint16_t connHandle, int *rcPtr = nullptr); + #endif private: - esp_ble_auth_req_t m_authReq; - esp_ble_io_cap_t m_iocap; - uint8_t m_initKey; - uint8_t m_respKey; - uint8_t m_keySize; + friend class BLEServer; + friend class BLEClient; + + /*************************************************************************** + * Common private properties * + ***************************************************************************/ + + static uint8_t m_iocap; + static uint8_t m_authReq; + static uint8_t m_initKey; + static uint8_t m_respKey; + static uint32_t m_passkey; + + /*************************************************************************** + * Bluedroid private properties * + ***************************************************************************/ + + #if defined(CONFIG_BLUEDROID_ENABLED) + static uint8_t m_keySize; + #endif }; // BLESecurity -/* +/** * @brief Callbacks to handle GAP events related to authorization */ class BLESecurityCallbacks { public: - virtual ~BLESecurityCallbacks(){}; + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ - /** - * @brief Its request from peer device to input authentication pin code displayed on peer device. - * It requires that our device is capable to input 6-digits code by end user - * @return Return 6-digits integer value from input device - */ + virtual ~BLESecurityCallbacks(){}; virtual uint32_t onPassKeyRequest() = 0; - - /** - * @brief Provide us 6-digits code to perform authentication. - * It requires that our device is capable to display this code to end user - * @param - */ virtual void onPassKeyNotify(uint32_t pass_key) = 0; + virtual bool onSecurityRequest() = 0; + virtual bool onConfirmPIN(uint32_t pin) = 0; - /** - * @brief Here we can make decision if we want to let negotiate authorization with peer device or not - * return Return true if we accept this peer device request - */ + /*************************************************************************** + * Bluedroid public declarations * + ***************************************************************************/ - virtual bool onSecurityRequest() = 0; - /** - * Provide us information when authentication process is completed - */ + #if defined(CONFIG_BLUEDROID_ENABLED) virtual void onAuthenticationComplete(esp_ble_auth_cmpl_t) = 0; + #endif + + /*************************************************************************** + * NimBLE public declarations * + ***************************************************************************/ + + #if defined(CONFIG_NIMBLE_ENABLED) + virtual void onAuthenticationComplete(ble_gap_conn_desc *) = 0; + #endif - virtual bool onConfirmPIN(uint32_t pin) = 0; }; // BLESecurityCallbacks -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ #endif // COMPONENTS_CPP_UTILS_BLESECURITY_H_ diff --git a/libraries/BLE/src/BLEServer.cpp b/libraries/BLE/src/BLEServer.cpp index a338cf61451..cc4e624b71f 100644 --- a/libraries/BLE/src/BLEServer.cpp +++ b/libraries/BLE/src/BLEServer.cpp @@ -3,14 +3,23 @@ * * Created on: Apr 16, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ + #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/*************************************************************************** + * Common includes * + ***************************************************************************/ + #include -#include #include "GeneralUtils.h" #include "BLEDevice.h" #include "BLEServer.h" @@ -21,6 +30,27 @@ #include #include "esp32-hal-log.h" +/*************************************************************************** + * Bluedroid includes * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) +#include +#endif + +/*************************************************************************** + * NimBLE includes * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +#include +#include +#endif + +/*************************************************************************** + * Common functions * + ***************************************************************************/ + /** * @brief Construct a %BLE Server * @@ -28,8 +58,17 @@ * the BLEDevice class. */ BLEServer::BLEServer() { - m_appId = ESP_GATT_IF_NONE; +#ifdef CONFIG_BLUEDROID_ENABLED m_gatts_if = ESP_GATT_IF_NONE; +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + memset(m_indWait, BLE_HS_CONN_HANDLE_NONE, sizeof(m_indWait)); + m_svcChanged = false; +#endif + + m_appId = ESP_GATT_IF_NONE; + m_gattsStarted = false; m_connectedCount = 0; m_connId = ESP_GATT_IF_NONE; m_pServerCallbacks = nullptr; @@ -37,7 +76,9 @@ BLEServer::BLEServer() { void BLEServer::createApp(uint16_t appId) { m_appId = appId; +#ifdef CONFIG_BLUEDROID_ENABLED registerApp(appId); +#endif } // createApp /** @@ -64,7 +105,9 @@ BLEService *BLEServer::createService(const char *uuid) { */ BLEService *BLEServer::createService(BLEUUID uuid, uint32_t numHandles, uint8_t inst_id) { log_v(">> createService - %s", uuid.toString().c_str()); +#ifdef CONFIG_BLUEDROID_ENABLED m_semaphoreCreateEvt.take("createService"); +#endif // Check that a service with the supplied UUID does not already exist. if (m_serviceMap.getByUUID(uuid) != nullptr) { @@ -76,7 +119,14 @@ BLEService *BLEServer::createService(BLEUUID uuid, uint32_t numHandles, uint8_t 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. +#ifdef CONFIG_BLUEDROID_ENABLED m_semaphoreCreateEvt.wait("createService"); +#endif + +#ifdef CONFIG_NIMBLE_ENABLED + m_semaphoreCreateEvt.give(); + serviceChanged(); +#endif log_v("<< createService"); return pService; @@ -121,6 +171,190 @@ uint32_t BLEServer::getConnectedCount() { return m_connectedCount; } // getConnectedCount +void BLEServer::start() { + if (m_gattsStarted) { + return; + } + +#ifdef CONFIG_NIMBLE_ENABLED + int rc = ble_gatts_start(); + if (rc != 0) { + log_e("ble_gatts_start; rc=%d, %s", rc, BLEUtils::returnCodeToString(rc)); + return; + } + +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG + ble_gatts_show_local(); +#endif + + BLEService *svc = m_serviceMap.getFirst(); + while (svc != nullptr) { + if (svc->m_removed == 0) { + rc = ble_gatts_find_svc(&svc->getUUID().getNative()->u, &svc->m_handle); + if (rc != 0) { + abort(); + } + } + + BLECharacteristic *chr = svc->m_characteristicMap.getFirst(); + while (chr != nullptr) { + if ((chr->m_properties & BLE_GATT_CHR_F_INDICATE) || (chr->m_properties & BLE_GATT_CHR_F_NOTIFY)) { + m_notifyChrVec.push_back(chr); + } + chr = svc->m_characteristicMap.getNext(); + } + + svc = m_serviceMap.getNext(); + } + +#endif + + m_gattsStarted = true; +} + +/** + * @brief Set the server 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 + +/* + * Remove service + */ +void BLEServer::removeService(BLEService *service) { +#if defined(CONFIG_BLUEDROID_ENABLED) + service->stop(); + service->executeDelete(); + m_serviceMap.removeService(service); +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + if (service->m_removed == 0) { + int rc = ble_gatts_svc_set_visibility(service->getHandle(), 0); + if (rc != 0) { + return; + } + service->m_removed = NIMBLE_ATT_REMOVE_DELETE; + serviceChanged(); + m_serviceMap.removeService(service); + BLEDevice::getAdvertising()->removeServiceUUID(service->getUUID()); + } +#endif +} + +/** + * @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() { + log_v(">> startAdvertising"); + BLEDevice::startAdvertising(); + log_v("<< startAdvertising"); +} // startAdvertising + +/* multi connect support */ +/* TODO do some more tweaks */ +void BLEServer::updatePeerMTU(uint16_t conn_id, uint16_t mtu) { + // set mtu in conn_status_t + const std::map::iterator it = m_connectedServersMap.find(conn_id); + if (it != m_connectedServersMap.end()) { + it->second.mtu = mtu; + std::swap(m_connectedServersMap[conn_id], it->second); + } +} + +std::map BLEServer::getPeerDevices(bool _client) { + return m_connectedServersMap; +} + +uint16_t BLEServer::getPeerMTU(uint16_t conn_id) { +#if defined(CONFIG_BLUEDROID_ENABLED) + return m_connectedServersMap.find(conn_id)->second.mtu; +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + return ble_att_mtu(conn_id); +#endif +} + +void BLEServer::addPeerDevice(void *peer, bool _client, uint16_t conn_id) { + conn_status_t status = {.peer_device = peer, .connected = true, .mtu = 23}; + + m_connectedServersMap.insert(std::pair(conn_id, status)); +} + +bool BLEServer::removePeerDevice(uint16_t conn_id, bool _client) { + return m_connectedServersMap.erase(conn_id) > 0; +} + +void BLEServerCallbacks::onConnect(BLEServer *pServer) { + log_d("BLEServerCallbacks", ">> onConnect(): Default"); + log_d("BLEServerCallbacks", "Device: %s", BLEDevice::toString().c_str()); + log_d("BLEServerCallbacks", "<< onConnect()"); +} // onConnect + +void BLEServerCallbacks::onDisconnect(BLEServer *pServer) { + log_d("BLEServerCallbacks", ">> onDisconnect(): Default"); + log_d("BLEServerCallbacks", "Device: %s", BLEDevice::toString().c_str()); + log_d("BLEServerCallbacks", "<< onDisconnect()"); +} // onDisconnect + +/*************************************************************************** + * Bluedroid functions * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + +/** + * Allow to connect GATT server to peer device + * Probably can be used in ANCS for iPhone + */ +bool BLEServer::connect(BLEAddress address) { + esp_bd_addr_t addr; + memcpy(&addr, address.getNative(), 6); + // Perform the open connection request against the target BLE Server. + m_semaphoreOpenEvt.take("connect"); + esp_err_t errRc = ::esp_ble_gatts_open( + getGattsIf(), + addr, // address + 1 // direct connection + ); + if (errRc != ESP_OK) { + log_e("esp_ble_gattc_open: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return false; + } + + uint32_t rc = m_semaphoreOpenEvt.wait("connect"); // Wait for the connection to complete. + log_v("<< connect(), rc=%d", rc == ESP_GATT_OK); + return rc == ESP_GATT_OK; +} // connect + +/** + * Update connection parameters can be called only after connection has been established + */ +void BLEServer::updateConnParams(esp_bd_addr_t remote_bda, uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t timeout) { + esp_ble_conn_update_params_t conn_params; + memcpy(conn_params.bda, remote_bda, sizeof(esp_bd_addr_t)); + conn_params.latency = latency; + conn_params.max_int = maxInterval; // max_int = 0x20*1.25ms = 40ms + conn_params.min_int = minInterval; // min_int = 0x10*1.25ms = 20ms + conn_params.timeout = timeout; // timeout = 400*10ms = 4000ms + esp_ble_gap_update_conn_params(&conn_params); +} + +void BLEServer::disconnect(uint16_t connId) { + esp_ble_gatts_close(m_gatts_if, connId); +} + uint16_t BLEServer::getGattsIf() { return m_gatts_if; } @@ -288,140 +522,432 @@ void BLEServer::registerApp(uint16_t m_appId) { log_v("<< registerApp"); } // registerApp +// Bluedroid callbacks + +void BLEServerCallbacks::onConnect(BLEServer *pServer, esp_ble_gatts_cb_param_t *param) { + log_d("BLEServerCallbacks", ">> onConnect(): Default"); + log_d("BLEServerCallbacks", "Device: %s", BLEDevice::toString().c_str()); + log_d("BLEServerCallbacks", "<< onConnect()"); +} // onConnect + +void BLEServerCallbacks::onDisconnect(BLEServer *pServer, esp_ble_gatts_cb_param_t *param) { + log_d("BLEServerCallbacks", ">> onDisconnect(): Default"); + log_d("BLEServerCallbacks", "Device: %s", BLEDevice::toString().c_str()); + log_d("BLEServerCallbacks", "<< onDisconnect()"); +} // onDisconnect + +void BLEServerCallbacks::onMtuChanged(BLEServer *pServer, esp_ble_gatts_cb_param_t *param) { + uint16_t mtu = param->mtu.mtu; + log_d("BLEServerCallbacks", ">> onMtuChanged(): Default"); + log_d("BLEServerCallbacks", "Device: %s MTU: %d", BLEDevice::toString().c_str(), mtu); + log_d("BLEServerCallbacks", "<< onMtuChanged()"); +} // onMtuChanged + +#endif + +/*************************************************************************** + * NimBLE functions * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + /** - * @brief Set the server callbacks. + * @brief Resets the GATT server, used when services are added/removed after initialization. + */ +void BLEServer::resetGATT() { + if (getConnectedCount() > 0) { + return; + } + + BLEDevice::stopAdvertising(); + ble_gatts_reset(); + ble_svc_gap_init(); + ble_svc_gatt_init(); + + BLEService *svc = m_serviceMap.getFirst(); + while (svc != nullptr) { + if (svc->m_removed > 0) { + if (svc->m_removed == NIMBLE_ATT_REMOVE_DELETE) { + m_serviceMap.removeService(svc); + delete svc; + } + } else { + svc->start(); + } + + svc = m_serviceMap.getNext(); + } + + m_svcChanged = false; + m_gattsStarted = false; +} + +/** + * @brief Handle a GATT Server Event. * - * 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] event + * @param [in] gatts_if + * @param [in] param * - * @param [in] pCallbacks The callbacks to be invoked. */ -void BLEServer::setCallbacks(BLEServerCallbacks *pCallbacks) { - m_pServerCallbacks = pCallbacks; -} // setCallbacks +int BLEServer::handleGATTServerEvent(struct ble_gap_event *event, void *arg) { + BLEServer *server = (BLEServer *)arg; + log_v(">> handleGAPEvent: %s", BLEUtils::gapEventToString(event->type)); + int rc = 0; + struct ble_gap_conn_desc desc; + + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: + { + if (event->connect.status != 0) { + /* Connection failed; resume advertising */ + log_e("Connection failed"); + BLEDevice::startAdvertising(); + } else { + server->addPeerDevice((void *)server, false, event->connect.conn_handle); + rc = ble_gap_conn_find(event->connect.conn_handle, &desc); + if (rc != 0) { + return 0; + } + + server->m_pServerCallbacks->onConnect(server); + server->m_pServerCallbacks->onConnect(server, &desc); + } -/* - * Remove service - */ -void BLEServer::removeService(BLEService *service) { - service->stop(); - service->executeDelete(); - m_serviceMap.removeService(service); + return 0; + } // BLE_GAP_EVENT_CONNECT + + case BLE_GAP_EVENT_DISCONNECT: + { + // If Host reset tell the device now before returning to prevent + // any errors caused by calling host functions before resyncing. + switch (event->disconnect.reason) { + case BLE_HS_ETIMEOUT_HCI: + case BLE_HS_EOS: + case BLE_HS_ECONTROLLER: + case BLE_HS_ENOTSYNCED: + log_d("Disconnect - host reset, rc=%d", event->disconnect.reason); + BLEDevice::onReset(event->disconnect.reason); + break; + default: break; + } + + server->removePeerDevice(event->disconnect.conn.conn_handle, false); + + if (server->m_svcChanged) { + server->resetGATT(); + } + + server->m_pServerCallbacks->onDisconnect(server); + server->m_pServerCallbacks->onDisconnect(server, &event->disconnect.conn); + + return 0; + } // BLE_GAP_EVENT_DISCONNECT + + case BLE_GAP_EVENT_SUBSCRIBE: + { + log_i("subscribe event; attr_handle=%d, subscribed: %s", event->subscribe.attr_handle, (event->subscribe.cur_notify ? "true" : "false")); + + for (auto &it : server->m_notifyChrVec) { + if (it->getHandle() == event->subscribe.attr_handle) { + if ((it->getProperties() & BLE_GATT_CHR_F_READ_AUTHEN) || (it->getProperties() & BLE_GATT_CHR_F_READ_AUTHOR) + || (it->getProperties() & BLE_GATT_CHR_F_READ_ENC)) { + rc = ble_gap_conn_find(event->subscribe.conn_handle, &desc); + if (rc != 0) { + break; + } + + if (!desc.sec_state.encrypted) { + BLESecurity::startSecurity(event->subscribe.conn_handle); + } + } + + it->setSubscribe(event); + break; + } + } + + return 0; + } // BLE_GAP_EVENT_SUBSCRIBE + + case BLE_GAP_EVENT_MTU: + { + log_i("mtu update event; conn_handle=%d mtu=%d", event->mtu.conn_handle, event->mtu.value); + rc = ble_gap_conn_find(event->mtu.conn_handle, &desc); + if (rc != 0) { + return 0; + } + + server->m_pServerCallbacks->onMtuChanged(server, &desc, event->mtu.value); + return 0; + } // BLE_GAP_EVENT_MTU + + case BLE_GAP_EVENT_NOTIFY_TX: + { + BLECharacteristic *pChar = nullptr; + + for (auto &it : server->m_notifyChrVec) { + if (it->getHandle() == event->notify_tx.attr_handle) { + pChar = it; + } + } + + if (pChar == nullptr) { + return 0; + } + + BLECharacteristicCallbacks::Status statusRC; + + if (event->notify_tx.indication) { + if (event->notify_tx.status != 0) { + if (event->notify_tx.status == BLE_HS_EDONE) { + statusRC = BLECharacteristicCallbacks::Status::SUCCESS_INDICATE; + } else if (rc == BLE_HS_ETIMEOUT) { + statusRC = BLECharacteristicCallbacks::Status::ERROR_INDICATE_TIMEOUT; + } else { + statusRC = BLECharacteristicCallbacks::Status::ERROR_INDICATE_FAILURE; + } + } else { + return 0; + } + + server->clearIndicateWait(event->notify_tx.conn_handle); + } else { + if (event->notify_tx.status == 0) { + statusRC = BLECharacteristicCallbacks::Status::SUCCESS_NOTIFY; + } else { + statusRC = BLECharacteristicCallbacks::Status::ERROR_GATT; + } + } + + pChar->m_pCallbacks->onStatus(pChar, statusRC, event->notify_tx.status); + + return 0; + } // BLE_GAP_EVENT_NOTIFY_TX + + case BLE_GAP_EVENT_ADV_COMPLETE: + { + log_d("Advertising Complete"); + BLEDevice::getAdvertising()->advCompleteCB(); + return 0; + } + + case BLE_GAP_EVENT_CONN_UPDATE: + { + log_d("Connection parameters updated."); + return 0; + } // BLE_GAP_EVENT_CONN_UPDATE + + case BLE_GAP_EVENT_REPEAT_PAIRING: + { + /* We already have a bond with the peer, but it is attempting to + * establish a new secure link. This app sacrifices security for + * convenience: just throw away the old bond and accept the new link. + */ + + /* Delete the old bond. */ + rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc); + if (rc != 0) { + return BLE_GAP_REPEAT_PAIRING_IGNORE; + } + + ble_store_util_delete_peer(&desc.peer_id_addr); + + /* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should + * continue with the pairing operation. + */ + return BLE_GAP_REPEAT_PAIRING_RETRY; + } // BLE_GAP_EVENT_REPEAT_PAIRING + + case BLE_GAP_EVENT_ENC_CHANGE: + { + rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc); + if (rc != 0) { + return BLE_ATT_ERR_INVALID_HANDLE; + } + + if (BLEDevice::m_securityCallbacks != nullptr) { + BLEDevice::m_securityCallbacks->onAuthenticationComplete(&desc); + } else { + server->m_pServerCallbacks->onAuthenticationComplete(&desc); + } + + return 0; + } // BLE_GAP_EVENT_ENC_CHANGE + + case BLE_GAP_EVENT_PASSKEY_ACTION: + { + struct ble_sm_io pkey = {0, 0}; + + if (event->passkey.params.action == BLE_SM_IOACT_DISP) { + pkey.action = event->passkey.params.action; + // backward compatibility + pkey.passkey = BLESecurity::m_passkey; + // if the (static)passkey is the default, check the callback for custom value + // both values default to the same. + if (pkey.passkey == BLE_SM_DEFAULT_PASSKEY) { + pkey.passkey = server->m_pServerCallbacks->onPassKeyRequest(); + } + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + log_d("BLE_SM_IOACT_DISP; ble_sm_inject_io result: %d", rc); + + } else if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) { + log_d("Passkey on device's display: %d", event->passkey.params.numcmp); + pkey.action = event->passkey.params.action; + // Compatibility only - Do not use, should be removed the in future + if (BLEDevice::m_securityCallbacks != nullptr) { + pkey.numcmp_accept = BLEDevice::m_securityCallbacks->onConfirmPIN(event->passkey.params.numcmp); + } else { + pkey.numcmp_accept = server->m_pServerCallbacks->onConfirmPIN(event->passkey.params.numcmp); + } + + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + log_d("BLE_SM_IOACT_NUMCMP; ble_sm_inject_io result: %d", rc); + + //TODO: Handle out of band pairing + } else if (event->passkey.params.action == BLE_SM_IOACT_OOB) { + static uint8_t tem_oob[16] = {0}; + pkey.action = event->passkey.params.action; + for (int i = 0; i < 16; i++) { + pkey.oob[i] = tem_oob[i]; + } + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + log_d("BLE_SM_IOACT_OOB; ble_sm_inject_io result: %d", rc); + } else if (event->passkey.params.action == BLE_SM_IOACT_INPUT) { + log_d("Enter the passkey"); + pkey.action = event->passkey.params.action; + + // Compatibility only - Do not use, should be removed the in future + if (BLEDevice::m_securityCallbacks != nullptr) { + pkey.passkey = BLEDevice::m_securityCallbacks->onPassKeyRequest(); + } else { + pkey.passkey = server->m_pServerCallbacks->onPassKeyRequest(); + } + + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + log_d("BLE_SM_IOACT_INPUT; ble_sm_inject_io result: %d", rc); + + } else if (event->passkey.params.action == BLE_SM_IOACT_NONE) { + log_d("No passkey action required"); + } + + log_d("<< handleGATTServerEvent"); + return 0; + } // BLE_GAP_EVENT_PASSKEY_ACTION + + default: break; + } + + log_d("<< handleGATTServerEvent"); + return 0; } /** - * @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. + * @brief Request an Update the connection parameters: + * * Can only be used after a connection has been established. + * @param [in] conn_handle The connection handle of the peer to send the request to. + * @param [in] minInterval The minimum connection interval in 1.25ms units. + * @param [in] maxInterval The maximum connection interval in 1.25ms units. + * @param [in] latency The number of packets allowed to skip (extends max interval). + * @param [in] timeout The timeout time in 10ms units before disconnecting. */ -void BLEServer::startAdvertising() { - log_v(">> startAdvertising"); - BLEDevice::startAdvertising(); - log_v("<< startAdvertising"); -} // startAdvertising +void BLEServer::updateConnParams(uint16_t conn_handle, uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t timeout) { + ble_gap_upd_params params; + + params.latency = latency; + params.itvl_max = maxInterval; // max_int = 0x20*1.25ms = 40ms + params.itvl_min = minInterval; // min_int = 0x10*1.25ms = 20ms + params.supervision_timeout = timeout; // timeout = 400*10ms = 4000ms + params.min_ce_len = BLE_GAP_INITIAL_CONN_MIN_CE_LEN; // Minimum length of connection event in 0.625ms units + params.max_ce_len = BLE_GAP_INITIAL_CONN_MAX_CE_LEN; // Maximum length of connection event in 0.625ms units + + int rc = ble_gap_update_params(conn_handle, ¶ms); + if (rc != 0) { + log_e("Update params error: %d, %s", rc, BLEUtils::returnCodeToString(rc)); + } +} // updateConnParams + +bool BLEServer::setIndicateWait(uint16_t conn_handle) { + for (auto i = 0; i < CONFIG_BT_NIMBLE_MAX_CONNECTIONS; i++) { + if (m_indWait[i] == conn_handle) { + return false; + } + } + + return true; +} + +void BLEServer::clearIndicateWait(uint16_t conn_handle) { + for (auto i = 0; i < CONFIG_BT_NIMBLE_MAX_CONNECTIONS; i++) { + if (m_indWait[i] == conn_handle) { + m_indWait[i] = BLE_HS_CONN_HANDLE_NONE; + return; + } + } +} /** - * Allow to connect GATT server to peer device - * Probably can be used in ANCS for iPhone + * @brief Disconnect the specified client with optional reason. + * @param [in] connId Connection Id of the client to disconnect. + * @param [in] reason code for disconnecting. + * @return NimBLE host return code. */ -bool BLEServer::connect(BLEAddress address) { - esp_bd_addr_t addr; - memcpy(&addr, address.getNative(), 6); - // Perform the open connection request against the target BLE Server. - m_semaphoreOpenEvt.take("connect"); - esp_err_t errRc = ::esp_ble_gatts_open( - getGattsIf(), - addr, // address - 1 // direct connection - ); - if (errRc != ESP_OK) { - log_e("esp_ble_gattc_open: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return false; +int BLEServer::disconnect(uint16_t connId, uint8_t reason) { + log_d(">> disconnect()"); + + int rc = ble_gap_terminate(connId, reason); + if (rc != 0) { + log_e("ble_gap_terminate failed: rc=%d %s", rc, BLEUtils::returnCodeToString(rc)); } - uint32_t rc = m_semaphoreOpenEvt.wait("connect"); // Wait for the connection to complete. - log_v("<< connect(), rc=%d", rc == ESP_GATT_OK); - return rc == ESP_GATT_OK; -} // connect + log_d("<< disconnect()"); + return rc; +} // disconnect -void BLEServerCallbacks::onConnect(BLEServer *pServer) { - log_d("BLEServerCallbacks", ">> onConnect(): Default"); - log_d("BLEServerCallbacks", "Device: %s", BLEDevice::toString().c_str()); - log_d("BLEServerCallbacks", "<< onConnect()"); -} // onConnect +/** + * @brief Set the service changed flag + */ +void BLEServer::serviceChanged() { + if (m_gattsStarted) { + m_svcChanged = true; + } +} // serviceChanged -void BLEServerCallbacks::onConnect(BLEServer *pServer, esp_ble_gatts_cb_param_t *param) { +// NimBLE callbacks + +void BLEServerCallbacks::onConnect(BLEServer *pServer, struct ble_gap_conn_desc *desc) { log_d("BLEServerCallbacks", ">> onConnect(): Default"); log_d("BLEServerCallbacks", "Device: %s", BLEDevice::toString().c_str()); log_d("BLEServerCallbacks", "<< onConnect()"); } // onConnect -void BLEServerCallbacks::onDisconnect(BLEServer *pServer) { +void BLEServerCallbacks::onDisconnect(BLEServer *pServer, struct ble_gap_conn_desc *desc) { log_d("BLEServerCallbacks", ">> onDisconnect(): Default"); log_d("BLEServerCallbacks", "Device: %s", BLEDevice::toString().c_str()); log_d("BLEServerCallbacks", "<< onDisconnect()"); } // onDisconnect -void BLEServerCallbacks::onDisconnect(BLEServer *pServer, esp_ble_gatts_cb_param_t *param) { - log_d("BLEServerCallbacks", ">> onDisconnect(): Default"); - log_d("BLEServerCallbacks", "Device: %s", BLEDevice::toString().c_str()); - log_d("BLEServerCallbacks", "<< onDisconnect()"); -} // onDisconnect - -void BLEServerCallbacks::onMtuChanged(BLEServer *pServer, esp_ble_gatts_cb_param_t *param) { +void BLEServerCallbacks::onMtuChanged(BLEServer *pServer, ble_gap_conn_desc *desc, uint16_t mtu) { log_d("BLEServerCallbacks", ">> onMtuChanged(): Default"); - log_d("BLEServerCallbacks", "Device: %s MTU: %d", BLEDevice::toString().c_str(), param->mtu.mtu); + log_d("BLEServerCallbacks", "Device: %s MTU: %d", BLEDevice::toString().c_str(), mtu); log_d("BLEServerCallbacks", "<< onMtuChanged()"); } // onMtuChanged -/* multi connect support */ -/* TODO do some more tweaks */ -void BLEServer::updatePeerMTU(uint16_t conn_id, uint16_t mtu) { - // set mtu in conn_status_t - const std::map::iterator it = m_connectedServersMap.find(conn_id); - if (it != m_connectedServersMap.end()) { - it->second.mtu = mtu; - std::swap(m_connectedServersMap[conn_id], it->second); - } -} - -std::map BLEServer::getPeerDevices(bool _client) { - return m_connectedServersMap; -} - -uint16_t BLEServer::getPeerMTU(uint16_t conn_id) { - return m_connectedServersMap.find(conn_id)->second.mtu; -} - -void BLEServer::addPeerDevice(void *peer, bool _client, uint16_t conn_id) { - conn_status_t status = {.peer_device = peer, .connected = true, .mtu = 23}; - - m_connectedServersMap.insert(std::pair(conn_id, status)); +uint32_t BLEServerCallbacks::onPassKeyRequest(){ + log_d("BLEServerCallbacks", "onPassKeyRequest: default: 123456"); + return 123456; } -bool BLEServer::removePeerDevice(uint16_t conn_id, bool _client) { - return m_connectedServersMap.erase(conn_id) > 0; +void BLEServerCallbacks::onAuthenticationComplete(ble_gap_conn_desc*){ + log_d("BLEServerCallbacks", "onAuthenticationComplete: default"); } -/* multi connect support */ -/** - * Update connection parameters can be called only after connection has been established - */ -void BLEServer::updateConnParams(esp_bd_addr_t remote_bda, uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t timeout) { - esp_ble_conn_update_params_t conn_params; - memcpy(conn_params.bda, remote_bda, sizeof(esp_bd_addr_t)); - conn_params.latency = latency; - conn_params.max_int = maxInterval; // max_int = 0x20*1.25ms = 40ms - conn_params.min_int = minInterval; // min_int = 0x10*1.25ms = 20ms - conn_params.timeout = timeout; // timeout = 400*10ms = 4000ms - esp_ble_gap_update_conn_params(&conn_params); +bool BLEServerCallbacks::onConfirmPIN(uint32_t pin){ + log_d("BLEServerCallbacks", "onConfirmPIN: default: true"); + return true; } -void BLEServer::disconnect(uint16_t connId) { - esp_ble_gatts_close(m_gatts_if, connId); -} +#endif // CONFIG_NIMBLE_ENABLED -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ diff --git a/libraries/BLE/src/BLEServer.h b/libraries/BLE/src/BLEServer.h index aa10f2210fa..8122b10e672 100644 --- a/libraries/BLE/src/BLEServer.h +++ b/libraries/BLE/src/BLEServer.h @@ -3,6 +3,10 @@ * * Created on: Apr 16, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ #ifndef COMPONENTS_CPP_UTILS_BLESERVER_H_ @@ -11,13 +15,16 @@ #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) -#include +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/***************************************************************************** + * Common includes * + *****************************************************************************/ #include #include -// #include "BLEDevice.h" - +#include "BLEDevice.h" +#include "BLEConnInfo.h" #include "BLEUUID.h" #include "BLEAdvertising.h" #include "BLECharacteristic.h" @@ -25,24 +32,51 @@ #include "BLESecurity.h" #include "RTOS.h" #include "BLEAddress.h" +#include "BLEUtils.h" +#include "BLETypes.h" + +/***************************************************************************** + * Bluedroid includes * + *****************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) +#include +#endif + +/***************************************************************************** + * NimBLE includes and definitions * + *****************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +#include +#define ESP_GATT_IF_NONE BLE_HS_CONN_HANDLE_NONE +#define NIMBLE_ATT_REMOVE_HIDE 1 +#define NIMBLE_ATT_REMOVE_DELETE 2 +#endif + +/***************************************************************************** + * Forward declarations * + *****************************************************************************/ class BLEServerCallbacks; -/* TODO possibly refactor this struct */ -typedef struct { - void *peer_device; // peer device BLEClient or BLEServer - maybe its better to have 2 structures or union here - bool connected; // do we need it? - uint16_t mtu; // every peer device negotiate own mtu -} conn_status_t; +class BLEService; +class BLECharacteristic; +class BLEDevice; +class BLESecurity; +class BLEAdvertising; /** - * @brief A data structure that manages the %BLE servers owned by a BLE server. + * @brief A data structure that manages the %BLE services owned by a BLE server. */ class BLEServiceMap { public: + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ + BLEService *getByHandle(uint16_t handle); BLEService *getByUUID(const char *uuid); BLEService *getByUUID(BLEUUID uuid, uint8_t inst_id = 0); - 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); @@ -52,7 +86,19 @@ class BLEServiceMap { void removeService(BLEService *service); int getRegisteredServiceCount(); + /*************************************************************************** + * Bluedroid public declarations * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); +#endif + private: + /*************************************************************************** + * Common private properties * + ***************************************************************************/ + std::map m_handleMap; std::map m_uuidMap; std::map::iterator m_iterator; @@ -63,6 +109,16 @@ class BLEServiceMap { */ class BLEServer { public: + /*************************************************************************** + * Common public properties * + ***************************************************************************/ + + uint16_t m_appId; + + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ + uint32_t getConnectedCount(); BLEService *createService(const char *uuid); BLEService *createService(BLEUUID uuid, uint32_t numHandles = 15, uint8_t inst_id = 0); @@ -72,12 +128,9 @@ class BLEServer { void removeService(BLEService *service); BLEService *getServiceByUUID(const char *uuid); BLEService *getServiceByUUID(BLEUUID uuid); - bool connect(BLEAddress address); - void disconnect(uint16_t connId); - uint16_t m_appId; - void updateConnParams(esp_bd_addr_t remote_bda, uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t timeout); + void start(); - /* multi connection support */ + // Connection management functions std::map getPeerDevices(bool client); void addPeerDevice(void *peer, bool is_client, uint16_t conn_id); bool removePeerDevice(uint16_t conn_id, bool client); @@ -86,28 +139,94 @@ class BLEServer { uint16_t getPeerMTU(uint16_t conn_id); uint16_t getConnId(); + /*************************************************************************** + * Bluedroid public declarations * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + bool connect(BLEAddress address); + void updateConnParams(esp_bd_addr_t remote_bda, uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t timeout); + void disconnect(uint16_t connId); +#endif + + /*************************************************************************** + * NimBLE public declarations * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + void updateConnParams(uint16_t conn_handle, uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t timeout); + int disconnect(uint16_t connId, uint8_t reason = BLE_ERR_REM_USER_CONN_TERM); +#endif + private: - BLEServer(); friend class BLEService; friend class BLECharacteristic; friend class BLEDevice; - esp_ble_adv_data_t m_adv_data; - // BLEAdvertising m_bleAdvertising; + friend class BLESecurity; + friend class BLEAdvertising; + + /*************************************************************************** + * Common private properties * + ***************************************************************************/ + uint16_t m_connId; uint32_t m_connectedCount; - uint16_t m_gatts_if; + bool m_gattsStarted; std::map m_connectedServersMap; - FreeRTOS::Semaphore m_semaphoreRegisterAppEvt = FreeRTOS::Semaphore("RegisterAppEvt"); FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); FreeRTOS::Semaphore m_semaphoreOpenEvt = FreeRTOS::Semaphore("OpenEvt"); BLEServiceMap m_serviceMap; BLEServerCallbacks *m_pServerCallbacks = nullptr; + /*************************************************************************** + * Bluedroid private properties * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + uint16_t m_gatts_if; + esp_ble_adv_data_t m_adv_data; +#endif + + /*************************************************************************** + * NimBLE private properties * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + bool m_svcChanged; + uint16_t m_indWait[CONFIG_BT_NIMBLE_MAX_CONNECTIONS]; + std::vector m_notifyChrVec; + ble_hs_adv_fields m_adv_data; +#endif + + /*************************************************************************** + * Common private declarations * + ***************************************************************************/ + + BLEServer(); void createApp(uint16_t appId); + + /*************************************************************************** + * Bluedroid private declarations * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) uint16_t getGattsIf(); - void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); void registerApp(uint16_t); + void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); +#endif + + /*************************************************************************** + * NimBLE private declarations * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + void serviceChanged(); + void resetGATT(); + bool setIndicateWait(uint16_t conn_handle); + void clearIndicateWait(uint16_t conn_handle); + static int handleGATTServerEvent(struct ble_gap_event *event, void *arg); +#endif }; // BLEServer /** @@ -115,37 +234,38 @@ class BLEServer { */ class BLEServerCallbacks { public: + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ + 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); - virtual void onConnect(BLEServer *pServer, esp_ble_gatts_cb_param_t *param); - /** - * @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); - virtual void onDisconnect(BLEServer *pServer, esp_ble_gatts_cb_param_t *param); - /** - * @brief Handle a new client connection. - * - * When the MTU changes this method is invoked. - * - * @param [in] pServer A reference to the %BLE server that received the client connection. - * @param [in] param A reference to esp_ble_gatts_cb_param_t. - */ + /*************************************************************************** + * Bluedroid public declarations * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + virtual void onConnect(BLEServer *pServer, esp_ble_gatts_cb_param_t *param); + virtual void onDisconnect(BLEServer *pServer, esp_ble_gatts_cb_param_t *param); virtual void onMtuChanged(BLEServer *pServer, esp_ble_gatts_cb_param_t *param); +#endif + + /*************************************************************************** + * NimBLE public declarations * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + virtual void onConnect(BLEServer *pServer, ble_gap_conn_desc *desc); + virtual void onDisconnect(BLEServer *pServer, ble_gap_conn_desc *desc); + virtual void onMtuChanged(BLEServer *pServer, ble_gap_conn_desc *desc, uint16_t mtu); + virtual uint32_t onPassKeyRequest(); + virtual void onAuthenticationComplete(ble_gap_conn_desc* desc); + virtual bool onConfirmPIN(uint32_t pin); +#endif }; // BLEServerCallbacks -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ #endif /* COMPONENTS_CPP_UTILS_BLESERVER_H_ */ diff --git a/libraries/BLE/src/BLEService.cpp b/libraries/BLE/src/BLEService.cpp index 58c5d4eb9c3..93e699a5e64 100644 --- a/libraries/BLE/src/BLEService.cpp +++ b/libraries/BLE/src/BLEService.cpp @@ -3,6 +3,10 @@ * * Created on: Mar 25, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ // A service is identified by a UUID. A service is also the container for one or more characteristics. @@ -11,10 +15,13 @@ #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) -#include -#include +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) +/***************************************************************************** + * Common includes * + *****************************************************************************/ + +#include #include #include #include @@ -25,8 +32,24 @@ #include "GeneralUtils.h" #include "esp32-hal-log.h" +/***************************************************************************** + * Bluedroid includes * + *****************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) +#include +#endif + +/***************************************************************************** + * Common definitions * + *****************************************************************************/ + #define NULL_HANDLE (0xffff) +/***************************************************************************** + * Common functions * + *****************************************************************************/ + /** * @brief Construct an instance of the BLEService * @param [in] uuid The UUID of the service. @@ -44,10 +67,32 @@ BLEService::BLEService(BLEUUID uuid, uint16_t numHandles) { m_handle = NULL_HANDLE; m_pServer = nullptr; //m_serializeMutex.setName("BLEService"); - m_lastCreatedCharacteristic = nullptr; m_numHandles = numHandles; +#ifdef CONFIG_BLUEDROID_ENABLED + m_lastCreatedCharacteristic = nullptr; +#endif +#if defined(CONFIG_NIMBLE_ENABLED) + m_pSvcDef = nullptr; +#endif } // BLEService +BLEService::~BLEService() { +#if defined(CONFIG_NIMBLE_ENABLED) + if (m_pSvcDef != nullptr) { + if (m_pSvcDef->characteristics != nullptr) { + for (int i = 0; m_pSvcDef->characteristics[i].uuid != NULL; ++i) { + if (m_pSvcDef->characteristics[i].descriptors) { + delete (m_pSvcDef->characteristics[i].descriptors); + } + } + delete (m_pSvcDef->characteristics); + } + + delete (m_pSvcDef); + } +#endif +} + /** * @brief Create the service. * Create the service. @@ -56,8 +101,9 @@ BLEService::BLEService(BLEUUID uuid, uint16_t numHandles) { */ void BLEService::executeCreate(BLEServer *pServer) { - log_v(">> executeCreate() - Creating service (esp_ble_gatts_create_service) service uuid: %s", getUUID().toString().c_str()); + log_v(">> executeCreate() - Creating service with uuid: %s", getUUID().toString().c_str()); m_pServer = pServer; +#if defined(CONFIG_BLUEDROID_ENABLED) m_semaphoreCreateEvt.take("executeCreate"); // Take the mutex and release at event ESP_GATTS_CREATE_EVT esp_gatt_srvc_id_t srvc_id; @@ -73,6 +119,7 @@ void BLEService::executeCreate(BLEServer *pServer) { } m_semaphoreCreateEvt.wait("executeCreate"); +#endif log_v("<< executeCreate"); } // executeCreate @@ -83,6 +130,7 @@ void BLEService::executeCreate(BLEServer *pServer) { */ void BLEService::executeDelete() { +#if defined(CONFIG_BLUEDROID_ENABLED) log_v(">> executeDelete()"); m_semaphoreDeleteEvt.take("executeDelete"); // Take the mutex and release at event ESP_GATTS_DELETE_EVT @@ -95,6 +143,11 @@ void BLEService::executeDelete() { m_semaphoreDeleteEvt.wait("executeDelete"); log_v("<< executeDelete"); +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) && CONFIG_BT_NIMBLE_DYNAMIC_SERVICE + ble_gatts_delete_svc(&m_uuid.getNative()->u); +#endif } // executeDelete /** @@ -114,49 +167,11 @@ 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(). - // - log_v(">> start(): Starting service (esp_ble_gatts_start_service): %s", toString().c_str()); - if (m_handle == NULL_HANDLE) { - log_e("<< !!! 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) { - log_e("<< esp_ble_gatts_start_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; - } - m_semaphoreStartEvt.wait("start"); - - log_v("<< start()"); -} // start - /** * @brief Stop the service. */ void BLEService::stop() { +#if defined(CONFIG_BLUEDROID_ENABLED) // 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(). @@ -176,13 +191,19 @@ void BLEService::stop() { m_semaphoreStopEvt.wait("stop"); log_v("<< stop()"); -} // start +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + log_w("NimBLE does not support stopping a service. Ignoring request."); +#endif +} // stop /** * @brief Set the handle associated with this service. * @param [in] handle The handle associated with the service. */ void BLEService::setHandle(uint16_t handle) { +#if defined(CONFIG_BLUEDROID_ENABLED) log_v(">> setHandle - Handle=0x%.2x, service UUID=%s)", handle, getUUID().toString().c_str()); if (m_handle != NULL_HANDLE) { log_e("!!! Handle is already set %.2x", m_handle); @@ -190,6 +211,11 @@ void BLEService::setHandle(uint16_t handle) { } m_handle = handle; log_v("<< setHandle"); +#endif + +#if defined(CONFIG_NIMBLE_ENABLED) + log_w("NimBLE does not support manually setting the handle of a service. Ignoring request."); +#endif } // setHandle /** @@ -213,14 +239,25 @@ void BLEService::addCharacteristic(BLECharacteristic *pCharacteristic) { log_d("Adding characteristic: 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) { + BLECharacteristic *pExisting = m_characteristicMap.getByUUID(pCharacteristic->getUUID()); + if (pExisting != nullptr) { log_w("<< Adding a new characteristic with the same UUID as a previous one"); - //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, pCharacteristic->getUUID()); +#if defined(CONFIG_NIMBLE_ENABLED) + if (pExisting != nullptr) { + pExisting->m_removed = 0; + } else +#endif + { + // 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, pCharacteristic->getUUID()); + } + +#if defined(CONFIG_NIMBLE_ENABLED) + getServer()->serviceChanged(); +#endif log_v("<< addCharacteristic()"); } // addCharacteristic @@ -247,6 +284,54 @@ BLECharacteristic *BLEService::createCharacteristic(BLEUUID uuid, uint32_t prope return pCharacteristic; } // createCharacteristic +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. + */ +String BLEService::toString() { + String res = "UUID: " + getUUID().toString(); + char hex[5]; + snprintf(hex, sizeof(hex), "%04x", getHandle()); + res += ", handle: 0x"; + res += hex; + return res; +} // toString + +/** + * @brief Get the BLE server associated with this service. + * @return The BLEServer associated with this service. + */ +BLEServer *BLEService::getServer() { + return m_pServer; +} // getServer + +/***************************************************************************** + * Bluedroid functions * + *****************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) +/** + * @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 Handle a GATTS server event. */ @@ -347,48 +432,221 @@ void BLEService::handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t 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. + * @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. */ -String BLEService::toString() { - String res = "UUID: " + getUUID().toString(); - char hex[5]; - snprintf(hex, sizeof(hex), "%04x", getHandle()); - res += ", handle: 0x"; - res += hex; - return res; -} // toString +bool 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(). + // + log_v(">> start(): Starting service (esp_ble_gatts_start_service): %s", toString().c_str()); + if (m_handle == NULL_HANDLE) { + log_e("<< !!! We attempted to start a service but don't know its handle!"); + return false; + } + + 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) { + log_e("<< esp_ble_gatts_start_service: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); + return false; + } + m_semaphoreStartEvt.wait("start"); + + log_v("<< start()"); + return true; +} // start + +#endif // CONFIG_BLUEDROID_ENABLED + +/***************************************************************************** + * NimBLE functions * + *****************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) /** - * @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. + * @brief Remove a characteristic from the service. Check if the characteristic was already removed and if so, check if this + * is being called to delete the object and do so if requested. Otherwise, ignore the call and return. + * @param [in] pCharacteristic - The characteristic to remove. + * @param [in] deleteChr - If true, delete the characteristic. */ -BLECharacteristic *BLEService::getLastCreatedCharacteristic() { - return m_lastCreatedCharacteristic; -} // getLastCreatedCharacteristic +void BLEService::removeCharacteristic(BLECharacteristic *pCharacteristic, bool deleteChr) { + if (pCharacteristic->m_removed > 0) { + if (deleteChr) { + BLECharacteristic *pExisting = m_characteristicMap.getByUUID(pCharacteristic->getUUID()); + if (pExisting != nullptr) { + m_characteristicMap.removeCharacteristic(pExisting); + delete pExisting; + } + } + + return; + } + + pCharacteristic->m_removed = deleteChr ? NIMBLE_ATT_REMOVE_DELETE : NIMBLE_ATT_REMOVE_HIDE; + getServer()->serviceChanged(); +} /** - * @brief Get the BLE server associated with this service. - * @return The BLEServer associated with this service. + * @brief Builds the database of characteristics/descriptors for the service + * and registers it with the NimBLE stack. + * @return bool success/failure . */ -BLEServer *BLEService::getServer() { - return m_pServer; -} // getServer +bool BLEService::start() { + log_d(">> start(): Starting service: %s", toString().c_str()); + + // Rebuild the service definition if the server attributes have changed. + if (getServer()->m_svcChanged && m_pSvcDef != nullptr) { + if (m_pSvcDef[0].characteristics) { + if (m_pSvcDef[0].characteristics[0].descriptors) { + delete (m_pSvcDef[0].characteristics[0].descriptors); + } + delete (m_pSvcDef[0].characteristics); + } + delete (m_pSvcDef); + m_pSvcDef = nullptr; + } + + if (m_pSvcDef == nullptr) { + // Nimble requires an array of services to be sent to the api + // Since we are adding 1 at a time we create an array of 2 and set the type + // of the second service to 0 to indicate the end of the array. + ble_gatt_svc_def *svc = new ble_gatt_svc_def[2]; + ble_gatt_chr_def *pChr_a = nullptr; + ble_gatt_dsc_def *pDsc_a = nullptr; + + svc[0].type = BLE_GATT_SVC_TYPE_PRIMARY; + svc[0].uuid = &m_uuid.getNative()->u; + svc[0].includes = NULL; + + int removedCount = 0; + BLECharacteristic *pCharacteristic; + + pCharacteristic = m_characteristicMap.getFirst(); + while (pCharacteristic != nullptr) { + if (pCharacteristic->m_removed > 0) { + if (pCharacteristic->m_removed == NIMBLE_ATT_REMOVE_DELETE) { + m_characteristicMap.removeCharacteristic(pCharacteristic); + delete pCharacteristic; + } else { + ++removedCount; + } + } else { + pCharacteristic->executeCreate(this); + } + + pCharacteristic = m_characteristicMap.getNext(); + } + + size_t numChrs = m_characteristicMap.getRegisteredCharacteristicCount() - removedCount; + log_d("Adding %d characteristics for service %s", numChrs, toString().c_str()); + + if (!numChrs) { + svc[0].characteristics = NULL; + } else { + // Nimble requires the last characteristic to have it's uuid = 0 to indicate the end + // of the characteristics for the service. We create 1 extra and set it to null + // for this purpose. + pChr_a = new ble_gatt_chr_def[numChrs + 1]; + uint8_t i = 0; + pCharacteristic = m_characteristicMap.getFirst(); + while (pCharacteristic != nullptr) { + if (pCharacteristic->m_removed <= 0) { + removedCount = 0; + BLEDescriptor *pDescriptor; + + pDescriptor = pCharacteristic->m_descriptorMap.getFirst(); + while (pDescriptor != nullptr) { + if (pDescriptor->m_removed > 0) { + if (pDescriptor->m_removed == NIMBLE_ATT_REMOVE_DELETE) { + pCharacteristic->m_descriptorMap.removeDescriptor(pDescriptor); + delete pDescriptor; + } else { + ++removedCount; + } + } + pDescriptor = pCharacteristic->m_descriptorMap.getNext(); + } + + size_t numDscs = pCharacteristic->m_descriptorMap.getRegisteredDescriptorCount() - removedCount; + + if (!numDscs) { + pChr_a[i].descriptors = NULL; + } else { + // Must have last descriptor uuid = 0 so we have to create 1 extra + pDsc_a = new ble_gatt_dsc_def[numDscs + 1]; + uint8_t d = 0; + pDescriptor = pCharacteristic->m_descriptorMap.getFirst(); + while (pDescriptor != nullptr) { + if (pDescriptor->m_removed <= 0) { + pDsc_a[d].uuid = &pDescriptor->m_bleUUID.getNative()->u; + pDsc_a[d].att_flags = pDescriptor->m_permissions; + pDsc_a[d].min_key_size = 0; + pDsc_a[d].access_cb = BLEDescriptor::handleGATTServerEvent; + pDsc_a[d].arg = pDescriptor; + ++d; + } + pDescriptor = pCharacteristic->m_descriptorMap.getNext(); + } + + pDsc_a[numDscs].uuid = NULL; + pChr_a[i].descriptors = pDsc_a; + } + + pChr_a[i].uuid = &pCharacteristic->m_bleUUID.getNative()->u; + pChr_a[i].access_cb = BLECharacteristic::handleGATTServerEvent; + pChr_a[i].arg = pCharacteristic; + pChr_a[i].flags = pCharacteristic->m_properties; + pChr_a[i].min_key_size = 0; + pChr_a[i].val_handle = &pCharacteristic->m_handle; + ++i; + } + + pCharacteristic = m_characteristicMap.getNext(); + } + + pChr_a[numChrs].uuid = NULL; + svc[0].characteristics = pChr_a; + } + + // end of services must indicate to api with type = 0 + svc[1].type = 0; + m_pSvcDef = svc; + } + + int rc = ble_gatts_count_cfg((const ble_gatt_svc_def *)m_pSvcDef); + if (rc != 0) { + log_e("ble_gatts_count_cfg failed, rc= %d, %s", rc, BLEUtils::returnCodeToString(rc)); + return false; + } + + rc = ble_gatts_add_svcs((const ble_gatt_svc_def *)m_pSvcDef); + if (rc != 0) { + log_e("ble_gatts_add_svcs, rc= %d, %s", rc, BLEUtils::returnCodeToString(rc)); + return false; + } + + log_d("<< start()"); + return true; +} // start + +#endif -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ diff --git a/libraries/BLE/src/BLEService.h b/libraries/BLE/src/BLEService.h index 61f867b2a02..c7fc19253fa 100644 --- a/libraries/BLE/src/BLEService.h +++ b/libraries/BLE/src/BLEService.h @@ -3,6 +3,10 @@ * * Created on: Mar 25, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ #ifndef COMPONENTS_CPP_UTILS_BLESERVICE_H_ @@ -11,15 +15,38 @@ #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) -#include +/***************************************************************************** + * Common includes * + *****************************************************************************/ #include "BLECharacteristic.h" #include "BLEServer.h" #include "BLEUUID.h" +#include "BLETypes.h" #include "RTOS.h" +/***************************************************************************** + * Bluedroid includes * + *****************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) +#include +#endif + +/***************************************************************************** + * NimBLE includes * + *****************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +#include "host/ble_gatt.h" +#endif + +/***************************************************************************** + * Forward declarations * + *****************************************************************************/ + class BLEServer; /** @@ -27,6 +54,10 @@ class BLEServer; */ class BLECharacteristicMap { public: + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ + void setByUUID(BLECharacteristic *pCharacteristic, const char *uuid); void setByUUID(BLECharacteristic *pCharacteristic, BLEUUID uuid); void setByHandle(uint16_t handle, BLECharacteristic *pCharacteristic); @@ -36,9 +67,30 @@ class BLECharacteristicMap { BLECharacteristic *getFirst(); BLECharacteristic *getNext(); String toString(); + int getRegisteredCharacteristicCount(); + void removeCharacteristic(BLECharacteristic *characteristic); + + /*************************************************************************** + * Bluedroid public declarations * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) void handleGATTServerEvent(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); +#endif + + /*************************************************************************** + * NimBLE public declarations * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + void handleGATTServerEvent(uint16_t conn_handle, uint16_t attr_handle, ble_gatt_access_ctxt *ctxt, void *arg); +#endif private: + /*************************************************************************** + * Common private properties * + ***************************************************************************/ + std::map m_uuidMap; std::map m_handleMap; std::map::iterator m_iterator; @@ -46,10 +98,19 @@ class BLECharacteristicMap { /** * @brief The model of a %BLE service. - * */ class BLEService { public: + /*************************************************************************** + * Common properties * + ***************************************************************************/ + + uint8_t m_instId = 0; + + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ + void addCharacteristic(BLECharacteristic *pCharacteristic); BLECharacteristic *createCharacteristic(const char *uuid, uint32_t properties); BLECharacteristic *createCharacteristic(BLEUUID uuid, uint32_t properties); @@ -60,40 +121,81 @@ class BLEService { BLECharacteristic *getCharacteristic(BLEUUID uuid); BLEUUID getUUID(); BLEServer *getServer(); - void start(); + bool start(); void stop(); String toString(); uint16_t getHandle(); - uint8_t m_instId = 0; + + /*************************************************************************** + * NimBLE public declarations * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + void removeCharacteristic(BLECharacteristic *pCharacteristic, bool deleteChr = false); +#endif private: - BLEService(const char *uuid, uint16_t numHandles); - BLEService(BLEUUID uuid, uint16_t numHandles); friend class BLEServer; friend class BLEServiceMap; friend class BLEDescriptor; friend class BLECharacteristic; friend class BLEDevice; + /*************************************************************************** + * Common private properties * + ***************************************************************************/ + BLECharacteristicMap m_characteristicMap; uint16_t m_handle; BLECharacteristic *m_lastCreatedCharacteristic = nullptr; BLEServer *m_pServer = nullptr; BLEUUID m_uuid; + FreeRTOS::Semaphore m_semaphoreStartEvt = FreeRTOS::Semaphore("StartEvt"); + uint16_t m_numHandles; + + /*************************************************************************** + * Bluedroid private properties * + ***************************************************************************/ +#if defined(CONFIG_BLUEDROID_ENABLED) FreeRTOS::Semaphore m_semaphoreCreateEvt = FreeRTOS::Semaphore("CreateEvt"); FreeRTOS::Semaphore m_semaphoreDeleteEvt = FreeRTOS::Semaphore("DeleteEvt"); - FreeRTOS::Semaphore m_semaphoreStartEvt = FreeRTOS::Semaphore("StartEvt"); FreeRTOS::Semaphore m_semaphoreStopEvt = FreeRTOS::Semaphore("StopEvt"); +#endif - uint16_t m_numHandles; + /*************************************************************************** + * NimBLE private properties * + ***************************************************************************/ +#if defined(CONFIG_NIMBLE_ENABLED) + uint8_t m_removed; + ble_gatt_svc_def *m_pSvcDef; +#endif + + /*************************************************************************** + * Common private declarations * + ***************************************************************************/ + + BLEService(const char *uuid, uint16_t numHandles); + BLEService(BLEUUID uuid, uint16_t numHandles); + ~BLEService(); + + /*************************************************************************** + * Common private declarations * + ***************************************************************************/ + + void setHandle(uint16_t handle); + + /*************************************************************************** + * Bluedroid private declarations * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) 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); +#endif }; // BLEService -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ #endif /* COMPONENTS_CPP_UTILS_BLESERVICE_H_ */ diff --git a/libraries/BLE/src/BLEServiceMap.cpp b/libraries/BLE/src/BLEServiceMap.cpp index 30a9db499f1..477da5b4cc2 100644 --- a/libraries/BLE/src/BLEServiceMap.cpp +++ b/libraries/BLE/src/BLEServiceMap.cpp @@ -3,16 +3,30 @@ * * Created on: Jun 22, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ + #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/***************************************************************************** + * Common includes * + *****************************************************************************/ + #include #include #include "BLEService.h" +/***************************************************************************** + * Common functions * + *****************************************************************************/ + /** * @brief Return the service by UUID. * @param [in] UUID The UUID to look up the service. @@ -82,13 +96,6 @@ String BLEServiceMap::toString() { return res; } // 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.first->handleGATTServerEvent(event, gatts_if, param); - } -} - /** * @brief Get the first service in the map. * @return The first service in the map. @@ -130,8 +137,21 @@ void BLEServiceMap::removeService(BLEService *service) { * @return amount of registered services */ int BLEServiceMap::getRegisteredServiceCount() { - return m_handleMap.size(); + return m_uuidMap.size(); +} + +/***************************************************************************** + * Bluedroid functions * + *****************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) +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.first->handleGATTServerEvent(event, gatts_if, param); + } } +#endif -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ diff --git a/libraries/BLE/src/BLETypes.h b/libraries/BLE/src/BLETypes.h new file mode 100644 index 00000000000..1fd3c9795c4 --- /dev/null +++ b/libraries/BLE/src/BLETypes.h @@ -0,0 +1,57 @@ +/* + * BLETypes.h + * + * Created on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + */ + +#ifndef COMPONENTS_CPP_BLE_BLETYPES_H_ +#define COMPONENTS_CPP_BLE_BLETYPES_H_ +#include "soc/soc_caps.h" +#if SOC_BLE_SUPPORTED + +#include "sdkconfig.h" +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/***************************************************************************** + * Common includes * + *****************************************************************************/ + +#include +#include "WString.h" + +/***************************************************************************** + * NimBLE includes * + *****************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +#include +#endif + +/***************************************************************************** + * Common type definitions * + *****************************************************************************/ + +typedef struct { + void *peer_device; // peer device BLEClient or BLEServer + bool connected; // connection status + uint16_t mtu; // negotiated MTU per peer device +} conn_status_t; + +/***************************************************************************** + * NimBLE type definitions * + *****************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +typedef struct { + void *pATT; + TaskHandle_t task; + int rc; + String *buf; +} ble_task_data_t; +#endif + + +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ +#endif /* SOC_BLE_SUPPORTED */ +#endif /* COMPONENTS_CPP_UTILS_BLEUTILS_H_ */ diff --git a/libraries/BLE/src/BLEUUID.cpp b/libraries/BLE/src/BLEUUID.cpp index 8074ea82f8f..a3dacbbba63 100644 --- a/libraries/BLE/src/BLEUUID.cpp +++ b/libraries/BLE/src/BLEUUID.cpp @@ -3,12 +3,22 @@ * * Created on: Jun 21, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ + #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/***************************************************************************** + * Common includes * + *****************************************************************************/ + #include #include #include @@ -18,25 +28,10 @@ #include "BLEUUID.h" #include "esp32-hal-log.h" -/** - * @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 - */ +/***************************************************************************** + * Common functions * + *****************************************************************************/ + static void memrcpy(uint8_t *target, uint8_t *source, uint32_t size) { assert(size > 0); target += (size - 1); // Point target to the last byte of the target data @@ -48,29 +43,15 @@ static void memrcpy(uint8_t *target, uint8_t *source, uint32_t 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() { + m_valueSet = false; +} // BLEUUID + BLEUUID::BLEUUID(String value) { - //Serial.printf("BLEUUID constructor from String=\"%s\"\n", value.c_str()); m_valueSet = true; if (value.length() == 4) { - m_uuid.len = ESP_UUID_LEN_16; - m_uuid.uuid.uuid16 = 0; + UUID_LEN(m_uuid) = BLE_UUID_16_BITS; + UUID_VAL_16(m_uuid) = 0; for (int i = 0; i < value.length();) { uint8_t MSB = value.c_str()[i]; uint8_t LSB = value.c_str()[i + 1]; @@ -81,12 +62,12 @@ BLEUUID::BLEUUID(String value) { if (LSB > '9') { LSB -= 7; } - m_uuid.uuid.uuid16 += (((MSB & 0x0F) << 4) | (LSB & 0x0F)) << (2 - i) * 4; + UUID_VAL_16(m_uuid) += (((MSB & 0x0F) << 4) | (LSB & 0x0F)) << (2 - i) * 4; i += 2; } } else if (value.length() == 8) { - m_uuid.len = ESP_UUID_LEN_32; - m_uuid.uuid.uuid32 = 0; + UUID_LEN(m_uuid) = BLE_UUID_32_BITS; + UUID_VAL_32(m_uuid) = 0; for (int i = 0; i < value.length();) { uint8_t MSB = value.c_str()[i]; uint8_t LSB = value.c_str()[i + 1]; @@ -97,18 +78,14 @@ BLEUUID::BLEUUID(String value) { if (LSB > '9') { LSB -= 7; } - m_uuid.uuid.uuid32 += (((MSB & 0x0F) << 4) | (LSB & 0x0F)) << (6 - i) * 4; + UUID_VAL_32(m_uuid) += (((MSB & 0x0F) << 4) | (LSB & 0x0F)) << (6 - i) * 4; i += 2; } - } else if (value.length() - == 16) { // How we can have 16 byte length string representing 128 bit uuid??? needs to be investigated (lack of time) - maybe raw data encoded as String (128b==16B)? - m_uuid.len = ESP_UUID_LEN_128; - memrcpy(m_uuid.uuid.uuid128, (uint8_t *)value.c_str(), 16); + } else if (value.length() == 16) { + UUID_LEN(m_uuid) = BLE_UUID_128_BITS; + memrcpy(UUID_VAL_128(m_uuid), (uint8_t *)value.c_str(), 16); } else if (value.length() == 36) { - //log_d("36 characters:"); - // 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; + UUID_LEN(m_uuid) = BLE_UUID_128_BITS; int n = 0; for (int i = 0; i < value.length();) { if (value.c_str()[i] == '-') { @@ -123,7 +100,7 @@ BLEUUID::BLEUUID(String value) { if (LSB > '9') { LSB -= 7; } - m_uuid.uuid.uuid128[15 - n++] = ((MSB & 0x0F) << 4) | (LSB & 0x0F); + UUID_VAL_128(m_uuid)[15 - n++] = ((MSB & 0x0F) << 4) | (LSB & 0x0F); i += 2; } } else { @@ -132,128 +109,73 @@ BLEUUID::BLEUUID(String value) { } } //BLEUUID(String) -/* -BLEUUID::BLEUUID(String value) { - this.BLEUUID(String(value.c_str(), value.length())); -} //BLEUUID(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) { log_e("ERROR: UUID length not 16 bytes"); return; } - m_uuid.len = ESP_UUID_LEN_128; + UUID_LEN(m_uuid) = BLE_UUID_128_BITS; if (msbFirst) { - memrcpy(m_uuid.uuid.uuid128, pData, 16); + memrcpy(UUID_VAL_128(m_uuid), pData, 16); } else { - memcpy(m_uuid.uuid.uuid128, pData, 16); + memcpy(UUID_VAL_128(m_uuid), 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; + UUID_LEN(m_uuid) = BLE_UUID_16_BITS; + UUID_VAL_16(m_uuid) = 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; + UUID_LEN(m_uuid) = BLE_UUID_32_BITS; + UUID_VAL_32(m_uuid) = 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; +BLEUUID::BLEUUID(uint32_t first, uint16_t second, uint16_t third, uint64_t fourth) { + UUID_LEN(m_uuid) = BLE_UUID_128_BITS; + memcpy(UUID_VAL_128(m_uuid) + 12, &first, 4); + memcpy(UUID_VAL_128(m_uuid) + 10, &second, 2); + memcpy(UUID_VAL_128(m_uuid) + 8, &third, 2); + memcpy(UUID_VAL_128(m_uuid), &fourth, 8); m_valueSet = true; -} // BLEUUID - -/** - * @brief Create a UUID from the ESP32 esp_gat_id_t. - * - * @param [in] gattId The data to create the UUID from. - */ -BLEUUID::BLEUUID(esp_gatt_id_t gattId) : BLEUUID(gattId.uuid) {} // BLEUUID - -BLEUUID::BLEUUID() { - m_valueSet = false; -} // BLEUUID +} -/** - * @brief Get the number of bits in this uuid. - * @return The number of bits in the UUID. One of 16, 32 or 128. - */ uint8_t BLEUUID::bitSize() { if (!m_valueSet) { return 0; } - switch (m_uuid.len) { - case ESP_UUID_LEN_16: return 16; - case ESP_UUID_LEN_32: return 32; - case ESP_UUID_LEN_128: return 128; - default: log_e("Unknown UUID length: %d", m_uuid.len); return 0; + switch (UUID_LEN(m_uuid)) { + case BLE_UUID_16_BITS: return 16; + case BLE_UUID_32_BITS: return 32; + case BLE_UUID_128_BITS: return 128; + default: log_e("Unknown UUID length: %d", UUID_LEN(m_uuid)); return 0; } // End of switch } // bitSize -/** - * @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) { - //log_d("Comparing: %s to %s", toString().c_str(), uuid.toString().c_str()); +bool BLEUUID::equals(const BLEUUID &uuid) const { if (!m_valueSet || !uuid.m_valueSet) { return false; } - if (uuid.m_uuid.len != m_uuid.len) { + if (UUID_LEN(uuid.m_uuid) != UUID_LEN(m_uuid)) { 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_LEN(uuid.m_uuid) == BLE_UUID_16_BITS) { + return UUID_VAL_16(uuid.m_uuid) == UUID_VAL_16(m_uuid); } - if (uuid.m_uuid.len == ESP_UUID_LEN_32) { - return uuid.m_uuid.uuid.uuid32 == m_uuid.uuid.uuid32; + if (UUID_LEN(uuid.m_uuid) == BLE_UUID_32_BITS) { + return UUID_VAL_32(uuid.m_uuid) == UUID_VAL_32(m_uuid); } - return memcmp(uuid.m_uuid.uuid.uuid128, m_uuid.uuid.uuid128, 16) == 0; + return memcmp(UUID_VAL_128(uuid.m_uuid), UUID_VAL_128(m_uuid), 16) == 0; } // equals -/** - * Create a BLEUUID from a string of the form: - * 0xNNNN - * 0xNNNNNNNN - * 0x - * NNNN - * NNNNNNNN - * - */ BLEUUID BLEUUID::fromString(String _uuid) { uint8_t start = 0; if (strstr(_uuid.c_str(), "0x") != nullptr) { // If the string starts with 0x, skip those characters. @@ -273,117 +195,143 @@ BLEUUID BLEUUID::fromString(String _uuid) { return BLEUUID(); } // fromString -/** - * @brief Get the native UUID value. - * - * @return The native UUID value or NULL if not set. - */ -esp_bt_uuid_t *BLEUUID::getNative() { - //log_d(">> getNative()") - if (m_valueSet == false) { - log_v("<< Return of un-initialized UUID!"); - return nullptr; - } - //log_d("<< 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() { - //log_v(">> 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 || m_uuid.len == ESP_UUID_LEN_128) { + if (!m_valueSet || UUID_LEN(m_uuid) == BLE_UUID_128_BITS) { 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; + if (UUID_LEN(m_uuid) == BLE_UUID_16_BITS) { + uint16_t temp = UUID_VAL_16(m_uuid); + UUID_VAL_128(m_uuid)[15] = 0; + UUID_VAL_128(m_uuid)[14] = 0; + UUID_VAL_128(m_uuid)[13] = (temp >> 8) & 0xff; + UUID_VAL_128(m_uuid)[12] = temp & 0xff; + } else if (UUID_LEN(m_uuid) == BLE_UUID_32_BITS) { + uint16_t temp = UUID_VAL_32(m_uuid); + UUID_VAL_128(m_uuid)[15] = (temp >> 24) & 0xff; + UUID_VAL_128(m_uuid)[14] = (temp >> 16) & 0xff; + UUID_VAL_128(m_uuid)[13] = (temp >> 8) & 0xff; + UUID_VAL_128(m_uuid)[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; + UUID_VAL_128(m_uuid)[11] = 0x00; + UUID_VAL_128(m_uuid)[10] = 0x00; + UUID_VAL_128(m_uuid)[9] = 0x10; + UUID_VAL_128(m_uuid)[8] = 0x00; + UUID_VAL_128(m_uuid)[7] = 0x80; + UUID_VAL_128(m_uuid)[6] = 0x00; + UUID_VAL_128(m_uuid)[5] = 0x00; + UUID_VAL_128(m_uuid)[4] = 0x80; + UUID_VAL_128(m_uuid)[3] = 0x5f; + UUID_VAL_128(m_uuid)[2] = 0x9b; + UUID_VAL_128(m_uuid)[1] = 0x34; + UUID_VAL_128(m_uuid)[0] = 0xfb; + + UUID_LEN(m_uuid) = BLE_UUID_128_BITS; + return *this; +} // to128 - m_uuid.uuid.uuid128[7] = 0x80; - m_uuid.uuid.uuid128[6] = 0x00; +BLEUUID BLEUUID::to16() { + if (!m_valueSet || UUID_LEN(m_uuid) == BLE_UUID_16_BITS) { + return *this; + } - 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; + if (UUID_LEN(m_uuid) == BLE_UUID_128_BITS) { + uint8_t base128[] = {0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, + 0x00, 0x80, 0x00, 0x10, 0x00, 0x00}; + if (memcmp(UUID_VAL_128(m_uuid), base128, sizeof(base128)) == 0 ) { + *this = BLEUUID(*(uint16_t*)(UUID_VAL_128(m_uuid) + 12)); + } + } - m_uuid.len = ESP_UUID_LEN_128; - //log_d("<< toFull <- %s", toString().c_str()); return *this; -} // to128 +} -/** - * @brief Get a string representation of the UUID. - * - * The format of a string is: - * 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 - * - * @return A string representation of the UUID. - */ -String BLEUUID::toString() { +String BLEUUID::toString() const { if (!m_valueSet) { return ""; // If we have no value, nothing to format. } - // If the UUIDs are 16 or 32 bit, pad correctly. - if (m_uuid.len == ESP_UUID_LEN_16) { // If the UUID is 16bit, pad correctly. + if (UUID_LEN(m_uuid) == BLE_UUID_16_BITS) { // If the UUID is 16bit, pad correctly. char hex[9]; - snprintf(hex, sizeof(hex), "%08x", m_uuid.uuid.uuid16); + snprintf(hex, sizeof(hex), "%08x", UUID_VAL_16(m_uuid)); return String(hex) + "-0000-1000-8000-00805f9b34fb"; } // End 16bit UUID - if (m_uuid.len == ESP_UUID_LEN_32) { // If the UUID is 32bit, pad correctly. + if (UUID_LEN(m_uuid) == BLE_UUID_32_BITS) { // If the UUID is 32bit, pad correctly. char hex[9]; - snprintf(hex, sizeof(hex), "%08lx", m_uuid.uuid.uuid32); + snprintf(hex, sizeof(hex), "%08lx", UUID_VAL_32(m_uuid)); return String(hex) + "-0000-1000-8000-00805f9b34fb"; } // End 32bit UUID // The UUID is not 16bit or 32bit which means that it is 128bit. - // - // UUID string format: - // AABBCCDD-EEFF-GGHH-IIJJ-KKLLMMNNOOPP auto size = 37; // 32 for UUID data, 4 for '-' delimiters and one for a terminator == 37 chars char *hex = (char *)malloc(size); snprintf( - hex, size, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", m_uuid.uuid.uuid128[15], m_uuid.uuid.uuid128[14], - m_uuid.uuid.uuid128[13], m_uuid.uuid.uuid128[12], m_uuid.uuid.uuid128[11], m_uuid.uuid.uuid128[10], m_uuid.uuid.uuid128[9], m_uuid.uuid.uuid128[8], - m_uuid.uuid.uuid128[7], m_uuid.uuid.uuid128[6], m_uuid.uuid.uuid128[5], m_uuid.uuid.uuid128[4], m_uuid.uuid.uuid128[3], m_uuid.uuid.uuid128[2], - m_uuid.uuid.uuid128[1], m_uuid.uuid.uuid128[0] + hex, size, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", UUID_VAL_128(m_uuid)[15], UUID_VAL_128(m_uuid)[14], + UUID_VAL_128(m_uuid)[13], UUID_VAL_128(m_uuid)[12], UUID_VAL_128(m_uuid)[11], UUID_VAL_128(m_uuid)[10], UUID_VAL_128(m_uuid)[9], UUID_VAL_128(m_uuid)[8], + UUID_VAL_128(m_uuid)[7], UUID_VAL_128(m_uuid)[6], UUID_VAL_128(m_uuid)[5], UUID_VAL_128(m_uuid)[4], UUID_VAL_128(m_uuid)[3], UUID_VAL_128(m_uuid)[2], + UUID_VAL_128(m_uuid)[1], UUID_VAL_128(m_uuid)[0] ); + String res(hex); free(hex); return res; } // toString -#endif /* CONFIG_BLUEDROID_ENABLED */ +bool BLEUUID::operator ==(const BLEUUID & rhs) const { + return equals(rhs); +} + +bool BLEUUID::operator !=(const BLEUUID & rhs) const { + return !equals(rhs); +} + +/***************************************************************************** + * Bluedroid functions * + *****************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) +BLEUUID::BLEUUID(esp_bt_uuid_t uuid) { + m_uuid = uuid; + m_valueSet = true; +} // BLEUUID + +BLEUUID::BLEUUID(esp_gatt_id_t gattId) : BLEUUID(gattId.uuid) {} // BLEUUID + +esp_bt_uuid_t *BLEUUID::getNative() { + if (m_valueSet == false) { + log_v("<< Return of un-initialized UUID!"); + return nullptr; + } + return &m_uuid; +} // getNative +#endif + +/***************************************************************************** + * NimBLE functions * + *****************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +BLEUUID::BLEUUID(ble_uuid_any_t uuid) { + m_uuid = uuid; + m_valueSet = true; +} // BLEUUID + +BLEUUID::BLEUUID(const ble_uuid128_t* uuid) { + m_uuid.u.type = BLE_UUID_TYPE_128; + memcpy(m_uuid.u128.value, uuid->value, 16); + m_valueSet = true; +} // BLEUUID + +const ble_uuid_any_t *BLEUUID::getNative() const { + if (m_valueSet == false) { + log_v("<< Return of un-initialized UUID!"); + return nullptr; + } + return &m_uuid; +} // getNative +#endif + +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ diff --git a/libraries/BLE/src/BLEUUID.h b/libraries/BLE/src/BLEUUID.h index 1be013942e3..7fe7bd9e265 100644 --- a/libraries/BLE/src/BLEUUID.h +++ b/libraries/BLE/src/BLEUUID.h @@ -3,42 +3,125 @@ * * Created on: Jun 21, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ #ifndef COMPONENTS_CPP_UTILS_BLEUUID_H_ #define COMPONENTS_CPP_UTILS_BLEUUID_H_ #include "soc/soc_caps.h" -#include "WString.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if CONFIG_BLUEDROID_ENABLED +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/***************************************************************************** + * Common includes * + *****************************************************************************/ + +#include "WString.h" + +/***************************************************************************** + * Bluedroid includes and definitions * + *****************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) #include +#define BLE_UUID_16_BITS ESP_UUID_LEN_16 +#define BLE_UUID_32_BITS ESP_UUID_LEN_32 +#define BLE_UUID_128_BITS ESP_UUID_LEN_128 +#define UUID_LEN(s) s.len +#define UUID_VAL_16(s) s.uuid.uuid16 +#define UUID_VAL_32(s) s.uuid.uuid32 +#define UUID_VAL_128(s) s.uuid.uuid128 +#endif + +/***************************************************************************** + * NimBLE includes and definitions * + *****************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +#include "host/ble_uuid.h" +#define BLE_UUID_16_BITS BLE_UUID_TYPE_16 +#define BLE_UUID_32_BITS BLE_UUID_TYPE_32 +#define BLE_UUID_128_BITS BLE_UUID_TYPE_128 +#define UUID_LEN(s) s.u.type +#define UUID_VAL_16(s) s.u16.value +#define UUID_VAL_32(s) s.u32.value +#define UUID_VAL_128(s) s.u128.value +#endif /** * @brief A model of a %BLE UUID. */ class BLEUUID { public: + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ + BLEUUID(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_id_t gattId); + BLEUUID(uint32_t first, uint16_t second, uint16_t third, uint64_t fourth); BLEUUID(); uint8_t bitSize(); // Get the number of bits in this uuid. - bool equals(BLEUUID uuid); - esp_bt_uuid_t *getNative(); + bool equals(const BLEUUID &uuid) const; BLEUUID to128(); - String toString(); + BLEUUID to16(); + String toString() const; static BLEUUID fromString(String uuid); // Create a BLEUUID from a string + bool operator ==(const BLEUUID & rhs) const; + bool operator !=(const BLEUUID & rhs) const; + + /*************************************************************************** + * Bluedroid public declarations * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + BLEUUID(esp_bt_uuid_t uuid); + BLEUUID(esp_gatt_id_t gattId); + esp_bt_uuid_t *getNative(); +#endif + + /*************************************************************************** + * NimBLE public declarations * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + BLEUUID(ble_uuid_any_t uuid); + BLEUUID(const ble_uuid128_t* uuid); + const ble_uuid_any_t *getNative() const; +#endif + private: - esp_bt_uuid_t m_uuid; // The underlying UUID structure that this class wraps. + /*************************************************************************** + * Common private properties * + ***************************************************************************/ + bool m_valueSet = false; // Is there a value set for this instance. + + /*************************************************************************** + * Bluedroid private properties * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + esp_bt_uuid_t m_uuid; // The underlying UUID structure that this class wraps. +#endif + + /*************************************************************************** + * NimBLE private properties * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + ble_uuid_any_t m_uuid; // The underlying UUID structure that this class wraps. +#endif }; // BLEUUID -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ #endif /* COMPONENTS_CPP_UTILS_BLEUUID_H_ */ diff --git a/libraries/BLE/src/BLEUtils.cpp b/libraries/BLE/src/BLEUtils.cpp index 05e1e32deed..45e3f0d8bac 100644 --- a/libraries/BLE/src/BLEUtils.cpp +++ b/libraries/BLE/src/BLEUtils.cpp @@ -3,12 +3,22 @@ * * Created on: Mar 25, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ + #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/***************************************************************************** + * Common includes * + *****************************************************************************/ + #include "BLEAddress.h" #include "BLEClient.h" #include "BLEUtils.h" @@ -17,17 +27,49 @@ #include #include -#include // ESP32 BLE -#include // ESP32 BLE -#include // ESP32 BLE -#include // ESP32 BLE -#include // ESP32 ESP-IDF -#include // Part of C++ STL +#include + +#include +#include #include #include #include "esp32-hal-log.h" +/***************************************************************************** + * Bluedroid includes * + *****************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) +#include +#include +#include +#endif + +/***************************************************************************** + * NimBLE includes and definitions * + *****************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG +#define CONFIG_NIMBLE_ENABLE_RETURN_CODE_TEXT +#define CONFIG_NIMBLE_ENABLE_ADVERTISMENT_TYPE_TEXT +#define CONFIG_NIMBLE_ENABLE_GAP_EVENT_CODE_TEXT +#endif +#include +#include +#include +#include +#include +#include +#include +#endif + +/***************************************************************************** + * Bluedroid types and constants * + *****************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) typedef struct { uint32_t assignedNumber; const char *name; @@ -593,13 +635,62 @@ static const gattService_t g_gattServices[] = { #endif {"", "", 0} }; +#endif + +/***************************************************************************** + * Common functions * + *****************************************************************************/ + +/** + * @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) { + log_e("buildHexData: malloc failed"); + return nullptr; + } + } + char *startOfData = (char *)target; + + for (int 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 + + +/***************************************************************************** + * Bluedroid functions * + *****************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) /** * @brief Convert characteristic properties into a string representation. * @param [in] prop Characteristic properties. * @return A string representation of characteristic properties. */ -String BLEUtils::characteristicPropertiesToString(esp_gatt_char_prop_t prop) { +String BLEUtils::characteristicPropertiesToString(uint8_t prop) { String res = "broadcast: "; res += ((prop & ESP_GATT_CHAR_PROP_BIT_BROADCAST) ? "1" : "0"); res += ", read: "; @@ -617,6 +708,24 @@ String BLEUtils::characteristicPropertiesToString(esp_gatt_char_prop_t prop) { return res; } // characteristicPropertiesToString +/** + * @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. + */ +String BLEUtils::buildPrintData(uint8_t *source, size_t length) { + String res; + for (int i = 0; i < length; i++) { + char c = *source; + res += (isprint(c) ? c : '.'); + source++; + } + return res; +} // buildPrintData + /** * @brief Convert an esp_gatt_id_t to a string. */ @@ -747,61 +856,6 @@ esp_gatt_srvc_id_t BLEUtils::buildGattSrvcId(esp_gatt_id_t gattId, bool is_prima 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) { - log_e("buildHexData: malloc failed"); - return nullptr; - } - } - char *startOfData = (char *)target; - - for (int 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. - */ -String BLEUtils::buildPrintData(uint8_t *source, size_t length) { - String res; - for (int i = 0; i < length; i++) { - char c = *source; - res += (isprint(c) ? c : '.'); - source++; - } - return res; -} // buildPrintData - /** * @brief Convert a close/disconnect reason to a string. * @param [in] reason The close reason. @@ -1817,5 +1871,357 @@ const char *BLEUtils::searchEventTypeToString(esp_gap_search_evt_t searchEvt) { } } // searchEventTypeToString -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif // CONFIG_BLUEDROID_ENABLED + +/***************************************************************************** + * NimBLE functions * + *****************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + +/** + * @brief A function for checking validity of connection parameters. + * @param [in] params A pointer to the structure containing the parameters to check. + * @return valid == 0 or error code. + */ +int BLEUtils::checkConnParams(ble_gap_conn_params *params) { + /* Check connection interval min */ + if ((params->itvl_min < BLE_HCI_CONN_ITVL_MIN) || (params->itvl_min > BLE_HCI_CONN_ITVL_MAX)) { + return BLE_ERR_INV_HCI_CMD_PARMS; + } + /* Check connection interval max */ + if ((params->itvl_max < BLE_HCI_CONN_ITVL_MIN) || (params->itvl_max > BLE_HCI_CONN_ITVL_MAX) || (params->itvl_max < params->itvl_min)) { + return BLE_ERR_INV_HCI_CMD_PARMS; + } + + /* Check connection latency */ + if (params->latency > BLE_HCI_CONN_LATENCY_MAX) { + return BLE_ERR_INV_HCI_CMD_PARMS; + } + + /* Check supervision timeout */ + if ((params->supervision_timeout < BLE_HCI_CONN_SPVN_TIMEOUT_MIN) || (params->supervision_timeout > BLE_HCI_CONN_SPVN_TIMEOUT_MAX)) { + return BLE_ERR_INV_HCI_CMD_PARMS; + } + + /* Check connection event length */ + if (params->min_ce_len > params->max_ce_len) { + return BLE_ERR_INV_HCI_CMD_PARMS; + } + + return 0; +} + +/** + * @brief Converts a return code from the NimBLE stack to a text string. + * @param [in] rc The return code to convert. + * @return A string representation of the return code. + */ +const char *BLEUtils::returnCodeToString(int rc) { +#if defined(CONFIG_NIMBLE_ENABLE_RETURN_CODE_TEXT) + switch (rc) { + case 0: return "SUCCESS"; + case BLE_HS_EAGAIN: return "Temporary failure; try again."; + case BLE_HS_EALREADY: return "Operation already in progress or completed."; + case BLE_HS_EINVAL: return "One or more arguments are invalid."; + case BLE_HS_EMSGSIZE: return "The provided buffer is too small."; + case BLE_HS_ENOENT: return "No entry matching the specified criteria."; + case BLE_HS_ENOMEM: return "Operation failed due to resource exhaustion."; + case BLE_HS_ENOTCONN: return "No open connection with the specified handle."; + case BLE_HS_ENOTSUP: return "Operation disabled at compile time."; + case BLE_HS_EAPP: return "Application callback behaved unexpectedly."; + case BLE_HS_EBADDATA: return "Command from peer is invalid."; + case BLE_HS_EOS: return "Mynewt OS error."; + case BLE_HS_ECONTROLLER: return "Event from controller is invalid."; + case BLE_HS_ETIMEOUT: return "Operation timed out."; + case BLE_HS_EDONE: return "Operation completed successfully."; + case BLE_HS_EBUSY: return "Operation cannot be performed until procedure completes."; + case BLE_HS_EREJECT: return "Peer rejected a connection parameter update request."; + case BLE_HS_EUNKNOWN: return "Unexpected failure; catch all."; + case BLE_HS_EROLE: return "Operation requires different role (e.g., central vs. peripheral)."; + case BLE_HS_ETIMEOUT_HCI: return "HCI request timed out; controller unresponsive."; + case BLE_HS_ENOMEM_EVT: return "Controller failed to send event due to memory exhaustion (combined host-controller only)."; + case BLE_HS_ENOADDR: return "Operation requires an identity address but none configured."; + case BLE_HS_ENOTSYNCED: return "Attempt to use the host before it is synced with controller."; + case BLE_HS_EAUTHEN: return "Insufficient authentication."; + case BLE_HS_EAUTHOR: return "Insufficient authorization."; + case BLE_HS_EENCRYPT: return "Insufficient encryption level."; + case BLE_HS_EENCRYPT_KEY_SZ: return "Insufficient key size."; + case BLE_HS_ESTORE_CAP: return "Storage at capacity."; + case BLE_HS_ESTORE_FAIL: return "Storage IO error."; + case BLE_HS_EPREEMPTED: return "Operation was preempted."; + case BLE_HS_EDISABLED: return "Operation disabled."; + case BLE_HS_ESTALLED: return "Operation stalled."; + case (0x0100 + BLE_ATT_ERR_INVALID_HANDLE): return "The attribute handle given was not valid on this server."; + case (0x0100 + BLE_ATT_ERR_READ_NOT_PERMITTED): return "The attribute cannot be read."; + case (0x0100 + BLE_ATT_ERR_WRITE_NOT_PERMITTED): return "The attribute cannot be written."; + case (0x0100 + BLE_ATT_ERR_INVALID_PDU): return "The attribute PDU was invalid."; + case (0x0100 + BLE_ATT_ERR_INSUFFICIENT_AUTHEN): return "The attribute requires authentication before it can be read or written."; + case (0x0100 + BLE_ATT_ERR_REQ_NOT_SUPPORTED): return "Attribute server does not support the request received from the client."; + case (0x0100 + BLE_ATT_ERR_INVALID_OFFSET): return "Offset specified was past the end of the attribute."; + case (0x0100 + BLE_ATT_ERR_INSUFFICIENT_AUTHOR): return "The attribute requires authorization before it can be read or written."; + case (0x0100 + BLE_ATT_ERR_PREPARE_QUEUE_FULL): return "Too many prepare writes have been queued."; + case (0x0100 + BLE_ATT_ERR_ATTR_NOT_FOUND): return "No attribute found within the given attribute handle range."; + case (0x0100 + BLE_ATT_ERR_ATTR_NOT_LONG): return "The attribute cannot be read or written using the Read Blob Request."; + case (0x0100 + BLE_ATT_ERR_INSUFFICIENT_KEY_SZ): return "The Encryption Key Size used for encrypting this link is insufficient."; + case (0x0100 + BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN): return "The attribute value length is invalid for the operation."; + case (0x0100 + BLE_ATT_ERR_UNLIKELY): return "The attribute request has encountered an error that was unlikely, could not be completed as requested."; + case (0x0100 + BLE_ATT_ERR_INSUFFICIENT_ENC): return "The attribute requires encryption before it can be read or written."; + case (0x0100 + BLE_ATT_ERR_UNSUPPORTED_GROUP): + return "The attribute type is not a supported grouping attribute as defined by a higher layer specification."; + case (0x0100 + BLE_ATT_ERR_INSUFFICIENT_RES): return "Insufficient Resources to complete the request."; + case (0x0200 + BLE_ERR_UNKNOWN_HCI_CMD): return "Unknown HCI Command"; + case (0x0200 + BLE_ERR_UNK_CONN_ID): return "Unknown Connection Identifier"; + case (0x0200 + BLE_ERR_HW_FAIL): return "Hardware Failure"; + case (0x0200 + BLE_ERR_PAGE_TMO): return "Page Timeout"; + case (0x0200 + BLE_ERR_AUTH_FAIL): return "Authentication Failure"; + case (0x0200 + BLE_ERR_PINKEY_MISSING): return "PIN or Key Missing"; + case (0x0200 + BLE_ERR_MEM_CAPACITY): return "Memory Capacity Exceeded"; + case (0x0200 + BLE_ERR_CONN_SPVN_TMO): return "Connection Timeout"; + case (0x0200 + BLE_ERR_CONN_LIMIT): return "Connection Limit Exceeded"; + case (0x0200 + BLE_ERR_SYNCH_CONN_LIMIT): return "Synchronous Connection Limit To A Device Exceeded"; + case (0x0200 + BLE_ERR_ACL_CONN_EXISTS): return "ACL Connection Already Exists"; + case (0x0200 + BLE_ERR_CMD_DISALLOWED): return "Command Disallowed"; + case (0x0200 + BLE_ERR_CONN_REJ_RESOURCES): return "Connection Rejected due to Limited Resources"; + case (0x0200 + BLE_ERR_CONN_REJ_SECURITY): return "Connection Rejected Due To Security Reasons"; + case (0x0200 + BLE_ERR_CONN_REJ_BD_ADDR): return "Connection Rejected due to Unacceptable BD_ADDR"; + case (0x0200 + BLE_ERR_CONN_ACCEPT_TMO): return "Connection Accept Timeout Exceeded"; + case (0x0200 + BLE_ERR_UNSUPPORTED): return "Unsupported Feature or Parameter Value"; + case (0x0200 + BLE_ERR_INV_HCI_CMD_PARMS): return "Invalid HCI Command Parameters"; + case (0x0200 + BLE_ERR_REM_USER_CONN_TERM): return "Remote User Terminated Connection"; + case (0x0200 + BLE_ERR_RD_CONN_TERM_RESRCS): return "Remote Device Terminated Connection due to Low Resources"; + case (0x0200 + BLE_ERR_RD_CONN_TERM_PWROFF): return "Remote Device Terminated Connection due to Power Off"; + case (0x0200 + BLE_ERR_CONN_TERM_LOCAL): return "Connection Terminated By Local Host"; + case (0x0200 + BLE_ERR_REPEATED_ATTEMPTS): return "Repeated Attempts"; + case (0x0200 + BLE_ERR_NO_PAIRING): return "Pairing Not Allowed"; + case (0x0200 + BLE_ERR_UNK_LMP): return "Unknown LMP PDU"; + case (0x0200 + BLE_ERR_UNSUPP_REM_FEATURE): return "Unsupported Remote Feature / Unsupported LMP Feature"; + case (0x0200 + BLE_ERR_SCO_OFFSET): return "SCO Offset Rejected"; + case (0x0200 + BLE_ERR_SCO_ITVL): return "SCO Interval Rejected"; + case (0x0200 + BLE_ERR_SCO_AIR_MODE): return "SCO Air Mode Rejected"; + case (0x0200 + BLE_ERR_INV_LMP_LL_PARM): return "Invalid LMP Parameters / Invalid LL Parameters"; + case (0x0200 + BLE_ERR_UNSPECIFIED): return "Unspecified Error"; + case (0x0200 + BLE_ERR_UNSUPP_LMP_LL_PARM): return "Unsupported LMP Parameter Value / Unsupported LL Parameter Value"; + case (0x0200 + BLE_ERR_NO_ROLE_CHANGE): return "Role Change Not Allowed"; + case (0x0200 + BLE_ERR_LMP_LL_RSP_TMO): return "LMP Response Timeout / LL Response Timeout"; + case (0x0200 + BLE_ERR_LMP_COLLISION): return "LMP Error Transaction Collision"; + case (0x0200 + BLE_ERR_LMP_PDU): return "LMP PDU Not Allowed"; + case (0x0200 + BLE_ERR_ENCRYPTION_MODE): return "Encryption Mode Not Acceptable"; + case (0x0200 + BLE_ERR_LINK_KEY_CHANGE): return "Link Key cannot be Changed"; + case (0x0200 + BLE_ERR_UNSUPP_QOS): return "Requested QoS Not Supported"; + case (0x0200 + BLE_ERR_INSTANT_PASSED): return "Instant Passed"; + case (0x0200 + BLE_ERR_UNIT_KEY_PAIRING): return "Pairing With Unit Key Not Supported"; + case (0x0200 + BLE_ERR_DIFF_TRANS_COLL): return "Different Transaction Collision"; + case (0x0200 + BLE_ERR_QOS_PARM): return "QoS Unacceptable Parameter"; + case (0x0200 + BLE_ERR_QOS_REJECTED): return "QoS Rejected"; + case (0x0200 + BLE_ERR_CHAN_CLASS): return "Channel Classification Not Supported"; + case (0x0200 + BLE_ERR_INSUFFICIENT_SEC): return "Insufficient Security"; + case (0x0200 + BLE_ERR_PARM_OUT_OF_RANGE): return "Parameter Out Of Mandatory Range"; + case (0x0200 + BLE_ERR_PENDING_ROLE_SW): return "Role Switch Pending"; + case (0x0200 + BLE_ERR_RESERVED_SLOT): return "Reserved Slot Violation"; + case (0x0200 + BLE_ERR_ROLE_SW_FAIL): return "Role Switch Failed"; + case (0x0200 + BLE_ERR_INQ_RSP_TOO_BIG): return "Extended Inquiry Response Too Large"; + case (0x0200 + BLE_ERR_SEC_SIMPLE_PAIR): return "Secure Simple Pairing Not Supported By Host"; + case (0x0200 + BLE_ERR_HOST_BUSY_PAIR): return "Host Busy - Pairing"; + case (0x0200 + BLE_ERR_CONN_REJ_CHANNEL): return "Connection Rejected, No Suitable Channel Found"; + case (0x0200 + BLE_ERR_CTLR_BUSY): return "Controller Busy"; + case (0x0200 + BLE_ERR_CONN_PARMS): return "Unacceptable Connection Parameters"; + case (0x0200 + BLE_ERR_DIR_ADV_TMO): return "Directed Advertising Timeout"; + case (0x0200 + BLE_ERR_CONN_TERM_MIC): return "Connection Terminated due to MIC Failure"; + case (0x0200 + BLE_ERR_CONN_ESTABLISHMENT): return "Connection Failed to be Established"; + case (0x0200 + BLE_ERR_MAC_CONN_FAIL): return "MAC Connection Failed"; + case (0x0200 + BLE_ERR_COARSE_CLK_ADJ): return "Coarse Clock Adjustment Rejected"; + case (0x0300 + BLE_L2CAP_SIG_ERR_CMD_NOT_UNDERSTOOD): return "Invalid or unsupported incoming L2CAP sig command."; + case (0x0300 + BLE_L2CAP_SIG_ERR_MTU_EXCEEDED): return "Incoming packet too large."; + case (0x0300 + BLE_L2CAP_SIG_ERR_INVALID_CID): return "No channel with specified ID."; + case (0x0400 + BLE_SM_ERR_PASSKEY): return "The user input of passkey failed, for example, the user canceled the operation."; + case (0x0400 + BLE_SM_ERR_OOB): return "The OOB data is not available."; + case (0x0400 + BLE_SM_ERR_AUTHREQ): + return "The pairing procedure cannot be performed as authentication requirements cannot be met due to IO capabilities of one or both devices."; + case (0x0400 + BLE_SM_ERR_CONFIRM_MISMATCH): return "The confirm value does not match the calculated compare value."; + case (0x0400 + BLE_SM_ERR_PAIR_NOT_SUPP): return "Pairing is not supported by the device."; + case (0x0400 + BLE_SM_ERR_ENC_KEY_SZ): return "The resultant encryption key size is insufficient for the security requirements of this device."; + case (0x0400 + BLE_SM_ERR_CMD_NOT_SUPP): return "The SMP command received is not supported on this device."; + case (0x0400 + BLE_SM_ERR_UNSPECIFIED): return "Pairing failed due to an unspecified reason."; + case (0x0400 + BLE_SM_ERR_REPEATED): + return "Pairing or authentication procedure disallowed, too little time has elapsed since last pairing request or security request."; + case (0x0400 + BLE_SM_ERR_INVAL): return "Command length is invalid or that a parameter is outside of the specified range."; + case (0x0400 + BLE_SM_ERR_DHKEY): return "DHKey Check value received doesn't match the one calculated by the local device."; + case (0x0400 + BLE_SM_ERR_NUMCMP): return "Confirm values in the numeric comparison protocol do not match."; + case (0x0400 + BLE_SM_ERR_ALREADY): return "Pairing over the LE transport failed - Pairing Request sent over the BR/EDR transport in process."; + case (0x0400 + BLE_SM_ERR_CROSS_TRANS): + return "BR/EDR Link Key generated on the BR/EDR transport cannot be used to derive and distribute keys for the LE transport."; + case (0x0500 + BLE_SM_ERR_PASSKEY): return "The user input of passkey failed or the user canceled the operation."; + case (0x0500 + BLE_SM_ERR_OOB): return "The OOB data is not available."; + case (0x0500 + BLE_SM_ERR_AUTHREQ): + return "The pairing procedure cannot be performed as authentication requirements cannot be met due to IO capabilities of one or both devices."; + case (0x0500 + BLE_SM_ERR_CONFIRM_MISMATCH): return "The confirm value does not match the calculated compare value."; + case (0x0500 + BLE_SM_ERR_PAIR_NOT_SUPP): return "Pairing is not supported by the device."; + case (0x0500 + BLE_SM_ERR_ENC_KEY_SZ): return "The resultant encryption key size is insufficient for the security requirements of this device."; + case (0x0500 + BLE_SM_ERR_CMD_NOT_SUPP): return "The SMP command received is not supported on this device."; + case (0x0500 + BLE_SM_ERR_UNSPECIFIED): return "Pairing failed due to an unspecified reason."; + case (0x0500 + BLE_SM_ERR_REPEATED): + return "Pairing or authentication procedure is disallowed because too little time has elapsed since last pairing request or security request."; + case (0x0500 + BLE_SM_ERR_INVAL): return "Command length is invalid or a parameter is outside of the specified range."; + case (0x0500 + BLE_SM_ERR_DHKEY): + return "Indicates to the remote device that the DHKey Check value received doesn't match the one calculated by the local device."; + case (0x0500 + BLE_SM_ERR_NUMCMP): return "Confirm values in the numeric comparison protocol do not match."; + case (0x0500 + BLE_SM_ERR_ALREADY): return "Pairing over the LE transport failed - Pairing Request sent over the BR/EDR transport in process."; + case (0x0500 + BLE_SM_ERR_CROSS_TRANS): + return "BR/EDR Link Key generated on the BR/EDR transport cannot be used to derive and distribute keys for the LE transport."; + default: return "Unknown"; + } +#else // #if defined(CONFIG_NIMBLE_ENABLE_RETURN_CODE_TEXT) + return ""; +#endif // #if defined(CONFIG_NIMBLE_ENABLE_RETURN_CODE_TEXT) +} + +/** + * @brief Convert the advertising type flag to a string. + * @param advType The type to convert. + * @return A string representation of the advertising flags. + */ +const char *BLEUtils::advTypeToString(uint8_t advType) { +#if defined(CONFIG_NIMBLE_ENABLE_ADVERTISMENT_TYPE_TEXT) + switch (advType) { + case BLE_HCI_ADV_TYPE_ADV_IND: //0 + return "Undirected - Connectable / Scannable"; + case BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_HD: //1 + return "Directed High Duty - Connectable"; + case BLE_HCI_ADV_TYPE_ADV_SCAN_IND: //2 + return "Non-Connectable - Scan Response Available"; + case BLE_HCI_ADV_TYPE_ADV_NONCONN_IND: //3 + return "Non-Connectable - No Scan Response"; + case BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_LD: //4 + return "Directed Low Duty - Connectable"; + default: return "Unknown flag"; + } +#else // #if defined(CONFIG_NIMBLE_ENABLE_ADVERTISMENT_TYPE_TEXT) + return ""; +#endif // #if defined(CONFIG_NIMBLE_ENABLE_ADVERTISMENT_TYPE_TEXT) +} // adFlagsToString + +/** + * @brief Utility function to log the gap event info. + * @param [in] event A pointer to the gap event structure. + * @param [in] arg Unused. + */ +void BLEUtils::dumpGapEvent(ble_gap_event *event, void *arg) { +#if defined(CONFIG_NIMBLE_ENABLE_GAP_EVENT_CODE_TEXT) + log_d("Received a GAP event: %s", gapEventToString(event->type)); +#endif +} + +/** + * @brief Convert a 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(uint8_t eventType) { +#if defined(CONFIG_NIMBLE_ENABLE_GAP_EVENT_CODE_TEXT) + switch (eventType) { + case BLE_GAP_EVENT_CONNECT: //0 + return "BLE_GAP_EVENT_CONNECT "; + + case BLE_GAP_EVENT_DISCONNECT: //1 + return "BLE_GAP_EVENT_DISCONNECT"; + + case BLE_GAP_EVENT_CONN_UPDATE: //3 + return "BLE_GAP_EVENT_CONN_UPDATE"; + + case BLE_GAP_EVENT_CONN_UPDATE_REQ: //4 + return "BLE_GAP_EVENT_CONN_UPDATE_REQ"; + + case BLE_GAP_EVENT_L2CAP_UPDATE_REQ: //5 + return "BLE_GAP_EVENT_L2CAP_UPDATE_REQ"; + + case BLE_GAP_EVENT_TERM_FAILURE: //6 + return "BLE_GAP_EVENT_TERM_FAILURE"; + + case BLE_GAP_EVENT_DISC: //7 + return "BLE_GAP_EVENT_DISC"; + + case BLE_GAP_EVENT_DISC_COMPLETE: //8 + return "BLE_GAP_EVENT_DISC_COMPLETE"; + + case BLE_GAP_EVENT_ADV_COMPLETE: //9 + return "BLE_GAP_EVENT_ADV_COMPLETE"; + + case BLE_GAP_EVENT_ENC_CHANGE: //10 + return "BLE_GAP_EVENT_ENC_CHANGE"; + + case BLE_GAP_EVENT_PASSKEY_ACTION: //11 + return "BLE_GAP_EVENT_PASSKEY_ACTION"; + + case BLE_GAP_EVENT_NOTIFY_RX: //12 + return "BLE_GAP_EVENT_NOTIFY_RX"; + + case BLE_GAP_EVENT_NOTIFY_TX: //13 + return "BLE_GAP_EVENT_NOTIFY_TX"; + + case BLE_GAP_EVENT_SUBSCRIBE: //14 + return "BLE_GAP_EVENT_SUBSCRIBE"; + + case BLE_GAP_EVENT_MTU: //15 + return "BLE_GAP_EVENT_MTU"; + + case BLE_GAP_EVENT_IDENTITY_RESOLVED: //16 + return "BLE_GAP_EVENT_IDENTITY_RESOLVED"; + + case BLE_GAP_EVENT_REPEAT_PAIRING: //17 + return "BLE_GAP_EVENT_REPEAT_PAIRING"; + + case BLE_GAP_EVENT_PHY_UPDATE_COMPLETE: //18 + return "BLE_GAP_EVENT_PHY_UPDATE_COMPLETE"; + + case BLE_GAP_EVENT_EXT_DISC: //19 + return "BLE_GAP_EVENT_EXT_DISC"; +#ifdef BLE_GAP_EVENT_PERIODIC_SYNC // IDF 4.0 does not support these + case BLE_GAP_EVENT_PERIODIC_SYNC: //20 + return "BLE_GAP_EVENT_PERIODIC_SYNC"; + + case BLE_GAP_EVENT_PERIODIC_REPORT: //21 + return "BLE_GAP_EVENT_PERIODIC_REPORT"; + + case BLE_GAP_EVENT_PERIODIC_SYNC_LOST: //22 + return "BLE_GAP_EVENT_PERIODIC_SYNC_LOST"; + + case BLE_GAP_EVENT_SCAN_REQ_RCVD: //23 + return "BLE_GAP_EVENT_SCAN_REQ_RCVD"; +#endif + default: log_d("gapEventToString: Unknown event type %d 0x%.2x", eventType, eventType); return "Unknown event type"; + } +#else // #if defined(CONFIG_NIMBLE_ENABLE_GAP_EVENT_CODE_TEXT) + return ""; +#endif // #if defined(CONFIG_NIMBLE_ENABLE_GAP_EVENT_CODE_TEXT) +} // gapEventToString + +/** + * @brief Convert characteristic properties into a string representation. + * @param [in] prop Characteristic properties. + * @return A string representation of characteristic properties. + */ +String BLEUtils::characteristicPropertiesToString(uint8_t prop) { + String res = "broadcast: "; + res += ((prop & BLE_GATT_CHR_PROP_BROADCAST) ? "1" : "0"); + res += ", read: "; + res += ((prop & BLE_GATT_CHR_PROP_READ) ? "1" : "0"); + res += ", write_nr: "; + res += ((prop & BLE_GATT_CHR_PROP_WRITE_NO_RSP) ? "1" : "0"); + res += ", write: "; + res += ((prop & BLE_GATT_CHR_PROP_WRITE) ? "1" : "0"); + res += ", notify: "; + res += ((prop & BLE_GATT_CHR_PROP_NOTIFY) ? "1" : "0"); + res += ", indicate: "; + res += ((prop & BLE_GATT_CHR_PROP_INDICATE) ? "1" : "0"); + res += ", auth_sign_write: "; + res += ((prop & BLE_GATT_CHR_PROP_AUTH_SIGN_WRITE) ? "1" : "0"); + res += ", extended: "; + res += ((prop & BLE_GATT_CHR_PROP_EXTENDED) ? "1" : "0"); + return res; +} // characteristicPropertiesToString + +#endif // CONFIG_NIMBLE_ENABLED + +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ diff --git a/libraries/BLE/src/BLEUtils.h b/libraries/BLE/src/BLEUtils.h index 7c6f58d284b..27c370101b4 100644 --- a/libraries/BLE/src/BLEUtils.h +++ b/libraries/BLE/src/BLEUtils.h @@ -3,6 +3,10 @@ * * Created on: Mar 25, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ #ifndef COMPONENTS_CPP_UTILS_BLEUTILS_H_ @@ -11,24 +15,63 @@ #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) -#include // ESP32 BLE -#include // ESP32 BLE -#include // ESP32 BLE +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/***************************************************************************** + * Common includes * + *****************************************************************************/ + #include +#include "BLEAddress.h" #include "BLEClient.h" +#include "BLETypes.h" +#include "WString.h" + +/***************************************************************************** + * Bluedroid includes * + *****************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) +#include +#include +#include +#endif + +/***************************************************************************** + * NimBLE includes * + *****************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) +#include +#endif + +/***************************************************************************** + * Forward declarations * + *****************************************************************************/ + +class BLEClient; /** * @brief A set of general %BLE utilities. */ class BLEUtils { public: - static const char *addressTypeToString(esp_ble_addr_type_t type); - static String adFlagsToString(uint8_t adFlags); - static const char *advTypeToString(uint8_t advType); + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ + static char *buildHexData(uint8_t *target, uint8_t *source, uint8_t length); static String buildPrintData(uint8_t *source, size_t length); - static String characteristicPropertiesToString(esp_gatt_char_prop_t prop); + static const char *advTypeToString(uint8_t advType); + static String characteristicPropertiesToString(uint8_t prop); + + /*************************************************************************** + * Bluedroid public declarations * + ***************************************************************************/ + +#if defined(CONFIG_BLUEDROID_ENABLED) + static const char *addressTypeToString(esp_ble_addr_type_t type); + static String adFlagsToString(uint8_t adFlags); static const char *devTypeToString(esp_bt_dev_type_t type); 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); @@ -52,8 +95,20 @@ class BLEUtils { static void registerByAddress(BLEAddress address, BLEClient *pDevice); static void registerByConnId(uint16_t conn_id, BLEClient *pDevice); static const char *searchEventTypeToString(esp_gap_search_evt_t searchEvt); +#endif + + /*************************************************************************** + * NimBLE public declarations * + ***************************************************************************/ + +#if defined(CONFIG_NIMBLE_ENABLED) + static void dumpGapEvent(ble_gap_event *event, void *arg); + static const char *gapEventToString(uint8_t eventType); + static const char *returnCodeToString(int rc); + static int checkConnParams(ble_gap_conn_params *params); +#endif }; -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ #endif /* COMPONENTS_CPP_UTILS_BLEUTILS_H_ */ diff --git a/libraries/BLE/src/BLEValue.cpp b/libraries/BLE/src/BLEValue.cpp index 26811c985ac..efc97697baa 100644 --- a/libraries/BLE/src/BLEValue.cpp +++ b/libraries/BLE/src/BLEValue.cpp @@ -3,15 +3,29 @@ * * Created on: Jul 17, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ + #include "soc/soc_caps.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/***************************************************************************** + * Common includes * + *****************************************************************************/ + #include "BLEValue.h" #include "esp32-hal-log.h" +/***************************************************************************** + * Common functions * + *****************************************************************************/ + BLEValue::BLEValue() { m_accumulation = ""; m_value = ""; @@ -120,5 +134,5 @@ void BLEValue::setValue(uint8_t *pData, size_t length) { m_value = String((char *)pData, length); } // setValue -#endif /* CONFIG_BLUEDROID_ENABLED */ +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ diff --git a/libraries/BLE/src/BLEValue.h b/libraries/BLE/src/BLEValue.h index f9c91bdcd4e..56a7a5bc4ec 100644 --- a/libraries/BLE/src/BLEValue.h +++ b/libraries/BLE/src/BLEValue.h @@ -3,22 +3,36 @@ * * Created on: Jul 17, 2017 * Author: kolban + * + * Modified on: Feb 18, 2025 + * Author: lucasssvaz (based on kolban's and h2zero's work) + * Description: Added support for NimBLE */ #ifndef COMPONENTS_CPP_UTILS_BLEVALUE_H_ #define COMPONENTS_CPP_UTILS_BLEVALUE_H_ + #include "soc/soc_caps.h" -#include "WString.h" #if SOC_BLE_SUPPORTED #include "sdkconfig.h" -#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) + +/***************************************************************************** + * Common includes * + *****************************************************************************/ + +#include "WString.h" /** * @brief The model of a %BLE value. */ class BLEValue { public: + /*************************************************************************** + * Common public declarations * + ***************************************************************************/ + BLEValue(); void addPart(String part); void addPart(uint8_t *pData, size_t length); @@ -33,10 +47,15 @@ class BLEValue { void setValue(uint8_t *pData, size_t length); private: + /*************************************************************************** + * Common private properties * + ***************************************************************************/ + String m_accumulation; uint16_t m_readOffset; String m_value; }; -#endif /* CONFIG_BLUEDROID_ENABLED */ + +#endif /* CONFIG_BLUEDROID_ENABLED || CONFIG_NIMBLE_ENABLED */ #endif /* SOC_BLE_SUPPORTED */ #endif /* COMPONENTS_CPP_UTILS_BLEVALUE_H_ */ From 6fbacb63a149bee51ac4d511167345ab2375d3cd Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Tue, 3 Jun 2025 20:37:18 +0300 Subject: [PATCH 02/10] fix(nimble): Fix unused variable warnings --- libraries/BLE/src/BLEDevice.cpp | 2 +- libraries/BLE/src/BLEServer.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/BLE/src/BLEDevice.cpp b/libraries/BLE/src/BLEDevice.cpp index f50126545c7..79d85590be7 100644 --- a/libraries/BLE/src/BLEDevice.cpp +++ b/libraries/BLE/src/BLEDevice.cpp @@ -236,7 +236,7 @@ String BLEDevice::getValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID ch */ void BLEDevice::init(String deviceName) { if (!initialized) { - esp_err_t errRc = ESP_OK; + [[maybe_unused]] esp_err_t errRc = ESP_OK; #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_NIMBLE_ENABLED) if (!btStart()) { errRc = ESP_FAIL; diff --git a/libraries/BLE/src/BLEServer.cpp b/libraries/BLE/src/BLEServer.cpp index cc4e624b71f..27378b3ed08 100644 --- a/libraries/BLE/src/BLEServer.cpp +++ b/libraries/BLE/src/BLEServer.cpp @@ -537,7 +537,7 @@ void BLEServerCallbacks::onDisconnect(BLEServer *pServer, esp_ble_gatts_cb_param } // onDisconnect void BLEServerCallbacks::onMtuChanged(BLEServer *pServer, esp_ble_gatts_cb_param_t *param) { - uint16_t mtu = param->mtu.mtu; + [[maybe_unused]] uint16_t mtu = param->mtu.mtu; log_d("BLEServerCallbacks", ">> onMtuChanged(): Default"); log_d("BLEServerCallbacks", "Device: %s MTU: %d", BLEDevice::toString().c_str(), mtu); log_d("BLEServerCallbacks", "<< onMtuChanged()"); From 37125334a04774d35fa8cb90e1a5bb84f0351fbf Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Wed, 4 Jun 2025 13:14:48 +0300 Subject: [PATCH 03/10] fix(bluedroid): Restore ExtScan functions --- libraries/BLE/src/BLEScan.cpp | 43 +++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/libraries/BLE/src/BLEScan.cpp b/libraries/BLE/src/BLEScan.cpp index 73890c39c27..d5479fd992f 100644 --- a/libraries/BLE/src/BLEScan.cpp +++ b/libraries/BLE/src/BLEScan.cpp @@ -207,6 +207,7 @@ void BLEScan::clearResults() { #if defined(CONFIG_BLUEDROID_ENABLED) #if defined(SOC_BLE_50_SUPPORTED) + void BLEScan::setExtendedScanCallback(BLEExtAdvertisingCallbacks *cb) { m_pExtendedScanCb = cb; } @@ -241,6 +242,48 @@ void BLEScan::setPeriodicScanCallback(BLEPeriodicScanCallbacks *cb) { m_pPeriodicScanCb = cb; } +/** +* @brief This function is used to set the extended scan parameters to be used on the advertising channels. +* +* +* @return - ESP_OK : success +* - other : failed +* +*/ +esp_err_t BLEScan::setExtScanParams() { + esp_ble_ext_scan_params_t ext_scan_params = { + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + .filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, + .scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE, + .cfg_mask = ESP_BLE_GAP_EXT_SCAN_CFG_UNCODE_MASK | ESP_BLE_GAP_EXT_SCAN_CFG_CODE_MASK, + .uncoded_cfg = {BLE_SCAN_TYPE_ACTIVE, 40, 40}, + .coded_cfg = {BLE_SCAN_TYPE_ACTIVE, 40, 40}, + }; + + esp_err_t rc = esp_ble_gap_set_ext_scan_params(&ext_scan_params); + if (rc) { + log_e("set extend scan params error, error code = %x", rc); + } + return rc; +} + +/** +* @brief This function is used to set the extended scan parameters to be used on the advertising channels. +* +* @param[in] params : scan parameters +* +* @return - ESP_OK : success +* - other : failed +* +*/ +esp_err_t BLEScan::setExtScanParams(esp_ble_ext_scan_params_t *ext_scan_params) { + esp_err_t rc = esp_ble_gap_set_ext_scan_params(ext_scan_params); + if (rc) { + log_e("set extend scan params error, error code = %x", rc); + } + return rc; +} + #endif // SOC_BLE_50_SUPPORTED /** From afc76150f4c55220056d0b9c8a8afecf126a6343 Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Wed, 4 Jun 2025 13:19:58 +0300 Subject: [PATCH 04/10] fix(nimble): Remove unused constructor --- libraries/BLE/src/BLEDescriptor.h | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/BLE/src/BLEDescriptor.h b/libraries/BLE/src/BLEDescriptor.h index 58a56a0aa65..c0255d78a52 100644 --- a/libraries/BLE/src/BLEDescriptor.h +++ b/libraries/BLE/src/BLEDescriptor.h @@ -163,7 +163,6 @@ class BLEDescriptorCallbacks { * Common public declarations * ***************************************************************************/ - BLEDescriptorCallbacks(); virtual ~BLEDescriptorCallbacks(); virtual void onRead(BLEDescriptor *pDescriptor); virtual void onWrite(BLEDescriptor *pDescriptor); From 4931b99db44d8f6df10da7a087849e2ddffa8164 Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Wed, 4 Jun 2025 21:16:18 +0300 Subject: [PATCH 05/10] fix(nimble): Fix core guards --- cores/esp32/esp32-hal-bt.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cores/esp32/esp32-hal-bt.c b/cores/esp32/esp32-hal-bt.c index a6dad2a7c17..a2a603bf5b9 100644 --- a/cores/esp32/esp32-hal-bt.c +++ b/cores/esp32/esp32-hal-bt.c @@ -15,8 +15,7 @@ #include "esp32-hal-bt.h" #if SOC_BT_SUPPORTED -#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) -#if defined(CONFIG_BT_BLUEDROID_ENABLED) && __has_include("esp_bt.h") +#if (defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)) && __has_include("esp_bt.h") #if CONFIG_IDF_TARGET_ESP32 bool btInUse() { @@ -117,7 +116,7 @@ bool btStop() { return false; } -#else // !defined(CONFIG_BLUEDROID_ENABLED) && !defined(CONFIG_NIMBLE_ENABLED) +#else // !__has_include("esp_bt.h") || !(defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)) bool btStarted() { return false; } @@ -130,6 +129,6 @@ bool btStop() { return false; } -#endif /* !defined(CONFIG_BLUEDROID_ENABLED) && !defined(CONFIG_NIMBLE_ENABLED) */ +#endif /* !__has_include("esp_bt.h") || !(defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)) */ #endif /* SOC_BT_SUPPORTED */ From a14714791ad422f9ba8ca4948c71d2b72beb077d Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Thu, 5 Jun 2025 18:41:56 +0300 Subject: [PATCH 06/10] fix(nimble): Fix crashes in server --- libraries/BLE/src/BLEServer.cpp | 26 +++++++++++++++++--------- libraries/BLE/src/BLEService.cpp | 23 ++++++++++++----------- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/libraries/BLE/src/BLEServer.cpp b/libraries/BLE/src/BLEServer.cpp index 27378b3ed08..eec848c5fe4 100644 --- a/libraries/BLE/src/BLEServer.cpp +++ b/libraries/BLE/src/BLEServer.cpp @@ -610,8 +610,10 @@ int BLEServer::handleGATTServerEvent(struct ble_gap_event *event, void *arg) { return 0; } - server->m_pServerCallbacks->onConnect(server); - server->m_pServerCallbacks->onConnect(server, &desc); + if (server->m_pServerCallbacks != nullptr) { + server->m_pServerCallbacks->onConnect(server); + server->m_pServerCallbacks->onConnect(server, &desc); + } } return 0; @@ -638,8 +640,10 @@ int BLEServer::handleGATTServerEvent(struct ble_gap_event *event, void *arg) { server->resetGATT(); } - server->m_pServerCallbacks->onDisconnect(server); - server->m_pServerCallbacks->onDisconnect(server, &event->disconnect.conn); + if (server->m_pServerCallbacks != nullptr) { + server->m_pServerCallbacks->onDisconnect(server); + server->m_pServerCallbacks->onDisconnect(server, &event->disconnect.conn); + } return 0; } // BLE_GAP_EVENT_DISCONNECT @@ -678,7 +682,9 @@ int BLEServer::handleGATTServerEvent(struct ble_gap_event *event, void *arg) { return 0; } - server->m_pServerCallbacks->onMtuChanged(server, &desc, event->mtu.value); + if (server->m_pServerCallbacks != nullptr) { + server->m_pServerCallbacks->onMtuChanged(server, &desc, event->mtu.value); + } return 0; } // BLE_GAP_EVENT_MTU @@ -768,7 +774,7 @@ int BLEServer::handleGATTServerEvent(struct ble_gap_event *event, void *arg) { if (BLEDevice::m_securityCallbacks != nullptr) { BLEDevice::m_securityCallbacks->onAuthenticationComplete(&desc); - } else { + } else if (server->m_pServerCallbacks != nullptr) { server->m_pServerCallbacks->onAuthenticationComplete(&desc); } @@ -786,7 +792,9 @@ int BLEServer::handleGATTServerEvent(struct ble_gap_event *event, void *arg) { // if the (static)passkey is the default, check the callback for custom value // both values default to the same. if (pkey.passkey == BLE_SM_DEFAULT_PASSKEY) { - pkey.passkey = server->m_pServerCallbacks->onPassKeyRequest(); + if (server->m_pServerCallbacks != nullptr) { + pkey.passkey = server->m_pServerCallbacks->onPassKeyRequest(); + } } rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); log_d("BLE_SM_IOACT_DISP; ble_sm_inject_io result: %d", rc); @@ -797,7 +805,7 @@ int BLEServer::handleGATTServerEvent(struct ble_gap_event *event, void *arg) { // Compatibility only - Do not use, should be removed the in future if (BLEDevice::m_securityCallbacks != nullptr) { pkey.numcmp_accept = BLEDevice::m_securityCallbacks->onConfirmPIN(event->passkey.params.numcmp); - } else { + } else if (server->m_pServerCallbacks != nullptr) { pkey.numcmp_accept = server->m_pServerCallbacks->onConfirmPIN(event->passkey.params.numcmp); } @@ -820,7 +828,7 @@ int BLEServer::handleGATTServerEvent(struct ble_gap_event *event, void *arg) { // Compatibility only - Do not use, should be removed the in future if (BLEDevice::m_securityCallbacks != nullptr) { pkey.passkey = BLEDevice::m_securityCallbacks->onPassKeyRequest(); - } else { + } else if (server->m_pServerCallbacks != nullptr) { pkey.passkey = server->m_pServerCallbacks->onPassKeyRequest(); } diff --git a/libraries/BLE/src/BLEService.cpp b/libraries/BLE/src/BLEService.cpp index 93e699a5e64..f7df05b76e2 100644 --- a/libraries/BLE/src/BLEService.cpp +++ b/libraries/BLE/src/BLEService.cpp @@ -527,13 +527,13 @@ bool BLEService::start() { // Nimble requires an array of services to be sent to the api // Since we are adding 1 at a time we create an array of 2 and set the type // of the second service to 0 to indicate the end of the array. - ble_gatt_svc_def *svc = new ble_gatt_svc_def[2]; + ble_gatt_svc_def *svc = new ble_gatt_svc_def[2]{}; ble_gatt_chr_def *pChr_a = nullptr; ble_gatt_dsc_def *pDsc_a = nullptr; svc[0].type = BLE_GATT_SVC_TYPE_PRIMARY; - svc[0].uuid = &m_uuid.getNative()->u; - svc[0].includes = NULL; + svc[0].uuid = (const ble_uuid_t *) &(m_uuid.getNative()->u); + svc[0].includes = nullptr; int removedCount = 0; BLECharacteristic *pCharacteristic; @@ -558,12 +558,12 @@ bool BLEService::start() { log_d("Adding %d characteristics for service %s", numChrs, toString().c_str()); if (!numChrs) { - svc[0].characteristics = NULL; + svc[0].characteristics = nullptr; } else { // Nimble requires the last characteristic to have it's uuid = 0 to indicate the end // of the characteristics for the service. We create 1 extra and set it to null // for this purpose. - pChr_a = new ble_gatt_chr_def[numChrs + 1]; + pChr_a = new ble_gatt_chr_def[numChrs + 1]{}; uint8_t i = 0; pCharacteristic = m_characteristicMap.getFirst(); while (pCharacteristic != nullptr) { @@ -585,17 +585,18 @@ bool BLEService::start() { } size_t numDscs = pCharacteristic->m_descriptorMap.getRegisteredDescriptorCount() - removedCount; + log_d("Adding %d descriptors for characteristic %s", numDscs, pCharacteristic->getUUID().toString().c_str()); if (!numDscs) { - pChr_a[i].descriptors = NULL; + pChr_a[i].descriptors = nullptr; } else { // Must have last descriptor uuid = 0 so we have to create 1 extra - pDsc_a = new ble_gatt_dsc_def[numDscs + 1]; + pDsc_a = new ble_gatt_dsc_def[numDscs + 1]{}; uint8_t d = 0; pDescriptor = pCharacteristic->m_descriptorMap.getFirst(); while (pDescriptor != nullptr) { if (pDescriptor->m_removed <= 0) { - pDsc_a[d].uuid = &pDescriptor->m_bleUUID.getNative()->u; + pDsc_a[d].uuid = (const ble_uuid_t *) &(pDescriptor->m_bleUUID.getNative()->u); pDsc_a[d].att_flags = pDescriptor->m_permissions; pDsc_a[d].min_key_size = 0; pDsc_a[d].access_cb = BLEDescriptor::handleGATTServerEvent; @@ -605,11 +606,11 @@ bool BLEService::start() { pDescriptor = pCharacteristic->m_descriptorMap.getNext(); } - pDsc_a[numDscs].uuid = NULL; + pDsc_a[numDscs].uuid = nullptr; pChr_a[i].descriptors = pDsc_a; } - pChr_a[i].uuid = &pCharacteristic->m_bleUUID.getNative()->u; + pChr_a[i].uuid = (const ble_uuid_t *) &(pCharacteristic->m_bleUUID.getNative()->u); pChr_a[i].access_cb = BLECharacteristic::handleGATTServerEvent; pChr_a[i].arg = pCharacteristic; pChr_a[i].flags = pCharacteristic->m_properties; @@ -621,7 +622,7 @@ bool BLEService::start() { pCharacteristic = m_characteristicMap.getNext(); } - pChr_a[numChrs].uuid = NULL; + pChr_a[numChrs].uuid = nullptr; svc[0].characteristics = pChr_a; } From f3745082efd2b4a1596d3fc82055fe3e079c3d8b Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Thu, 5 Jun 2025 20:38:50 +0300 Subject: [PATCH 07/10] fix(nimble): Fix crashes in Client --- libraries/BLE/src/BLEClient.cpp | 53 +++++++++++++++++++++++++++++++-- libraries/BLE/src/BLEClient.h | 5 +++- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/libraries/BLE/src/BLEClient.cpp b/libraries/BLE/src/BLEClient.cpp index 83afd186fd7..d2741ef0ebb 100644 --- a/libraries/BLE/src/BLEClient.cpp +++ b/libraries/BLE/src/BLEClient.cpp @@ -488,16 +488,17 @@ bool BLEClient::connect(BLEAddress address, uint8_t type, uint32_t timeoutMs) { /** * @brief Disconnect from the peer. - * @return N/A. + * @return error code from bluedroid, 0 = success. */ -void BLEClient::disconnect() { +int BLEClient::disconnect(uint8_t reason) { log_v(">> disconnect()"); esp_err_t errRc = ::esp_ble_gattc_close(getGattcIf(), getConnId()); if (errRc != ESP_OK) { log_e("esp_ble_gattc_close: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); - return; + return errRc; } log_v("<< disconnect()"); + return ESP_OK; } // disconnect /** @@ -1241,6 +1242,52 @@ int BLEClient::handleGAPEvent(struct ble_gap_event *event, void *arg) { return 0; } // handleGAPEvent +/** + * @brief Disconnect from the peer. + * @return Error code from NimBLE stack, 0 = success. + */ +int BLEClient::disconnect(uint8_t reason) { + log_d(">> disconnect()"); + int rc = 0; + if(isConnected()) { + // If the timer was already started, ignore this call. + if(ble_npl_callout_is_active(&m_dcTimer)) { + log_i("Already disconnecting, timer started"); + return BLE_HS_EALREADY; + } + + ble_gap_conn_desc desc; + if(ble_gap_conn_find(m_conn_id, &desc) != 0){ + log_i("Connection ID not found"); + return BLE_HS_EALREADY; + } + + // We use a timer to detect a controller error in the event that it does + // not inform the stack when disconnection is complete. + // This is a common error in certain esp-idf versions. + // The disconnect timeout time is the supervison timeout time + 1 second. + // In the case that the event happenss shortly after the supervision timeout + // we don't want to prematurely reset the host. + ble_npl_time_t ticks; + ble_npl_time_ms_to_ticks((desc.supervision_timeout + 100) * 10, &ticks); + ble_npl_callout_reset(&m_dcTimer, ticks); + + rc = ble_gap_terminate(m_conn_id, reason); + if (rc != 0) { + if(rc != BLE_HS_EALREADY) { + ble_npl_callout_stop(&m_dcTimer); + } + log_e("ble_gap_terminate failed: rc=%d %s", rc, BLEUtils::returnCodeToString(rc)); + } else { + log_d("Not connected to any peers"); + } + } + + log_d("<< disconnect()"); + m_lastErr = rc; + return rc; +} // disconnect + bool BLEClientCallbacks::onConnParamsUpdateRequest(BLEClient *pClient, const ble_gap_upd_params *params) { log_d("BLEClientCallbacks", "onConnParamsUpdateRequest: default"); return true; diff --git a/libraries/BLE/src/BLEClient.h b/libraries/BLE/src/BLEClient.h index 7f90de4c733..87510139809 100644 --- a/libraries/BLE/src/BLEClient.h +++ b/libraries/BLE/src/BLEClient.h @@ -37,6 +37,9 @@ #if defined(CONFIG_BLUEDROID_ENABLED) #include +#ifndef BLE_ERR_REM_USER_CONN_TERM +#define BLE_ERR_REM_USER_CONN_TERM 0x13 +#endif #endif /*************************************************************************** @@ -87,7 +90,7 @@ class BLEClient { bool connectTimeout(BLEAdvertisedDevice *device, uint32_t timeoutMS = portMAX_DELAY); bool connect(BLEAddress address, uint8_t type = 0, uint32_t timeoutMS = portMAX_DELAY); bool secureConnection(); - void disconnect(); + int disconnect(uint8_t reason = BLE_ERR_REM_USER_CONN_TERM); BLEAddress getPeerAddress(); int getRssi(); std::map *getServices(); From 592e42779eb45500f79c76c5432823e6282d77d9 Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Fri, 6 Jun 2025 10:58:08 +0300 Subject: [PATCH 08/10] fix(nimble): Add missing guards --- cores/esp32/esp32-hal-misc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cores/esp32/esp32-hal-misc.c b/cores/esp32/esp32-hal-misc.c index 6b46f688305..66c1d104612 100644 --- a/cores/esp32/esp32-hal-misc.c +++ b/cores/esp32/esp32-hal-misc.c @@ -25,9 +25,9 @@ #include "esp_ota_ops.h" #endif //CONFIG_APP_ROLLBACK_ENABLE #include "esp_private/startup_internal.h" -#if defined(CONFIG_BT_BLUEDROID_ENABLED) && SOC_BT_SUPPORTED +#if (defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)) && SOC_BT_SUPPORTED && __has_include("esp_bt.h") #include "esp_bt.h" -#endif //CONFIG_BT_BLUEDROID_ENABLED +#endif #include #include "soc/rtc.h" #if !defined(CONFIG_IDF_TARGET_ESP32C2) && !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(CONFIG_IDF_TARGET_ESP32H2) && !defined(CONFIG_IDF_TARGET_ESP32P4) && !defined(CONFIG_IDF_TARGET_ESP32C5) @@ -245,7 +245,7 @@ bool verifyRollbackLater() { } #endif -#ifdef CONFIG_BT_BLUEDROID_ENABLED +#if defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED) #if CONFIG_IDF_TARGET_ESP32 //overwritten in esp32-hal-bt.c bool btInUse() __attribute__((weak)); @@ -307,7 +307,7 @@ void initArduino() { if (err) { log_e("Failed to initialize NVS! Error: %u", err); } -#if defined(CONFIG_BT_BLUEDROID_ENABLED) && SOC_BT_SUPPORTED +#if (defined(CONFIG_BLUEDROID_ENABLED) || defined(CONFIG_NIMBLE_ENABLED)) && SOC_BT_SUPPORTED if (!btInUse()) { esp_bt_controller_mem_release(ESP_BT_MODE_BTDM); } From 34284b84a2819a798240a6f985b7799f00d6b1f6 Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Fri, 6 Jun 2025 19:04:46 +0300 Subject: [PATCH 09/10] fix(ble_device): Fix init guards --- libraries/BLE/src/BLEDevice.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/BLE/src/BLEDevice.cpp b/libraries/BLE/src/BLEDevice.cpp index 79d85590be7..ecaf4ff4313 100644 --- a/libraries/BLE/src/BLEDevice.cpp +++ b/libraries/BLE/src/BLEDevice.cpp @@ -236,8 +236,9 @@ String BLEDevice::getValue(BLEAddress bdAddress, BLEUUID serviceUUID, BLEUUID ch */ void BLEDevice::init(String deviceName) { if (!initialized) { - [[maybe_unused]] esp_err_t errRc = ESP_OK; -#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_NIMBLE_ENABLED) + esp_err_t errRc = ESP_OK; +#if defined(CONFIG_BLUEDROID_ENABLED) +#if defined(ARDUINO_ARCH_ESP32) if (!btStart()) { errRc = ESP_FAIL; return; @@ -261,7 +262,6 @@ void BLEDevice::init(String deviceName) { esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); #endif -#ifdef CONFIG_BLUEDROID_ENABLED esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); errRc = esp_bt_controller_init(&bt_cfg); if (errRc != ESP_OK) { @@ -282,6 +282,7 @@ void BLEDevice::init(String deviceName) { return; } #endif +#endif // !ARDUINO_ARCH_ESP32 esp_bluedroid_status_t bt_state = esp_bluedroid_get_status(); if (bt_state == ESP_BLUEDROID_STATUS_UNINITIALIZED) { @@ -335,8 +336,8 @@ void BLEDevice::init(String deviceName) { log_e("esp_ble_gap_set_security_param: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; }; -#endif #endif // CONFIG_BLE_SMP_ENABLE +#endif // CONFIG_BLUEDROID_ENABLED #if defined(CONFIG_NIMBLE_ENABLED) errRc = nimble_port_init(); @@ -373,7 +374,6 @@ void BLEDevice::init(String deviceName) { while (!m_synced) { ble_npl_time_delay(1); } -#endif #endif // CONFIG_NIMBLE_ENABLED initialized = true; // Set the initialization flag to ensure we are only initialized once. } From 2e34cb799afd943ec6453ec2ceed839e8ee7b80c Mon Sep 17 00:00:00 2001 From: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com> Date: Mon, 9 Jun 2025 14:28:12 +0300 Subject: [PATCH 10/10] fix(nimble): Fix scan --- libraries/BLE/src/BLEAdvertisedDevice.cpp | 30 ++--- libraries/BLE/src/BLEScan.cpp | 10 +- libraries/BLE/src/BLEUtils.cpp | 155 +++++++++------------- libraries/BLE/src/BLEUtils.h | 2 +- 4 files changed, 90 insertions(+), 107 deletions(-) diff --git a/libraries/BLE/src/BLEAdvertisedDevice.cpp b/libraries/BLE/src/BLEAdvertisedDevice.cpp index 305794dbccc..2831e8160a1 100644 --- a/libraries/BLE/src/BLEAdvertisedDevice.cpp +++ b/libraries/BLE/src/BLEAdvertisedDevice.cpp @@ -295,36 +295,36 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t *payload, size_t total_len) length--; char *pHex = BLEUtils::buildHexData(nullptr, payload, length); - log_d("Type: 0x%.2x (%s), length: %d, data: %s", ad_type, BLEUtils::advTypeToString(ad_type), length, pHex); + log_d("Type: 0x%.2x (%s), length: %d, data: %s", ad_type, BLEUtils::advDataTypeToString(ad_type), length, pHex); free(pHex); switch (ad_type) { - case 0x09: + case ESP_BLE_AD_TYPE_NAME_CMPL: // 0x09 { // Adv Data Type: ESP_BLE_AD_TYPE_NAME_CMPL setName(String(reinterpret_cast(payload), length)); break; } // 0x09 - case 0x0A: + case ESP_BLE_AD_TYPE_TX_PWR: // 0x0A { // Adv Data Type: ESP_BLE_AD_TYPE_TX_PWR setTXPower(*payload); break; } // 0x0A - case 0x19: + case ESP_BLE_AD_TYPE_APPEARANCE: // 0x19 { // Adv Data Type: ESP_BLE_AD_TYPE_APPEARANCE setAppearance(*reinterpret_cast(payload)); break; } // 0x19 - case 0x01: + case ESP_BLE_AD_TYPE_FLAG: // 0x01 { // Adv Data Type: ESP_BLE_AD_TYPE_FLAG setAdFlag(*payload); break; } // 0x01 - case 0x02: - case 0x03: + case ESP_BLE_AD_TYPE_16SRV_PART: // 0x02 + case ESP_BLE_AD_TYPE_16SRV_CMPL: // 0x03 { // Adv Data Type: ESP_BLE_AD_TYPE_16SRV_PART/CMPL for (int var = 0; var < length / 2; ++var) { setServiceUUID(BLEUUID(*reinterpret_cast(payload + var * 2))); @@ -332,8 +332,8 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t *payload, size_t total_len) break; } // 0x02, 0x03 - case 0x04: - case 0x05: + case ESP_BLE_AD_TYPE_32SRV_PART: // 0x04 + case ESP_BLE_AD_TYPE_32SRV_CMPL: // 0x05 { // Adv Data Type: ESP_BLE_AD_TYPE_32SRV_PART/CMPL for (int var = 0; var < length / 4; ++var) { setServiceUUID(BLEUUID(*reinterpret_cast(payload + var * 4))); @@ -341,26 +341,26 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t *payload, size_t total_len) break; } // 0x04, 0x05 - case 0x07: + case ESP_BLE_AD_TYPE_128SRV_CMPL: // 0x07 { // Adv Data Type: ESP_BLE_AD_TYPE_128SRV_CMPL setServiceUUID(BLEUUID(payload, 16, false)); break; } // 0x07 - case 0x06: + case ESP_BLE_AD_TYPE_128SRV_PART: // 0x06 { // Adv Data Type: ESP_BLE_AD_TYPE_128SRV_PART setServiceUUID(BLEUUID(payload, 16, false)); break; } // 0x06 // See CSS Part A 1.4 Manufacturer Specific Data - case 0xFF: + case ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE: // 0xFF { setManufacturerData(String(reinterpret_cast(payload), length)); break; } // 0xFF - case 0x16: + case ESP_BLE_AD_TYPE_SERVICE_DATA: // 0x16 { // Adv Data Type: ESP_BLE_AD_TYPE_SERVICE_DATA - 2 byte UUID if (length < 2) { log_e("Length too small for SERVICE_DATA"); @@ -374,7 +374,7 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t *payload, size_t total_len) break; } // 0x16 - case 0x20: + case ESP_BLE_AD_TYPE_32SERVICE_DATA: // 0x20 { // Adv Data Type: ESP_BLE_AD_TYPE_32SERVICE_DATA - 4 byte UUID if (length < 4) { log_e("Length too small for 32SERVICE_DATA"); @@ -388,7 +388,7 @@ void BLEAdvertisedDevice::parseAdvertisement(uint8_t *payload, size_t total_len) break; } // 0x20 - case 0x21: + case ESP_BLE_AD_TYPE_128SERVICE_DATA: // 0x21 { // Adv Data Type: ESP_BLE_AD_TYPE_128SERVICE_DATA - 16 byte UUID if (length < 16) { log_e("Length too small for 128SERVICE_DATA"); diff --git a/libraries/BLE/src/BLEScan.cpp b/libraries/BLE/src/BLEScan.cpp index d5479fd992f..3f8dfe8d5b0 100644 --- a/libraries/BLE/src/BLEScan.cpp +++ b/libraries/BLE/src/BLEScan.cpp @@ -638,6 +638,7 @@ int BLEScan::handleGAPEvent(ble_gap_event *event, void *arg) { } advertisedDevice = new BLEAdvertisedDevice(); advertisedDevice->setAddress(advertisedAddress); + advertisedDevice->setAddressType(event->disc.addr.type); advertisedDevice->setAdvType(event->disc.event_type); pScan->m_scanResults.m_vectorAdvertisedDevices.insert(std::pair(advertisedAddress.toString().c_str(), advertisedDevice)); log_i("New advertiser: %s", advertisedAddress.toString().c_str()); @@ -649,7 +650,14 @@ int BLEScan::handleGAPEvent(ble_gap_event *event, void *arg) { } advertisedDevice->setRSSI(event->disc.rssi); - advertisedDevice->setPayload((uint8_t *)event->disc.data, event->disc.length_data, event->disc.event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP); + + if (pScan->m_shouldParse) { + advertisedDevice->parseAdvertisement((uint8_t *)event->disc.data, event->disc.length_data); + } else { + advertisedDevice->setPayload((uint8_t *)event->disc.data, event->disc.length_data, event->disc.event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP); + } + + advertisedDevice->setScan(pScan); if (pScan->m_pAdvertisedDeviceCallbacks) { // If not active scanning or scan response is not available diff --git a/libraries/BLE/src/BLEUtils.cpp b/libraries/BLE/src/BLEUtils.cpp index 45e3f0d8bac..340ffc49177 100644 --- a/libraries/BLE/src/BLEUtils.cpp +++ b/libraries/BLE/src/BLEUtils.cpp @@ -678,6 +678,71 @@ char *BLEUtils::buildHexData(uint8_t *target, uint8_t *source, uint8_t length) { return startOfData; } // buildHexData +/** + * @brief Given an advertising data 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::advDataTypeToString(uint8_t advType) { + switch (advType) { +#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG + case ESP_BLE_AD_TYPE_FLAG: // 0x01 + return "ESP_BLE_AD_TYPE_FLAG"; + case ESP_BLE_AD_TYPE_16SRV_PART: // 0x02 + return "ESP_BLE_AD_TYPE_16SRV_PART"; + case ESP_BLE_AD_TYPE_16SRV_CMPL: // 0x03 + return "ESP_BLE_AD_TYPE_16SRV_CMPL"; + case ESP_BLE_AD_TYPE_32SRV_PART: // 0x04 + return "ESP_BLE_AD_TYPE_32SRV_PART"; + case ESP_BLE_AD_TYPE_32SRV_CMPL: // 0x05 + return "ESP_BLE_AD_TYPE_32SRV_CMPL"; + case ESP_BLE_AD_TYPE_128SRV_PART: // 0x06 + return "ESP_BLE_AD_TYPE_128SRV_PART"; + case ESP_BLE_AD_TYPE_128SRV_CMPL: // 0x07 + return "ESP_BLE_AD_TYPE_128SRV_CMPL"; + case ESP_BLE_AD_TYPE_NAME_SHORT: // 0x08 + return "ESP_BLE_AD_TYPE_NAME_SHORT"; + case ESP_BLE_AD_TYPE_NAME_CMPL: // 0x09 + return "ESP_BLE_AD_TYPE_NAME_CMPL"; + case ESP_BLE_AD_TYPE_TX_PWR: // 0x0a + return "ESP_BLE_AD_TYPE_TX_PWR"; + case ESP_BLE_AD_TYPE_DEV_CLASS: // 0x0b + return "ESP_BLE_AD_TYPE_DEV_CLASS"; + case ESP_BLE_AD_TYPE_SM_TK: // 0x10 + return "ESP_BLE_AD_TYPE_SM_TK"; + case ESP_BLE_AD_TYPE_SM_OOB_FLAG: // 0x11 + return "ESP_BLE_AD_TYPE_SM_OOB_FLAG"; + case ESP_BLE_AD_TYPE_INT_RANGE: // 0x12 + return "ESP_BLE_AD_TYPE_INT_RANGE"; + case ESP_BLE_AD_TYPE_SOL_SRV_UUID: // 0x14 + return "ESP_BLE_AD_TYPE_SOL_SRV_UUID"; + case ESP_BLE_AD_TYPE_128SOL_SRV_UUID: // 0x15 + return "ESP_BLE_AD_TYPE_128SOL_SRV_UUID"; + case ESP_BLE_AD_TYPE_SERVICE_DATA: // 0x16 + return "ESP_BLE_AD_TYPE_SERVICE_DATA"; + case ESP_BLE_AD_TYPE_PUBLIC_TARGET: // 0x17 + return "ESP_BLE_AD_TYPE_PUBLIC_TARGET"; + case ESP_BLE_AD_TYPE_RANDOM_TARGET: // 0x18 + return "ESP_BLE_AD_TYPE_RANDOM_TARGET"; + case ESP_BLE_AD_TYPE_APPEARANCE: // 0x19 + return "ESP_BLE_AD_TYPE_APPEARANCE"; + case ESP_BLE_AD_TYPE_ADV_INT: // 0x1a + return "ESP_BLE_AD_TYPE_ADV_INT"; + case ESP_BLE_AD_TYPE_32SOL_SRV_UUID: // 0x1f + return "ESP_BLE_AD_TYPE_32SOL_SRV_UUID"; + case ESP_BLE_AD_TYPE_32SERVICE_DATA: // 0x20 + return "ESP_BLE_AD_TYPE_32SERVICE_DATA"; + case ESP_BLE_AD_TYPE_128SERVICE_DATA: // 0x21 + return "ESP_BLE_AD_TYPE_128SERVICE_DATA"; + case ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE: // 0xff + return "ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE"; +#endif + default: log_v(" adv data type: 0x%x", advType); return ""; + } // End switch +} // advDataTypeToString /***************************************************************************** * Bluedroid functions * @@ -777,71 +842,6 @@ String BLEUtils::adFlagsToString(uint8_t adFlags) { return res; } // adFlagsToString -/** - * @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) { -#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG - case ESP_BLE_AD_TYPE_FLAG: // 0x01 - return "ESP_BLE_AD_TYPE_FLAG"; - case ESP_BLE_AD_TYPE_16SRV_PART: // 0x02 - return "ESP_BLE_AD_TYPE_16SRV_PART"; - case ESP_BLE_AD_TYPE_16SRV_CMPL: // 0x03 - return "ESP_BLE_AD_TYPE_16SRV_CMPL"; - case ESP_BLE_AD_TYPE_32SRV_PART: // 0x04 - return "ESP_BLE_AD_TYPE_32SRV_PART"; - case ESP_BLE_AD_TYPE_32SRV_CMPL: // 0x05 - return "ESP_BLE_AD_TYPE_32SRV_CMPL"; - case ESP_BLE_AD_TYPE_128SRV_PART: // 0x06 - return "ESP_BLE_AD_TYPE_128SRV_PART"; - case ESP_BLE_AD_TYPE_128SRV_CMPL: // 0x07 - return "ESP_BLE_AD_TYPE_128SRV_CMPL"; - case ESP_BLE_AD_TYPE_NAME_SHORT: // 0x08 - return "ESP_BLE_AD_TYPE_NAME_SHORT"; - case ESP_BLE_AD_TYPE_NAME_CMPL: // 0x09 - return "ESP_BLE_AD_TYPE_NAME_CMPL"; - case ESP_BLE_AD_TYPE_TX_PWR: // 0x0a - return "ESP_BLE_AD_TYPE_TX_PWR"; - case ESP_BLE_AD_TYPE_DEV_CLASS: // 0x0b - return "ESP_BLE_AD_TYPE_DEV_CLASS"; - case ESP_BLE_AD_TYPE_SM_TK: // 0x10 - return "ESP_BLE_AD_TYPE_SM_TK"; - case ESP_BLE_AD_TYPE_SM_OOB_FLAG: // 0x11 - return "ESP_BLE_AD_TYPE_SM_OOB_FLAG"; - case ESP_BLE_AD_TYPE_INT_RANGE: // 0x12 - return "ESP_BLE_AD_TYPE_INT_RANGE"; - case ESP_BLE_AD_TYPE_SOL_SRV_UUID: // 0x14 - return "ESP_BLE_AD_TYPE_SOL_SRV_UUID"; - case ESP_BLE_AD_TYPE_128SOL_SRV_UUID: // 0x15 - return "ESP_BLE_AD_TYPE_128SOL_SRV_UUID"; - case ESP_BLE_AD_TYPE_SERVICE_DATA: // 0x16 - return "ESP_BLE_AD_TYPE_SERVICE_DATA"; - case ESP_BLE_AD_TYPE_PUBLIC_TARGET: // 0x17 - return "ESP_BLE_AD_TYPE_PUBLIC_TARGET"; - case ESP_BLE_AD_TYPE_RANDOM_TARGET: // 0x18 - return "ESP_BLE_AD_TYPE_RANDOM_TARGET"; - case ESP_BLE_AD_TYPE_APPEARANCE: // 0x19 - return "ESP_BLE_AD_TYPE_APPEARANCE"; - case ESP_BLE_AD_TYPE_ADV_INT: // 0x1a - 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: // 0x20 - return "ESP_BLE_AD_TYPE_32SERVICE_DATA"; - case ESP_BLE_AD_TYPE_128SERVICE_DATA: // 0x21 - return "ESP_BLE_AD_TYPE_128SERVICE_DATA"; - case ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE: // 0xff - return "ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE"; -#endif - default: log_v(" adv data type: 0x%x", advType); return ""; - } // 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; @@ -2076,31 +2076,6 @@ const char *BLEUtils::returnCodeToString(int rc) { #endif // #if defined(CONFIG_NIMBLE_ENABLE_RETURN_CODE_TEXT) } -/** - * @brief Convert the advertising type flag to a string. - * @param advType The type to convert. - * @return A string representation of the advertising flags. - */ -const char *BLEUtils::advTypeToString(uint8_t advType) { -#if defined(CONFIG_NIMBLE_ENABLE_ADVERTISMENT_TYPE_TEXT) - switch (advType) { - case BLE_HCI_ADV_TYPE_ADV_IND: //0 - return "Undirected - Connectable / Scannable"; - case BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_HD: //1 - return "Directed High Duty - Connectable"; - case BLE_HCI_ADV_TYPE_ADV_SCAN_IND: //2 - return "Non-Connectable - Scan Response Available"; - case BLE_HCI_ADV_TYPE_ADV_NONCONN_IND: //3 - return "Non-Connectable - No Scan Response"; - case BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_LD: //4 - return "Directed Low Duty - Connectable"; - default: return "Unknown flag"; - } -#else // #if defined(CONFIG_NIMBLE_ENABLE_ADVERTISMENT_TYPE_TEXT) - return ""; -#endif // #if defined(CONFIG_NIMBLE_ENABLE_ADVERTISMENT_TYPE_TEXT) -} // adFlagsToString - /** * @brief Utility function to log the gap event info. * @param [in] event A pointer to the gap event structure. diff --git a/libraries/BLE/src/BLEUtils.h b/libraries/BLE/src/BLEUtils.h index 27c370101b4..d6c3cc65959 100644 --- a/libraries/BLE/src/BLEUtils.h +++ b/libraries/BLE/src/BLEUtils.h @@ -62,7 +62,7 @@ class BLEUtils { static char *buildHexData(uint8_t *target, uint8_t *source, uint8_t length); static String buildPrintData(uint8_t *source, size_t length); - static const char *advTypeToString(uint8_t advType); + static const char *advDataTypeToString(uint8_t advType); static String characteristicPropertiesToString(uint8_t prop); /***************************************************************************