From e089fae11a001b47712549ee3ab29a1381547ddb Mon Sep 17 00:00:00 2001
From: Simon Hailes
Date: Sun, 17 Jan 2021 17:05:10 +0000
Subject: [PATCH 1/2] update NimBLE to 5dc72ab10d9f928442a25ef3bdcf8a31a7e16301
---
lib/libesp32/NimBLE-Arduino/CHANGELOG.md | 69 ++++
lib/libesp32/NimBLE-Arduino/README.md | 4 +-
.../docs/Command_line_config.md | 93 +++++
.../NimBLE_Secure_Client.ino | 91 +++++
.../NimBLE_Secure_Server.ino | 37 ++
.../BLE_notify/BLE_notify.ino | 6 +-
.../BLE_server/BLE_server.ino | 7 +-
.../BLE_server_multiconnect.ino | 6 +-
lib/libesp32/NimBLE-Arduino/src/FreeRTOS.cpp | 10 +-
lib/libesp32/NimBLE-Arduino/src/FreeRTOS.h | 6 +-
.../NimBLE-Arduino/src/NimBLE2904.cpp | 2 +-
.../NimBLE-Arduino/src/NimBLEAdvertising.cpp | 289 ++++++++++++----
.../NimBLE-Arduino/src/NimBLEAdvertising.h | 11 +-
.../src/NimBLECharacteristic.cpp | 3 +-
.../NimBLE-Arduino/src/NimBLEClient.cpp | 318 ++++++++++++------
.../NimBLE-Arduino/src/NimBLEClient.h | 11 +-
.../NimBLE-Arduino/src/NimBLEDevice.cpp | 94 ++++--
.../NimBLE-Arduino/src/NimBLEDevice.h | 2 +
.../NimBLE-Arduino/src/NimBLEHIDDevice.cpp | 233 +++++++++++++
.../NimBLE-Arduino/src/NimBLEHIDDevice.h | 85 +++++
.../src/NimBLERemoteCharacteristic.cpp | 46 ++-
.../src/NimBLERemoteDescriptor.cpp | 4 +-
.../src/NimBLERemoteService.cpp | 34 +-
.../NimBLE-Arduino/src/NimBLERemoteService.h | 1 +
.../NimBLE-Arduino/src/NimBLEScan.cpp | 103 +++---
lib/libesp32/NimBLE-Arduino/src/NimBLEScan.h | 4 +-
.../NimBLE-Arduino/src/NimBLEServer.cpp | 5 +
.../NimBLE-Arduino/src/NimBLEServer.h | 9 +
.../NimBLE-Arduino/src/NimBLEUUID.cpp | 31 ++
.../src/esp-hci/src/esp_nimble_hci.c | 97 ++++--
.../NimBLE-Arduino/src/esp_nimble_cfg.h | 4 +
.../src/nimble/host/src/ble_eddystone.c | 2 +-
.../src/nimble/host/src/ble_gap.c | 6 +-
.../src/nimble/host/src/ble_hs_conn.c | 106 +++---
.../host/store/config/src/ble_store_nvs.c | 42 ++-
lib/libesp32/NimBLE-Arduino/src/nimconfig.h | 44 ++-
36 files changed, 1537 insertions(+), 378 deletions(-)
create mode 100644 lib/libesp32/NimBLE-Arduino/docs/Command_line_config.md
create mode 100644 lib/libesp32/NimBLE-Arduino/examples/NimBLE_Secure_Client/NimBLE_Secure_Client.ino
create mode 100644 lib/libesp32/NimBLE-Arduino/examples/NimBLE_Secure_Server/NimBLE_Secure_Server.ino
create mode 100644 lib/libesp32/NimBLE-Arduino/src/NimBLEHIDDevice.cpp
create mode 100644 lib/libesp32/NimBLE-Arduino/src/NimBLEHIDDevice.h
diff --git a/lib/libesp32/NimBLE-Arduino/CHANGELOG.md b/lib/libesp32/NimBLE-Arduino/CHANGELOG.md
index 8dfc5a141..128d3c93d 100644
--- a/lib/libesp32/NimBLE-Arduino/CHANGELOG.md
+++ b/lib/libesp32/NimBLE-Arduino/CHANGELOG.md
@@ -2,6 +2,75 @@
All notable changes to this project will be documented in this file.
+## [Unreleased]
+
+### Added
+- `NimBLEDevice::setOwnAddrType` added to enable the use of random and random-resolvable addresses, by asukiaaa
+
+- New examples for securing and authenticating client/server connections, by mblasee.
+
+- `NimBLEAdvertiseing::SetMinPreferred` and `NimBLEAdvertiseing::SetMinPreferred` re-added.
+
+- Conditional checks added for command line config options in `nimconfig.h` to support custom configuration in platformio.
+
+- `NimBLEClient::setValue` Now takes an extra bool parameter `response` to enable the use of write with response (default = false).
+
+- `NimBLEClient::getCharacteristic(uint16_t handle)` Enabling the use of the characteristic handle to be used to find
+the NimBLERemoteCharacteristic object.
+
+- `NimBLEHIDDevice` class added by wakwak-koba.
+
+- `NimBLEServerCallbacks::onDisconnect` overloaded callback added to provide a ble_gap_conn_desc parameter for the application
+to obtain information about the disconnected client.
+
+- Conditional checks in `nimconfig.h` for command line defined macros to support platformio config settings.
+
+### Changed
+- `NimBLEAdvertising::start` now returns a bool value to indicate success/failure.
+
+- Some asserts were removed in `NimBLEAdvertising::start` and replaced with better return code handling and logging.
+
+- If a host reset event occurs, scanning and advertising will now only be restarted if their previous duration was indefinite.
+
+- `NimBLERemoteCharacteristic::subscribe` and `NimBLERemoteCharacteristic::registerForNotify` will now set the callback
+regardless of the existance of the CCCD and return true unless the descriptor write operation failed.
+
+- Advertising tx power level is now sent in the advertisement packet instead of scan response.
+
+- `NimBLEScan` When the scan ends the scan stopped flag is now set before calling the scan complete callback (if used)
+this allows the starting of a new scan from the callback function.
+
+### Fixed
+- Sometimes `NimBLEClient::connect` would hang on the task block if no event arrived to unblock.
+A time limit has been added to timeout appropriately.
+
+- When getting descriptors for a characterisic the end handle of the service was used as a proxy for the characteristic end
+handle. This would be rejected by some devices and has been changed to use the next characteristic handle as the end when possible.
+
+- An exception could occur when deleting a client instance if a notification arrived while the attribute vectors were being
+deleted. A flag has been added to prevent this.
+
+- An exception could occur after a host reset event when the host re-synced if the tasks that were stopped during the event did
+not finish processing. A yield has been added after re-syncing to allow tasks to finish before proceeding.
+
+- Occasionally the controller would fail to send a disconnected event causing the client to indicate it is connected
+and would be unable to reconnect. A timer has been added to reset the host/controller if it expires.
+
+- Occasionally the call to start scanning would get stuck in a loop on BLE_HS_EBUSY, this loop has been removed.
+
+- 16bit and 32bit UUID's in some cases were not discovered or compared correctly if the device
+advertised them as 16/32bit but resolved them to 128bits. Both are now checked.
+
+- `FreeRTOS` compile errors resolved in latest Ardruino core and IDF v3.3.
+
+- Multiple instances of `time()` called inside critical sections caused sporadic crashes, these have been moved out of critical regions.
+
+- Advertisement type now correctly set when using non-connectable (advertiser only) mode.
+
+- Advertising payload length correction, now accounts for appearance.
+
+- (Arduino) Ensure controller mode is set to BLE Only.
+
## [1.0.2] - 2020-09-13
### Changed
diff --git a/lib/libesp32/NimBLE-Arduino/README.md b/lib/libesp32/NimBLE-Arduino/README.md
index 120b0c782..ea28b8811 100644
--- a/lib/libesp32/NimBLE-Arduino/README.md
+++ b/lib/libesp32/NimBLE-Arduino/README.md
@@ -68,9 +68,9 @@ such as increasing max connections, default is 3, absolute maximum connections i
# Development Status
-This Library is tracking the esp-nimble repo, nimble-1.2.0-idf master branch, currently [@95bd864.](https://github.com/espressif/esp-nimble)
+This Library is tracking the esp-nimble repo, nimble-1.2.0-idf master branch, currently [@f4ae049.](https://github.com/espressif/esp-nimble)
-Also tracking the NimBLE related changes in ESP-IDF, master branch, currently [@2ef4890.](https://github.com/espressif/esp-idf/tree/master/components/bt/host/nimble)
+Also tracking the NimBLE related changes in ESP-IDF, master branch, currently [@3caa969.](https://github.com/espressif/esp-idf/tree/master/components/bt/host/nimble)
# Acknowledgments
diff --git a/lib/libesp32/NimBLE-Arduino/docs/Command_line_config.md b/lib/libesp32/NimBLE-Arduino/docs/Command_line_config.md
new file mode 100644
index 000000000..813156f76
--- /dev/null
+++ b/lib/libesp32/NimBLE-Arduino/docs/Command_line_config.md
@@ -0,0 +1,93 @@
+# Arduino command line and platformio config options
+
+`CONFIG_BT_NIMBLE_ROLE_CENTRAL_DISABLED`
+
+ If defined, NimBLE Client functions will not be included.
+- Reduces flash size by approx. 7kB.
+
+
+`CONFIG_BT_NIMBLE_ROLE_OBSERVER_DISABLED`
+
+If defined, NimBLE Scan functions will not be included.
+- Reduces flash size by approx. 26kB.
+
+
+`CONFIG_BT_NIMBLE_ROLE_PERIPHERAL_DISABLED`
+
+If defined NimBLE Server functions will not be included.
+- Reduces flash size by approx. 16kB.
+
+
+`CONFIG_BT_NIMBLE_ROLE_BROADCASTER_DISABLED`
+
+If defined, NimBLE Advertising functions will not be included.
+- Reduces flash size by approx. 5kB.
+
+
+`CONFIG_BT_NIMBLE_DEBUG`
+
+If defined, enables debug log messages from the NimBLE host
+- Uses approx. 32kB of flash memory.
+
+
+`CONFIG_NIMBLE_CPP_ENABLE_RETURN_CODE_TEXT`
+
+If defined, NimBLE host return codes will be printed as text in debug log messages.
+- Uses approx. 7kB of flash memory.
+
+
+`CONFIG_NIMBLE_CPP_ENABLE_GAP_EVENT_CODE_TEXT`
+
+If defined, GAP event codes will be printed as text in debug log messages.
+- Uses approx. 1kB of flash memory.
+
+
+`CONFIG_NIMBLE_CPP_ENABLE_ADVERTISMENT_TYPE_TEXT`
+
+If defined, advertisment types will be printed as text while scanning in debug log messages.
+- Uses approx. 250 bytes of flash memory.
+
+
+`CONFIG_BT_NIMBLE_PINNED_TO_CORE`
+
+Sets the core the NimBLE host stack will run on
+- Options: 0 or 1
+
+
+`CONFIG_BT_NIMBLE_TASK_STACK_SIZE`
+
+Set the task stack size for the NimBLE core.
+- Default is 4096
+
+
+
+`CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL`
+
+Sets the NimBLE stack to use external PSRAM will be loaded
+- Must be defined with a value of 1; Default is CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_INTERNAL 1
+
+
+`CONFIG_BT_NIMBLE_MAX_CONNECTIONS`
+
+Sets the number of simultaneous connections (esp controller max is 9)
+- Default value is 3
+
+
+`CONFIG_BT_NIMBLE_MAX_BONDS`
+
+Sets the number of devices allowed to store/bond with
+- Default value is 3
+
+
+`CONFIG_BT_NIMBLE_MAX_CCCDS`
+
+Sets the maximum number of CCCD subscriptions to store
+- Default value is 8
+
+
+`CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME`
+
+Set the default device name
+- Default value is "nimble"
+
+
diff --git a/lib/libesp32/NimBLE-Arduino/examples/NimBLE_Secure_Client/NimBLE_Secure_Client.ino b/lib/libesp32/NimBLE-Arduino/examples/NimBLE_Secure_Client/NimBLE_Secure_Client.ino
new file mode 100644
index 000000000..6f9af4f74
--- /dev/null
+++ b/lib/libesp32/NimBLE-Arduino/examples/NimBLE_Secure_Client/NimBLE_Secure_Client.ino
@@ -0,0 +1,91 @@
+/** NimBLE_Secure_Client Demo:
+ *
+ * This example demonstrates the secure passkey protected conenction and communication between an esp32 server and an esp32 client.
+ * Please note that esp32 stores auth info in nvs memory. After a successful connection it is possible that a passkey change will be ineffective.
+ * To avoid this clear the memory of the esp32's between security testings. esptool.py is capable of this, example: esptool.py --port /dev/ttyUSB0 erase_flash.
+ *
+ * Created: on Jan 08 2021
+ * Author: mblasee
+ */
+
+#include
+
+class ClientCallbacks : public NimBLEClientCallbacks
+{
+ uint32_t onPassKeyRequest()
+ {
+ Serial.println("Client Passkey Request");
+ /** return the passkey to send to the server */
+ /** Change this to be different from NimBLE_Secure_Server if you want to test what happens on key mismatch */
+ return 123456;
+ };
+};
+static ClientCallbacks clientCB;
+
+void setup()
+{
+ Serial.begin(115200);
+ Serial.println("Starting NimBLE Client");
+
+ NimBLEDevice::init("");
+ NimBLEDevice::setPower(ESP_PWR_LVL_P9);
+ NimBLEDevice::setSecurityAuth(true, true, true);
+ NimBLEDevice::setSecurityIOCap(BLE_HS_IO_KEYBOARD_ONLY);
+ NimBLEScan *pScan = NimBLEDevice::getScan();
+ NimBLEScanResults results = pScan->start(5);
+
+ NimBLEUUID serviceUuid("ABCD");
+
+ for (int i = 0; i < results.getCount(); i++)
+ {
+ NimBLEAdvertisedDevice device = results.getDevice(i);
+ Serial.println(device.getName().c_str());
+
+ if (device.isAdvertisingService(serviceUuid))
+ {
+ NimBLEClient *pClient = NimBLEDevice::createClient();
+ pClient->setClientCallbacks(&clientCB, false);
+
+ if (pClient->connect(&device))
+ {
+ pClient->secureConnection();
+ NimBLERemoteService *pService = pClient->getService(serviceUuid);
+ if (pService != nullptr)
+ {
+ NimBLERemoteCharacteristic *pNonSecureCharacteristic = pService->getCharacteristic("1234");
+
+ if (pNonSecureCharacteristic != nullptr)
+ {
+ // Testing to read a non secured characteristic, you should be able to read this even if you have mismatching passkeys.
+ std::string value = pNonSecureCharacteristic->readValue();
+ // print or do whatever you need with the value
+ Serial.println(value.c_str());
+ }
+
+ NimBLERemoteCharacteristic *pSecureCharacteristic = pService->getCharacteristic("1235");
+
+ if (pSecureCharacteristic != nullptr)
+ {
+ // Testing to read a secured characteristic, you should be able to read this only if you have matching passkeys, otherwise you should
+ // get an error like this. E NimBLERemoteCharacteristic: "<< readValue rc=261"
+ // This means you are trying to do something without the proper permissions.
+ std::string value = pSecureCharacteristic->readValue();
+ // print or do whatever you need with the value
+ Serial.println(value.c_str());
+ }
+ }
+ }
+ else
+ {
+ // failed to connect
+ Serial.println("failed to connect");
+ }
+
+ NimBLEDevice::deleteClient(pClient);
+ }
+ }
+}
+
+void loop()
+{
+}
diff --git a/lib/libesp32/NimBLE-Arduino/examples/NimBLE_Secure_Server/NimBLE_Secure_Server.ino b/lib/libesp32/NimBLE-Arduino/examples/NimBLE_Secure_Server/NimBLE_Secure_Server.ino
new file mode 100644
index 000000000..f63cbc162
--- /dev/null
+++ b/lib/libesp32/NimBLE-Arduino/examples/NimBLE_Secure_Server/NimBLE_Secure_Server.ino
@@ -0,0 +1,37 @@
+/** NimBLE_Secure_Server Demo:
+ *
+ * This example demonstrates the secure passkey protected conenction and communication between an esp32 server and an esp32 client.
+ * Please note that esp32 stores auth info in nvs memory. After a successful connection it is possible that a passkey change will be ineffective.
+ * To avoid this clear the memory of the esp32's between security testings. esptool.py is capable of this, example: esptool.py --port /dev/ttyUSB0 erase_flash.
+ *
+ * Created: on Jan 08 2021
+ * Author: mblasee
+ */
+
+#include
+
+void setup() {
+ Serial.begin(115200);
+ Serial.println("Starting NimBLE Server");
+ NimBLEDevice::init("NimBLE");
+ NimBLEDevice::setPower(ESP_PWR_LVL_P9);
+
+ NimBLEDevice::setSecurityAuth(true, true, true);
+ NimBLEDevice::setSecurityPasskey(123456);
+ NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_ONLY);
+ NimBLEServer *pServer = NimBLEDevice::createServer();
+ NimBLEService *pService = pServer->createService("ABCD");
+ NimBLECharacteristic *pNonSecureCharacteristic = pService->createCharacteristic("1234", NIMBLE_PROPERTY::READ );
+ NimBLECharacteristic *pSecureCharacteristic = pService->createCharacteristic("1235", NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::READ_AUTHEN);
+
+ pService->start();
+ pNonSecureCharacteristic->setValue("Hello Non Secure BLE");
+ pSecureCharacteristic->setValue("Hello Secure BLE");
+
+ NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
+ pAdvertising->addServiceUUID("ABCD");
+ pAdvertising->start();
+}
+
+void loop() {
+}
diff --git a/lib/libesp32/NimBLE-Arduino/examples/Refactored_original_examples/BLE_notify/BLE_notify.ino b/lib/libesp32/NimBLE-Arduino/examples/Refactored_original_examples/BLE_notify/BLE_notify.ino
index 83f129b83..cb0488819 100644
--- a/lib/libesp32/NimBLE-Arduino/examples/Refactored_original_examples/BLE_notify/BLE_notify.ino
+++ b/lib/libesp32/NimBLE-Arduino/examples/Refactored_original_examples/BLE_notify/BLE_notify.ino
@@ -116,9 +116,9 @@ void setup() {
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(false);
- /**This method is removed as it was no longer useful and consumed advertising space
- * pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter
- */
+ /** Note, this could be left out as that is the default value */
+ pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter
+
BLEDevice::startAdvertising();
Serial.println("Waiting a client connection to notify...");
}
diff --git a/lib/libesp32/NimBLE-Arduino/examples/Refactored_original_examples/BLE_server/BLE_server.ino b/lib/libesp32/NimBLE-Arduino/examples/Refactored_original_examples/BLE_server/BLE_server.ino
index 652d77685..faa4d88ea 100644
--- a/lib/libesp32/NimBLE-Arduino/examples/Refactored_original_examples/BLE_server/BLE_server.ino
+++ b/lib/libesp32/NimBLE-Arduino/examples/Refactored_original_examples/BLE_server/BLE_server.ino
@@ -44,10 +44,9 @@ void setup() {
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
- /**These methods are removed as they are no longer useful and consumed advertising space
- * pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue
- * pAdvertising->setMinPreferred(0x12);
- */
+ pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue
+ pAdvertising->setMaxPreferred(0x12);
+
BLEDevice::startAdvertising();
Serial.println("Characteristic defined! Now you can read it in your phone!");
}
diff --git a/lib/libesp32/NimBLE-Arduino/examples/Refactored_original_examples/BLE_server_multiconnect/BLE_server_multiconnect.ino b/lib/libesp32/NimBLE-Arduino/examples/Refactored_original_examples/BLE_server_multiconnect/BLE_server_multiconnect.ino
index 2ec38c481..9ae3859c7 100644
--- a/lib/libesp32/NimBLE-Arduino/examples/Refactored_original_examples/BLE_server_multiconnect/BLE_server_multiconnect.ino
+++ b/lib/libesp32/NimBLE-Arduino/examples/Refactored_original_examples/BLE_server_multiconnect/BLE_server_multiconnect.ino
@@ -120,9 +120,9 @@ void setup() {
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(false);
- /**This method is removed it was no longer useful and consumed advertising space
- * pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter
- */
+ /** Note, this could be left out as that is the default value */
+ pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter
+
BLEDevice::startAdvertising();
Serial.println("Waiting a client connection to notify...");
}
diff --git a/lib/libesp32/NimBLE-Arduino/src/FreeRTOS.cpp b/lib/libesp32/NimBLE-Arduino/src/FreeRTOS.cpp
index 4ebab3958..1c398cbf3 100644
--- a/lib/libesp32/NimBLE-Arduino/src/FreeRTOS.cpp
+++ b/lib/libesp32/NimBLE-Arduino/src/FreeRTOS.cpp
@@ -264,10 +264,14 @@ void FreeRTOS::Semaphore::setName(std::string name) {
* @param [in] length The amount of storage to allocate for the ring buffer.
* @param [in] type The type of buffer. One of RINGBUF_TYPE_NOSPLIT, RINGBUF_TYPE_ALLOWSPLIT, RINGBUF_TYPE_BYTEBUF.
*/
-#if defined(ESP_IDF_VERSION) && !defined(ESP_IDF_VERSION_VAL) //Quick hack to detect if using IDF version that replaced ringbuf_type_t, ESP_IDF_VERSION_VAL is for IDF>4.0.0
-Ringbuffer::Ringbuffer(size_t length, RingbufferType_t type) {
+#ifdef ESP_IDF_VERSION //Quick hack to detect if using IDF version that replaced ringbuf_type_t
+#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0)
+ Ringbuffer::Ringbuffer(size_t length, RingbufferType_t type) {
#else
-Ringbuffer::Ringbuffer(size_t length, ringbuf_type_t type) {
+ Ringbuffer::Ringbuffer(size_t length, ringbuf_type_t type) {
+#endif
+#else
+ Ringbuffer::Ringbuffer(size_t length, ringbuf_type_t type) {
#endif
m_handle = ::xRingbufferCreate(length, type);
} // Ringbuffer
diff --git a/lib/libesp32/NimBLE-Arduino/src/FreeRTOS.h b/lib/libesp32/NimBLE-Arduino/src/FreeRTOS.h
index f93f0b1a0..fa33921fe 100644
--- a/lib/libesp32/NimBLE-Arduino/src/FreeRTOS.h
+++ b/lib/libesp32/NimBLE-Arduino/src/FreeRTOS.h
@@ -68,8 +68,12 @@ public:
*/
class Ringbuffer {
public:
-#if defined(ESP_IDF_VERSION) && !defined(ESP_IDF_VERSION_VAL) //Quick hack to detect if using IDF version that replaced ringbuf_type_t, ESP_IDF_VERSION_VAL is for IDF>4.0.0
+#ifdef ESP_IDF_VERSION //Quick hack to detect if using IDF version that replaced ringbuf_type_t
+#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0)
Ringbuffer(size_t length, RingbufferType_t type = RINGBUF_TYPE_NOSPLIT);
+#else
+ Ringbuffer(size_t length, ringbuf_type_t type = RINGBUF_TYPE_NOSPLIT);
+#endif
#else
Ringbuffer(size_t length, ringbuf_type_t type = RINGBUF_TYPE_NOSPLIT);
#endif
diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLE2904.cpp b/lib/libesp32/NimBLE-Arduino/src/NimBLE2904.cpp
index d85cd87e4..80318b5b8 100644
--- a/lib/libesp32/NimBLE-Arduino/src/NimBLE2904.cpp
+++ b/lib/libesp32/NimBLE-Arduino/src/NimBLE2904.cpp
@@ -37,7 +37,7 @@ NimBLE2904::NimBLE2904(NimBLECharacteristic* pCharacterisitic)
m_data.m_unit = 0;
m_data.m_description = 0;
setValue((uint8_t*) &m_data, sizeof(m_data));
-} // BLE2902
+} // BLE2904
/**
diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEAdvertising.cpp b/lib/libesp32/NimBLE-Arduino/src/NimBLEAdvertising.cpp
index 36bdbf9e9..a10480410 100644
--- a/lib/libesp32/NimBLE-Arduino/src/NimBLEAdvertising.cpp
+++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEAdvertising.cpp
@@ -32,7 +32,7 @@ static const char* LOG_TAG = "NimBLEAdvertising";
/**
* @brief Construct a default advertising object.
*/
-NimBLEAdvertising::NimBLEAdvertising() {
+NimBLEAdvertising::NimBLEAdvertising() : m_slaveItvl() {
memset(&m_advData, 0, sizeof m_advData);
memset(&m_scanData, 0, sizeof m_scanData);
memset(&m_advParams, 0, sizeof m_advParams);
@@ -41,15 +41,20 @@ NimBLEAdvertising::NimBLEAdvertising() {
m_advData.name = (uint8_t *)name;
m_advData.name_len = strlen(name);
m_advData.name_is_complete = 1;
- m_scanData.tx_pwr_lvl_is_present = 1;
- m_scanData.tx_pwr_lvl = NimBLEDevice::getPower();
+ m_advData.tx_pwr_lvl_is_present = 1;
+ m_advData.tx_pwr_lvl = NimBLEDevice::getPower();
m_advData.flags = (BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP);
m_advData.appearance = 0;
m_advData.appearance_is_present = 0;
m_advData.mfg_data_len = 0;
m_advData.mfg_data = nullptr;
+ m_advData.slave_itvl_range = nullptr;
+#if !defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
+ m_advParams.conn_mode = BLE_GAP_CONN_MODE_NON;
+#else
m_advParams.conn_mode = BLE_GAP_CONN_MODE_UND;
+#endif
m_advParams.disc_mode = BLE_GAP_DISC_MODE_GEN;
m_advParams.itvl_min = 0;
m_advParams.itvl_max = 0;
@@ -58,6 +63,8 @@ NimBLEAdvertising::NimBLEAdvertising() {
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;
} // NimBLEAdvertising
@@ -86,7 +93,6 @@ void NimBLEAdvertising::addServiceUUID(const char* serviceUUID) {
* @param [in] serviceUUID The UUID of the service to expose.
*/
void NimBLEAdvertising::removeServiceUUID(const NimBLEUUID &serviceUUID) {
- //m_serviceUUIDs.erase(std::remove_if(m_serviceUUIDs.begin(), m_serviceUUIDs.end(),[serviceUUID](const NimBLEUUID &s) {return serviceUUID == s;}), m_serviceUUIDs.end());
for(auto it = m_serviceUUIDs.begin(); it != m_serviceUUIDs.end(); ++it) {
if((*it) == serviceUUID) {
m_serviceUUIDs.erase(it);
@@ -112,11 +118,9 @@ void NimBLEAdvertising::setAppearance(uint16_t appearance) {
/**
* @brief Set the type of advertisment to use.
* @param [in] adv_type:
- * * BLE_HCI_ADV_TYPE_ADV_IND (0) - indirect advertising
- * * BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_HD (1) - direct advertisng - high duty cycle
- * * BLE_HCI_ADV_TYPE_ADV_SCAN_IND (2) - indirect scan response
- * * BLE_HCI_ADV_TYPE_ADV_NONCONN_IND (3) - indirect advertisng - not connectable
- * * BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_LD (4) - direct advertising - low duty cycle
+ * * BLE_GAP_CONN_MODE_NON (0) - not connectable advertising
+ * * BLE_GAP_CONN_MODE_DIR (1) - directed connectable advertising
+ * * BLE_GAP_CONN_MODE_UND (2) - undirected connectable advertising
*/
void NimBLEAdvertising::setAdvertisementType(uint8_t adv_type){
m_advParams.conn_mode = adv_type;
@@ -141,6 +145,64 @@ void NimBLEAdvertising::setMaxInterval(uint16_t maxinterval) {
} // setMaxInterval
+/**
+ * @brief Set the advertised min connection interval preferred by this device.
+ * @param [in] mininterval the max interval value. Range = 0x0006 to 0x0C80.
+ * @details Values not within the range will cancel advertising of this data.\n
+ * Consumes 6 bytes of advertising space (combined with max interval).
+ */
+void NimBLEAdvertising::setMinPreferred(uint16_t mininterval) {
+ // invalid paramters, 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];
+ }
+} // setMinPreferred
+
+
+/**
+ * @brief Set the advertised max connection interval preferred by this device.
+ * @param [in] maxinterval the max interval value. Range = 0x0006 to 0x0C80.
+ * @details Values not within the range will cancel advertising of this data.\n
+ * Consumes 6 bytes of advertising space (combined with min interval).
+ */
+void NimBLEAdvertising::setMaxPreferred(uint16_t maxinterval) {
+ // invalid paramters, 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];
+ }
+} // setMaxPreferred
+
+
/**
* @brief Set if scan response is available.
* @param [in] set true = scan response available.
@@ -156,7 +218,8 @@ void NimBLEAdvertising::setScanResponse(bool set) {
* @param [in] connectWhitelistOnly If true, only allow connections from those on the white list.
*/
void NimBLEAdvertising::setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly) {
- NIMBLE_LOGD(LOG_TAG, ">> setScanFilter: scanRequestWhitelistOnly: %d, connectWhitelistOnly: %d", scanRequestWhitelistOnly, connectWhitelistOnly);
+ NIMBLE_LOGD(LOG_TAG, ">> setScanFilter: scanRequestWhitelistOnly: %d, connectWhitelistOnly: %d",
+ scanRequestWhitelistOnly, connectWhitelistOnly);
if (!scanRequestWhitelistOnly && !connectWhitelistOnly) {
m_advParams.filter_policy = BLE_HCI_ADV_FILT_NONE;
NIMBLE_LOGD(LOG_TAG, "<< setScanFilter");
@@ -194,7 +257,8 @@ void NimBLEAdvertising::setAdvertisementData(NimBLEAdvertisementData& advertisem
(uint8_t*)advertisementData.getPayload().data(),
advertisementData.getPayload().length());
if (rc != 0) {
- NIMBLE_LOGE(LOG_TAG, "ble_gap_adv_set_data: %d %s", rc, NimBLEUtils::returnCodeToString(rc));
+ NIMBLE_LOGE(LOG_TAG, "ble_gap_adv_set_data: %d %s",
+ rc, NimBLEUtils::returnCodeToString(rc));
}
m_customAdvData = true; // Set the flag that indicates we are using custom advertising data.
NIMBLE_LOGD(LOG_TAG, "<< setAdvertisementData");
@@ -213,7 +277,8 @@ void NimBLEAdvertising::setScanResponseData(NimBLEAdvertisementData& advertiseme
(uint8_t*)advertisementData.getPayload().data(),
advertisementData.getPayload().length());
if (rc != 0) {
- NIMBLE_LOGE(LOG_TAG, "ble_gap_adv_rsp_set_data: %d %s", rc, NimBLEUtils::returnCodeToString(rc));
+ NIMBLE_LOGE(LOG_TAG, "ble_gap_adv_rsp_set_data: %d %s",
+ rc, NimBLEUtils::returnCodeToString(rc));
}
m_customScanResponseData = true; // Set the flag that indicates we are using custom scan response data.
NIMBLE_LOGD(LOG_TAG, "<< setScanResponseData");
@@ -225,13 +290,14 @@ void NimBLEAdvertising::setScanResponseData(NimBLEAdvertisementData& advertiseme
* @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.
*/
-void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdvertising *pAdv)) {
- NIMBLE_LOGD(LOG_TAG, ">> Advertising start: customAdvData: %d, customScanResponseData: %d", m_customAdvData, m_customScanResponseData);
+bool NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdvertising *pAdv)) {
+ NIMBLE_LOGD(LOG_TAG, ">> Advertising start: customAdvData: %d, customScanResponseData: %d",
+ m_customAdvData, m_customScanResponseData);
// If Host is not synced we cannot start advertising.
if(!NimBLEDevice::m_synced) {
NIMBLE_LOGE(LOG_TAG, "Host reset, wait for sync.");
- return;
+ return false;
}
#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
@@ -240,17 +306,21 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv
if(!pServer->m_gattsStarted){
pServer->start();
} else if(pServer->getConnectedCount() >= NIMBLE_MAX_CONNECTIONS) {
- NIMBLE_LOGW(LOG_TAG, "Max connections reached - not advertising");
- return;
+ NIMBLE_LOGE(LOG_TAG, "Max connections reached - not advertising");
+ return false;
}
}
#endif
// If already advertising just return
if(ble_gap_adv_active()) {
- return;
+ NIMBLE_LOGW(LOG_TAG, "Advertising already active");
+ return false;
}
+ // Save the duration incase of host reset so we can restart with the same params
+ m_duration = duration;
+
if(duration == 0){
duration = BLE_HS_FOREVER;
}
@@ -260,16 +330,31 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv
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 = 3;
+ uint8_t payloadLen = (2 + 1);
+ if(m_advData.appearance_is_present)
+ payloadLen += (2 + BLE_HS_ADV_APPEARANCE_LEN);
+ if(m_advData.tx_pwr_lvl_is_present)
+ payloadLen += (2 + 1);
+ if(m_advData.slave_itvl_range != nullptr)
+ payloadLen += (2 + 4);
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) > 31){
+ if((payloadLen + add) > BLE_HS_ADV_MAX_SZ){
m_advData.uuids16_is_complete = 0;
continue;
}
@@ -278,7 +363,7 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv
if(nullptr == (m_advData.uuids16 = (ble_uuid16_t*)realloc(m_advData.uuids16,
(m_advData.num_uuids16 + 1) * sizeof(ble_uuid16_t))))
{
- NIMBLE_LOGE(LOG_TAG, "Error, no mem");
+ NIMBLE_LOGC(LOG_TAG, "Error, no mem");
abort();
}
memcpy(&m_advData.uuids16[m_advData.num_uuids16].value,
@@ -290,7 +375,7 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv
}
if(it.getNative()->u.type == BLE_UUID_TYPE_32) {
int add = (m_advData.num_uuids32 > 0) ? 4 : 6;
- if((payloadLen + add) > 31){
+ if((payloadLen + add) > BLE_HS_ADV_MAX_SZ){
m_advData.uuids32_is_complete = 0;
continue;
}
@@ -299,7 +384,7 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv
if(nullptr == (m_advData.uuids32 = (ble_uuid32_t*)realloc(m_advData.uuids32,
(m_advData.num_uuids32 + 1) * sizeof(ble_uuid32_t))))
{
- NIMBLE_LOGE(LOG_TAG, "Error, no mem");
+ NIMBLE_LOGC(LOG_TAG, "Error, no mem");
abort();
}
memcpy(&m_advData.uuids32[m_advData.num_uuids32].value,
@@ -311,7 +396,7 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv
}
if(it.getNative()->u.type == BLE_UUID_TYPE_128){
int add = (m_advData.num_uuids128 > 0) ? 16 : 18;
- if((payloadLen + add) > 31){
+ if((payloadLen + add) > BLE_HS_ADV_MAX_SZ){
m_advData.uuids128_is_complete = 0;
continue;
}
@@ -320,7 +405,7 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv
if(nullptr == (m_advData.uuids128 = (ble_uuid128_t*)realloc(m_advData.uuids128,
(m_advData.num_uuids128 + 1) * sizeof(ble_uuid128_t))))
{
- NIMBLE_LOGE(LOG_TAG, "Error, no mem");
+ NIMBLE_LOGC(LOG_TAG, "Error, no mem");
abort();
}
memcpy(&m_advData.uuids128[m_advData.num_uuids128].value,
@@ -333,54 +418,74 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv
}
// check if there is room for the name, if not put it in scan data
- if((payloadLen + m_advData.name_len) > 29) {
+ if((payloadLen + (2 + m_advData.name_len)) > BLE_HS_ADV_MAX_SZ) {
if(m_scanResp){
m_scanData.name = m_advData.name;
m_scanData.name_len = m_advData.name_len;
- m_scanData.name_is_complete = m_advData.name_is_complete;
+ 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 = 0;
+ 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.
- m_advData.name_len = (29 - payloadLen);
+ 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;
+ }
}
- m_advData.name_is_complete = 0;
- }
-
- if(m_advData.name_len > 0) {
- payloadLen += (m_advData.name_len + 2);
}
if(m_scanResp) {
- // name length + type byte + length byte + tx power type + length + data
- if((m_scanData.name_len + 5) > 31) {
- // prioritize name data over tx power
- m_scanData.tx_pwr_lvl_is_present = 0;
- m_scanData.tx_pwr_lvl = 0;
- // limit name to 29 to leave room for the data specifiers
- if(m_scanData.name_len > 29) {
- m_scanData.name_len = 29;
- m_scanData.name_is_complete = false;
- }
- }
-
rc = ble_gap_adv_rsp_set_fields(&m_scanData);
- if (rc != 0) {
- NIMBLE_LOGC(LOG_TAG, "error setting scan response data; rc=%d, %s", rc, NimBLEUtils::returnCodeToString(rc));
- abort();
+ switch(rc) {
+ case 0:
+ break;
+
+ case BLE_HS_EBUSY:
+ NIMBLE_LOGE(LOG_TAG, "Already advertising");
+ break;
+
+ case BLE_HS_EMSGSIZE:
+ NIMBLE_LOGE(LOG_TAG, "Scan data too long");
+ break;
+
+ default:
+ NIMBLE_LOGE(LOG_TAG, "Error setting scan response data; rc=%d, %s",
+ rc, NimBLEUtils::returnCodeToString(rc));
+ break;
}
- // if not using scan response and there is room,
- // put the tx power data into the advertisment
- } else if (payloadLen < 29) {
- m_advData.tx_pwr_lvl_is_present = 1;
- m_advData.tx_pwr_lvl = NimBLEDevice::getPower();
}
- rc = ble_gap_adv_set_fields(&m_advData);
- if (rc != 0) {
- NIMBLE_LOGC(LOG_TAG, "error setting advertisement data; rc=%d, %s", rc, NimBLEUtils::returnCodeToString(rc));
- abort();
+ if(rc == 0) {
+ rc = ble_gap_adv_set_fields(&m_advData);
+ switch(rc) {
+ case 0:
+ break;
+
+ case BLE_HS_EBUSY:
+ NIMBLE_LOGE(LOG_TAG, "Already advertising");
+ break;
+
+ case BLE_HS_EMSGSIZE:
+ NIMBLE_LOGE(LOG_TAG, "Advertisement data too long");
+ break;
+
+ default:
+ NIMBLE_LOGE(LOG_TAG, "Error setting advertisement data; rc=%d, %s",
+ rc, NimBLEUtils::returnCodeToString(rc));
+ break;
+ }
}
if(m_advData.num_uuids128 > 0) {
@@ -401,24 +506,54 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv
m_advData.num_uuids16 = 0;
}
+ if(rc !=0) {
+ return false;
+ }
+
m_advDataSet = true;
}
#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
- rc = ble_gap_adv_start(0, NULL, duration,
+ rc = ble_gap_adv_start(NimBLEDevice::m_own_addr_type, NULL, duration,
&m_advParams,
- (pServer != nullptr) ? NimBLEServer::handleGapEvent : NimBLEAdvertising::handleGapEvent,
+ (pServer != nullptr) ? NimBLEServer::handleGapEvent :
+ NimBLEAdvertising::handleGapEvent,
(pServer != nullptr) ? (void*)pServer : (void*)this);
#else
- rc = ble_gap_adv_start(0, NULL, duration,
+ rc = ble_gap_adv_start(NimBLEDevice::m_own_addr_type, NULL, duration,
&m_advParams, NimBLEAdvertising::handleGapEvent, this);
#endif
- if (rc != 0) {
- NIMBLE_LOGC(LOG_TAG, "Error enabling advertising; rc=%d, %s", rc, NimBLEUtils::returnCodeToString(rc));
- abort();
+ switch(rc) {
+ case 0:
+ break;
+
+ case BLE_HS_EINVAL:
+ NIMBLE_LOGE(LOG_TAG, "Unable to advertise - Duration too long");
+ break;
+
+ case BLE_HS_EPREEMPTED:
+ NIMBLE_LOGE(LOG_TAG, "Unable to advertise - busy");
+ break;
+
+ case BLE_HS_ETIMEOUT_HCI:
+ case BLE_HS_EOS:
+ case BLE_HS_ECONTROLLER:
+ case BLE_HS_ENOTSYNCED:
+ NIMBLE_LOGE(LOG_TAG, "Unable to advertise - Host Reset");
+ break;
+
+ default:
+ NIMBLE_LOGE(LOG_TAG, "Error enabling advertising; rc=%d, %s",
+ rc, NimBLEUtils::returnCodeToString(rc));
+ break;
+ }
+
+ if(rc != 0) {
+ return false;
}
NIMBLE_LOGD(LOG_TAG, "<< Advertising start");
+ return true;
} // start
@@ -427,9 +562,11 @@ void NimBLEAdvertising::start(uint32_t duration, void (*advCompleteCB)(NimBLEAdv
*/
void NimBLEAdvertising::stop() {
NIMBLE_LOGD(LOG_TAG, ">> stop");
+
int rc = ble_gap_adv_stop();
if (rc != 0 && rc != BLE_HS_EALREADY) {
- NIMBLE_LOGE(LOG_TAG, "ble_gap_adv_stop rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
+ NIMBLE_LOGE(LOG_TAG, "ble_gap_adv_stop rc=%d %s",
+ rc, NimBLEUtils::returnCodeToString(rc));
return;
}
@@ -460,8 +597,17 @@ bool NimBLEAdvertising::isAdvertising() {
* Host reset seems to clear advertising data,
* we need clear the flag so it reloads it.
*/
-void NimBLEAdvertising::onHostReset() {
+void NimBLEAdvertising::onHostSync() {
+ NIMBLE_LOGD(LOG_TAG, "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();
+ }
}
@@ -475,6 +621,19 @@ int NimBLEAdvertising::handleGapEvent(struct ble_gap_event *event, void *arg) {
NimBLEAdvertising *pAdv = (NimBLEAdvertising*)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:
+ NIMBLE_LOGC(LOG_TAG, "host reset, rc=%d", event->adv_complete.reason);
+ NimBLEDevice::onReset(event->adv_complete.reason);
+ return 0;
+ default:
+ break;
+ }
pAdv->advCompleteCB();
}
return 0;
diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEAdvertising.h b/lib/libesp32/NimBLE-Arduino/src/NimBLEAdvertising.h
index 2fab71004..0d97ecbbc 100644
--- a/lib/libesp32/NimBLE-Arduino/src/NimBLEAdvertising.h
+++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEAdvertising.h
@@ -77,7 +77,7 @@ public:
void addServiceUUID(const NimBLEUUID &serviceUUID);
void addServiceUUID(const char* serviceUUID);
void removeServiceUUID(const NimBLEUUID &serviceUUID);
- void start(uint32_t duration = 0, void (*advCompleteCB)(NimBLEAdvertising *pAdv) = nullptr);
+ bool start(uint32_t duration = 0, void (*advCompleteCB)(NimBLEAdvertising *pAdv) = nullptr);
void stop();
void setAppearance(uint16_t appearance);
void setAdvertisementType(uint8_t adv_type);
@@ -87,13 +87,15 @@ public:
void setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly);
void setScanResponseData(NimBLEAdvertisementData& advertisementData);
void setScanResponse(bool);
+ void setMinPreferred(uint16_t);
+ void setMaxPreferred(uint16_t);
void advCompleteCB();
bool isAdvertising();
private:
friend class NimBLEDevice;
- void onHostReset();
+ void onHostSync();
static int handleGapEvent(struct ble_gap_event *event, void *arg);
ble_hs_adv_fields m_advData;
@@ -104,8 +106,9 @@ private:
bool m_customScanResponseData;
bool m_scanResp;
bool m_advDataSet;
- void (*m_advCompCB)(NimBLEAdvertising *pAdv);
-
+ void (*m_advCompCB)(NimBLEAdvertising *pAdv);
+ uint8_t m_slaveItvl[4];
+ uint32_t m_duration;
};
#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLECharacteristic.cpp b/lib/libesp32/NimBLE-Arduino/src/NimBLECharacteristic.cpp
index 6f7d3d7e0..e9a5a49c9 100644
--- a/lib/libesp32/NimBLE-Arduino/src/NimBLECharacteristic.cpp
+++ b/lib/libesp32/NimBLE-Arduino/src/NimBLECharacteristic.cpp
@@ -473,9 +473,10 @@ void NimBLECharacteristic::setValue(const uint8_t* data, size_t length) {
return;
}
+ time_t t = time(nullptr);
portENTER_CRITICAL(&m_valMux);
m_value = std::string((char*)data, length);
- m_timestamp = time(nullptr);
+ m_timestamp = t;
portEXIT_CRITICAL(&m_valMux);
NIMBLE_LOGD(LOG_TAG, "<< setValue");
diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEClient.cpp b/lib/libesp32/NimBLE-Arduino/src/NimBLEClient.cpp
index 539a7a016..ddd3abecc 100644
--- a/lib/libesp32/NimBLE-Arduino/src/NimBLEClient.cpp
+++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEClient.cpp
@@ -24,6 +24,9 @@
#include
#include
+#include "nimble/nimble_port.h"
+
+
static const char* LOG_TAG = "NimBLEClient";
static NimBLEClientCallbacks defaultCallbacks;
@@ -56,11 +59,10 @@ static NimBLEClientCallbacks defaultCallbacks;
NimBLEClient::NimBLEClient(const NimBLEAddress &peerAddress) : m_peerAddress(peerAddress) {
m_pClientCallbacks = &defaultCallbacks;
m_conn_id = BLE_HS_CONN_HANDLE_NONE;
- m_isConnected = false;
- m_waitingToConnect = false;
m_connectTimeout = 30000;
m_deleteCallbacks = false;
m_pTaskData = nullptr;
+ m_connEstablished = false;
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)
@@ -70,6 +72,9 @@ NimBLEClient::NimBLEClient(const NimBLEAddress &peerAddress) : m_peerAddress(pee
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
+
+ ble_npl_callout_init(&m_dcTimer, nimble_port_get_dflt_eventq(),
+ NimBLEClient::dcTimerCb, this);
} // NimBLEClient
@@ -89,6 +94,19 @@ NimBLEClient::~NimBLEClient() {
} // ~NimBLEClient
+/**
+ * @brief If we have asked to disconnect and the event does not
+ * occur within the supervision timeout + added delay, this will
+ * be called to reset the host in the case of a stalled controller.
+ */
+void NimBLEClient::dcTimerCb(ble_npl_event *event) {
+ NimBLEClient *pClient = (NimBLEClient*)event->arg;
+ NIMBLE_LOGC(LOG_TAG, "Timed out disconnecting from %s - resetting host",
+ std::string(pClient->getPeerAddress()).c_str());
+ ble_hs_sched_reset(BLE_HS_ECONTROLLER);
+}
+
+
/**
* @brief Delete all service objects created by this client and clear the vector.
*/
@@ -164,12 +182,9 @@ bool NimBLEClient::connect(const NimBLEAddress &address, bool deleteAttibutes) {
return false;
}
- if(ble_gap_conn_active()) {
- NIMBLE_LOGE(LOG_TAG, "Connection in progress - must wait.");
- return false;
- }
-
- if(!NimBLEDevice::getScan()->stop()) {
+ if(isConnected() || m_pTaskData != nullptr) {
+ NIMBLE_LOGE(LOG_TAG, "Client busy, connected to %s, id=%d",
+ std::string(m_peerAddress).c_str(), getConnId());
return false;
}
@@ -180,54 +195,97 @@ bool NimBLEClient::connect(const NimBLEAddress &address, bool deleteAttibutes) {
m_peerAddress = address;
}
- ble_addr_t peerAddrt;
- memcpy(&peerAddrt.val, m_peerAddress.getNative(),6);
- peerAddrt.type = m_peerAddress.getType();
+ ble_addr_t peerAddr_t;
+ memcpy(&peerAddr_t.val, m_peerAddress.getNative(),6);
+ peerAddr_t.type = m_peerAddress.getType();
+
ble_task_data_t taskData = {this, xTaskGetCurrentTaskHandle(), 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(BLE_OWN_ADDR_PUBLIC, &peerAddrt, m_connectTimeout, &m_pConnParams,
- NimBLEClient::handleGapEvent, this);
- if(rc == BLE_HS_EBUSY) {
- vTaskDelay(1 / portTICK_PERIOD_MS);
+ do {
+ rc = ble_gap_connect(NimBLEDevice::m_own_addr_type, &peerAddr_t,
+ m_connectTimeout, &m_pConnParams,
+ NimBLEClient::handleGapEvent, this);
+ switch (rc) {
+ case 0:
+ m_pTaskData = &taskData;
+ break;
+
+ case BLE_HS_EBUSY:
+ // Scan was still running, stop it and try again
+ if (!NimBLEDevice::getScan()->stop()) {
+ return false;
+ }
+ break;
+
+ case BLE_HS_EDONE:
+ // A connection to this device already exists, do not connect twice.
+ NIMBLE_LOGE(LOG_TAG, "Already connected to device; addr=%s",
+ std::string(m_peerAddress).c_str());
+ return false;
+
+ case BLE_HS_EALREADY:
+ // Already attemting to connect to this device, cancel the previous
+ // attempt and report failure here so we don't get 2 connections.
+ NIMBLE_LOGE(LOG_TAG, "Already attempting to connect to %s - cancelling",
+ std::string(m_peerAddress).c_str());
+ ble_gap_conn_cancel();
+ return false;
+
+ default:
+ NIMBLE_LOGE(LOG_TAG, "Failed to connect to %s, rc=%d; %s",
+ std::string(m_peerAddress).c_str(),
+ rc, NimBLEUtils::returnCodeToString(rc));
+ return false;
}
- }while(rc == BLE_HS_EBUSY);
- if (rc != 0 && rc != BLE_HS_EDONE) {
- NIMBLE_LOGE(LOG_TAG, "Error: Failed to connect to device; "
- "addr=%s, rc=%d; %s",
- std::string(m_peerAddress).c_str(),
- rc, NimBLEUtils::returnCodeToString(rc));
+ } while (rc == BLE_HS_EBUSY);
+
+ // 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;
- m_waitingToConnect = false;
+ // If a connection was made but no response from MTU exchange; disconnect
+ if(isConnected()) {
+ NIMBLE_LOGE(LOG_TAG, "Connect timeout - no response");
+ disconnect();
+ } else {
+ // workaround; if the controller doesn't cancel the connection
+ // at the timeout cancel it here.
+ NIMBLE_LOGE(LOG_TAG, "Connect timeout - cancelling");
+ ble_gap_conn_cancel();
+ }
+
return false;
- }
- m_waitingToConnect = true;
-
- // Wait for the connection to complete.
- ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
-
- if(taskData.rc != 0){
+ } else if(taskData.rc != 0){
+ NIMBLE_LOGE(LOG_TAG, "Connection failed; status=%d %s",
+ taskData.rc,
+ NimBLEUtils::returnCodeToString(taskData.rc));
+ // If the failure was not a result of a disconnection
+ // make sure we disconnect now to avoid dangling connections
+ if(isConnected()) {
+ ble_gap_terminate(m_conn_id, BLE_ERR_REM_USER_CONN_TERM);
+ }
return false;
+ } else {
+ NIMBLE_LOGI(LOG_TAG, "Connection established");
}
if(deleteAttibutes) {
deleteServices();
}
+ m_connEstablished = true;
m_pClientCallbacks->onConnect(this);
NIMBLE_LOGD(LOG_TAG, "<< connect()");
- return true;
+ // Check if still connected before returning
+ return isConnected();
} // connect
@@ -267,13 +325,31 @@ bool NimBLEClient::secureConnection() {
*/
int NimBLEClient::disconnect(uint8_t reason) {
NIMBLE_LOGD(LOG_TAG, ">> disconnect()");
+
int rc = 0;
- if(m_isConnected){
+ if(isConnected()){
rc = ble_gap_terminate(m_conn_id, reason);
- if(rc != 0){
- NIMBLE_LOGE(LOG_TAG, "ble_gap_terminate failed: rc=%d %s", rc,
- NimBLEUtils::returnCodeToString(rc));
+ if (rc == 0) {
+ ble_addr_t peerAddr_t;
+ memcpy(&peerAddr_t.val, m_peerAddress.getNative(),6);
+ peerAddr_t.type = m_peerAddress.getType();
+
+ // Set the disconnect timeout to the supervison timeout time + 1 second
+ // In case the event triggers shortly after the supervision timeout.
+ // We don't want to prematurely reset the host.
+ ble_gap_conn_desc desc;
+ if(ble_gap_conn_find_by_addr(&peerAddr_t, &desc) == 0){
+ ble_npl_time_t ticks;
+ ble_npl_time_ms_to_ticks((desc.supervision_timeout + 100) * 10, &ticks);
+ ble_npl_callout_reset(&m_dcTimer, ticks);
+ NIMBLE_LOGD(LOG_TAG, "DC TIMEOUT = %dms", (desc.supervision_timeout + 100) * 10);
+ }
+ } else if (rc != BLE_HS_EALREADY) {
+ NIMBLE_LOGE(LOG_TAG, "ble_gap_terminate failed: rc=%d %s",
+ rc, NimBLEUtils::returnCodeToString(rc));
}
+ } else {
+ NIMBLE_LOGD(LOG_TAG, "Not connected to any peers");
}
NIMBLE_LOGD(LOG_TAG, "<< disconnect()");
@@ -454,6 +530,16 @@ NimBLERemoteService* NimBLEClient::getService(const NimBLEUUID &uuid) {
if(m_servicesVector.size() > prev_size) {
return m_servicesVector.back();
}
+
+ // If the request was successful but 16/32 bit service not found
+ // try again with the 128 bit uuid.
+ if(uuid.bitSize() == BLE_UUID_TYPE_16 ||
+ uuid.bitSize() == BLE_UUID_TYPE_32)
+ {
+ NimBLEUUID uuid128(uuid);
+ uuid128.to128();
+ return getService(uuid128);
+ }
}
NIMBLE_LOGD(LOG_TAG, "<< getService: not found");
@@ -510,7 +596,7 @@ bool NimBLEClient::retrieveServices(const NimBLEUUID *uuid_filter) {
NIMBLE_LOGD(LOG_TAG, ">> retrieveServices");
- if(!m_isConnected){
+ if(!isConnected()){
NIMBLE_LOGE(LOG_TAG, "Disconnected, could not retrieve services -aborting");
return false;
}
@@ -618,10 +704,11 @@ std::string NimBLEClient::getValue(const NimBLEUUID &serviceUUID, const NimBLEUU
* @param [in] serviceUUID The service that owns the characteristic.
* @param [in] characteristicUUID The characteristic whose value we wish to write.
* @param [in] value The value to write to the characteristic.
+ * @param [in] response If true, uses write with response operation.
* @returns true if successful otherwise false
*/
bool NimBLEClient::setValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &characteristicUUID,
- const std::string &value)
+ const std::string &value, bool response)
{
NIMBLE_LOGD(LOG_TAG, ">> setValue: serviceUUID: %s, characteristicUUID: %s",
serviceUUID.toString().c_str(), characteristicUUID.toString().c_str());
@@ -632,7 +719,7 @@ bool NimBLEClient::setValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &cha
if(pService != nullptr) {
NimBLERemoteCharacteristic* pChar = pService->getCharacteristic(characteristicUUID);
if(pChar != nullptr) {
- ret = pChar->writeValue(value);
+ ret = pChar->writeValue(value, response);
}
}
@@ -641,6 +728,31 @@ bool NimBLEClient::setValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &cha
} // setValue
+/**
+ * @brief Get the remote characteristic with the specified handle.
+ * @param [in] handle The handle of the desired characteristic.
+ * @returns The matching remote characteristic, nullptr otherwise.
+ */
+NimBLERemoteCharacteristic* NimBLEClient::getCharacteristic(const uint16_t handle)
+{
+ NimBLERemoteService *pService = nullptr;
+ for(auto it = m_servicesVector.begin(); it != m_servicesVector.end(); ++it) {
+ if ((*it)->getStartHandle() <= handle && handle <= (*it)->getEndHandle()) {
+ pService = *it;
+ break;
+ }
+ }
+
+ if (pService != nullptr) {
+ for (auto it = pService->begin(); it != pService->end(); ++it) {
+ if ((*it)->getHandle() == handle) {
+ return *it;
+ }
+ }
+ }
+
+ return nullptr;
+}
/**
* @brief Get the current mtu of this connection.
@@ -656,7 +768,8 @@ uint16_t NimBLEClient::getMTU() {
* @param [in] event The event structure sent by the NimBLE stack.
* @param [in] arg A pointer to the client instance that registered for this callback.
*/
- /*STATIC*/ int NimBLEClient::handleGapEvent(struct ble_gap_event *event, void *arg) {
+ /*STATIC*/
+ int NimBLEClient::handleGapEvent(struct ble_gap_event *event, void *arg) {
NimBLEClient* client = (NimBLEClient*)arg;
int rc;
@@ -665,61 +778,66 @@ uint16_t NimBLEClient::getMTU() {
switch(event->type) {
case BLE_GAP_EVENT_DISCONNECT: {
- if(!client->m_isConnected)
- return 0;
-
- if(client->m_conn_id != event->disconnect.conn.conn_handle)
- return 0;
-
- client->m_isConnected = false;
- client->m_waitingToConnect=false;
- // Remove the device from ignore list so we will scan it again
- NimBLEDevice::removeIgnored(client->m_peerAddress);
-
- NIMBLE_LOGI(LOG_TAG, "disconnect; reason=%d, %s", event->disconnect.reason,
- NimBLEUtils::returnCodeToString(event->disconnect.reason));
-
+ 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(event->disconnect.reason) {
- case BLE_HS_ETIMEOUT_HCI:
- case BLE_HS_EOS:
+ switch(rc) {
case BLE_HS_ECONTROLLER:
+ case BLE_HS_ETIMEOUT_HCI:
case BLE_HS_ENOTSYNCED:
- NIMBLE_LOGC(LOG_TAG, "Disconnect - host reset, rc=%d", event->disconnect.reason);
- NimBLEDevice::onReset(event->disconnect.reason);
+ case BLE_HS_EOS:
+ NIMBLE_LOGC(LOG_TAG, "Disconnect - host reset, rc=%d", rc);
+ NimBLEDevice::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;
}
- //client->m_conn_id = BLE_HS_CONN_HANDLE_NONE;
+ client->m_conn_id = BLE_HS_CONN_HANDLE_NONE;
+
+ // 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
+ NimBLEDevice::removeIgnored(client->m_peerAddress);
+
+ // 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_connEstablished)
+ break;
+
+ NIMBLE_LOGI(LOG_TAG, "disconnect; reason=%d, %s",
+ rc, NimBLEUtils::returnCodeToString(rc));
+
+ client->m_connEstablished = false;
client->m_pClientCallbacks->onDisconnect(client);
- rc = event->disconnect.reason;
break;
} // BLE_GAP_EVENT_DISCONNECT
case BLE_GAP_EVENT_CONNECT: {
-
- if(!client->m_waitingToConnect)
+ // 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;
+ }
- //if(client->m_conn_id != BLE_HS_CONN_HANDLE_NONE)
- // return 0;
-
- client->m_waitingToConnect=false;
-
- if (event->connect.status == 0) {
- client->m_isConnected = true;
-
- NIMBLE_LOGD(LOG_TAG, "Connection established");
+ rc = event->connect.status;
+ if (rc == 0) {
+ NIMBLE_LOGI(LOG_TAG, "Connected event");
client->m_conn_id = event->connect.conn_handle;
rc = ble_gattc_exchange_mtu(client->m_conn_id, NULL,NULL);
if(rc != 0) {
- NIMBLE_LOGE(LOG_TAG, "ble_gattc_exchange_mtu: rc=%d %s",rc,
- NimBLEUtils::returnCodeToString(rc));
+ NIMBLE_LOGE(LOG_TAG, "MTU exchange error; rc=%d %s",
+ rc, NimBLEUtils::returnCodeToString(rc));
break;
}
@@ -727,14 +845,10 @@ uint16_t NimBLEClient::getMTU() {
// scanning since we are already connected to it
NimBLEDevice::addIgnored(client->m_peerAddress);
} else {
- NIMBLE_LOGE(LOG_TAG, "Error: Connection failed; status=%d %s",
- event->connect.status,
- NimBLEUtils::returnCodeToString(event->connect.status));
-
- client->m_isConnected = false;
- rc = event->connect.status;
+ client->m_conn_id = BLE_HS_CONN_HANDLE_NONE;
break;
}
+
return 0;
} // BLE_GAP_EVENT_CONNECT
@@ -742,7 +856,14 @@ uint16_t NimBLEClient::getMTU() {
if(client->m_conn_id != event->notify_rx.conn_handle)
return 0;
- NIMBLE_LOGD(LOG_TAG, "Notify Recieved for handle: %d",event->notify_rx.attr_handle);
+ // If a notification comes before this flag is set we might
+ // access a vector while it is being cleared in connect()
+ if(!client->m_connEstablished) {
+ return 0;
+ }
+
+ NIMBLE_LOGD(LOG_TAG, "Notify Recieved for handle: %d",
+ event->notify_rx.attr_handle);
for(auto &it: client->m_servicesVector) {
// Dont waste cycles searching services without this handle in its range
@@ -752,8 +873,8 @@ uint16_t NimBLEClient::getMTU() {
auto cVector = &it->m_characteristicVector;
NIMBLE_LOGD(LOG_TAG, "checking service %s for handle: %d",
- it->getUUID().toString().c_str(),
- event->notify_rx.attr_handle);
+ it->getUUID().toString().c_str(),
+ event->notify_rx.attr_handle);
auto characteristic = cVector->cbegin();
for(; characteristic != cVector->cend(); ++characteristic) {
@@ -762,16 +883,19 @@ uint16_t NimBLEClient::getMTU() {
}
if(characteristic != cVector->cend()) {
- NIMBLE_LOGD(LOG_TAG, "Got Notification for characteristic %s", (*characteristic)->toString().c_str());
+ NIMBLE_LOGD(LOG_TAG, "Got Notification for characteristic %s",
+ (*characteristic)->toString().c_str());
+ time_t t = time(nullptr);
portENTER_CRITICAL(&(*characteristic)->m_valMux);
- (*characteristic)->m_value = std::string((char *)event->notify_rx.om->om_data, event->notify_rx.om->om_len);
- (*characteristic)->m_timestamp = time(nullptr);
+ (*characteristic)->m_value = std::string((char *)event->notify_rx.om->om_data,
+ event->notify_rx.om->om_len);
+ (*characteristic)->m_timestamp = t;
portEXIT_CRITICAL(&(*characteristic)->m_valMux);
if ((*characteristic)->m_notifyCallback != nullptr) {
NIMBLE_LOGD(LOG_TAG, "Invoking callback for notification on characteristic %s",
- (*characteristic)->toString().c_str());
+ (*characteristic)->toString().c_str());
(*characteristic)->m_notifyCallback(*characteristic, event->notify_rx.om->om_data,
event->notify_rx.om->om_len,
!event->notify_rx.indication);
@@ -790,10 +914,10 @@ uint16_t NimBLEClient::getMTU() {
}
NIMBLE_LOGD(LOG_TAG, "Peer requesting to update connection parameters");
NIMBLE_LOGD(LOG_TAG, "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);
+ 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;
@@ -827,7 +951,9 @@ uint16_t NimBLEClient::getMTU() {
return 0;
}
- if(event->enc_change.status == 0 || event->enc_change.status == (BLE_HS_ERR_HCI_BASE + BLE_ERR_PINKEY_MISSING)) {
+ 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);
@@ -922,7 +1048,9 @@ uint16_t NimBLEClient::getMTU() {
if(client->m_pTaskData != nullptr) {
client->m_pTaskData->rc = rc;
- xTaskNotifyGive(client->m_pTaskData->task);
+ if(client->m_pTaskData->task) {
+ xTaskNotifyGive(client->m_pTaskData->task);
+ }
client->m_pTaskData = nullptr;
}
@@ -935,7 +1063,7 @@ uint16_t NimBLEClient::getMTU() {
* @return True if we are connected and false if we are not connected.
*/
bool NimBLEClient::isConnected() {
- return m_isConnected;
+ return m_conn_id != BLE_HS_CONN_HANDLE_NONE;
} // isConnected
diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEClient.h b/lib/libesp32/NimBLE-Arduino/src/NimBLEClient.h
index ddeef3cc3..a4b847000 100644
--- a/lib/libesp32/NimBLE-Arduino/src/NimBLEClient.h
+++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEClient.h
@@ -30,6 +30,7 @@
#include
class NimBLERemoteService;
+class NimBLERemoteCharacteristic;
class NimBLEClientCallbacks;
class NimBLEAdvertisedDevice;
@@ -54,7 +55,8 @@ public:
size_t deleteService(const NimBLEUUID &uuid);
std::string getValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &characteristicUUID);
bool setValue(const NimBLEUUID &serviceUUID, const NimBLEUUID &characteristicUUID,
- const std::string &value);
+ const std::string &value, bool response = false);
+ NimBLERemoteCharacteristic* getCharacteristic(const uint16_t handle);
bool isConnected();
void setClientCallbacks(NimBLEClientCallbacks *pClientCallbacks,
bool deleteCallbacks = true);
@@ -82,16 +84,17 @@ private:
const struct ble_gatt_error *error,
const struct ble_gatt_svc *service,
void *arg);
+ static void dcTimerCb(ble_npl_event *event);
bool retrieveServices(const NimBLEUUID *uuid_filter = nullptr);
NimBLEAddress m_peerAddress;
uint16_t m_conn_id;
- bool m_isConnected;
- bool m_waitingToConnect;
+ bool m_connEstablished;
bool m_deleteCallbacks;
int32_t m_connectTimeout;
NimBLEClientCallbacks* m_pClientCallbacks;
- ble_task_data_t *m_pTaskData;
+ ble_task_data_t* m_pTaskData;
+ ble_npl_callout m_dcTimer;
std::vector m_servicesVector;
diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEDevice.cpp b/lib/libesp32/NimBLE-Arduino/src/NimBLEDevice.cpp
index fb36e6e57..f34a522f7 100644
--- a/lib/libesp32/NimBLE-Arduino/src/NimBLEDevice.cpp
+++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEDevice.cpp
@@ -25,6 +25,7 @@
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
+#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"
@@ -60,6 +61,7 @@ std::list NimBLEDevice::m_cList;
#endif
std::list NimBLEDevice::m_ignoreList;
NimBLESecurityCallbacks* NimBLEDevice::m_securityCallbacks = nullptr;
+uint8_t NimBLEDevice::m_own_addr_type = BLE_OWN_ADDR_PUBLIC;
/**
@@ -144,8 +146,8 @@ void NimBLEDevice::stopAdvertising() {
#if defined(CONFIG_BT_NIMBLE_ROLE_CENTRAL)
/* STATIC */ NimBLEClient* NimBLEDevice::createClient(NimBLEAddress peerAddress) {
if(m_cList.size() >= NIMBLE_MAX_CONNECTIONS) {
- NIMBLE_LOGW("Number of clients exceeds Max connections. Max=(%d)",
- NIMBLE_MAX_CONNECTIONS);
+ NIMBLE_LOGW(LOG_TAG,"Number of clients exceeds Max connections. Cur=%d Max=%d",
+ m_cList.size(), NIMBLE_MAX_CONNECTIONS);
}
NimBLEClient* pClient = new NimBLEClient(peerAddress);
@@ -165,26 +167,31 @@ void NimBLEDevice::stopAdvertising() {
return false;
}
+ // Set the connection established flag to false to stop notifications
+ // from accessing the attribute vectors while they are being deleted.
+ pClient->m_connEstablished = false;
int rc =0;
- if(pClient->m_isConnected) {
+ if(pClient->isConnected()) {
rc = pClient->disconnect();
if (rc != 0 && rc != BLE_HS_EALREADY && rc != BLE_HS_ENOTCONN) {
return false;
}
- while(pClient->m_isConnected) {
- vTaskDelay(10);
+ while(pClient->isConnected()) {
+ taskYIELD();
}
- }
+ // Since we set the flag to false the app will not get a callback
+ // in the disconnect event so we call it here for good measure.
+ pClient->m_pClientCallbacks->onDisconnect(pClient);
- if(pClient->m_waitingToConnect) {
+ } else if(pClient->m_pTaskData != nullptr) {
rc = ble_gap_conn_cancel();
if (rc != 0 && rc != BLE_HS_EALREADY) {
return false;
}
- while(pClient->m_waitingToConnect) {
- vTaskDelay(10);
+ while(pClient->m_pTaskData != nullptr) {
+ taskYIELD();
}
}
@@ -405,30 +412,16 @@ void NimBLEDevice::stopAdvertising() {
m_synced = false;
-#if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
- if(m_pScan != nullptr) {
- m_pScan->onHostReset();
- }
-#endif
-
-/* Not needed
- if(m_pServer != nullptr) {
- m_pServer->onHostReset();
- }
-
- for(auto it = m_cList.cbegin(); it != m_cList.cend(); ++it) {
- (*it)->onHostReset();
- }
-*/
-
-#if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER)
- if(m_bleAdvertising != nullptr) {
- m_bleAdvertising->onHostReset();
- }
-#endif
-
NIMBLE_LOGC(LOG_TAG, "Resetting state; reason=%d, %s", reason,
NimBLEUtils::returnCodeToString(reason));
+
+#if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
+ if(initialized) {
+ if(m_pScan != nullptr) {
+ m_pScan->onHostReset();
+ }
+ }
+#endif
} // onReset
@@ -448,20 +441,22 @@ void NimBLEDevice::stopAdvertising() {
int rc = ble_hs_util_ensure_addr(0);
assert(rc == 0);
+ // Yield for houskeeping before returning to operations.
+ // Occasionally triggers exception without.
+ taskYIELD();
+
m_synced = true;
if(initialized) {
#if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
if(m_pScan != nullptr) {
- // Restart scanning with the last values sent, allow to clear results.
- m_pScan->start(m_pScan->m_duration, m_pScan->m_scanCompleteCB);
+ m_pScan->onHostSync();
}
#endif
#if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER)
if(m_bleAdvertising != nullptr) {
- // Restart advertisng, parameters should already be set.
- m_bleAdvertising->start();
+ m_bleAdvertising->onHostSync();
}
#endif
}
@@ -705,6 +700,35 @@ void NimBLEDevice::setSecurityCallbacks(NimBLESecurityCallbacks* callbacks) {
} // setSecurityCallbacks
+/**
+ * @brief Set the own address type.
+ * @param [in] own_addr_type Own Bluetooth Device address type.\n
+ * The available bits are defined as:
+ * * 0x00: BLE_OWN_ADDR_PUBLIC
+ * * 0x01: BLE_OWN_ADDR_RANDOM
+ * * 0x02: BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT
+ * * 0x03: BLE_OWN_ADDR_RPA_RANDOM_DEFAULT
+ * @param [in] useNRPA If true, and address type is random, uses a non-resolvable random address.
+ */
+void NimBLEDevice::setOwnAddrType(uint8_t own_addr_type, bool useNRPA) {
+ m_own_addr_type = own_addr_type;
+ switch (own_addr_type) {
+ case BLE_OWN_ADDR_PUBLIC:
+ ble_hs_pvcy_rpa_config(NIMBLE_HOST_DISABLE_PRIVACY);
+ break;
+ case BLE_OWN_ADDR_RANDOM:
+ setSecurityInitKey(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID);
+ ble_hs_pvcy_rpa_config(useNRPA ? NIMBLE_HOST_ENABLE_NRPA : NIMBLE_HOST_ENABLE_RPA);
+ break;
+ case BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT:
+ case BLE_OWN_ADDR_RPA_RANDOM_DEFAULT:
+ setSecurityInitKey(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID);
+ ble_hs_pvcy_rpa_config(NIMBLE_HOST_ENABLE_RPA);
+ break;
+ }
+} // setOwnAddrType
+
+
/**
* @brief Start the connection securing and authorization for this connection.
* @param conn_id The connection id of the peer device.
diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEDevice.h b/lib/libesp32/NimBLE-Arduino/src/NimBLEDevice.h
index 252c52afd..2d586bb42 100644
--- a/lib/libesp32/NimBLE-Arduino/src/NimBLEDevice.h
+++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEDevice.h
@@ -116,6 +116,7 @@ public:
static void setSecurityPasskey(uint32_t pin);
static uint32_t getSecurityPasskey();
static void setSecurityCallbacks(NimBLESecurityCallbacks* pCallbacks);
+ static void setOwnAddrType(uint8_t own_addr_type, bool useNRPA=false);
static int startSecurity(uint16_t conn_id);
static int setMTU(uint16_t mtu);
static uint16_t getMTU();
@@ -182,6 +183,7 @@ private:
static uint32_t m_passkey;
static ble_gap_event_listener m_listener;
static gap_event_handler m_customGapHandler;
+ static uint8_t m_own_addr_type;
};
diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEHIDDevice.cpp b/lib/libesp32/NimBLE-Arduino/src/NimBLEHIDDevice.cpp
new file mode 100644
index 000000000..39f07dede
--- /dev/null
+++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEHIDDevice.cpp
@@ -0,0 +1,233 @@
+/*
+ * NimBLEHIDDevice.cpp
+ *
+ * Created: on Oct 06 2020
+ * Author wakwak-koba
+ *
+ * Originally:
+ *
+ * BLEHIDDevice.cpp
+ *
+ * Created on: Jan 03, 2018
+ * Author: chegewara
+ */
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+
+#include "nimconfig.h"
+#if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
+
+#include "NimBLEHIDDevice.h"
+#include "NimBLE2904.h"
+
+NimBLEHIDDevice::NimBLEHIDDevice(NimBLEServer* server) {
+ /*
+ * Here we create mandatory services described in bluetooth specification
+ */
+ m_deviceInfoService = server->createService(NimBLEUUID((uint16_t) 0x180a));
+ m_hidService = server->createService(NimBLEUUID((uint16_t) 0x1812), 40);
+ m_batteryService = server->createService(NimBLEUUID((uint16_t) 0x180f));
+
+ /*
+ * Mandatory characteristic for device info service
+ */
+ m_pnpCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t) 0x2a50, NIMBLE_PROPERTY::READ);
+
+ /*
+ * Mandatory characteristics for HID service
+ */
+ m_hidInfoCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4a, NIMBLE_PROPERTY::READ);
+ m_reportMapCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4b, NIMBLE_PROPERTY::READ);
+ m_hidControlCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4c, NIMBLE_PROPERTY::WRITE_NR);
+ m_protocolModeCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4e, NIMBLE_PROPERTY::WRITE_NR | NIMBLE_PROPERTY::READ);
+
+ /*
+ * Mandatory battery level characteristic with notification and presence descriptor
+ */
+ m_batteryLevelCharacteristic = m_batteryService->createCharacteristic((uint16_t) 0x2a19, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY);
+ NimBLE2904* batteryLevelDescriptor = (NimBLE2904*)m_batteryLevelCharacteristic->createDescriptor((uint16_t) 0x2904);
+ batteryLevelDescriptor->setFormat(NimBLE2904::FORMAT_UINT8);
+ batteryLevelDescriptor->setNamespace(1);
+ batteryLevelDescriptor->setUnit(0x27ad);
+
+ /*
+ * This value is setup here because its default value in most usage cases, its very rare to use boot mode
+ * and we want to simplify library using as much as possible
+ */
+ const uint8_t pMode[] = { 0x01 };
+ protocolMode()->setValue((uint8_t*) pMode, 1);
+}
+
+NimBLEHIDDevice::~NimBLEHIDDevice() {
+}
+
+/*
+ * @brief
+ */
+void NimBLEHIDDevice::reportMap(uint8_t* map, uint16_t size) {
+ m_reportMapCharacteristic->setValue(map, size);
+}
+
+/*
+ * @brief This function suppose to be called at the end, when we have created all characteristics we need to build HID service
+ */
+void NimBLEHIDDevice::startServices() {
+ m_deviceInfoService->start();
+ m_hidService->start();
+ m_batteryService->start();
+}
+
+/*
+ * @brief Create manufacturer characteristic (this characteristic is optional)
+ */
+NimBLECharacteristic* NimBLEHIDDevice::manufacturer() {
+ m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t) 0x2a29, NIMBLE_PROPERTY::READ);
+ return m_manufacturerCharacteristic;
+}
+
+/*
+ * @brief Set manufacturer name
+ * @param [in] name manufacturer name
+ */
+void NimBLEHIDDevice::manufacturer(std::string name) {
+ m_manufacturerCharacteristic->setValue(name);
+}
+
+/*
+ * @brief
+ */
+void NimBLEHIDDevice::pnp(uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version) {
+ uint8_t pnp[] = { sig, (uint8_t) (vid >> 8), (uint8_t) vid, (uint8_t) (pid >> 8), (uint8_t) pid, (uint8_t) (version >> 8), (uint8_t) version };
+ m_pnpCharacteristic->setValue(pnp, sizeof(pnp));
+}
+
+/*
+ * @brief
+ */
+void NimBLEHIDDevice::hidInfo(uint8_t country, uint8_t flags) {
+ uint8_t info[] = { 0x11, 0x1, country, flags };
+ m_hidInfoCharacteristic->setValue(info, sizeof(info));
+}
+
+/*
+ * @brief Create input report characteristic that need to be saved as new characteristic object so can be further used
+ * @param [in] reportID input report ID, the same as in report map for input object related to created characteristic
+ * @return pointer to new input report characteristic
+ */
+NimBLECharacteristic* NimBLEHIDDevice::inputReport(uint8_t reportID) {
+ NimBLECharacteristic* inputReportCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4d, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY);
+ NimBLEDescriptor* inputReportDescriptor = inputReportCharacteristic->createDescriptor((uint16_t) 0x2908);
+
+ uint8_t desc1_val[] = { reportID, 0x01 };
+ inputReportDescriptor->setValue((uint8_t*) desc1_val, 2);
+
+ return inputReportCharacteristic;
+}
+
+/*
+ * @brief Create output report characteristic that need to be saved as new characteristic object so can be further used
+ * @param [in] reportID Output report ID, the same as in report map for output object related to created characteristic
+ * @return Pointer to new output report characteristic
+ */
+NimBLECharacteristic* NimBLEHIDDevice::outputReport(uint8_t reportID) {
+ NimBLECharacteristic* outputReportCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4d, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::WRITE_ENC);
+ NimBLEDescriptor* outputReportDescriptor = outputReportCharacteristic->createDescriptor((uint16_t) 0x2908);
+
+ uint8_t desc1_val[] = { reportID, 0x02 };
+ outputReportDescriptor->setValue((uint8_t*) desc1_val, 2);
+
+ return outputReportCharacteristic;
+}
+
+/*
+ * @brief Create feature report characteristic that need to be saved as new characteristic object so can be further used
+ * @param [in] reportID Feature report ID, the same as in report map for feature object related to created characteristic
+ * @return Pointer to new feature report characteristic
+ */
+NimBLECharacteristic* NimBLEHIDDevice::featureReport(uint8_t reportID) {
+ NimBLECharacteristic* featureReportCharacteristic = m_hidService->createCharacteristic((uint16_t) 0x2a4d, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::WRITE_ENC);
+ NimBLEDescriptor* featureReportDescriptor = featureReportCharacteristic->createDescriptor((uint16_t) 0x2908);
+
+ uint8_t desc1_val[] = { reportID, 0x03 };
+ featureReportDescriptor->setValue((uint8_t*) desc1_val, 2);
+
+ return featureReportCharacteristic;
+}
+
+/*
+ * @brief
+ */
+NimBLECharacteristic* NimBLEHIDDevice::bootInput() {
+ return m_hidService->createCharacteristic((uint16_t) 0x2a22, NIMBLE_PROPERTY::NOTIFY);
+}
+
+/*
+ * @brief
+ */
+NimBLECharacteristic* NimBLEHIDDevice::bootOutput() {
+ return m_hidService->createCharacteristic((uint16_t) 0x2a32, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR);
+}
+
+/*
+ * @brief
+ */
+NimBLECharacteristic* NimBLEHIDDevice::hidControl() {
+ return m_hidControlCharacteristic;
+}
+
+/*
+ * @brief
+ */
+NimBLECharacteristic* NimBLEHIDDevice::protocolMode() {
+ return m_protocolModeCharacteristic;
+}
+
+void NimBLEHIDDevice::setBatteryLevel(uint8_t level) {
+ m_batteryLevelCharacteristic->setValue(&level, 1);
+}
+/*
+ * @brief Returns battery level characteristic
+ * @ return battery level characteristic
+ *//*
+BLECharacteristic* BLEHIDDevice::batteryLevel() {
+ return m_batteryLevelCharacteristic;
+}
+
+
+
+BLECharacteristic* BLEHIDDevice::reportMap() {
+ return m_reportMapCharacteristic;
+}
+
+BLECharacteristic* BLEHIDDevice::pnp() {
+ return m_pnpCharacteristic;
+}
+
+
+BLECharacteristic* BLEHIDDevice::hidInfo() {
+ return m_hidInfoCharacteristic;
+}
+*/
+/*
+ * @brief
+ */
+NimBLEService* NimBLEHIDDevice::deviceInfo() {
+ return m_deviceInfoService;
+}
+
+/*
+ * @brief
+ */
+NimBLEService* NimBLEHIDDevice::hidService() {
+ return m_hidService;
+}
+
+/*
+ * @brief
+ */
+NimBLEService* NimBLEHIDDevice::batteryService() {
+ return m_batteryService;
+}
+
+#endif // #if defined(CONFIG_BT_NIMBLE_ROLE_PERIPHERAL)
+#endif // #if defined(CONFIG_BT_ENABLED)
diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEHIDDevice.h b/lib/libesp32/NimBLE-Arduino/src/NimBLEHIDDevice.h
new file mode 100644
index 000000000..3e7a6f759
--- /dev/null
+++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEHIDDevice.h
@@ -0,0 +1,85 @@
+/*
+ * NimBLEHIDDevice.h
+ *
+ * Created: on Oct 06 2020
+ * Author wakwak-koba
+ *
+ * Originally:
+ *
+ * BLEHIDDevice.h
+ *
+ * Created on: Jan 03, 2018
+ * Author: chegewara
+ */
+
+#ifndef _BLEHIDDEVICE_H_
+#define _BLEHIDDEVICE_H_
+
+#include "sdkconfig.h"
+#if defined(CONFIG_BT_ENABLED)
+
+#include "nimconfig.h"
+#if defined(CONFIG_BT_NIMBLE_ROLE_BROADCASTER)
+
+#include "NimBLECharacteristic.h"
+#include "NimBLEService.h"
+#include "NimBLEDescriptor.h"
+#include "HIDTypes.h"
+
+#define GENERIC_HID 0x03C0
+#define HID_KEYBOARD 0x03C1
+#define HID_MOUSE 0x03C2
+#define HID_JOYSTICK 0x03C3
+#define HID_GAMEPAD 0x03C4
+#define HID_TABLET 0x03C5
+#define HID_CARD_READER 0x03C6
+#define HID_DIGITAL_PEN 0x03C7
+#define HID_BARCODE 0x03C8
+
+class NimBLEHIDDevice {
+public:
+ NimBLEHIDDevice(NimBLEServer*);
+ virtual ~NimBLEHIDDevice();
+
+ void reportMap(uint8_t* map, uint16_t);
+ void startServices();
+
+ NimBLEService* deviceInfo();
+ NimBLEService* hidService();
+ NimBLEService* batteryService();
+
+ NimBLECharacteristic* manufacturer();
+ void manufacturer(std::string name);
+ //NimBLECharacteristic* pnp();
+ void pnp(uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version);
+ //NimBLECharacteristic* hidInfo();
+ void hidInfo(uint8_t country, uint8_t flags);
+ //NimBLECharacteristic* batteryLevel();
+ void setBatteryLevel(uint8_t level);
+
+
+ //NimBLECharacteristic* reportMap();
+ NimBLECharacteristic* hidControl();
+ NimBLECharacteristic* inputReport(uint8_t reportID);
+ NimBLECharacteristic* outputReport(uint8_t reportID);
+ NimBLECharacteristic* featureReport(uint8_t reportID);
+ NimBLECharacteristic* protocolMode();
+ NimBLECharacteristic* bootInput();
+ NimBLECharacteristic* bootOutput();
+
+private:
+ NimBLEService* m_deviceInfoService; //0x180a
+ NimBLEService* m_hidService; //0x1812
+ NimBLEService* m_batteryService = 0; //0x180f
+
+ NimBLECharacteristic* m_manufacturerCharacteristic; //0x2a29
+ NimBLECharacteristic* m_pnpCharacteristic; //0x2a50
+ NimBLECharacteristic* m_hidInfoCharacteristic; //0x2a4a
+ NimBLECharacteristic* m_reportMapCharacteristic; //0x2a4b
+ NimBLECharacteristic* m_hidControlCharacteristic; //0x2a4c
+ NimBLECharacteristic* m_protocolModeCharacteristic; //0x2a4e
+ NimBLECharacteristic* m_batteryLevelCharacteristic; //0x2a19
+};
+#endif // CONFIG_BT_NIMBLE_ROLE_BROADCASTER
+#endif // CONFIG_BT_ENABLED
+#endif /* _BLEHIDDEVICE_H_ */
diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteCharacteristic.cpp b/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteCharacteristic.cpp
index 154206c73..8bc5090b1 100644
--- a/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteCharacteristic.cpp
+++ b/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteCharacteristic.cpp
@@ -38,7 +38,7 @@ static const char* LOG_TAG = "NimBLERemoteCharacteristic";
NimBLERemoteCharacteristic::NimBLERemoteCharacteristic(NimBLERemoteService *pRemoteService,
const struct ble_gatt_chr *chr)
{
-
+ NIMBLE_LOGD(LOG_TAG, ">> NimBLERemoteCharacteristic()");
switch (chr->uuid.u.type) {
case BLE_UUID_TYPE_16:
m_uuid = NimBLEUUID(chr->uuid.u16.value);
@@ -50,7 +50,6 @@ static const char* LOG_TAG = "NimBLERemoteCharacteristic";
m_uuid = NimBLEUUID(const_cast(&chr->uuid.u128));
break;
default:
- m_uuid = nullptr;
break;
}
@@ -61,6 +60,8 @@ static const char* LOG_TAG = "NimBLERemoteCharacteristic";
m_notifyCallback = nullptr;
m_timestamp = 0;
m_valMux = portMUX_INITIALIZER_UNLOCKED;
+
+ NIMBLE_LOGD(LOG_TAG, "<< NimBLERemoteCharacteristic(): %s", m_uuid.toString().c_str());
} // NimBLERemoteCharacteristic
@@ -208,15 +209,21 @@ int NimBLERemoteCharacteristic::descriptorDiscCB(uint16_t conn_handle,
bool NimBLERemoteCharacteristic::retrieveDescriptors(const NimBLEUUID *uuid_filter) {
NIMBLE_LOGD(LOG_TAG, ">> retrieveDescriptors() for characteristic: %s", getUUID().toString().c_str());
+ uint16_t endHandle = getRemoteService()->getEndHandle(this);
+ if(m_handle >= endHandle) {
+ return false;
+ }
+
int rc = 0;
ble_task_data_t taskData = {this, xTaskGetCurrentTaskHandle(), 0, nullptr};
desc_filter_t filter = {uuid_filter, &taskData};
rc = ble_gattc_disc_all_dscs(getRemoteService()->getClient()->getConnId(),
m_handle,
- getRemoteService()->getEndHandle(),
+ endHandle,
NimBLERemoteCharacteristic::descriptorDiscCB,
&filter);
+
if (rc != 0) {
NIMBLE_LOGE(LOG_TAG, "ble_gattc_disc_all_chrs: rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc));
return false;
@@ -225,12 +232,13 @@ bool NimBLERemoteCharacteristic::retrieveDescriptors(const NimBLEUUID *uuid_filt
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if(taskData.rc != 0) {
+ NIMBLE_LOGE(LOG_TAG, "ble_gattc_disc_all_chrs: startHandle:%d endHandle:%d taskData.rc=%d %s", m_handle, endHandle, taskData.rc, NimBLEUtils::returnCodeToString(0x0100+taskData.rc));
return false;
}
return true;
NIMBLE_LOGD(LOG_TAG, "<< retrieveDescriptors(): Found %d descriptors.", m_descriptorVector.size());
-} // getDescriptors
+} // retrieveDescriptors
/**
@@ -243,7 +251,7 @@ NimBLERemoteDescriptor* NimBLERemoteCharacteristic::getDescriptor(const NimBLEUU
for(auto &it: m_descriptorVector) {
if(it->getUUID() == uuid) {
- NIMBLE_LOGD(LOG_TAG, "<< getDescriptor: found");
+ NIMBLE_LOGD(LOG_TAG, "<< getDescriptor: found the descriptor with uuid: %s", uuid.toString().c_str());
return it;
}
}
@@ -253,7 +261,18 @@ NimBLERemoteDescriptor* NimBLERemoteCharacteristic::getDescriptor(const NimBLEUU
if(m_descriptorVector.size() > prev_size) {
return m_descriptorVector.back();
}
+
+ // If the request was successful but 16/32 bit descriptor not found
+ // try again with the 128 bit uuid.
+ if(uuid.bitSize() == BLE_UUID_TYPE_16 ||
+ uuid.bitSize() == BLE_UUID_TYPE_32)
+ {
+ NimBLEUUID uuid128(uuid);
+ uuid128.to128();
+ return getDescriptor(uuid128);
+ }
}
+
NIMBLE_LOGD(LOG_TAG, "<< getDescriptor: Not found");
return nullptr;
} // getDescriptor
@@ -447,9 +466,10 @@ std::string NimBLERemoteCharacteristic::readValue(time_t *timestamp) {
}
} while(rc != 0 && retryCount--);
+ time_t t = time(nullptr);
portENTER_CRITICAL(&m_valMux);
m_value = value;
- m_timestamp = time(nullptr);
+ m_timestamp = t;
if(timestamp != nullptr) {
*timestamp = m_timestamp;
}
@@ -506,19 +526,19 @@ int NimBLERemoteCharacteristic::onReadCB(uint16_t conn_handle,
* @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 true if successful.
+ * @return false if writing to the descriptor failed.
*/
bool NimBLERemoteCharacteristic::setNotify(uint16_t val, notify_callback notifyCallback, bool response) {
NIMBLE_LOGD(LOG_TAG, ">> setNotify(): %s, %02x", toString().c_str(), val);
+ m_notifyCallback = notifyCallback;
+
NimBLERemoteDescriptor* desc = getDescriptor(NimBLEUUID((uint16_t)0x2902));
if(desc == nullptr) {
- NIMBLE_LOGE(LOG_TAG, "<< setNotify(): Could not get descriptor");
- return false;
+ NIMBLE_LOGW(LOG_TAG, "<< setNotify(): Callback set, CCCD not found");
+ return true;
}
- m_notifyCallback = notifyCallback;
-
NIMBLE_LOGD(LOG_TAG, "<< setNotify()");
return desc->writeValue((uint8_t *)&val, 2, response);
@@ -531,7 +551,7 @@ bool NimBLERemoteCharacteristic::setNotify(uint16_t val, notify_callback notifyC
* @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 true if successful.
+ * @return false if writing to the descriptor failed.
*/
bool NimBLERemoteCharacteristic::subscribe(bool notifications, notify_callback notifyCallback, bool response) {
if(notifications) {
@@ -545,7 +565,7 @@ bool NimBLERemoteCharacteristic::subscribe(bool notifications, notify_callback n
/**
* @brief Unsubscribe for notifications or indications.
* @param [in] response bool if true, require a write response from the descriptor write operation.
- * @return true if successful.
+ * @return false if writing to the descriptor failed.
*/
bool NimBLERemoteCharacteristic::unsubscribe(bool response) {
return setNotify(0x00, nullptr, response);
diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteDescriptor.cpp b/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteDescriptor.cpp
index 9281e7df7..fc0f06b67 100644
--- a/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteDescriptor.cpp
+++ b/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteDescriptor.cpp
@@ -31,6 +31,7 @@ static const char* LOG_TAG = "NimBLERemoteDescriptor";
NimBLERemoteDescriptor::NimBLERemoteDescriptor(NimBLERemoteCharacteristic* pRemoteCharacteristic,
const struct ble_gatt_dsc *dsc)
{
+ NIMBLE_LOGD(LOG_TAG, ">> NimBLERemoteDescriptor()");
switch (dsc->uuid.u.type) {
case BLE_UUID_TYPE_16:
m_uuid = NimBLEUUID(dsc->uuid.u16.value);
@@ -42,12 +43,13 @@ NimBLERemoteDescriptor::NimBLERemoteDescriptor(NimBLERemoteCharacteristic* pRemo
m_uuid = NimBLEUUID(const_cast(&dsc->uuid.u128));
break;
default:
- m_uuid = nullptr;
break;
}
m_handle = dsc->handle;
m_pRemoteCharacteristic = pRemoteCharacteristic;
+
+ NIMBLE_LOGD(LOG_TAG, "<< NimBLERemoteDescriptor(): %s", m_uuid.toString().c_str());
}
diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteService.cpp b/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteService.cpp
index 8901175dc..cb20e0b3f 100644
--- a/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteService.cpp
+++ b/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteService.cpp
@@ -44,12 +44,11 @@ NimBLERemoteService::NimBLERemoteService(NimBLEClient* pClient, const struct ble
m_uuid = NimBLEUUID(const_cast(&service->uuid.u128));
break;
default:
- m_uuid = nullptr;
break;
}
m_startHandle = service->start_handle;
m_endHandle = service->end_handle;
- NIMBLE_LOGD(LOG_TAG, "<< NimBLERemoteService()");
+ NIMBLE_LOGD(LOG_TAG, "<< NimBLERemoteService(): %s", m_uuid.toString().c_str());
}
@@ -95,8 +94,11 @@ NimBLERemoteCharacteristic* NimBLERemoteService::getCharacteristic(const char* u
* @return A pointer to the characteristic object, or nullptr if not found.
*/
NimBLERemoteCharacteristic* NimBLERemoteService::getCharacteristic(const NimBLEUUID &uuid) {
+ NIMBLE_LOGD(LOG_TAG, ">> getCharacteristic: uuid: %s", uuid.toString().c_str());
+
for(auto &it: m_characteristicVector) {
if(it->getUUID() == uuid) {
+ NIMBLE_LOGD(LOG_TAG, "<< getCharacteristic: found the characteristic with uuid: %s", uuid.toString().c_str());
return it;
}
}
@@ -106,8 +108,19 @@ NimBLERemoteCharacteristic* NimBLERemoteService::getCharacteristic(const NimBLEU
if(m_characteristicVector.size() > prev_size) {
return m_characteristicVector.back();
}
+
+ // If the request was successful but 16/32 bit characteristic not found
+ // try again with the 128 bit uuid.
+ if(uuid.bitSize() == BLE_UUID_TYPE_16 ||
+ uuid.bitSize() == BLE_UUID_TYPE_32)
+ {
+ NimBLEUUID uuid128(uuid);
+ uuid128.to128();
+ return getCharacteristic(uuid128);
+ }
}
+ NIMBLE_LOGD(LOG_TAG, "<< getCharacteristic: not found");
return nullptr;
} // getCharacteristic
@@ -236,6 +249,23 @@ uint16_t NimBLERemoteService::getEndHandle() {
return m_endHandle;
} // getEndHandle
+/**
+ * @brief Get the end handle of specified NimBLERemoteCharacteristic.
+ */
+
+uint16_t NimBLERemoteService::getEndHandle(NimBLERemoteCharacteristic *pCharacteristic) {
+ uint16_t endHandle = m_endHandle;
+
+ for(auto &it: m_characteristicVector) {
+ uint16_t defHandle = it->getDefHandle() - 1;
+ if(defHandle > pCharacteristic->getDefHandle() && endHandle > defHandle) {
+ endHandle = defHandle;
+ }
+ }
+
+ return endHandle;
+} // getEndHandle
+
/**
* @brief Get the service start handle.
diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteService.h b/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteService.h
index 751c9effb..4920844e4 100644
--- a/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteService.h
+++ b/lib/libesp32/NimBLE-Arduino/src/NimBLERemoteService.h
@@ -70,6 +70,7 @@ private:
uint16_t getStartHandle();
uint16_t getEndHandle();
+ uint16_t getEndHandle(NimBLERemoteCharacteristic *pCharacteristic);
void releaseSemaphores();
// Properties
diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEScan.cpp b/lib/libesp32/NimBLE-Arduino/src/NimBLEScan.cpp
index dc82de549..122ff3332 100644
--- a/lib/libesp32/NimBLE-Arduino/src/NimBLEScan.cpp
+++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEScan.cpp
@@ -30,7 +30,6 @@ static const char* LOG_TAG = "NimBLEScan";
* @brief Scan constuctor.
*/
NimBLEScan::NimBLEScan() {
- m_own_addr_type = 0;
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.itvl = 0; // 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)
@@ -38,9 +37,10 @@ NimBLEScan::NimBLEScan() {
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_stopped = true;
+ m_ignoreResults = false;
m_wantDuplicates = false;
m_pTaskData = nullptr;
+ m_duration = BLE_HS_FOREVER; // make sure this is non-zero in the event of a host reset
}
@@ -63,8 +63,8 @@ NimBLEScan::~NimBLEScan() {
switch(event->type) {
case BLE_GAP_EVENT_DISC: {
- if(pScan->m_stopped) {
- NIMBLE_LOGE(LOG_TAG, "Scan stop called, ignoring results.");
+ if(pScan->m_ignoreResults) {
+ NIMBLE_LOGE(LOG_TAG, "Scan op in progress - ignoring results");
return 0;
}
@@ -129,7 +129,6 @@ NimBLEScan::~NimBLEScan() {
pScan->m_scanCompleteCB(pScan->m_scanResults);
}
- pScan->m_stopped = true;
if(pScan->m_pTaskData != nullptr) {
pScan->m_pTaskData->rc = event->disc_complete.reason;
xTaskNotifyGive(pScan->m_pTaskData->task);
@@ -238,7 +237,7 @@ void NimBLEScan::setWindow(uint16_t windowMSecs) {
* @return true if scanning or scan starting.
*/
bool NimBLEScan::isScanning() {
- return !m_stopped;
+ return ble_gap_disc_active();
}
@@ -252,25 +251,6 @@ bool NimBLEScan::isScanning() {
bool NimBLEScan::start(uint32_t duration, void (*scanCompleteCB)(NimBLEScanResults), bool is_continue) {
NIMBLE_LOGD(LOG_TAG, ">> start(duration=%d)", duration);
- // If Host is not synced we cannot start scanning.
- if(!NimBLEDevice::m_synced) {
- NIMBLE_LOGC(LOG_TAG, "Host reset, wait for sync.");
- return false;
- }
-
- if(ble_gap_conn_active()) {
- NIMBLE_LOGE(LOG_TAG, "Connection in progress - must wait.");
- return false;
- }
-
- // If we are already scanning don't start again or we will get stuck on the semaphore.
- if(!m_stopped || ble_gap_disc_active()) { // double check - can cause host reset.
- NIMBLE_LOGE(LOG_TAG, "Scan already in progress");
- return false;
- }
-
- m_stopped = false;
-
// 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.
@@ -281,32 +261,51 @@ bool NimBLEScan::start(uint32_t duration, void (*scanCompleteCB)(NimBLEScanResul
duration = BLE_HS_FOREVER;
}
else{
- duration = duration*1000; // convert duration to milliseconds
+ // convert duration to milliseconds
+ duration = duration * 1000;
}
- // if we are connecting to devices that are advertising even after being connected, multiconnecting peripherals
- // then we should not clear vector or we will connect the same device few times
+ // Set the flag to ignore the results while we are deleting the vector
if(!is_continue) {
- clearResults();
+ m_ignoreResults = true;
}
- int rc = 0;
- do{
- rc = ble_gap_disc(m_own_addr_type, duration, &m_scan_params,
- NimBLEScan::handleGapEvent, this);
- if(rc == BLE_HS_EBUSY) {
- vTaskDelay(1 / portTICK_PERIOD_MS);
- }
- } while(rc == BLE_HS_EBUSY);
+ int rc = ble_gap_disc(NimBLEDevice::m_own_addr_type, duration, &m_scan_params,
+ NimBLEScan::handleGapEvent, this);
- if (rc != 0 && rc != BLE_HS_EDONE) {
- NIMBLE_LOGE(LOG_TAG, "Error initiating GAP discovery procedure; rc=%d, %s",
- rc, NimBLEUtils::returnCodeToString(rc));
- m_stopped = true;
+ switch(rc) {
+ case 0:
+ if(!is_continue) {
+ clearResults();
+ }
+ break;
+
+ case BLE_HS_EALREADY:
+ break;
+
+ case BLE_HS_EBUSY:
+ NIMBLE_LOGE(LOG_TAG, "Unable to scan - connection in progress.");
+ break;
+
+ case BLE_HS_ETIMEOUT_HCI:
+ case BLE_HS_EOS:
+ case BLE_HS_ECONTROLLER:
+ case BLE_HS_ENOTSYNCED:
+ NIMBLE_LOGC(LOG_TAG, "Unable to scan - Host Reset");
+ break;
+
+ default:
+ NIMBLE_LOGE(LOG_TAG, "Error initiating GAP discovery procedure; rc=%d, %s",
+ rc, NimBLEUtils::returnCodeToString(rc));
+ break;
+ }
+
+ m_ignoreResults = false;
+ NIMBLE_LOGD(LOG_TAG, "<< start()");
+
+ if(rc != 0 && rc != BLE_HS_EALREADY) {
return false;
}
-
- NIMBLE_LOGD(LOG_TAG, "<< start()");
return true;
} // start
@@ -347,8 +346,6 @@ bool NimBLEScan::stop() {
return false;
}
- m_stopped = true;
-
if (rc != BLE_HS_EALREADY && m_scanCompleteCB != nullptr) {
m_scanCompleteCB(m_scanResults);
}
@@ -381,13 +378,25 @@ void NimBLEScan::erase(const NimBLEAddress &address) {
/**
- * @brief If the host reset the scan will have stopped so we should set the flag as stopped.
+ * @brief Called when host reset, we set a flag to stop scanning until synced.
*/
void NimBLEScan::onHostReset() {
- m_stopped = true;
+ m_ignoreResults = true;
}
+/**
+ * @brief If the host reset and re-synced this is called.
+ * If the application was scanning indefinitely with a callback, restart it.
+ */
+void NimBLEScan::onHostSync() {
+ m_ignoreResults = false;
+
+ if(m_duration == 0 && m_pAdvertisedDeviceCallbacks != nullptr) {
+ start(m_duration, m_scanCompleteCB);
+ }
+}
+
/**
* @brief Get the results of the scan.
* @return NimBLEScanResults object.
diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEScan.h b/lib/libesp32/NimBLE-Arduino/src/NimBLEScan.h
index 822629025..9007a7dd8 100644
--- a/lib/libesp32/NimBLE-Arduino/src/NimBLEScan.h
+++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEScan.h
@@ -83,12 +83,12 @@ private:
~NimBLEScan();
static int handleGapEvent(ble_gap_event* event, void* arg);
void onHostReset();
+ void onHostSync();
NimBLEAdvertisedDeviceCallbacks* m_pAdvertisedDeviceCallbacks = nullptr;
void (*m_scanCompleteCB)(NimBLEScanResults scanResults);
ble_gap_disc_params m_scan_params;
- uint8_t m_own_addr_type;
- bool m_stopped;
+ bool m_ignoreResults;
bool m_wantDuplicates;
NimBLEScanResults m_scanResults;
uint32_t m_duration;
diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEServer.cpp b/lib/libesp32/NimBLE-Arduino/src/NimBLEServer.cpp
index 8c75192a9..655511aea 100644
--- a/lib/libesp32/NimBLE-Arduino/src/NimBLEServer.cpp
+++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEServer.cpp
@@ -296,6 +296,7 @@ size_t NimBLEServer::getConnectedCount() {
}
server->m_pServerCallbacks->onDisconnect(server);
+ server->m_pServerCallbacks->onDisconnect(server, &event->disconnect.conn);
if(server->m_advertiseOnDisconnect) {
server->startAdvertising();
@@ -658,6 +659,10 @@ void NimBLEServerCallbacks::onDisconnect(NimBLEServer* pServer) {
NIMBLE_LOGD("NimBLEServerCallbacks", "onDisconnect(): Default");
} // onDisconnect
+void NimBLEServerCallbacks::onDisconnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) {
+ NIMBLE_LOGD("NimBLEServerCallbacks", "onDisconnect(): Default");
+} // onDisconnect
+
uint32_t NimBLEServerCallbacks::onPassKeyRequest(){
NIMBLE_LOGD("NimBLEServerCallbacks", "onPassKeyRequest: default: 123456");
return 123456;
diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEServer.h b/lib/libesp32/NimBLE-Arduino/src/NimBLEServer.h
index 1fa24b23c..bedf9cf58 100644
--- a/lib/libesp32/NimBLE-Arduino/src/NimBLEServer.h
+++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEServer.h
@@ -114,6 +114,15 @@ public:
*/
virtual void onDisconnect(NimBLEServer* pServer);
+ /**
+ * @brief Handle a client disconnection.
+ * This is called when a client discconnects.
+ * @param [in] pServer A pointer to the %BLE server that received the client disconnection.
+ * @param [in] desc A pointer to the connection description structure containig information
+ * about the connection.
+ */
+ virtual void onDisconnect(NimBLEServer* pServer, ble_gap_conn_desc* desc);
+
/**
* @brief Called when a client requests a passkey for pairing.
* @return The passkey to be sent to the client.
diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEUUID.cpp b/lib/libesp32/NimBLE-Arduino/src/NimBLEUUID.cpp
index 1b00a3237..21ff27047 100644
--- a/lib/libesp32/NimBLE-Arduino/src/NimBLEUUID.cpp
+++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEUUID.cpp
@@ -264,6 +264,37 @@ std::string NimBLEUUID::toString() const {
*/
bool NimBLEUUID::operator ==(const NimBLEUUID & rhs) const {
if(m_valueSet && rhs.m_valueSet) {
+ NIMBLE_LOGD(LOG_TAG,"Comparing UUIDs; type %u to %u; UUID %s to %s",
+ m_uuid.u.type, rhs.m_uuid.u.type,
+ this->toString().c_str(), rhs.toString().c_str());
+
+ if(m_uuid.u.type != rhs.m_uuid.u.type) {
+ uint8_t uuidBase[16] = {
+ 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80,
+ 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ if(m_uuid.u.type == BLE_UUID_TYPE_128){
+ if(rhs.m_uuid.u.type == BLE_UUID_TYPE_16){
+ memcpy(uuidBase+12, &rhs.m_uuid.u16.value, 2);
+ } else if (rhs.m_uuid.u.type == BLE_UUID_TYPE_32){
+ memcpy(uuidBase+12, &rhs.m_uuid.u32.value, 4);
+ }
+ return memcmp(m_uuid.u128.value,uuidBase,16) == 0;
+
+ } else if(rhs.m_uuid.u.type == BLE_UUID_TYPE_128) {
+ if(m_uuid.u.type == BLE_UUID_TYPE_16){
+ memcpy(uuidBase+12, &m_uuid.u16.value, 2);
+ } else if (m_uuid.u.type == BLE_UUID_TYPE_32){
+ memcpy(uuidBase+12, &m_uuid.u32.value, 4);
+ }
+ return memcmp(rhs.m_uuid.u128.value,uuidBase,16) == 0;
+
+ } else {
+ return false;
+ }
+ }
+
return ble_uuid_cmp(&m_uuid.u, &rhs.m_uuid.u) == 0;
}
diff --git a/lib/libesp32/NimBLE-Arduino/src/esp-hci/src/esp_nimble_hci.c b/lib/libesp32/NimBLE-Arduino/src/esp-hci/src/esp_nimble_hci.c
index 0ba15e9c3..98c133fa5 100644
--- a/lib/libesp32/NimBLE-Arduino/src/esp-hci/src/esp_nimble_hci.c
+++ b/lib/libesp32/NimBLE-Arduino/src/esp-hci/src/esp_nimble_hci.c
@@ -19,6 +19,13 @@
* under the License.
*/
+/*
+ * This file has been modified by Ryan Powell, aka h2zero.
+ * The modifications are for the purpose of improving performance and support
+ * for Esprssif versions used by the ardruino-esp32 core that are less current
+ * than the esp-idf releases.
+ */
+
#include
#include "sysinit/sysinit.h"
#include "nimble/hci_common.h"
@@ -30,8 +37,10 @@
#include "esp_bt.h"
#include "freertos/semphr.h"
#include "esp_compiler.h"
+/* IPC is used to improve performance when calls come from a processor not running the NimBLE stack */
+/* but does not exist for solo */
#ifndef CONFIG_FREERTOS_UNICORE
-#include "esp_ipc.h"
+ #include "esp_ipc.h"
#endif
#define NIMBLE_VHCI_TIMEOUT_MS 2000
@@ -81,31 +90,40 @@ void ble_hci_trans_cfg_hs(ble_hci_trans_rx_cmd_fn *cmd_cb,
ble_hci_rx_acl_hs_arg = acl_arg;
}
-void ble_hci_trans_hs_cmd_tx_on_core_0(void *arg)
+/* Added; Called from the core NimBLE is running on, not used for unicore */
+#ifndef CONFIG_FREERTOS_UNICORE
+void ble_hci_trans_hs_cmd_tx_on_core(void *arg)
{
- uint8_t *cmd = arg;
- uint16_t len = BLE_HCI_CMD_HDR_LEN + cmd[3] + 1;
- esp_vhci_host_send_packet(cmd, len);
+ // Ugly but necessary as the arduino core does not provide enough IPC stack for variables.
+ esp_vhci_host_send_packet((uint8_t*)arg, *((uint8_t*)arg + 3) + 1 + BLE_HCI_CMD_HDR_LEN);
}
+#endif
+/* Modified to use ipc calls in arduino to correct performance issues */
int ble_hci_trans_hs_cmd_tx(uint8_t *cmd)
{
+ uint16_t len;
uint8_t rc = 0;
assert(cmd != NULL);
*cmd = BLE_HCI_UART_H4_CMD;
+ len = BLE_HCI_CMD_HDR_LEN + cmd[3] + 1;
if (!esp_vhci_host_check_send_available()) {
ESP_LOGD(TAG, "Controller not ready to receive packets");
}
if (xSemaphoreTake(vhci_send_sem, NIMBLE_VHCI_TIMEOUT_MS / portTICK_PERIOD_MS) == pdTRUE) {
- if (xPortGetCoreID() != 0) {
+/* esp_ipc_call_blocking does not exist for solo */
#ifndef CONFIG_FREERTOS_UNICORE
- esp_ipc_call_blocking(0, ble_hci_trans_hs_cmd_tx_on_core_0, cmd);
-#endif
+ if (xPortGetCoreID() != CONFIG_BT_NIMBLE_PINNED_TO_CORE) {
+ esp_ipc_call_blocking(CONFIG_BT_NIMBLE_PINNED_TO_CORE,
+ ble_hci_trans_hs_cmd_tx_on_core, cmd);
} else {
- ble_hci_trans_hs_cmd_tx_on_core_0(cmd);
+ esp_vhci_host_send_packet(cmd, len);
}
+#else /* Unicore */
+ esp_vhci_host_send_packet(cmd, len);
+#endif
} else {
rc = BLE_HS_ETIMEOUT_HCI;
}
@@ -124,21 +142,21 @@ int ble_hci_trans_ll_evt_tx(uint8_t *hci_ev)
return rc;
}
-void ble_hci_trans_hs_acl_tx_on_core_0(void *arg)
+/* Added; Called from the core NimBLE is running on, not used for unicore */
+#ifndef CONFIG_FREERTOS_UNICORE
+void ble_hci_trans_hs_acl_tx_on_core(void *arg)
{
- uint8_t data[MYNEWT_VAL(BLE_ACL_BUF_SIZE) + 1];
- struct os_mbuf *om = arg;
- uint16_t len = 1 + OS_MBUF_PKTLEN(om);
-
- data[0] = BLE_HCI_UART_H4_ACL;
- os_mbuf_copydata(om, 0, OS_MBUF_PKTLEN(om), &data[1]);
-
- esp_vhci_host_send_packet(data, len);
+ // Ugly but necessary as the arduino core does not provide enough IPC stack for variables.
+ esp_vhci_host_send_packet((uint8_t*)arg + 2, *(uint16_t*)arg);
}
+#endif
+/* Modified to use ipc calls in arduino to correct performance issues */
int ble_hci_trans_hs_acl_tx(struct os_mbuf *om)
{
- uint8_t rc = 0;
+ uint16_t len = 0;
+ uint8_t data[MYNEWT_VAL(BLE_ACL_BUF_SIZE) + 3], rc = 0;
+ bool tx_using_nimble_core = 0;
/* If this packet is zero length, just free it */
if (OS_MBUF_PKTLEN(om) == 0) {
os_mbuf_free_chain(om);
@@ -149,14 +167,36 @@ int ble_hci_trans_hs_acl_tx(struct os_mbuf *om)
ESP_LOGD(TAG, "Controller not ready to receive packets");
}
- if (xSemaphoreTake(vhci_send_sem, NIMBLE_VHCI_TIMEOUT_MS / portTICK_PERIOD_MS) == pdTRUE) {
- if (xPortGetCoreID() != 0) {
+ len = 1 + OS_MBUF_PKTLEN(om);
+/* Don't check core ID if unicore */
#ifndef CONFIG_FREERTOS_UNICORE
- esp_ipc_call_blocking(0, ble_hci_trans_hs_acl_tx_on_core_0, om);
+ tx_using_nimble_core = xPortGetCoreID() != CONFIG_BT_NIMBLE_PINNED_TO_CORE;
+ if (tx_using_nimble_core) {
+ data[0] = len;
+ data[1] = (len >> 8);
+ data[2] = BLE_HCI_UART_H4_ACL;
+ os_mbuf_copydata(om, 0, OS_MBUF_PKTLEN(om), &data[3]);
+ } else {
+ data[0] = BLE_HCI_UART_H4_ACL;
+ os_mbuf_copydata(om, 0, OS_MBUF_PKTLEN(om), &data[1]);
+ }
+#else /* Unicore */
+ data[0] = BLE_HCI_UART_H4_ACL;
+ os_mbuf_copydata(om, 0, OS_MBUF_PKTLEN(om), &data[1]);
#endif
+
+ if (xSemaphoreTake(vhci_send_sem, NIMBLE_VHCI_TIMEOUT_MS / portTICK_PERIOD_MS) == pdTRUE) {
+/* esp_ipc_call_blocking does not exist for solo */
+#ifndef CONFIG_FREERTOS_UNICORE
+ if (tx_using_nimble_core) {
+ esp_ipc_call_blocking(CONFIG_BT_NIMBLE_PINNED_TO_CORE,
+ ble_hci_trans_hs_acl_tx_on_core, data);
} else {
- ble_hci_trans_hs_acl_tx_on_core_0(om);
+ esp_vhci_host_send_packet(data, len);
}
+#else /* Unicore */
+ esp_vhci_host_send_packet(data, len);
+#endif
} else {
rc = BLE_HS_ETIMEOUT_HCI;
}
@@ -367,6 +407,13 @@ static int host_rcv_pkt(uint8_t *data, uint16_t len)
totlen = BLE_HCI_EVENT_HDR_LEN + data[2];
assert(totlen <= UINT8_MAX + BLE_HCI_EVENT_HDR_LEN);
+ if (totlen > MYNEWT_VAL(BLE_HCI_EVT_BUF_SIZE)) {
+ ESP_LOGE(TAG, "Received HCI data length at host (%d) exceeds maximum configured HCI event buffer size (%d).",
+ totlen, MYNEWT_VAL(BLE_HCI_EVT_BUF_SIZE));
+ ble_hs_sched_reset(BLE_HS_ECONTROLLER);
+ return 0;
+ }
+
if (data[1] == BLE_HCI_EVCODE_HW_ERROR) {
assert(0);
}
@@ -476,6 +523,9 @@ esp_err_t esp_nimble_hci_and_controller_init(void)
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
+ /* Added to ensure BLE only mode */
+ bt_cfg.mode = ESP_BT_MODE_BLE;
+ /* Added to set max connections from nimconfig */
bt_cfg.ble_max_conn = CONFIG_BT_NIMBLE_MAX_CONNECTIONS;
if ((ret = esp_bt_controller_init(&bt_cfg)) != ESP_OK) {
@@ -485,6 +535,7 @@ esp_err_t esp_nimble_hci_and_controller_init(void)
if ((ret = esp_bt_controller_enable(ESP_BT_MODE_BLE)) != ESP_OK) {
return ret;
}
+
return esp_nimble_hci_init();
}
diff --git a/lib/libesp32/NimBLE-Arduino/src/esp_nimble_cfg.h b/lib/libesp32/NimBLE-Arduino/src/esp_nimble_cfg.h
index 384ec4a56..bafbeac8f 100644
--- a/lib/libesp32/NimBLE-Arduino/src/esp_nimble_cfg.h
+++ b/lib/libesp32/NimBLE-Arduino/src/esp_nimble_cfg.h
@@ -483,6 +483,10 @@
#define MYNEWT_VAL_BLE_L2CAP_COC_MAX_NUM CONFIG_BT_NIMBLE_L2CAP_COC_MAX_NUM
#endif
+#ifndef MYNEWT_VAL_BLE_L2CAP_COC_MPS
+#define MYNEWT_VAL_BLE_L2CAP_COC_MPS (MYNEWT_VAL_MSYS_1_BLOCK_SIZE - 8)
+#endif
+
#ifndef MYNEWT_VAL_BLE_L2CAP_JOIN_RX_FRAGS
#define MYNEWT_VAL_BLE_L2CAP_JOIN_RX_FRAGS (1)
#endif
diff --git a/lib/libesp32/NimBLE-Arduino/src/nimble/host/src/ble_eddystone.c b/lib/libesp32/NimBLE-Arduino/src/nimble/host/src/ble_eddystone.c
index 7d80d134d..eccb3e988 100644
--- a/lib/libesp32/NimBLE-Arduino/src/nimble/host/src/ble_eddystone.c
+++ b/lib/libesp32/NimBLE-Arduino/src/nimble/host/src/ble_eddystone.c
@@ -76,7 +76,7 @@ ble_eddystone_set_adv_data_gen(struct ble_hs_adv_fields *adv_fields,
if (adv_fields->num_uuids16 > BLE_EDDYSTONE_MAX_UUIDS16) {
return BLE_HS_EINVAL;
}
- if (svc_data_len > BLE_EDDYSTONE_MAX_SVC_DATA_LEN) {
+ if (svc_data_len > (BLE_EDDYSTONE_MAX_SVC_DATA_LEN - BLE_EDDYSTONE_SVC_DATA_BASE_SZ)) {
return BLE_HS_EINVAL;
}
if (adv_fields->num_uuids16 > 0 && !adv_fields->uuids16_is_complete) {
diff --git a/lib/libesp32/NimBLE-Arduino/src/nimble/host/src/ble_gap.c b/lib/libesp32/NimBLE-Arduino/src/nimble/host/src/ble_gap.c
index 09ae27047..d77ff6a87 100644
--- a/lib/libesp32/NimBLE-Arduino/src/nimble/host/src/ble_gap.c
+++ b/lib/libesp32/NimBLE-Arduino/src/nimble/host/src/ble_gap.c
@@ -1017,7 +1017,7 @@ ble_gap_master_failed(int status)
#endif
default:
- BLE_HS_DBG_ASSERT(0);
+ //BLE_HS_DBG_ASSERT(0);
break;
}
}
@@ -1458,8 +1458,8 @@ ble_gap_rx_periodic_adv_rpt(struct hci_le_subev_periodic_adv_rpt *evt)
{
struct ble_hs_periodic_sync *psync;
struct ble_gap_event event;
- ble_gap_event_fn *cb;
- void *cb_arg;
+ ble_gap_event_fn *cb = NULL;
+ void *cb_arg = NULL;
ble_hs_lock();
psync = ble_hs_periodic_sync_find_by_handle(evt->sync_handle);
diff --git a/lib/libesp32/NimBLE-Arduino/src/nimble/host/src/ble_hs_conn.c b/lib/libesp32/NimBLE-Arduino/src/nimble/host/src/ble_hs_conn.c
index eb65e3288..b45128e23 100644
--- a/lib/libesp32/NimBLE-Arduino/src/nimble/host/src/ble_hs_conn.c
+++ b/lib/libesp32/NimBLE-Arduino/src/nimble/host/src/ble_hs_conn.c
@@ -470,29 +470,52 @@ ble_hs_conn_timer(void)
int32_t time_diff;
uint16_t conn_handle;
- conn_handle = BLE_HS_CONN_HANDLE_NONE;
- next_exp_in = BLE_HS_FOREVER;
- now = ble_npl_time_get();
+ for (;;) {
+ conn_handle = BLE_HS_CONN_HANDLE_NONE;
+ next_exp_in = BLE_HS_FOREVER;
+ now = ble_npl_time_get();
- ble_hs_lock();
+ ble_hs_lock();
- /* This loop performs one of two tasks:
- * 1. Determine if any connections need to be terminated due to timeout.
- * If so, break out of the loop and terminate the connection. This
- * function will need to be executed again.
- * 2. Otherwise, determine when the next timeout will occur.
- */
- SLIST_FOREACH(conn, &ble_hs_conns, bhc_next) {
- if (!(conn->bhc_flags & BLE_HS_CONN_F_TERMINATING)) {
+ /* This loop performs one of two tasks:
+ * 1. Determine if any connections need to be terminated due to timeout.
+ * If so, break out of the loop and terminate the connection. This
+ * function will need to be executed again.
+ * 2. Otherwise, determine when the next timeout will occur.
+ */
+ SLIST_FOREACH(conn, &ble_hs_conns, bhc_next) {
+ if (!(conn->bhc_flags & BLE_HS_CONN_F_TERMINATING)) {
#if MYNEWT_VAL(BLE_L2CAP_RX_FRAG_TIMEOUT) != 0
- /* Check each connection's rx fragment timer. If too much time
- * passes after a partial packet is received, the connection is
- * terminated.
- */
- if (conn->bhc_rx_chan != NULL) {
- time_diff = conn->bhc_rx_timeout - now;
+ /* Check each connection's rx fragment timer. If too much time
+ * passes after a partial packet is received, the connection is
+ * terminated.
+ */
+ if (conn->bhc_rx_chan != NULL) {
+ time_diff = conn->bhc_rx_timeout - now;
+ if (time_diff <= 0) {
+ /* ACL reassembly has timed out. Remember the connection
+ * handle so it can be terminated after the mutex is
+ * unlocked.
+ */
+ conn_handle = conn->bhc_handle;
+ break;
+ }
+
+ /* Determine if this connection is the soonest to time out. */
+ if (time_diff < next_exp_in) {
+ next_exp_in = time_diff;
+ }
+ }
+#endif
+
+#if BLE_HS_ATT_SVR_QUEUED_WRITE_TMO
+ /* Check each connection's rx queued write timer. If too much
+ * time passes after a prep write is received, the queue is
+ * cleared.
+ */
+ time_diff = ble_att_svr_ticks_until_tmo(&conn->bhc_att_svr, now);
if (time_diff <= 0) {
/* ACL reassembly has timed out. Remember the connection
* handle so it can be terminated after the mutex is
@@ -506,45 +529,22 @@ ble_hs_conn_timer(void)
if (time_diff < next_exp_in) {
next_exp_in = time_diff;
}
- }
#endif
-
-#if BLE_HS_ATT_SVR_QUEUED_WRITE_TMO
- /* Check each connection's rx queued write timer. If too much
- * time passes after a prep write is received, the queue is
- * cleared.
- */
- time_diff = ble_att_svr_ticks_until_tmo(&conn->bhc_att_svr, now);
- if (time_diff <= 0) {
- /* ACL reassembly has timed out. Remember the connection
- * handle so it can be terminated after the mutex is
- * unlocked.
- */
- conn_handle = conn->bhc_handle;
- break;
}
-
- /* Determine if this connection is the soonest to time out. */
- if (time_diff < next_exp_in) {
- next_exp_in = time_diff;
- }
-#endif
}
+
+ ble_hs_unlock();
+
+ /* If a connection has timed out, terminate it. We need to repeatedly
+ * call this function again to determine when the next timeout is.
+ */
+ if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
+ ble_gap_terminate(conn_handle, BLE_ERR_REM_USER_CONN_TERM);
+ continue;
+ }
+
+ return next_exp_in;
}
-
- ble_hs_unlock();
-
- /* If a connection has timed out, terminate it. We need to recursively
- * call this function again to determine when the next timeout is. This
- * is a tail-recursive call, so it should be optimized to execute in the
- * same stack frame.
- */
- if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
- ble_gap_terminate(conn_handle, BLE_ERR_REM_USER_CONN_TERM);
- return ble_hs_conn_timer();
- }
-
- return next_exp_in;
}
int
diff --git a/lib/libesp32/NimBLE-Arduino/src/nimble/host/store/config/src/ble_store_nvs.c b/lib/libesp32/NimBLE-Arduino/src/nimble/host/store/config/src/ble_store_nvs.c
index a6fea44c7..13dae8d2b 100644
--- a/lib/libesp32/NimBLE-Arduino/src/nimble/host/store/config/src/ble_store_nvs.c
+++ b/lib/libesp32/NimBLE-Arduino/src/nimble/host/store/config/src/ble_store_nvs.c
@@ -180,7 +180,9 @@ static int
get_nvs_db_attribute(int obj_type, bool empty, void *value, int num_value)
{
union ble_store_value cur = {0};
+#if MYNEWT_VAL(BLE_HOST_BASED_PRIVACY)
struct ble_hs_dev_records p_dev_rec = {0};
+#endif
esp_err_t err;
int i, count = 0, max_limit = 0;
char key_string[NIMBLE_NVS_STR_NAME_MAX_LEN];
@@ -190,11 +192,15 @@ get_nvs_db_attribute(int obj_type, bool empty, void *value, int num_value)
for (i = 1; i <= max_limit; i++) {
get_nvs_key_string(obj_type, i, key_string);
+#if MYNEWT_VAL(BLE_HOST_BASED_PRIVACY)
if (obj_type != BLE_STORE_OBJ_TYPE_PEER_DEV_REC) {
+#endif
err = get_nvs_db_value(obj_type, key_string, &cur);
+#if MYNEWT_VAL(BLE_HOST_BASED_PRIVACY)
} else {
err = get_nvs_peer_record(key_string, &p_dev_rec);
}
+#endif
/* Check if the user is searching for empty index to write to */
if (err == ESP_ERR_NVS_NOT_FOUND) {
if (empty) {
@@ -206,10 +212,13 @@ get_nvs_db_attribute(int obj_type, bool empty, void *value, int num_value)
/* If user has provided value, then the purpose is to find
* non-matching entry from NVS */
if (value) {
+#if MYNEWT_VAL(BLE_HOST_BASED_PRIVACY)
if (obj_type == BLE_STORE_OBJ_TYPE_PEER_DEV_REC) {
err = get_nvs_matching_index(&p_dev_rec, value, num_value,
sizeof(struct ble_hs_dev_records));
- } else {
+ } else
+#endif
+ {
if (obj_type != BLE_STORE_OBJ_TYPE_CCCD) {
err = get_nvs_matching_index(&cur.sec, value, num_value,
sizeof(struct ble_store_value_sec));
@@ -376,7 +385,9 @@ populate_db_from_nvs(int obj_type, void *dst, int *db_num)
{
uint8_t *db_item = (uint8_t *)dst;
union ble_store_value cur = {0};
+#if MYNEWT_VAL(BLE_HOST_BASED_PRIVACY)
struct ble_hs_dev_records p_dev_rec = {0};
+#endif
esp_err_t err;
int i;
@@ -385,8 +396,9 @@ populate_db_from_nvs(int obj_type, void *dst, int *db_num)
for (i = 1; i <= get_nvs_max_obj_value(obj_type); i++) {
get_nvs_key_string(obj_type, i, key_string);
+#if MYNEWT_VAL(BLE_HOST_BASED_PRIVACY)
if (obj_type != BLE_STORE_OBJ_TYPE_PEER_DEV_REC) {
-
+#endif
err = get_nvs_db_value(obj_type, key_string, &cur);
if (err == ESP_ERR_NVS_NOT_FOUND) {
continue;
@@ -394,6 +406,7 @@ populate_db_from_nvs(int obj_type, void *dst, int *db_num)
ESP_LOGE(TAG, "NVS read operation failed !!");
return -1;
}
+#if MYNEWT_VAL(BLE_HOST_BASED_PRIVACY)
} else {
err = get_nvs_peer_record(key_string, &p_dev_rec);
if (err == ESP_ERR_NVS_NOT_FOUND) {
@@ -410,7 +423,9 @@ populate_db_from_nvs(int obj_type, void *dst, int *db_num)
memcpy(db_item, &p_dev_rec, sizeof(struct ble_hs_dev_records));
db_item += sizeof(struct ble_hs_dev_records);
(*db_num)++;
- } else {
+ } else
+#endif
+ {
if (obj_type == BLE_STORE_OBJ_TYPE_CCCD) {
ESP_LOGD(TAG, "CCCD in RAM is filled up from NVS index = %d", i);
memcpy(db_item, &cur.cccd, sizeof(struct ble_store_value_cccd));
@@ -492,6 +507,11 @@ int ble_store_config_persist_cccds(void)
union ble_store_value val;
nvs_count = get_nvs_db_attribute(BLE_STORE_OBJ_TYPE_CCCD, 0, NULL, 0);
+ if (nvs_count == -1) {
+ ESP_LOGE(TAG, "NVS operation failed while persisting CCCD");
+ return BLE_HS_ESTORE_FAIL;
+ }
+
if (nvs_count < ble_store_config_num_cccds) {
/* NVS db count less than RAM count, write operation */
@@ -518,6 +538,11 @@ int ble_store_config_persist_peer_secs(void)
union ble_store_value val;
nvs_count = get_nvs_db_attribute(BLE_STORE_OBJ_TYPE_PEER_SEC, 0, NULL, 0);
+ if (nvs_count == -1) {
+ ESP_LOGE(TAG, "NVS operation failed while persisting peer sec");
+ return BLE_HS_ESTORE_FAIL;
+ }
+
if (nvs_count < ble_store_config_num_peer_secs) {
/* NVS db count less than RAM count, write operation */
@@ -544,6 +569,11 @@ int ble_store_config_persist_our_secs(void)
union ble_store_value val;
nvs_count = get_nvs_db_attribute(BLE_STORE_OBJ_TYPE_OUR_SEC, 0, NULL, 0);
+ if (nvs_count == -1) {
+ ESP_LOGE(TAG, "NVS operation failed while persisting our sec");
+ return BLE_HS_ESTORE_FAIL;
+ }
+
if (nvs_count < ble_store_config_num_our_secs) {
/* NVS db count less than RAM count, write operation */
@@ -573,7 +603,13 @@ int ble_store_persist_peer_records(void)
struct ble_hs_dev_records *peer_dev_rec = ble_rpa_get_peer_dev_records();
nvs_count = get_nvs_db_attribute(BLE_STORE_OBJ_TYPE_PEER_DEV_REC, 0, NULL, 0);
+ if (nvs_count == -1) {
+ ESP_LOGE(TAG, "NVS operation failed while persisting peer_dev_rec");
+ return BLE_HS_ESTORE_FAIL;
+ }
+
if (nvs_count < ble_store_num_peer_dev_rec) {
+
/* NVS db count less than RAM count, write operation */
ESP_LOGD(TAG, "Persisting peer dev record to NVS...");
peer_rec = peer_dev_rec[ble_store_num_peer_dev_rec - 1];
diff --git a/lib/libesp32/NimBLE-Arduino/src/nimconfig.h b/lib/libesp32/NimBLE-Arduino/src/nimconfig.h
index 2f10fa2fc..d90921fa1 100644
--- a/lib/libesp32/NimBLE-Arduino/src/nimconfig.h
+++ b/lib/libesp32/NimBLE-Arduino/src/nimconfig.h
@@ -15,8 +15,13 @@
* This converts them to "CONFIG_BT_NIMBLE_" format used in the latest IDF.
*/
-/* Detect if using ESP-IDF or Arduino (Arduino won't have these defines in sdkconfig) */
-#if defined(CONFIG_BT_NIMBLE_TASK_STACK_SIZE) || defined(CONFIG_NIMBLE_TASK_STACK_SIZE)
+/* Detect if using ESP-IDF or Arduino (Arduino won't have these defines in sdkconfig)
+ *
+ * Note: We do not use #ifdef CONFIG_BT_NIMBLE_ENABLED since we cannot enable NimBLE when using
+ * Arduino as a component and the esp-nimble-compnent, so we check if other config options are defined.
+ * We also need to use a config parameter that must be present and not likely defined in the command line.
+ */
+#if defined(CONFIG_BT_NIMBLE_GAP_DEVICE_NAME_MAX_LEN) || defined(CONFIG_NIMBLE_GAP_DEVICE_NAME_MAX_LEN)
#if defined(CONFIG_NIMBLE_ENABLED) && !defined(CONFIG_BT_NIMBLE_ENABLED)
#define CONFIG_BT_NIMBLE_ENABLED
@@ -51,22 +56,30 @@
/** @brief Comment out if not using NimBLE Client functions \n
* Reduces flash size by approx. 7kB.
*/
+#ifndef CONFIG_BT_NIMBLE_ROLE_CENTRAL_DISABLED
#define CONFIG_BT_NIMBLE_ROLE_CENTRAL
+#endif
/** @brief Comment out if not using NimBLE Scan functions \n
* Reduces flash size by approx. 26kB.
*/
+#ifndef CONFIG_BT_NIMBLE_ROLE_OBSERVER_DISABLED
#define CONFIG_BT_NIMBLE_ROLE_OBSERVER
+#endif
/** @brief Comment out if not using NimBLE Server functions \n
* Reduces flash size by approx. 16kB.
*/
-// #define CONFIG_BT_NIMBLE_ROLE_PERIPHERAL
+#ifndef CONFIG_BT_NIMBLE_ROLE_PERIPHERAL_DISABLED
+#define CONFIG_BT_NIMBLE_ROLE_PERIPHERAL
+#endif
/** @brief Comment out if not using NimBLE Advertising functions \n
* Reduces flash size by approx. 5kB.
*/
-// #define CONFIG_BT_NIMBLE_ROLE_BROADCASTER
+#ifndef CONFIG_BT_NIMBLE_ROLE_BROADCASTER_DISABLED
+#define CONFIG_BT_NIMBLE_ROLE_BROADCASTER
+#endif
/* Uncomment to see debug log messages from the NimBLE host
* Uses approx. 32kB of flash memory.
@@ -89,29 +102,46 @@
// #define CONFIG_NIMBLE_CPP_ENABLE_ADVERTISMENT_TYPE_TEXT
/** @brief Sets the core NimBLE host runs on */
+#ifndef CONFIG_BT_NIMBLE_PINNED_TO_CORE
#define CONFIG_BT_NIMBLE_PINNED_TO_CORE 0
+#endif
/** @brief Sets the stack size for the NimBLE host task */
+#ifndef CONFIG_BT_NIMBLE_TASK_STACK_SIZE
#define CONFIG_BT_NIMBLE_TASK_STACK_SIZE 4096
+#endif
/**
* @brief Sets the memory pool where NimBLE will be loaded
* @details By default NimBLE is loaded in internal ram.\n
* To use external PSRAM you must change this to `#define CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL 1`
*/
+#ifndef CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL
#define CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_INTERNAL 1
+#endif
/** @brief Sets the number of simultaneous connections (esp controller max is 9) */
+#ifndef CONFIG_BT_NIMBLE_MAX_CONNECTIONS
#define CONFIG_BT_NIMBLE_MAX_CONNECTIONS 3
+#endif
/** @brief Sets the number of devices allowed to store/bond with */
+#ifndef CONFIG_BT_NIMBLE_MAX_BONDS
#define CONFIG_BT_NIMBLE_MAX_BONDS 3
+#endif
/** @brief Sets the maximum number of CCCD subscriptions to store */
+#ifndef CONFIG_BT_NIMBLE_MAX_CCCDS
#define CONFIG_BT_NIMBLE_MAX_CCCDS 8
+#endif
+
+/** @brief Default device name */
+#ifndef CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME
+#define CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME "nimble"
+#endif
/** @brief Set if CCCD's and bond data should be stored in NVS */
-#define CONFIG_BT_NIMBLE_NVS_PERSIST 0
+#define CONFIG_BT_NIMBLE_NVS_PERSIST 1
/** @brief Allow legacy paring */
#define CONFIG_BT_NIMBLE_SM_LEGACY 1
@@ -119,9 +149,6 @@
/** @brief Allow BLE secure connections */
#define CONFIG_BT_NIMBLE_SM_SC 1
-/** @brief Default device name */
-#define CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME "nimble"
-
/** @brief Max device name length (bytes) */
#define CONFIG_BT_NIMBLE_GAP_DEVICE_NAME_MAX_LEN 31
@@ -154,7 +181,6 @@
*/
#define CONFIG_BT_NIMBLE_MSYS1_BLOCK_COUNT 12
-
/** @brief Random address refresh time in seconds */
#define CONFIG_BT_NIMBLE_RPA_TIMEOUT 900
From 37beaca1198da70b2dc31aab1d5383e5961773c7 Mon Sep 17 00:00:00 2001
From: Simon Hailes
Date: Sun, 17 Jan 2021 17:08:54 +0000
Subject: [PATCH 2/2] Implements xdrv_52 - BLE_ESP32 Modifies xsns_52 -
iBeacon, and xsns_62 - MI32 to use the new BLE driver
---
tasmota/my_user_config.h | 3 +
tasmota/support_command.ino | 11 +
tasmota/xdrv_01_webserver.ino | 14 +-
tasmota/xdrv_52_BLE_ESP32.ino | 3613 ++++++++++++++++++++++++
tasmota/xsns_52_ibeacon.ino | 17 +-
tasmota/xsns_52_ibeacon_BLE_ESP32.ino | 952 +++++++
tasmota/xsns_62_MI_ESP32.ino | 53 +-
tasmota/xsns_62_MI_ESP32_BLE_ESP32.ino | 2742 ++++++++++++++++++
8 files changed, 7376 insertions(+), 29 deletions(-)
create mode 100644 tasmota/xdrv_52_BLE_ESP32.ino
create mode 100644 tasmota/xsns_52_ibeacon_BLE_ESP32.ino
create mode 100644 tasmota/xsns_62_MI_ESP32_BLE_ESP32.ino
diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h
index 8f3fa5a89..8cf0316f8 100644
--- a/tasmota/my_user_config.h
+++ b/tasmota/my_user_config.h
@@ -861,6 +861,9 @@
//#define USE_SPI // Add support for hardware SPI
#define USE_MI_ESP32 // Add support for ESP32 as a BLE-bridge (+9k2 mem, +292k flash)
+//#define USE_BLE_ESP32 // Add support for ESP32 as a BLE-bridge (+9k2? mem, +292k? flash)
+//#define USE_IBEACON // Add support for bluetooth LE passive scan of ibeacon devices (uses HM17 module)
+//#define USE_IBEACON_ESP32
//#define USE_WEBCAM // Add support for webcam
#endif // ESP32
diff --git a/tasmota/support_command.ino b/tasmota/support_command.ino
index 1c5e32474..32180783d 100644
--- a/tasmota/support_command.ino
+++ b/tasmota/support_command.ino
@@ -690,6 +690,12 @@ void CmndSleep(void)
}
+#ifdef USE_BLE_ESP32
+ // declare the fn
+ int ExtStopBLE();
+#endif
+
+
void CmndUpgrade(void)
{
// Check if the payload is numerically 1, and had no trailing chars.
@@ -700,6 +706,11 @@ void CmndUpgrade(void)
TasmotaGlobal.ota_state_flag = 3;
char stemp1[TOPSZ];
Response_P(PSTR("{\"%s\":\"" D_JSON_VERSION " %s " D_JSON_FROM " %s\"}"), XdrvMailbox.command, TasmotaGlobal.version, GetOtaUrl(stemp1, sizeof(stemp1)));
+
+#ifdef USE_BLE_ESP32
+ ExtStopBLE();
+#endif
+
} else {
Response_P(PSTR("{\"%s\":\"" D_JSON_ONE_OR_GT "\"}"), XdrvMailbox.command, TasmotaGlobal.version);
}
diff --git a/tasmota/xdrv_01_webserver.ino b/tasmota/xdrv_01_webserver.ino
index d68cf9cd0..f148a5a6e 100644
--- a/tasmota/xdrv_01_webserver.ino
+++ b/tasmota/xdrv_01_webserver.ino
@@ -2374,6 +2374,11 @@ void UploadServices(uint32_t start_service) {
}
}
+#ifdef USE_BLE_ESP32
+ // declare the fn
+ int ExtStopBLE();
+#endif
+
void HandleUploadLoop(void) {
// Based on ESP8266HTTPUpdateServer.cpp uses ESP8266WebServer Parsing.cpp and Cores Updater.cpp (Update)
static uint32_t upload_size;
@@ -2409,6 +2414,11 @@ void HandleUploadLoop(void) {
}
SettingsSave(1); // Free flash for upload
+#ifdef USE_BLE_ESP32
+ ExtStopBLE();
+#endif
+
+
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_UPLOAD D_FILE " %s"), upload.filename.c_str());
if (UPL_SETTINGS == Web.upload_file_type) {
@@ -2632,7 +2642,9 @@ void HandleUploadLoop(void) {
Web.upload_error = 7; // Upload aborted
if (UPL_TASMOTA == Web.upload_file_type) { Update.end(); }
}
- delay(0);
+ // do actually wait a little to allow ESP32 tasks to tick
+ // fixes task timeout in ESP32Solo1 style unicore code.
+ delay(10);
OsWatchLoop();
// Scheduler(); // Feed OsWatch timer to prevent restart on long uploads
}
diff --git a/tasmota/xdrv_52_BLE_ESP32.ino b/tasmota/xdrv_52_BLE_ESP32.ino
new file mode 100644
index 000000000..90739757d
--- /dev/null
+++ b/tasmota/xdrv_52_BLE_ESP32.ino
@@ -0,0 +1,3613 @@
+/*
+ xdrv_52_BLE_ESP32.ino - BLE via ESP32 support for Tasmota
+
+ Copyright (C) 2020 Christian Baars and Theo Arends and Simon Hailes
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+
+ --------------------------------------------------------------------------------------------
+ Version yyyymmdd Action Description
+ --------------------------------------------------------------------------------------------
+*/
+
+/*
+ xdrv_52:
+ This driver uses the ESP32 BLE functionality to hopefully provide enough
+ BLE functionality to implement specific drivers on top of it.
+
+ As a generic driver, it can:
+ Be asked to
+ connect/write to a MAC/Service/Characteristic
+ connect/read from a MAC/Service/Characteristic
+ connect/write/awaitnotify from a MAC/Service/Characteristic/NotifyCharacteristic
+ connect/read/awaitnotify from a MAC/Service/Characteristic/NotifyCharacteristic
+
+ Cmnds:
+ BLEOp0 - requests status of operations
+ BLEOp1 MAC - create an operation in preparation, and populate it's MAC address
+ BLEOp2 Service - add a serviceUUID to the operation in preparation
+ BLEOp3 Characteristic - add a CharacteristicUUID to the operation in preparation for read/write
+ BLEOp4 writedata - optional:add data to write to the operation in preparation - hex string
+ BLEOp5 - optional:signify that a read should be done
+ BLEOp6 NotifyCharacteristic - optional:add a NotifyCharacteristicUUID to the operation in preparation to wait for a notify
+ BLEOp9 - publish the 'operation in preparation' to MQTT.
+ BLEOp10 - add the 'operation in preparation' to the queue of operations to perform.
+
+ Other drivers can add callbacks to receive advertisements
+ Other drivers can add 'operations' to be performed and receive callbacks from the operation's success or failure
+
+Example:
+Write and request next notify:
+backlog BLEOp1 001A22092EE0; BLEOp2 3e135142-654f-9090-134a-a6ff5bb77046; BLEOp3 3fa4585a-ce4a-3bad-db4b-b8df8179ea09; BLEOp4 03; BLEOp6 d0e8434d-cd29-0996-af41-6c90f4e0eb2a;
+BLEOp10 ->
+19:25:08 RSL: tele/tasmota_E89E98/SENSOR = {"BLEOperation":{"opid":"3,"state":"1,"MAC":"001A22092EE0","svc":"3e135142-654f-9090-134a-a6ff5bb77046","char":"3fa4585a-ce4a-3bad-db4b-b8df8179ea09","wrote":"03}}
+19:25:08 queued 0 sent {"BLEOperation":{"opid":"3,"state":"1,"MAC":"001A22092EE0","svc":"3e135142-654f-9090-134a-a6ff5bb77046","char":"3fa4585a-ce4a-3bad-db4b-b8df8179ea09","wrote":"03}}
+19:25:08 RSL: stat/tasmota_E89E98/RESULT = {"BLEOp":"Done"}
+.....
+19:25:11 RSL: tele/tasmota_E89E98/SENSOR = {"BLEOperation":{"opid":"3,"state":"11,"MAC":"001A22092EE0","svc":"3e135142-654f-9090-134a-a6ff5bb77046","char":"3fa4585a-ce4a-3bad-db4b-b8df8179ea09","wrote":"03","notify":"020109000428}}
+
+state: 1 -> starting,
+7 -> read complete
+8 -> write complete
+11 -> notify complete
+-ve + -> failure (see GEN_STATE_FAILED_XXXX constants below.)
+
+
+The driver can also be used by other drivers, using the functions:
+
+void registerForAdvertismentCallbacks(char *loggingtag, ADVERTISMENT_CALLBACK* pFn);
+void registerForOpCallbacks(char *loggingtag, OPCOMPLETE_CALLBACK* pFn);
+bool extQueueOperation(generic_sensor_t** op);
+
+These allow other code to
+ receive advertisements
+ receive operation callbacks.
+ create and start an operation, and get a callback when done/failed.
+
+i.e. the Bluetooth of the ESP can be shared without conflict.
+
+*/
+
+
+// TEMPORARILY define ESP32 and USE_BLE_ESP32 so VSCODE shows highlighting....
+//#define VSCODE_DEV
+
+#ifdef VSCODE_DEV
+#define ESP32
+#define USE_BLE_ESP32
+#endif
+
+#ifdef ESP32 // ESP32 only. Use define USE_HM10 for ESP8266 support
+
+#ifdef USE_BLE_ESP32
+
+#define BLE_ESP32_ALIASES
+
+// uncomment for more diagnostic/information messages - + more flash use.
+//#define BLE_ESP32_DEBUG
+
+
+
+#define XDRV_52 52
+#define USE_MI_DECRYPTION
+
+#include
+#include
+#include
+#include
+#ifdef USE_MI_DECRYPTION
+#include
+#endif //USE_MI_DECRYPTION
+
+#include
+#include
+#include "NimBLEEddystoneURL.h"
+#include "NimBLEEddystoneTLM.h"
+#include "NimBLEBeacon.h"
+
+// from ble_gap.c
+extern "C" void ble_gap_conn_broken(uint16_t conn_handle, int reason);
+
+void installExamples();
+void sendExample();
+
+
+namespace BLE_ESP32 {
+
+
+// generic sensor type used as during
+// connect/read/wrtie/notify operations
+// only one operation will happen at a time
+
+#pragma pack( push, 0 ) // aligned structures for speed. but be sepcific
+
+/////////////////////////////////////////////////////
+// states for runTaskDoneOperation
+#define GEN_STATE_IDLE 0
+#define GEN_STATE_START 1
+#define GEN_STATE_STARTED 2
+
+#define GEN_STATE_READDONE 3
+#define GEN_STATE_WRITEDONE 4
+#define GEN_STATE_WAITNOTIFY 5
+#define GEN_STATE_WAITINDICATE 6
+
+#define GEN_STATE_NOTIFIED 7
+
+
+// Errors are all base on 0x100
+#define GEN_STATE_FAILED -1
+#define GEN_STATE_FAILED_CANTNOTIFYORINDICATE -2
+#define GEN_STATE_FAILED_CANTREAD -3
+#define GEN_STATE_FAILED_CANTWRITE -4
+#define GEN_STATE_FAILED_NOSERVICE -5
+#define GEN_STATE_FAILED_NO_RW_CHAR -6
+#define GEN_STATE_FAILED_NONOTIFYCHAR -7
+#define GEN_STATE_FAILED_NOTIFYTIMEOUT -8
+#define GEN_STATE_FAILED_READ -9
+#define GEN_STATE_FAILED_WRITE -10
+#define GEN_STATE_FAILED_CONNECT -11
+#define GEN_STATE_FAILED_NOTIFY -12
+#define GEN_STATE_FAILED_INDICATE -13
+#define GEN_STATE_FAILED_NODEVICE -14
+#define GEN_STATE_FAILED_NOREADWRITE -15
+#define GEN_STATE_FAILED_CANCEL -16
+//
+/////////////////////////////////////////////////////
+
+#define BLE_ESP32_MAXNAMELEN 32
+#define BLE_ESP32_MAXALIASLEN 20
+
+
+#define MAX_BLE_DATA_LEN 100
+struct generic_sensor_t {
+ int16_t state;
+ uint32_t opid; // incrementing id so we can find them
+ uint64_t notifytimer;
+
+ // uint8_t cancel;
+ // uint8_t requestType;
+ NimBLEAddress addr;
+ NimBLEUUID serviceUUID;
+ NimBLEUUID characteristicUUID;
+ NimBLEUUID notificationCharacteristicUUID;
+ uint8_t dataToWrite[MAX_BLE_DATA_LEN];
+ uint8_t writelen;
+ uint8_t dataRead[MAX_BLE_DATA_LEN];
+ uint8_t readlen;
+ uint8_t readtruncated;
+ uint8_t dataNotify[MAX_BLE_DATA_LEN];
+ uint8_t notifylen;
+ uint8_t notifytruncated;
+
+ // NOTE!!!: this callback is called DIRECTLY from the operation task, so be careful about cross-thread access of data
+ // if is called after read, so that you can do a read/modify/write operation on a characteristic.
+ // i.e. modify dataToWrite and writelen according to what you see in readData and readlen.
+ // for a normal read, please use the OPCOMPLETE_CALLBACK 'completecallback'
+ // normally null
+ void *readmodifywritecallback; // READ_CALLBACK function, used by external drivers
+
+ void *completecallback; // OPCOMPLETE_CALLBACK function, used by external drivers
+ void *context; // opaque context, used by external drivers, or can be set to a long for MQTT
+};
+
+
+////////////////////////////////////////////////////////////////
+// structure for callbacks from other drivers from advertisements.
+struct ble_advertisment_t {
+ BLEAdvertisedDevice *advertisedDevice; // the full NimBLE advertisment, in case people need MORE info.
+ uint32_t totalCount;
+
+ uint8_t addr[6];
+ uint8_t addrtype;
+ int8_t RSSI;
+ char name[BLE_ESP32_MAXNAMELEN+1];
+};
+
+struct ble_alias_t {
+ uint8_t addr[6];
+ char name[BLE_ESP32_MAXALIASLEN+1];
+};
+
+/*
+This is probabyl what you are looking for:
+ble_gap_addr_t gap_addr;
+gap_addr.addr_type = BLE_GAP_ADDR_TYPE_PUBLIC; //Public address 0x00
+gap_addr.addr_type = BLE_GAP_ADDR_TYPE_RANDOM_STATIC; //Random static address 0x01
+gap_addr.addr_type = BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE; //Random private resolvable address 0x02
+gap_addr.addr_type = BLE_GAP_ADDR_TYPE_RANDOM_PRIVATE_NON_RESOLVABLE; //Random private non-resolvable address 0x03
+*/
+
+#pragma pack( pop ) // byte-aligned structures to read the sensor data
+
+////////////////////////////////////////////////////////////////
+
+///////////////////////////////////////////////////////////////////////
+// External interface to this driver for use by others.
+//
+// callback types to be used by external drivers
+//
+// returns -
+// 0 = let others see this,
+// 1 = I processed this, no need to give it to the next callback
+// 2 = I want this device erased from the scan
+typedef int ADVERTISMENT_CALLBACK(BLE_ESP32::ble_advertisment_t *pStruct);
+// returns - 0 = let others see this, 1 = I processed this, no need to give it to the next callback, or post on MQTT
+typedef int OPCOMPLETE_CALLBACK(BLE_ESP32::generic_sensor_t *pStruct);
+
+// NOTE!!!: this callback is called DIRECTLY from the operation task, so be careful about cross-thread access of data
+// if is called after read, so that you can do a read/modify/write operation on a characteristic.
+typedef int READ_CALLBACK(BLE_ESP32::generic_sensor_t *pStruct);
+
+typedef int SCANCOMPLETE_CALLBACK(NimBLEScanResults results);
+
+// tag is just a name for logging
+void registerForAdvertismentCallbacks(const char *tag, BLE_ESP32::ADVERTISMENT_CALLBACK* pFn);
+void registerForOpCallbacks(const char *tag, BLE_ESP32::OPCOMPLETE_CALLBACK* pFn);
+void registerForScanCallbacks(const char *tag, BLE_ESP32::SCANCOMPLETE_CALLBACK* pFn);
+
+////////////////////////////////////////////////////
+// BLE operations: these are currently 'new'ed and 'delete'ed.
+// in the future, they may be allocated from some constant menory store to avoid fragmentation.
+// so PLEASE don't create or destroy them yourselves.
+// create a new BLE operation.
+int newOperation(BLE_ESP32::generic_sensor_t** op);
+// free a BLE operation - this should be done if you did not call extQueueOperation for some reason
+int freeOperation(BLE_ESP32::generic_sensor_t** op);
+// queue a BLE operation - it will happen some time in the future.
+// Note: you do not need to free an operation once it have been queued. it will be freed by the driver.
+int extQueueOperation(BLE_ESP32::generic_sensor_t** op);
+const char * getStateString(int state);
+///////////////////////////////////////////////////////////////////////
+
+#define USE_NATIVE_LOGGING
+
+
+// a temporay safe logging mechanism. This has a max of 40 chars, and a max of 15 slots per 50ms
+//int SafeAddLog_P(uint32_t loglevel, PGM_P formatP, ...);
+
+static void BLEDiag();
+const char *getAlias(uint8_t *addr);
+//void BLEAliasMqttList();
+void BLEAliasListResp();
+////////////////////////////////////////////////////////////////////////
+// utilities
+// dump a binary to hex
+char * dump(char *dest, int maxchars, const uint8_t *src, int len);
+
+
+
+
+struct BLE_simple_device_t {
+ uint8_t mac[6];
+ uint8_t addrtype;
+ char name[BLE_ESP32_MAXNAMELEN+1];
+ int8_t RSSI;
+ uint64_t lastseen; // last seen us
+ uint16_t maxAge; // maximum observed age of this device
+};
+
+
+
+// this protects our queues, which can be accessed by multiple tasks
+SemaphoreHandle_t BLEOperationsRecursiveMutex;
+SemaphoreHandle_t BLEDevicesMutex;
+
+
+// only run from main thread, because it deletes things that were newed there...
+static void mainThreadOpCallbacks();
+static void mainThreadBLETimeouts();
+
+int addOperation(std::deque *ops, BLE_ESP32::generic_sensor_t** op);
+BLE_ESP32::generic_sensor_t* nextOperation(std::deque *ops);
+std::string BLETriggerResponse(BLE_ESP32::generic_sensor_t *toSend);
+static void BLEscanEndedCB(NimBLEScanResults results);
+static void BLEGenNotifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify);
+
+// this is called from the advert callback, be careful
+void BLEPostAdvert(ble_advertisment_t *Advertisment);
+static void BLEPostMQTTSeenDevices(int type);
+
+static void BLEShow(bool json);
+static void BLEPostMQTT(bool json);
+static void BLEStartOperationTask();
+
+// these are only run from the run task
+static void BLETaskRunCurrentOperation(BLE_ESP32::generic_sensor_t** pCurrentOperation, NimBLEClient **ppClient);
+static void BLETaskRunTaskDoneOperation(BLE_ESP32::generic_sensor_t** op, NimBLEClient **ppClient);
+int BLETaskStartScan(int time);
+
+
+// these are run from main thread
+static int StartBLE(void);
+static int StopBLE(void);
+
+// called from advert callback
+void setDetails(ble_advertisment_t *ad);
+
+#undef EXAMPLE_ADVERTISMENT_CALLBACK
+#undef EXAMPLE_OPERATION_CALLBACK
+
+#ifdef EXAMPLE_ADVERTISMENT_CALLBACK
+int myAdvertCallback(BLE_ESP32::ble_advertisment_t *pStruct);
+#endif
+#ifdef EXAMPLE_OPERATION_CALLBACK
+int myOpCallback(BLE_ESP32::generic_sensor_t *pStruct);
+int myOpCallback2(BLE_ESP32::generic_sensor_t *pStruct);
+#endif
+
+
+// single storage for advert callbacks....
+static ble_advertisment_t BLEAdvertisment;
+
+
+//////////////////////////////////////////////////
+// general variables for running the driver
+TaskHandle_t TasmotaMainTask;
+
+
+static int BLEMasterEnable = 0;
+static int BLEInitState = 0;
+static int BLERunningScan = 0;
+static uint32_t BLEScanCount = 0;
+static uint8_t BLEScanActiveMode = 0;
+static uint32_t BLELoopCount = 0;
+static uint32_t BLEOpCount = 0;
+
+static int BLEPublishDevices = 0; // causes MQTT publish of device list (each scan end)
+static BLEScan* ble32Scan = nullptr;
+bool BLERunning = false;
+// time we last started a scan in uS using esp_timer_get_time();
+// used to setect a scan which did not call back?
+uint64_t BLEScanStartedAt = 0;
+uint64_t BLEScanToEndBefore = 0;
+uint8_t BLEStopScan = 0;
+uint8_t BLEOtaStallBLE = 0;
+uint8_t BLEDebugMode = 0;
+int BLEMaxTaskLoopTime = 120; // we expect the task to NOT take > 120s per loop!!!
+uint64_t BLELastLoopTime = 0;
+int BLEScanTimeS = 20; // scan duraiton in S
+int BLEMaxTimeBetweenAdverts = 120; // we expect an advert at least this frequently, else restart BLE (in S)
+uint64_t BLEScanLastAdvertismentAt = 0;
+uint32_t lastopid = 0; // incrementing uinique opid
+uint32_t BLEResets = 0;
+// controls request of details about one device
+uint8_t BLEDetailsRequest = 0;
+uint8_t BLEDetailsMac[6];
+uint8_t BLEAliasListTrigger = 0;
+// triggers send for ALL operations known about
+uint8_t BLEPostMQTTTrigger = 0;
+int BLEMaxAge = 60*10; // 10 minutes
+int BLEAddressFilter = 3;
+
+
+//////////////////////////////////////////////////
+
+
+// operation being prepared through commands
+BLE_ESP32::generic_sensor_t* prepOperation = nullptr;
+
+// operations which have been queued
+std::deque queuedOperations;
+// operations in progress (at the moment, only one)
+std::deque currentOperations;
+// operations which have completed or failed, ready to send to MQTT
+std::deque completedOperations;
+
+// seen devices
+#define MAX_BLE_DEVICES_LOGGED 80
+std::deque seenDevices;
+std::deque freeDevices;
+
+
+
+// list of registered callbacks for advertisements
+// register using void registerForAdvertismentCallbacks(const char *somename ADVERTISMENT_CALLBACK* pFN);
+std::deque advertismentCallbacks;
+
+std::deque operationsCallbacks;
+
+std::deque scancompleteCallbacks;
+
+
+#ifdef BLE_ESP32_ALIASES
+std::deque aliases;
+#endif
+
+
+/*********************************************************************************************\
+ * constants
+\*********************************************************************************************/
+
+#define D_CMND_BLE "BLE"
+
+const char kBLE_Commands[] PROGMEM = D_CMND_BLE "|"
+ "Period|Adv|Op|Mode|Details|Scan|Alias|Name|Debug|Devices|MaxAge|AddrFilter";
+
+static void CmndBLEPeriod(void);
+static void CmndBLEAdv(void);
+static void CmndBLEOperation(void);
+static void CmndBLEMode(void);
+static void CmndBLEDetails(void);
+static void CmndBLEScan(void);
+static void CmndBLEAlias(void);
+static void CmndBLEName(void);
+static void CmndBLEDebug(void);
+static void CmndBLEDevices(void);
+static void CmndBLEMaxAge(void);
+static void CmndBLEAddrFilter(void);
+
+void (*const BLE_Commands[])(void) PROGMEM = {
+ &BLE_ESP32::CmndBLEPeriod,
+ &BLE_ESP32::CmndBLEAdv,
+ &BLE_ESP32::CmndBLEOperation,
+ &BLE_ESP32::CmndBLEMode,
+ &BLE_ESP32::CmndBLEDetails,
+ &BLE_ESP32::CmndBLEScan,
+ &BLE_ESP32::CmndBLEAlias,
+ &BLE_ESP32::CmndBLEName,
+ &BLE_ESP32::CmndBLEDebug,
+ &BLE_ESP32::CmndBLEDevices,
+ &BLE_ESP32::CmndBLEMaxAge,
+ &BLE_ESP32::CmndBLEAddrFilter
+};
+
+const char *successStates[] PROGMEM = {
+ PSTR("IDLE"), // 0
+ PSTR("START"),
+ PSTR("STARTED"),
+ PSTR("DONEREAD"),
+ PSTR("DONEWRITE"),
+ PSTR("WAITNOTIFY"),
+ PSTR("WAITINDICATE"),
+ PSTR("DONENOTIFIED") // 7
+};
+
+const char *failStates[] PROGMEM = {
+ PSTR("IDLE"), //0
+ PSTR("FAILED"), //-1
+ PSTR("FAILCANTNOTIFYORINDICATE"),
+ PSTR("FAILCANTREAD"),
+ PSTR("FAILCANTWRITE"),
+ PSTR("FAILNOSERVICE"),
+ PSTR("FAILNORWCHAR"), //-6
+ PSTR("FAILNONOTIFYCHAR"),
+ PSTR("FAILNOTIFYTIMEOUT"),
+ PSTR("FAILEREAD"),
+ PSTR("FAILWRITE"),
+ PSTR("FAILCONNECT"),
+ PSTR("FAILNOTIFY"),
+ PSTR("FAILINDICATE"),
+ PSTR("FAILNODEVICE"),
+ PSTR("FAILNOREADWRITE"),
+ PSTR("FAILCANCEL")// -16
+};
+
+const char * getStateString(int state){
+ if ((state >= 0) && (state < sizeof(successStates)/sizeof(*successStates))){
+ return successStates[state];
+ }
+
+ state = -state;
+ if ((state >= 0) && (state < sizeof(failStates)/sizeof(*failStates))){
+ return failStates[state];
+ }
+
+ return PSTR("STATEINVALID");
+}
+
+/*********************************************************************************************\
+ * enumerations
+\*********************************************************************************************/
+
+enum BLE_Commands { // commands useable in console or rules
+ CMND_BLE_PERIOD, // set period like TELE-period in seconds between read-cycles
+ CMND_BLE_ADV, // change advertisment options at runtime
+ CMND_BLE_OP, // connect/read/write/notify operations
+ CMND_BLE_MODE, // change mode of ble - BLE_MODES
+ CMND_BLE_DETAILS, // get details for one device's adverts
+ CMND_BLE_SCAN // Scan control
+ };
+
+enum {
+ BLEModeDisabled = 0, // BLE is disabled
+ BLEModeScanByCommand = 1, // BLE is activeated by commands only
+ BLEModeRegularScan = 2, // BLE is scanning all the time
+} BLE_SCAN_MODES;
+
+// values of BLEAdvertMode
+enum {
+ BLE_NO_ADV_SEND = 0, // driver is silent on MQTT regarding adverts
+ BLE_ADV_TELE = 1, // driver sends a summary at tele period
+ //BLE_ADV_ALL = 2, // driver sends every advert with full data to MQTT
+} BLEADVERTMODE;
+
+
+uint8_t BLEMode = BLEModeRegularScan;
+//uint8_t BLEMode = BLEModeScanByCommand;
+uint8_t BLETriggerScan = 0;
+uint8_t BLEAdvertMode = BLE_ADV_TELE;
+uint8_t BLEdeviceLimitReached = 0;
+
+uint8_t BLEStop = 0;
+uint64_t BLEStopAt = 0;
+
+uint8_t BLERestartTasmota = 0;
+uint8_t BLERestartNimBLE = 0;
+const char *BLE_RESTART_TEAMOTA_REASON_UNKNOWN = PSTR("unknown");
+const char *BLE_RESTART_TEAMOTA_REASON_RESTARTING_BLE_TIMEOUT = PSTR("restarting BLE took > 5s");
+const char *BLE_RESTART_TEAMOTA_REASON_BLE_LOOP_STALLED = PSTR("BLE loop stalled > 120s");
+const char *BLE_RESTART_TEAMOTA_REASON_BLE_DISCONNECT_FAIL = PSTR("BLE disconnect taking > 60s");
+const char *BLERestartTasmotaReason = BLE_RESTART_TEAMOTA_REASON_UNKNOWN;
+
+const char *BLE_RESTART_BLE_REASON_UNKNOWN = PSTR("unknown");
+const char *BLE_RESTART_BLE_REASON_ADVERT_BLE_TIMEOUT = PSTR("no adverts in 120s");
+const char *BLE_RESTART_BLE_REASON_CONN_LIMIT = PSTR("connect failed with connection limit reached");
+const char *BLE_RESTART_BLE_REASON_CONN_EXISTS = PSTR("connect failed with connection exists");
+const char *BLERestartBLEReason = nullptr;
+
+
+/*********************************************************************************************\
+ * log of all devices present
+\*********************************************************************************************/
+
+void initSeenDevices(){
+ /* added dynamically below, but never removed.
+ for (int i = 0; i < MAX_BLE_DEVICES_LOGGED; i++){
+ BLE_ESP32::BLE_simple_device_t* dev = new BLE_ESP32::BLE_simple_device_t;
+ freeDevices.push_back(dev);
+ }
+ */
+ return;
+}
+
+int addSeenDevice(const uint8_t *mac, uint8_t addrtype, const char *name, int8_t RSSI){
+ int res = 0;
+ uint64_t now = esp_timer_get_time();
+ TasAutoMutex localmutex(&BLEDevicesMutex, "BLEAdd");
+
+ int devicefound = 0;
+ // do we already know this device?
+ for (int i = 0; i < seenDevices.size(); i++){
+ if (!memcmp(seenDevices[i]->mac, mac, 6)){
+ seenDevices[i]->lastseen = now;
+ seenDevices[i]->addrtype = addrtype;
+ seenDevices[i]->RSSI = RSSI;
+ if ((!seenDevices[i]->name[0]) && name[0]){
+ strncpy(seenDevices[i]->name, name, sizeof(seenDevices[i]->name));
+ seenDevices[i]->name[sizeof(seenDevices[i]->name)-1] = 0;
+ }
+ devicefound = 1;
+ break;
+ }
+ }
+ if (!devicefound){
+ // if no free slots, add one if we have not reached our limit
+ if (!freeDevices.size()){
+ int total = seenDevices.size();
+ if (total < MAX_BLE_DEVICES_LOGGED){
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_INFO,PSTR("new seendev slot %d"), total);
+#endif
+ BLE_ESP32::BLE_simple_device_t* dev = new BLE_ESP32::BLE_simple_device_t;
+ freeDevices.push_back(dev);
+ } else {
+ // flag we hit the limit
+ BLEdeviceLimitReached ++;
+ if (BLEdeviceLimitReached >= 254){
+ BLEdeviceLimitReached = 254;
+ }
+ }
+ }
+
+ // get a new device from the free list
+ if (freeDevices.size()){
+ BLE_ESP32::BLE_simple_device_t* dev = freeDevices[0];
+ freeDevices.erase(freeDevices.begin());
+ memcpy(dev->mac, mac, 6);
+ strncpy(dev->name, name, sizeof(dev->name));
+ dev->name[sizeof(dev->name)-1] = 0;
+ dev->lastseen = now;
+ dev->addrtype = addrtype;
+ dev->RSSI = RSSI;
+ dev->maxAge = 1;
+ seenDevices.push_back(dev);
+ res = 2; // added
+ }
+ } else {
+ res = 1; // already there
+ }
+ return res;
+}
+
+// remove devices from the seen list by age, and add them to the free list
+// set ageS to 0 to delete all...
+int deleteSeenDevices(int ageS = 0){
+ int res = 0;
+ uint64_t now = esp_timer_get_time();
+ now = now/1000L;
+ now = now/1000L;
+ uint32_t nowS = (uint32_t)now;
+ uint32_t mintime = nowS - ageS;
+
+ {
+ TasAutoMutex localmutex(&BLEDevicesMutex, "BLEDel");
+
+ for (int i = seenDevices.size()-1; i >= 0; i--){
+ BLE_ESP32::BLE_simple_device_t* dev = seenDevices[i];
+ uint64_t lastseen = dev->lastseen/1000L;
+ lastseen = lastseen/1000L;
+ uint32_t lastseenS = (uint32_t) lastseen;
+ uint32_t del_at = lastseenS + ageS;
+ uint32_t devAge = nowS - lastseenS;
+ if (dev->maxAge < devAge){
+ dev->maxAge = devAge;
+ }
+
+ uint8_t filter = 0;
+ if (dev->addrtype > BLEAddressFilter){
+ filter = 1;
+ }
+
+ if ((del_at < nowS) || (ageS == 0) || filter){
+#ifdef BLE_ESP32_DEBUG
+ char addr[20];
+ dump(addr, 20, dev->mac, 6);
+ const char *alias = getAlias(dev->mac);
+ if (!filter){
+ AddLog_P(LOG_LEVEL_INFO,PSTR("delete device %s(%s) by age lastseen %u + maxage %u < now %u."),
+ addr, alias, lastseenS, ageS, nowS);
+ } else {
+ AddLog_P(LOG_LEVEL_INFO,PSTR("delete device %s(%s) by addrtype filter %d > %d."),
+ addr, alias, dev->addrtype, BLEAddressFilter);
+ }
+#endif
+ seenDevices.erase(seenDevices.begin()+i);
+ freeDevices.push_back(dev);
+ res++;
+ }
+ }
+ }
+ if (res){
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_INFO,PSTR("BLE deleted %d devices"), res);
+#endif
+ }
+ return res;
+}
+
+int deleteSeenDevice(uint8_t *mac){
+ int res = 0;
+ TasAutoMutex localmutex(&BLEDevicesMutex, "BLEDel2");
+ for (int i = 0; i < seenDevices.size(); i++){
+ if (!memcmp(seenDevices[i]->mac, mac, 6)){
+ BLE_ESP32::BLE_simple_device_t* dev = seenDevices[i];
+ seenDevices.erase(seenDevices.begin()+i);
+ freeDevices.push_back(dev);
+ res = 1;
+ break;
+ }
+ }
+ return res;
+}
+
+
+void checkDeviceTimouts(){
+ if (BLEMaxAge){
+ deleteSeenDevices(BLEMaxAge);
+ }
+}
+
+
+///////////////////////////////////////////////////////
+// returns age of device or 0. if age IS0, returns 1s
+uint32_t devicePresent(uint8_t *mac){
+ int res = 0;
+ uint64_t now = esp_timer_get_time();
+ now = now/1000L;
+ now = now/1000L;
+ uint32_t nowS = (uint32_t)now;
+
+ TasAutoMutex localmutex(&BLEDevicesMutex, "BLEPRes");
+ for (int i = 0; i < seenDevices.size(); i++){
+ if (!memcmp(seenDevices[i]->mac, mac, 6)){
+ uint64_t lastseen = seenDevices[i]->lastseen/1000L;
+ lastseen = lastseen/1000L;
+ uint32_t lastseenS = (uint32_t) lastseen;
+ uint32_t ageS = nowS-lastseenS;
+ if (!ageS) ageS++;
+ res = ageS;
+ break;
+ }
+ }
+ return res;
+}
+
+
+// the MAX we could expect.
+#define MAX_DEV_JSON_NAME_LEN BLE_ESP32_MAXNAMELEN
+#define MAX_DEV_JSON_RSSI_LEN 3
+#define MAX_DEV_JSON_INDEX_LEN 3
+#define MAX_DEV_JSON_ALIAS_LEN BLE_ESP32_MAXALIASLEN
+// "001122334455":{"i":123,"n":"01234567890123456789","r":-77}\0
+#define MIN_REQUIRED_DEVJSON_LEN \
+ (1+12+1 + 1 + 1 + \
+ +4 + MAX_DEV_JSON_INDEX_LEN \
+ +1 + 4 + MAX_DEV_JSON_NAME_LEN + 2 \
+ +1 + 4 + MAX_DEV_JSON_RSSI_LEN + 2 \
+ +1 + 4 + MAX_DEV_JSON_ALIAS_LEN + 2 \
+ +1 +1 \
+ )
+int getSeenDeviceToJson(int index, BLE_ESP32::BLE_simple_device_t* dev, char **dest, int *maxlen){
+ char *p = *dest;
+ // add 20 to be sure
+ if (*maxlen < MIN_REQUIRED_DEVJSON_LEN+20){
+ return 0;
+ }
+ // add mac as key
+ *((*dest)++) = '"';
+ dump((*dest), 20, dev->mac, 6);
+ (*dest) += 12;
+ *((*dest)++) = '"';
+ *((*dest)++) = ':';
+
+ // add a structure, so we COULD add more than name later
+ *((*dest)++) = '{';
+ *((*dest)++) = '"';
+ *((*dest)++) = 'i'; // index
+ *((*dest)++) = '"';
+ *((*dest)++) = ':';
+ sprintf((*dest), "%d", index);
+ (*dest) += strlen((*dest));
+
+ if (dev->name[0]){
+ *((*dest)++) = ',';
+ *((*dest)++) = '"';
+ *((*dest)++) = 'n';
+ *((*dest)++) = '"';
+ *((*dest)++) = ':';
+ *((*dest)++) = '"';
+ *(*dest) = 0; // must term, else it adds to the *end* of old data!
+ strncat((*dest), dev->name, MAX_DEV_JSON_NAME_LEN);
+ (*dest) += strlen((*dest));
+ *((*dest)++) = '"';
+ }
+ *((*dest)++) = ',';
+ *((*dest)++) = '"';
+ *((*dest)++) = 'r';
+ *((*dest)++) = '"';
+ *((*dest)++) = ':';
+ sprintf((*dest), "%d", dev->RSSI);
+ (*dest) += strlen((*dest));
+
+ const char *alias = getAlias(dev->mac);
+ if (alias && alias[0]){
+ *((*dest)++) = ',';
+ *((*dest)++) = '"';
+ *((*dest)++) = 'a';
+ *((*dest)++) = '"';
+ *((*dest)++) = ':';
+ *((*dest)++) = '"';
+ sprintf((*dest), "%s", alias);
+ (*dest) += strlen((*dest));
+ *((*dest)++) = '"';
+ }
+
+ *((*dest)++) = '}';
+ *maxlen -= (*dest - p);
+ return 1;
+}
+
+
+int nextSeenDev = 0;
+
+int getSeenDevicesToJson(char *dest, int maxlen){
+
+ if ((nextSeenDev == 0) || (nextSeenDev >= seenDevices.size())){
+ nextSeenDev = 0;
+ }
+
+ // deliberate test of SafeAddLog_P from main thread...
+ //AddLog_P(LOG_LEVEL_INFO,PSTR("getSeen %d"), seenDevices.size());
+
+
+ int len;
+ if (!maxlen) return 0;
+ strcpy((dest), ",\"BLEDevices\":{");
+ len = strlen(dest);
+ dest += len;
+ maxlen -= len;
+
+ int added = 0;
+ TasAutoMutex localmutex(&BLEDevicesMutex, "BLEGet");
+
+ snprintf((dest), maxlen-5, "\"total\":%d", seenDevices.size());
+ len = strlen(dest);
+ dest += len;
+ maxlen -= len;
+ added = 1; // trigger ','
+
+ for (; nextSeenDev < seenDevices.size(); nextSeenDev++){
+ if (maxlen > MIN_REQUIRED_DEVJSON_LEN + 3){
+ if (added){
+ *(dest++) = ',';
+ maxlen--;
+ }
+ int res = getSeenDeviceToJson(nextSeenDev, seenDevices[nextSeenDev], &dest, &maxlen);
+ if (res) {
+ added++;
+ } else {
+ if (added){
+ dest--; // reverse out comma it the string did not get added
+ maxlen++;
+ break;
+ }
+ }
+ } else {
+ break;
+ }
+ }
+ *(dest++) = '}';
+ *(dest++) = '}';
+ *(dest++) = 0;
+ int remains = (seenDevices.size() - nextSeenDev);
+ return remains;
+}
+
+
+
+
+/*********************************************************************************************\
+ * Mutex protected logging - max 5 logs of 40 chars
+\*********************************************************************************************/
+
+/*
+#ifdef BLE_ESP32_DEBUG
+ #define MAX_SAFELOG_LEN 40
+ #define MAX_SAFELOG_COUNT 25
+#else
+ #define MAX_SAFELOG_LEN 20
+ #define MAX_SAFELOG_COUNT 5
+#endif
+
+struct safelogdata {
+ int level;
+ char log_data[MAX_SAFELOG_LEN];
+};
+
+std::deque freelogs;
+std::deque filledlogs;
+uint8_t filledlogsOverflows = 0;
+SemaphoreHandle_t SafeLogMutex;
+
+
+void initSafeLog(){
+ TasmotaMainTask = xTaskGetCurrentTaskHandle();
+ SafeLogMutex = xSemaphoreCreateMutex();
+
+ for (int i = 0; i < MAX_SAFELOG_COUNT; i++){
+ BLE_ESP32::safelogdata* logdata = new BLE_ESP32::safelogdata;
+ freelogs.push_back(logdata);
+ }
+}
+
+int SafeAddLog_P(uint32_t loglevel, PGM_P formatP, ...) {
+ TaskHandle_t thistask = xTaskGetCurrentTaskHandle();
+ int added = 0;
+
+ // if the log would not be output do nothing here.
+ if ((loglevel > Settings.weblog_level) &&
+ (loglevel > TasmotaGlobal.seriallog_level) &&
+ (loglevel > Settings.mqttlog_level) &&
+ (loglevel > TasmotaGlobal.syslog_level)){
+ return added;
+ }
+
+ char BLE_temp_log_data[MAX_SAFELOG_LEN];
+ // as these are'expensive', let's not bother unless they are lower than the serial log level
+#ifndef USE_NATIVE_LOGGING
+ xSemaphoreTake(SafeLogMutex, portMAX_DELAY);
+#endif
+ int maxlen = sizeof(BLE_temp_log_data)-3;
+ if (thistask == TasmotaMainTask){
+ maxlen -= 13; // room for "-!MAINTHREAD!"
+ }
+ // assume this is thread safe - it may not be
+ va_list arg;
+ va_start(arg, formatP);
+ vsnprintf_P(BLE_temp_log_data, maxlen, formatP, arg);
+ va_end(arg);
+#ifdef USE_NATIVE_LOGGING
+ AddLog_P(loglevel, PSTR("%s"), BLE_temp_log_data);
+ return 1;
+#else
+ if (thistask == TasmotaMainTask){
+ loglevel = LOG_LEVEL_ERROR;
+ snprintf(BLE_temp_log_data + strlen(BLE_temp_log_data), 13, "-!MAINTHREAD!");
+ xSemaphoreGive(SafeLogMutex); // release mutex
+ AddLog_P(loglevel, PSTR("%s"), BLE_temp_log_data);
+ return 0;
+ }
+
+ if (freelogs.size()){
+ BLE_ESP32::safelogdata* logdata = (freelogs)[0];
+ freelogs.pop_front();
+ logdata->level = loglevel;
+ memcpy(logdata->log_data, BLE_temp_log_data, sizeof(logdata->log_data));
+ filledlogs.push_back(logdata);
+ added = 1;
+ } else {
+ // can't log it?
+ filledlogsOverflows++;
+ }
+ xSemaphoreGive(SafeLogMutex); // release mutex
+ return added;
+#endif
+}
+
+BLE_ESP32::safelogdata* GetSafeLog() {
+ xSemaphoreTake(SafeLogMutex, portMAX_DELAY);
+ if (filledlogs.size()){
+ BLE_ESP32::safelogdata* logdata = (filledlogs)[0];
+ filledlogs.pop_front();
+ xSemaphoreGive(SafeLogMutex); // release mutex
+ return logdata;
+ }
+ xSemaphoreGive(SafeLogMutex); // release mutex
+ return nullptr;
+}
+
+void ReleaseSafeLog(BLE_ESP32::safelogdata* logdata){
+ xSemaphoreTake(SafeLogMutex, portMAX_DELAY);
+ freelogs.push_back(logdata);
+ xSemaphoreGive(SafeLogMutex); // release mutex
+}
+*/
+
+/*********************************************************************************************\
+ * Helper functions
+\*********************************************************************************************/
+
+/**
+ * @brief Simple pair of functions to dump to a hex string.
+ *
+ */
+static const char h[] PROGMEM = "0123456789ABCDEF";
+void hex(char *dest, uint8_t v){
+ *(dest++) = h[(v>>4)&0xf];
+ *(dest++) = h[v&0xf];
+ *(dest) = 0;
+}
+
+// convert from binary to hex.
+// add a '+' on the end if not enough room.
+char * dump(char *dest, int maxchars, const uint8_t *src, int len){
+ int lenmax = (maxchars-1)/2;
+ int actuallen = 0;
+ for (actuallen = 0; actuallen < lenmax && actuallen < len; actuallen++){
+ if (actuallen < lenmax){
+ hex(dest+actuallen*2, src[actuallen]);
+ }
+ }
+ if (actuallen != len){
+ *(dest+(actuallen*2)) = '+';
+ *(dest+(actuallen*2)+1) = 0;
+ }
+ return dest;
+}
+
+// convert from a hex string to binary
+int fromHex(uint8_t *dest, const char *src, int maxlen){
+ int srclen = strlen(src)/2;
+ if (srclen > maxlen){
+ return 0;
+ }
+
+ for (int i = 0; i < srclen; i++){
+ char t[3];
+ if (!isalnum(src[i*2])){
+ return 0;
+ }
+ if (!isalnum(src[i*2 + 1])){
+ return 0;
+ }
+
+ t[0] = src[i*2];
+ t[1] = src[i*2 + 1];
+ t[2] = 0;
+
+ int byte = strtol(t, NULL, 16);
+ *dest++ = byte;
+ }
+ return srclen;
+}
+
+
+/**
+ * @brief Reverse an array of 6 bytes
+ *
+ * @param _mac a byte array of size 6 (typicalliy representing a MAC address)
+ */
+void ReverseMAC(uint8_t _mac[]){
+ uint8_t _reversedMAC[6];
+ for (uint8_t i=0; i<6; i++){
+ _reversedMAC[5-i] = _mac[i];
+ }
+ memcpy(_mac,_reversedMAC, sizeof(_reversedMAC));
+}
+
+
+
+
+/*********************************************************************************************\
+ * Advertisment details
+\*********************************************************************************************/
+
+//ble_advertisment_t BLEAdvertismentDetails;
+#define MAX_ADVERT_DETAILS 200
+char BLEAdvertismentDetailsJson[MAX_ADVERT_DETAILS];
+uint8_t BLEAdvertismentDetailsJsonSet = 0;
+uint8_t BLEAdvertismentDetailsJsonLost = 0;
+
+
+void setDetails(ble_advertisment_t *ad){
+ TasAutoMutex localmutex(&BLEOperationsRecursiveMutex, "BLESetDet");
+ if (BLEAdvertismentDetailsJsonSet){
+ BLEAdvertismentDetailsJsonLost = 1;
+ return;
+ }
+ char *p = BLEAdvertismentDetailsJson;
+ int maxlen = sizeof(BLEAdvertismentDetailsJson);
+ // just in case someone tries to read whilst we are writing
+ BLEAdvertismentDetailsJson[sizeof(BLEAdvertismentDetailsJson)-1] = 0;
+
+ *(p++) = '{';
+ maxlen--;
+ strcpy(p, "\"details\":{");
+ int len = strlen(p);
+ p += len;
+ maxlen -= len;
+
+ strcpy(p, "\"mac\":\"");
+ len = strlen(p);
+ p += len;
+ maxlen -= len;
+ dump(p, 14, ad->addr, 6);
+ len = strlen(p);
+ p += len;
+ maxlen -= len;
+ *(p++) = '\"'; maxlen--;
+
+ if (BLEAdvertismentDetailsJsonLost){
+ BLEAdvertismentDetailsJsonLost = 0;
+ strcpy(p, ",\"lost\":true");
+ len = strlen(p);
+ p += len;
+ maxlen -= len;
+ }
+
+ BLEAdvertisedDevice *advertisedDevice = ad->advertisedDevice;
+
+ uint8_t* payload = advertisedDevice->getPayload();
+ size_t payloadlen = advertisedDevice->getPayloadLength();
+ if (payloadlen && (maxlen > 30)){ // will truncate if not enough space
+ strcpy(p, ",\"p\":\"");
+ p += 6;
+ maxlen -= 6;
+ dump(p, maxlen-10, payload, payloadlen);
+ int len = strlen(p);
+ p += len;
+ maxlen -= len;
+ *(p++) = '\"'; maxlen--;
+ }
+
+ int svcdataCount = advertisedDevice->getServiceDataCount();
+ if (svcdataCount){
+ for (int i = 0; i < svcdataCount; i++){
+ NimBLEUUID UUID = advertisedDevice->getServiceDataUUID(i);//.getNative()->u16.value;
+ std::string ServiceData = advertisedDevice->getServiceData(i);
+
+ size_t ServiceDataLength = ServiceData.length();
+ const uint8_t *serviceData = (const uint8_t *)ServiceData.data();
+
+ //char svcuuidstr[20];
+ std::string strUUID = UUID;
+
+ int svclen = strUUID.length();
+ svclen++; // ,
+ svclen += 3; // "":
+ svclen += ServiceDataLength*2;
+ svclen += 3; // ""}
+
+ if (maxlen -10 > svclen){
+ *(p++) = ',';
+ *(p++) = '\"';
+ strcpy(p, strUUID.c_str());
+ p += strUUID.length();
+ *(p++) = '\"';
+ *(p++) = ':';
+ *(p++) = '\"';
+ dump(p, ServiceDataLength*2+2, (uint8_t*)serviceData, ServiceDataLength);
+ int len = strlen(p);
+ p += len;
+ *(p++) = '\"';
+ maxlen -= len;
+ }
+ }
+ }
+
+ *(p++) = '}'; maxlen--;
+ *(p++) = '}'; maxlen--;
+ *(p++) = 0; maxlen--;
+
+ BLEAdvertismentDetailsJsonSet = 1;
+}
+
+
+// call from main thread only!
+// post advertisment detail if available, then clear.
+void postAdvertismentDetails(){
+// if (TasmotaGlobal.ota_state_flag) return;
+
+ TasAutoMutex localmutex(&BLEOperationsRecursiveMutex, "BLEPostAdd");
+ if (BLEAdvertismentDetailsJsonSet){
+ strncpy(TasmotaGlobal.mqtt_data, BLEAdvertismentDetailsJson, sizeof(TasmotaGlobal.mqtt_data));
+ TasmotaGlobal.mqtt_data[sizeof(TasmotaGlobal.mqtt_data)-1] = 0;
+ BLEAdvertismentDetailsJsonSet = 0;
+ // we got the data, give before MQTT call.
+ localmutex.give();
+ // no retain - this is present devices, not historic
+ MqttPublishPrefixTopic_P(TELE, PSTR("BLE"), 0);
+ } else {
+ }
+}
+
+
+
+/*********************************************************************************************\
+ * Classes
+\*********************************************************************************************/
+
+// does not really take any action
+class BLESensorCallback : public NimBLEClientCallbacks {
+ void onConnect(NimBLEClient* pClient) {
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("onConnect %s"), ((std::string)pClient->getPeerAddress()).c_str());
+#endif
+ }
+ void onDisconnect(NimBLEClient* pClient) {
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("onDisconnect %s"), ((std::string)pClient->getPeerAddress()).c_str());
+#endif
+ }
+ bool onConnParamsUpdateRequest(NimBLEClient* pClient, const ble_gap_upd_params* params) {
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("onConnParamsUpdateRequest %s"), ((std::string)pClient->getPeerAddress()).c_str());
+#endif
+
+// if(params->itvl_min < 24) { /** 1.25ms units */
+// return false;
+// } else if(params->itvl_max > 300) { /** 1.25ms units */
+// return false;
+// } else if(params->latency > 2) { /** Number of intervals allowed to skip */
+// return false;
+// } else if(params->supervision_timeout > 6000) { /** 10ms units */
+// return false;
+// }
+
+/*
+ if(params->itvl_min < 24) { // 1.25ms units
+ return false;
+ } else if(params->itvl_max > 40) { // 1.25ms units
+ return false;
+ } else if(params->latency > 2) { // Number of intervals allowed to skip
+ return false;
+ } else if(params->supervision_timeout > 200) { /// 10ms units
+ return false;
+ }
+
+ return true;
+*/
+ // just always reject thiers, and use ours.
+ return false;
+
+ }
+};
+
+static BLESensorCallback clientCB;
+
+
+class BLEAdvCallbacks: public NimBLEAdvertisedDeviceCallbacks {
+ void onResult(NimBLEAdvertisedDevice* advertisedDevice) {
+ TasAutoMutex localmutex(&BLEOperationsRecursiveMutex, "BLEAddCB");
+ uint64_t now = esp_timer_get_time();
+ BLEScanLastAdvertismentAt = now; // note the time of the last advertisment
+
+ uint32_t totalCount = BLEAdvertisment.totalCount;
+ memset(&BLEAdvertisment, 0, sizeof(BLEAdvertisment));
+ BLEAdvertisment.totalCount = totalCount+1;
+
+ BLEAdvertisment.advertisedDevice = advertisedDevice;
+
+ // keep sign - char seems unsigned
+ int8_t RSSI = (char)advertisedDevice->getRSSI();
+ NimBLEAddress address = advertisedDevice->getAddress();
+
+ BLEAdvertisment.addrtype = address.getType();
+
+ memcpy(BLEAdvertisment.addr, address.getNative(), 6);
+ ReverseMAC(BLEAdvertisment.addr);
+
+ BLEAdvertisment.RSSI = RSSI;
+
+ char addrstr[20];
+ dump(addrstr, 20, BLEAdvertisment.addr, 6);
+
+ // this mjust survive the scope of the callbacks
+ std::string name = "";
+ const char *namestr = name.c_str();
+ if (advertisedDevice->haveName()){
+ name = advertisedDevice->getName();
+ namestr = name.c_str();
+ strncpy(BLEAdvertisment.name, namestr, sizeof(BLEAdvertisment.name)-1);
+ BLEAdvertisment.name[sizeof(BLEAdvertisment.name)-1] = 0;
+ }
+
+
+ // log this device safely
+ if (BLEAdvertisment.addrtype <= BLEAddressFilter){
+ addSeenDevice(BLEAdvertisment.addr, BLEAdvertisment.addrtype, BLEAdvertisment.name, BLEAdvertisment.RSSI);
+ }
+
+ if (BLEDetailsRequest){
+ switch (BLEDetailsRequest){
+ case 1:{ // one advert for one device
+ BLEDetailsRequest = 0; // only one requested if 2, it's a request all
+ if (!memcmp(BLEDetailsMac, BLEAdvertisment.addr, 6)){
+ setDetails(&BLEAdvertisment);
+ }
+ } break;
+ case 2:{ // all adverts for one device - may not get them all
+ if (!memcmp(BLEDetailsMac, BLEAdvertisment.addr, 6)){
+ setDetails(&BLEAdvertisment);
+ }
+ } break;
+ case 3:{ // all adverts for ALL DEVICES - may not get them all
+ // ignore from here on if filtered on addrtype
+ if (BLEAdvertisment.addrtype > BLEAddressFilter){
+ return;
+ }
+ setDetails(&BLEAdvertisment);
+ } break;
+ }
+ }
+
+ // ignore from here on if filtered on addrtype
+ if (BLEAdvertisment.addrtype > BLEAddressFilter){
+ return;
+ }
+
+ // call anyone who asked about advertisements
+ for (int i = 0; i < advertismentCallbacks.size(); i++) {
+ try {
+ ADVERTISMENT_CALLBACK* pFN;
+ pFN = advertismentCallbacks[i];
+ int res = pFN(&BLEAdvertisment);
+
+ // if this callback wants to stop here, then do so.
+ if (1 == res) break;
+
+ // if this callback wants to kill this device
+ if (2 == res) {
+ //BLEScan->erase(address);
+ }
+ } catch(const std::exception& e){
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("exception in advertismentCallbacks"));
+#endif
+ }
+ }
+
+ }
+};
+
+
+static BLEAdvCallbacks BLEScanCallbacks;
+static BLESensorCallback BLESensorCB;
+
+/*********************************************************************************************\
+ * BLE callback functions
+\*********************************************************************************************/
+
+static void BLEscanEndedCB(NimBLEScanResults results){
+
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("Scan ended"));
+#endif
+ for (int i = 0; i < scancompleteCallbacks.size(); i++){
+ try {
+ SCANCOMPLETE_CALLBACK *pFn = scancompleteCallbacks[i];
+ int callbackres = pFn(results);
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("scancompleteCallbacks %d %d"), i, callbackres);
+#endif
+ } catch(const std::exception& e){
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("exception in operationsCallbacks"));
+#endif
+ }
+ }
+
+ BLERunningScan = 2;
+ BLEScanToEndBefore = 0L;
+ BLEScanCount++;
+}
+
+
+///////////////////////////////////////////////////////////////////////
+// !!!!!!!!!!@@@@@@@@@@@@@@@@
+// NOTE: this can callback BEFORE the write is completed.
+// so we should not do any actions against the device if we can help it
+// this COULD be the reason for the BLE stack hanging up....
+///////////////////////////////////////////////////////////////////////
+static void BLEGenNotifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify){
+ NimBLEClient *pRClient;
+
+ if (!pRemoteCharacteristic){
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_DEBUG,PSTR("Notify: no remote char!!??"));
+#endif
+ return;
+ }
+
+
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("Notified length: %u"),length);
+#endif
+ // find the operation this is associated with
+ NimBLERemoteService *pSvc = pRemoteCharacteristic->getRemoteService();
+
+ if (!pSvc){
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("Notify: no remote service found"));
+#endif
+ return;
+ }
+
+ pRClient = pSvc->getClient();
+ if (!pRClient){
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("Notify: no remote client!!??"));
+#endif
+ return;
+ }
+ NimBLEAddress devaddr = pRClient->getPeerAddress();
+
+ generic_sensor_t *thisop = nullptr;
+ {
+ // make sure we are not disturbed
+ TasAutoMutex localmutex(&BLEOperationsRecursiveMutex, "BLENotif");
+
+ for (int i = 0; i < currentOperations.size(); i++){
+ generic_sensor_t *op = currentOperations[i];
+ if (!op){
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("Notify: null op in currentOperations!!??"));
+#endif
+ } else {
+ if (devaddr == op->addr){
+ thisop = op;
+ break;
+ }
+ }
+ }
+ }
+
+ // we'll try without
+ //pRemoteCharacteristic->unsubscribe();
+
+ if (!thisop){
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_DEBUG,PSTR("no op for notify"));
+#endif
+ return;
+ }
+
+ for (int i = 0; i < length && i < sizeof(thisop->dataNotify); i++){
+ thisop->dataNotify[i] = pData[i];
+ }
+ thisop->notifylen = length;
+ if (length > sizeof(thisop->dataNotify)){
+ thisop->notifytruncated = 1;
+ } else {
+ thisop->notifytruncated = 0;
+ }
+ // we will NOT change the state here...
+ // rely on thisop->notifylen as a flag notify is complete
+ //thisop->state = GEN_STATE_NOTIFIED;
+
+ // this triggers our notify complete, either at the end of read/write, or next 1s cycle.
+ thisop->notifytimer = 0;
+
+}
+
+
+
+
+/*********************************************************************************************\
+ * functions for registering callbacks against the driver
+\*********************************************************************************************/
+
+void registerForAdvertismentCallbacks(const char *tag, BLE_ESP32::ADVERTISMENT_CALLBACK* pFn){
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_INFO,PSTR("BLE: registerForAdvertismentCallbacks %s:%x"), tag, (uint32_t) pFn);
+#endif
+ advertismentCallbacks.push_back(pFn);
+}
+
+void registerForOpCallbacks(const char *tag, BLE_ESP32::OPCOMPLETE_CALLBACK* pFn){
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_INFO,PSTR("BLE: registerForOpCallbacks %s:%x"), tag, (uint32_t) pFn);
+#endif
+ operationsCallbacks.push_back(pFn);
+}
+
+void registerForScanCallbacks(const char *tag, BLE_ESP32::SCANCOMPLETE_CALLBACK* pFn){
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_INFO,PSTR("BLE: registerForScnCallbacks %s:%x"), tag, (uint32_t) pFn);
+#endif
+ scancompleteCallbacks.push_back(pFn);
+}
+
+
+/*********************************************************************************************\
+ * init NimBLE
+\*********************************************************************************************/
+static void BLEPreInit(void) {
+ BLEInitState = 0;
+ prepOperation = nullptr;
+}
+
+
+static void BLEInit(void) {
+ if (BLEMode == BLEModeDisabled) return;
+
+ if (BLEInitState) { return; }
+
+ if (TasmotaGlobal.global_state.wifi_down) { return; }
+ if (WiFi.getSleep() == false) {
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_DEBUG,PSTR("BLE: WiFi modem not in sleep mode, BLE cannot start yet"));
+#endif
+ if (0 == Settings.flag3.sleep_normal) {
+ AddLog_P(LOG_LEVEL_DEBUG,PSTR("BLE: About to restart to put WiFi modem in sleep mode"));
+ Settings.flag3.sleep_normal = 1; // SetOption60 - Enable normal sleep instead of dynamic sleep
+ TasmotaGlobal.restart_flag = 2;
+ }
+ return;
+ }
+
+
+ // this is only for testing, does nothin if examples are undefed
+ installExamples();
+ //initSafeLog();
+ initSeenDevices();
+
+ uint64_t now = esp_timer_get_time();
+ BLEScanLastAdvertismentAt = now; // initialise the time of the last advertisment
+ BLELastLoopTime = now;
+
+ BLEInitState = 1;
+
+ // dont start of disabled
+ BLEMasterEnable = Settings.flag5.mi32_enable;
+ if (!BLEMasterEnable) return;
+
+
+ StartBLE();
+
+ return;
+}
+
+/*********************************************************************************************\
+ * Task section
+\*********************************************************************************************/
+
+static void BLEOperationTask(void *pvParameters);
+
+static void BLEStartOperationTask(){
+ if (BLERunning == false){
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_DEBUG,PSTR("%s: Start operations"),D_CMND_BLE);
+#endif
+ BLERunning = true;
+
+ xTaskCreatePinnedToCore(
+ BLE_ESP32::BLEOperationTask, /* Function to implement the task */
+ "BLEOperationTask", /* Name of the task */
+ 4096, /* Stack size in bytes */
+ NULL, /* Task input parameter */
+ 0, /* Priority of the task */
+ NULL, /* Task handle. */
+#ifdef CONFIG_FREERTOS_UNICORE
+ 0); /* Core where the task should run */
+#else
+ 1); /* Core where the task should run */
+#endif
+ }
+}
+
+
+static void BLETaskStopStartNimBLE(NimBLEClient **ppClient, bool start = true){
+
+ if (*ppClient){
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("BLETask:Stopping NimBLE"));
+
+ (*ppClient)->setClientCallbacks(nullptr, false);
+
+ try {
+ if ((*ppClient)->isConnected()){
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_INFO,PSTR("disconnecting connected client"));
+#endif
+ (*ppClient)->disconnect();
+ }
+ NimBLEDevice::deleteClient((*ppClient));
+ (*ppClient) = nullptr;
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_INFO,PSTR("deleted client"));
+#endif
+ } catch(const std::exception& e){
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("Stopping NimBLE:exception in delete client"));
+#endif
+ }
+
+ if (ble32Scan){
+ ble32Scan->setAdvertisedDeviceCallbacks(nullptr,true);
+ ble32Scan->stop();
+ ble32Scan = nullptr;
+ }
+
+ // wait second
+ vTaskDelay(100/ portTICK_PERIOD_MS);
+ NimBLEDevice::deinit(true);
+ }
+ BLERunningScan = 0;
+
+ if (start){
+ AddLog_P(LOG_LEVEL_INFO,PSTR("BLETask:Starting NimBLE"));
+ NimBLEDevice::init("BLE_ESP32");
+
+ *ppClient = NimBLEDevice::createClient();
+ (*ppClient)->setClientCallbacks(&clientCB, false);
+ /** Set initial connection parameters: These settings are 15ms interval, 0 latency, 120ms timout.
+ * These settings are safe for 3 clients to connect reliably, can go faster if you have less
+ * connections. Timeout should be a multiple of the interval, minimum is 100ms.
+ * Min interval: 12 * 1.25ms = 15, Max interval: 12 * 1.25ms = 15, 0 latency, 51 * 10ms = 510ms timeout
+ */
+ (*ppClient)->setConnectionParams(12,12,0,51);
+ /** Set how long we are willing to wait for the connection to complete (seconds), default is 30. */
+ (*ppClient)->setConnectTimeout(15);
+ }
+
+ uint64_t now = esp_timer_get_time();
+
+ // don't restart because of these for a while
+ BLELastLoopTime = now; // initialise the time of the last advertisment
+ BLEScanLastAdvertismentAt = now; // initialise the time of the last advertisment
+
+}
+
+int BLETaskStartScan(int time){
+ if (!ble32Scan) return -1;
+ if (BLEMode == BLEModeDisabled) return -4;
+ // don't scan whilst OTA in progress
+ if (BLEOtaStallBLE) return -5;
+ if (currentOperations.size()) return -3;
+
+ if (BLERunningScan) {
+ // if we hit 2, wait one more time before starting
+ if (BLERunningScan == 2){
+ // wait 100ms
+ vTaskDelay(100/ portTICK_PERIOD_MS);
+ BLERunningScan = 0;
+ }
+ return -2;
+ }
+
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("BLETask: Startscan"));
+#endif
+ //vTaskDelay(500/ portTICK_PERIOD_MS);
+ ble32Scan->setActiveScan(BLEScanActiveMode ? 1: 0);
+
+
+ // seems we could get the callback within the start call....
+ // so set these before starting
+ BLERunningScan = 1;
+ BLEScanStartedAt = esp_timer_get_time();
+ if (BLETriggerScan){
+ time = BLETriggerScan;
+ BLETriggerScan = 0;
+ }
+ ble32Scan->start(time, BLEscanEndedCB, (BLEScanActiveMode == 2)); // 20s scans, restarted when then finish
+ //vTaskDelay(500/ portTICK_PERIOD_MS);
+ return 0;
+}
+
+// this runs one operation
+// if the passed pointer is empty, it tries to get a next one.
+static void BLETaskRunCurrentOperation(BLE_ESP32::generic_sensor_t** pCurrentOperation, NimBLEClient **ppClient){
+ if (!pCurrentOperation) return;
+
+ NimBLEClient *pClient = *ppClient;
+ if (!*pCurrentOperation) {
+ *pCurrentOperation = nextOperation(&queuedOperations);
+ if (*pCurrentOperation){
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("BLETask: new currentOperation"));
+#endif
+ BLEOpCount++;
+ generic_sensor_t* temp = *pCurrentOperation;
+ //this will null it out, so save and restore.
+ addOperation(¤tOperations, pCurrentOperation);
+ *pCurrentOperation = temp;
+ }
+ }
+ if (!*pCurrentOperation) return;
+
+
+
+ // if awaiting notification
+ if ((*pCurrentOperation)->notifytimer){
+ // if it took too long, then disconnect
+ uint64_t now = esp_timer_get_time();
+ uint64_t diff = now - (*pCurrentOperation)->notifytimer;
+ diff = diff/1000;
+ if (diff > 20000){ // 20s
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_DEBUG,PSTR("BLETask: notify timeout"));
+#endif
+ (*pCurrentOperation)->state = GEN_STATE_FAILED_NOTIFYTIMEOUT;
+ (*pCurrentOperation)->notifytimer = 0;
+ }
+ // we can't process any further, because op will be at state readdone or writedone
+ return;
+ }
+
+
+ switch((*pCurrentOperation)->state){
+ case GEN_STATE_WAITINDICATE:
+ case GEN_STATE_WAITNOTIFY:
+ //(*pCurrentOperation)->notifytimer == 0 at this point, so must be done
+ (*pCurrentOperation)->state = GEN_STATE_NOTIFIED;
+ // just stay here until this is removed by the main thread
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("BLETask: notify operation complete"));
+#endif
+ BLE_ESP32::BLETaskRunTaskDoneOperation(pCurrentOperation, ppClient);
+ pClient = *ppClient;
+ return;
+ break;
+ case GEN_STATE_READDONE:
+ case GEN_STATE_WRITEDONE:
+ case GEN_STATE_NOTIFIED: // - may have completed DURING our read/write to get here
+ // just stay here until this is removed by the main thread
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("BLETask: operation complete"));
+#endif
+ BLE_ESP32::BLETaskRunTaskDoneOperation(pCurrentOperation, ppClient);
+ pClient = *ppClient;
+ return;
+ break;
+
+ case GEN_STATE_START:
+ // continue to start the process here.
+ break;
+
+ default:
+ break;
+ }
+
+
+ if (!*pCurrentOperation) return;
+
+ if ((*pCurrentOperation)->state <= GEN_STATE_FAILED){
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("BLETask: op failed %d"), (*pCurrentOperation)->state);
+#endif
+ BLE_ESP32::BLETaskRunTaskDoneOperation(pCurrentOperation, ppClient);
+ pClient = *ppClient;
+ return;
+ }
+
+ if ((*pCurrentOperation)->state != GEN_STATE_START){
+ return;
+ }
+
+ if (pClient->isConnected()){
+ // don't do anything if we are still connected
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_DEBUG,PSTR("BLETask: still connected"));
+#endif
+ return;
+ }
+
+
+ // if we managed to run operations back to back with long connection timeouts,
+ // then we may NOT see advertisements.
+ // so to prevent triggering of the advert timeout restart mechanism,
+ // set the last advert time each time we start an operation
+ uint64_t now = esp_timer_get_time();
+ BLEScanLastAdvertismentAt = now; // initialise the time of the last advertisment
+
+
+ generic_sensor_t* op = *pCurrentOperation;
+
+ int newstate = GEN_STATE_STARTED;
+ op->state = GEN_STATE_STARTED;
+
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("BLETask: attempt connect %s"), ((std::string)op->addr).c_str());
+#endif
+
+ if (!op->serviceUUID.bitSize()){
+ op->state = GEN_STATE_FAILED_NOSERVICE;
+ return;
+ }
+
+ if (pClient->connect(op->addr, true)) {
+
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("connected %s -> getservice"), ((std::string)op->addr).c_str());
+#endif
+ NimBLERemoteService *pService = pClient->getService(op->serviceUUID);
+ int waitNotify = false;
+ int notifystate = 0;
+ op->notifytimer = 0L;
+
+ if (pService != nullptr) {
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("got service"));
+#endif
+ // pre-set to fail if no operations requested
+ //newstate = GEN_STATE_FAILED_NOREADWRITE;
+
+ ///////////////////////////////////////////////////////////////////////
+ // !!!!!!!!!!@@@@@@@@@@@@@@@@
+ // NOTE: Notify callback can happen BEFORE the read/write is completed.
+ // this COULD be the reason for the BLE stack hanging up....
+ ///////////////////////////////////////////////////////////////////////
+
+ // if we have been asked to get a notification
+ if (op->notificationCharacteristicUUID.bitSize()) {
+ NimBLERemoteCharacteristic *pNCharacteristic =
+ pService->getCharacteristic(op->notificationCharacteristicUUID);
+ if (pNCharacteristic != nullptr) {
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("got notify characteristic"));
+#endif
+ op->notifylen = 0;
+ if(pNCharacteristic->canNotify()) {
+ if(pNCharacteristic->subscribe(true, BLE_ESP32::BLEGenNotifyCB)) {
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("subscribe for notify"));
+#endif
+ uint64_t now = esp_timer_get_time();
+ op->notifytimer = now;
+ // this will get changed to read or write,
+ // but here in case it's notify only (can that happen?)
+ notifystate = GEN_STATE_WAITNOTIFY;
+ waitNotify = true;
+ } else {
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("failed subscribe for notify"));
+#endif
+ newstate = GEN_STATE_FAILED_NOTIFY;
+ }
+ } else {
+ if(pNCharacteristic->canIndicate()) {
+ if(pNCharacteristic->subscribe(false, BLE_ESP32::BLEGenNotifyCB)) {
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_DEBUG,PSTR("subscribe for indicate"));
+#endif
+ notifystate = GEN_STATE_WAITINDICATE;
+ uint64_t now = esp_timer_get_time();
+ op->notifytimer = now;
+ waitNotify = true;
+ } else {
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("failed subscribe for indicate"));
+#endif
+ newstate = GEN_STATE_FAILED_INDICATE;
+ }
+ } else {
+ newstate = GEN_STATE_FAILED_CANTNOTIFYORINDICATE;
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("characteristic can't notify"));
+#endif
+ }
+ }
+ } else {
+ newstate = GEN_STATE_FAILED_NONOTIFYCHAR;
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("notify characteristic not found"));
+#endif
+ }
+
+ // force the 'error' of the notify coming in before the read/write for testing
+ //vTaskDelay(1000/ portTICK_PERIOD_MS);
+ } // no supplied notify char is ok
+
+ // this will only happen if you ask for a notify char which is not there?
+ if (!(newstate <= GEN_STATE_FAILED)){
+ if (op->characteristicUUID.bitSize()) {
+ // read or write characteristic - we always need this?
+ NimBLERemoteCharacteristic *pCharacteristic = nullptr;
+
+ pCharacteristic = pService->getCharacteristic(op->characteristicUUID);
+ if (pCharacteristic != nullptr) {
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("got read/write characteristic"));
+#endif
+ newstate = GEN_STATE_FAILED_NOREADWRITE; // overwritten on failure
+
+ if (op->readlen){
+ if(pCharacteristic->canRead()) {
+ std::string value = pCharacteristic->readValue();
+ op->readlen = value.length();
+ memcpy(op->dataRead, value.data(),
+ (op->readlen > sizeof(op->dataRead))?
+ sizeof(op->dataRead):
+ op->readlen);
+ if (op->readlen > sizeof(op->dataRead)){
+ op->readtruncated = 1;
+ } else {
+ op->readtruncated = 0;
+ }
+ if (op->readmodifywritecallback){
+ READ_CALLBACK *pFn = (READ_CALLBACK *)op->readmodifywritecallback;
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("read characteristic with readmodifywritecallback"));
+#endif
+ pFn(op);
+ } else {
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("read characteristic"));
+#endif
+ }
+
+ // only change it to a 'finished' state if we really are
+ if (!waitNotify && !op->writelen) newstate = GEN_STATE_READDONE;
+
+ } else {
+ newstate = GEN_STATE_FAILED_CANTREAD;
+ }
+ }
+ if (op->writelen){
+ if(pCharacteristic->canWrite()) {
+ if (!pCharacteristic->writeValue(op->dataToWrite, op->writelen, true)){
+ newstate = GEN_STATE_FAILED_WRITE;
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_DEBUG,PSTR("characteristic write fail"));
+#endif
+ } else {
+ if (!waitNotify) newstate = GEN_STATE_WRITEDONE;
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("write characteristic"));
+#endif
+ }
+ } else {
+ newstate = GEN_STATE_FAILED_CANTWRITE;
+ }
+ }
+ // print or do whatever you need with the value
+
+ } else {
+ newstate = GEN_STATE_FAILED_NO_RW_CHAR;
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_DEBUG,PSTR("r/w characteristic not found"));
+#endif
+ }
+ }
+ }
+
+
+ // disconnect if not waiting for notify,
+ if (!op->notifytimer){
+ if (waitNotify){
+ vTaskDelay(50/ portTICK_PERIOD_MS);
+ // must have completed during our read/write operation
+ newstate = GEN_STATE_NOTIFIED;
+ }
+ } else {
+ newstate = notifystate;
+ }
+ } else {
+ newstate = GEN_STATE_FAILED_NOSERVICE;
+ // failed to get a service
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_DEBUG,PSTR("failed - svc not on device?"));
+#endif
+ }
+
+ } else { // connect itself failed
+ newstate = GEN_STATE_FAILED_CONNECT;
+#ifdef NIMBLE_CLIENT_HAS_GETRESULT
+ int rc = pClient->getResult();
+
+ switch (rc){
+ case (0x0200+BLE_ERR_CONN_LIMIT ):
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("Hit connection limit? - restarting NimBLE"));
+#endif
+ BLERestartNimBLE = 1;
+ BLERestartBLEReason = BLE_RESTART_BLE_REASON_CONN_LIMIT;
+ break;
+ case (0x0200+BLE_ERR_ACL_CONN_EXISTS):
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("Connection exists? - restarting NimBLE"));
+#endif
+ BLERestartNimBLE = 1;
+ BLERestartBLEReason = BLE_RESTART_BLE_REASON_CONN_EXISTS;
+ break;
+ }
+#endif
+
+ // failed to connect
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_DEBUG,PSTR("failed to connect to device %d"), rc);
+#endif
+ }
+ op->state = newstate;
+}
+
+
+
+// this disconnects from a device if necessary, and then
+// moves the operation from 'currentOperations' to 'completedOperations'.
+
+// for safety's sake, only call from the run task
+static void BLETaskRunTaskDoneOperation(BLE_ESP32::generic_sensor_t** op, NimBLEClient **ppClient){
+ try {
+ if ((*ppClient)->isConnected()){
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("runTaskDoneOperation: disconnecting connected client"));
+#endif
+ (*ppClient)->disconnect();
+ // wait for 1/2 second after disconnect
+ int waits = 0;
+ do {
+ vTaskDelay(500/ portTICK_PERIOD_MS);
+ if (waits) {
+ //(*ppClient)->disconnect();
+ // we will stall here forever!!! - as testing
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("BLE wait discon%d"), waits);
+#endif
+ vTaskDelay(500/ portTICK_PERIOD_MS);
+ }
+ waits++;
+ if (waits == 5){
+ int conn_id = (*ppClient)->getConnId();
+ ble_gap_conn_broken(conn_id, -1);
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("BLE wait discon%d - kill connection"), waits);
+#endif
+ }
+ if (waits == 60){
+ AddLog_P(LOG_LEVEL_ERROR,PSTR(">60s waiting -> BLE Failed, restart Tasmota %d"), waits);
+ BLEStop = 1;
+ BLEStopAt = esp_timer_get_time();
+
+ BLERestartTasmota = 10;
+ BLERestartTasmotaReason = BLE_RESTART_TEAMOTA_REASON_BLE_DISCONNECT_FAIL;
+ break;
+ }
+ } while ((*ppClient)->isConnected());
+ }
+ } catch(const std::exception& e){
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("runTaskDoneOperation: exception in disconnect"));
+#endif
+ }
+
+
+ {
+ TasAutoMutex localmutex(&BLEOperationsRecursiveMutex, "BLEDoneOp");
+
+ // find this operation in currentOperations, and remove it.
+ for (int i = 0; i < currentOperations.size(); i++){
+ if (currentOperations[i]->opid == (*op)->opid){
+ currentOperations.erase(currentOperations.begin() + i);
+ break;
+ }
+ }
+ }
+
+
+ // by adding it to this list, this will cause it to be sent to MQTT
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("runTaskDoneOperation: add to completedOperations"));
+#endif
+ addOperation(&completedOperations, op);
+ return;
+}
+
+
+
+
+// this IS as task.
+// we MAY be able to run a few of these simultaneously, but this is not yet tested.
+// and probably not required. But everything is there to do so....
+static void BLEOperationTask(void *pvParameters){
+
+ BLELoopCount = 0;
+ BLEOpCount = 0;;
+
+ uint32_t timer = 0;
+ // operation which is currently in progress in THIS TASK
+ generic_sensor_t* currentOperation = nullptr;
+
+ NimBLEClient *pClient = nullptr;
+ BLE_ESP32::BLETaskStopStartNimBLE(&pClient);
+
+ for(;;){
+ BLELastLoopTime = esp_timer_get_time();
+ BLELoopCount++;
+
+ BLE_ESP32::BLETaskRunCurrentOperation(¤tOperation, &pClient);
+
+ // start a scan if possible
+ if ((BLEMode == BLEModeRegularScan) || (BLETriggerScan)){
+ BLEScan* lastScan = ble32Scan;
+ ble32Scan = NimBLEDevice::getScan();
+ if (lastScan != ble32Scan){
+ //ble32Scan->setInterval(70);
+ //ble32Scan->setWindow(50);
+ ble32Scan->setInterval(0x40);
+ ble32Scan->setWindow(0x20);
+ ble32Scan->setAdvertisedDeviceCallbacks(&BLEScanCallbacks,true);
+ }
+
+ BLE_ESP32::BLETaskStartScan(20);
+ }
+
+ if (BLEStopScan){
+ ble32Scan->stop();
+ BLEStopScan = 0;
+ }
+
+ // come around every 1/10s
+ vTaskDelay(100/ portTICK_PERIOD_MS);
+
+ if (BLEStop == 1){
+ break;
+ }
+
+ if (BLERestartNimBLE){
+ BLERestartNimBLE = 0;
+ BLERestartTasmota = 10;
+ BLERestartTasmotaReason = BLE_RESTART_TEAMOTA_REASON_RESTARTING_BLE_TIMEOUT;
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("BLETask: Restart NimBLE - restart Tasmota in 10 if not complt"));
+ BLE_ESP32::BLETaskStopStartNimBLE(&pClient);
+ BLERestartTasmotaReason = BLE_RESTART_TEAMOTA_REASON_UNKNOWN;
+ BLERestartTasmota = 0;
+ BLEResets ++;
+ }
+ }
+
+ BLE_ESP32::BLETaskStopStartNimBLE(&pClient, false);
+
+ // wait 1/10 second
+ vTaskDelay(100/ portTICK_PERIOD_MS);
+
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_DEBUG,PSTR("BLEOperationTask: Left task"));
+#endif
+ deleteSeenDevices();
+
+ BLEStop = 2;
+ BLERunning = false;
+ vTaskDelete( NULL );
+}
+
+
+
+
+
+/***********************************************************************\
+ * Regular Tasmota called functions
+ *
+\***********************************************************************/
+void BLEEvery50mSecond(){
+/* if (BLEAliasListTrigger){
+ BLEAliasListTrigger = 0;
+ BLEAliasMqttList();
+ }*/
+ postAdvertismentDetails();
+}
+
+
+
+/**
+ * @brief Main loop of the driver, "high level"-loop
+ *
+ */
+
+static void BLEEverySecond(bool restart){
+
+ BLEDiag();
+
+ checkDeviceTimouts();
+
+
+ if (Settings.flag5.mi32_enable != BLEMasterEnable){
+ if (Settings.flag5.mi32_enable){
+ if (StartBLE()){
+ BLEMasterEnable = Settings.flag5.mi32_enable;
+ }
+ } else {
+ if (StopBLE()){
+ BLEMasterEnable = Settings.flag5.mi32_enable;
+ }
+ }
+ AddLog_P(LOG_LEVEL_INFO,PSTR("BLE: MasterEnable->%d"), BLEMasterEnable);
+ }
+
+
+ // check for application callbacks here.
+ // this may remove complete items.
+ BLE_ESP32::mainThreadOpCallbacks();
+
+ // post any MQTT data if we completed anything in the last second
+ if (completedOperations.size()){
+ BLE_ESP32::BLEPostMQTT(true); // send only completed
+ }
+
+ // request send of ALL oeprations prepped, queued, in progress -
+ // in separate MQTT messages.
+ if (BLEPostMQTTTrigger){
+ BLEPostMQTTTrigger = 0;
+ BLE_ESP32::BLEPostMQTT(false); // show all operations, not just completed
+ }
+
+ if (BLEPublishDevices){
+ BLEPostMQTTSeenDevices(BLEPublishDevices);
+ BLEPublishDevices = 0;
+ }
+
+ // we have been asked to restart in this many seconds....
+ if (BLERestartTasmota){
+ BLERestartTasmota--;
+ // 2 seconds to go, post to BLE topic on MQTT our reason
+ if (BLERestartTasmota == 2){
+ if (!BLERestartTasmotaReason) BLERestartTasmotaReason = BLE_RESTART_TEAMOTA_REASON_UNKNOWN;
+ snprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), PSTR("{\"reboot\":\"%s\"}"), BLERestartTasmotaReason);
+ MqttPublishPrefixTopic_P(TELE, PSTR("BLE"), Settings.flag.mqtt_sensor_retain);
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("BLE Failure! Restarting Tasmota in %d seconds because %s"), BLERestartTasmota, BLERestartTasmotaReason);
+ }
+
+ if (!BLERestartTasmota){
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("BLE Failure! Restarting Tasmota because %s"), BLERestartTasmotaReason);
+ // just a normal restart
+ TasmotaGlobal.restart_flag = 1;
+ }
+ }
+
+ if (BLERestartBLEReason){ // just use the ptr as the trigger to send MQTT
+ snprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), PSTR("{\"blerestart\":\"%s\"}"), BLERestartBLEReason);
+ MqttPublishPrefixTopic_P(TELE, PSTR("BLE"), Settings.flag.mqtt_sensor_retain);
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("BLE Failure! Restarting BLE Stack because %s"), BLERestartBLEReason);
+ BLERestartBLEReason = nullptr;
+ }
+
+
+ BLE_ESP32::mainThreadBLETimeouts();
+ if (!BLEMasterEnable){
+ return;
+ }
+
+}
+
+
+
+
+
+/*********************************************************************************************\
+ * Operations queue functions - all to do with read/write and notify for a device
+\*********************************************************************************************/
+
+// this retrievs the next operation from the passed list, and removes it from the list.
+// or returns null if none.
+generic_sensor_t* nextOperation(std::deque *ops){
+ generic_sensor_t* op = nullptr;
+ if (ops->size()){
+ TasAutoMutex localmutex(&BLEOperationsRecursiveMutex, "BLENExtOp");
+ op = (*ops)[0];
+ ops->pop_front();
+ }
+ return op;
+}
+
+// this adds an operation to the end of passed list.
+// it also sets the op pointer passed to null.
+int addOperation(std::deque *ops, generic_sensor_t** op){
+ int res = 0;
+ {
+ TasAutoMutex localmutex(&BLEOperationsRecursiveMutex, "BLEAddOp");
+ if (ops->size() < 10){
+ ops->push_back(*op);
+ *op = nullptr;
+ res = 1;
+ }
+ }
+ if (res){
+ //AddLog_P(LOG_LEVEL_DEBUG,PSTR("added operation"));
+ } else {
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("BLE op - no room"));
+ }
+ return res;
+}
+
+
+int newOperation(BLE_ESP32::generic_sensor_t** op){
+ if (!op) {
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("BLE op inv in newOperation"));
+ return 0;
+ }
+
+ BLE_ESP32::generic_sensor_t *o = new BLE_ESP32::generic_sensor_t;
+
+ // clear to zeros, but not the NimBLE classes
+ o->state = 0;
+ o->opid = 0; // incrementing id so we can find them
+ o->notifytimer = 0L;
+ //uint8_t writeRead[MAX_BLE_DATA_LEN];
+ o->writelen = 0;
+ //uint8_t dataRead[MAX_BLE_DATA_LEN];
+ o->readlen = 0;
+ o->readtruncated = 0;
+ //uint8_t dataNotify[MAX_BLE_DATA_LEN];
+ o->notifylen = 0;
+ o->notifytruncated = 0;
+ o->readmodifywritecallback = nullptr; // READ_CALLBACK function, used by external drivers
+ o->completecallback = nullptr; // OPCOMPLETE_CALLBACK function, used by external drivers
+ o->context = nullptr; // opaque context, used by external drivers, or can be set to a long for MQTT
+
+ (*op) = o;
+ return 1;
+}
+
+int freeOperation(BLE_ESP32::generic_sensor_t** op){
+ if (!op) {
+ return 0;
+ }
+
+ delete (*op);
+ (*op) = nullptr;
+ return 1;
+}
+
+
+int extQueueOperation(BLE_ESP32::generic_sensor_t** op){
+ if (!op || !(*op)) {
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("BLE: op invalid"));
+ return 0;
+ }
+ (*op)->state = GEN_STATE_START; // trigger request later
+ (*op)->opid = lastopid++;
+
+ int res = addOperation(&queuedOperations, op);
+ if (!res){
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("extQueueOperation: op added id %d failed"), (lastopid-1));
+ }
+ return res;
+}
+
+
+/*********************************************************************************************\
+ * BLE Name alisaes
+\*********************************************************************************************/
+#ifdef BLE_ESP32_ALIASES
+int addAlias( uint8_t *addr, char *name){
+ if (!addr || !name){
+ return 0;
+ }
+
+ int count = aliases.size();
+ // replace name for existing address
+ for (int i = 0; i < count; i++){
+ if (!memcmp(aliases[i]->addr, addr, 6)){
+ strncpy(aliases[i]->name, name, sizeof(aliases[i]->name));
+ aliases[i]->name[sizeof(aliases[i]->name)-1] = 0;
+ return 2;
+ }
+ }
+
+ // replace addr for existing name
+ for (int i = 0; i < count; i++){
+ if (!strcmp(aliases[i]->name, name)){
+ memcpy(aliases[i]->addr, addr, 6);
+ return 2;
+ }
+ }
+
+ BLE_ESP32::ble_alias_t *alias = new BLE_ESP32::ble_alias_t;
+ memcpy(alias->addr, addr, 6);
+ strncpy(alias->name, name, sizeof(alias->name));
+ alias->name[sizeof(alias->name)-1] = 0;
+ aliases.push_back(alias);
+ return 1;
+}
+#endif
+
+/**
+ * @brief Remove all colons from null terminated char array
+ *
+ * @param _string Typically representing a MAC-address like AA:BB:CC:DD:EE:FF
+ */
+void stripColon(char* _string){
+ uint32_t _length = strlen(_string);
+ uint32_t _index = 0;
+ while (_index < _length) {
+ char c = _string[_index];
+ if(c==':'){
+ memmove(_string+_index,_string+_index+1,_length-_index);
+ }
+ _index++;
+ }
+ _string[_index] = 0;
+}
+
+
+//////////////////////////////////////////////////
+// use this for address interpretaton from string
+// it looks for aliases, and converts AABBCCDDEEFF and AA:BB:CC:DD:EE:FF
+int getAddr(uint8_t *dest, char *src){
+ if (!dest || !src){
+ return 0;
+ }
+#ifdef BLE_ESP32_ALIASES
+ for (int i = 0; i < aliases.size(); i++){
+ if (!strcmp(aliases[i]->name, src)){
+ memcpy(dest, aliases[i]->addr, 6);
+ return 2; //was an alias
+ }
+ }
+#endif
+
+ char tmp[12+5+1];
+ if (strlen(src) == 12+5){
+ strcpy(tmp, src);
+ stripColon(tmp);
+ src = tmp;
+ }
+
+ int len = fromHex(dest, src, 6);
+ if (len == 6){
+ return 1;
+ }
+ // not found
+ return 0;
+}
+
+static const char *noAlias = PSTR("");
+
+////////////////////////////////////////////
+// use to display the alias name if required
+const char *getAlias(uint8_t *addr){
+ if (!addr){
+ return noAlias;
+ }
+#ifdef BLE_ESP32_ALIASES
+ for (int i = 0; i < aliases.size(); i++){
+ if (!memcmp(aliases[i]->addr, addr, 6)){
+ return aliases[i]->name; //was an alias
+ }
+ }
+#endif
+ return noAlias;
+}
+
+
+/*********************************************************************************************\
+ * Highest level BLE task control functions
+\*********************************************************************************************/
+
+static int StartBLE(void) {
+ if (BLEStop != 1){
+ BLE_ESP32::BLEStartOperationTask();
+ return 1;
+ }
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("StartBLE - wait as BLEStop==1"));
+
+ return 0;
+}
+
+static int StopBLE(void){
+ if (BLERunning){
+ if (BLEStop != 1){
+ BLEStop = 1;
+ AddLog_P(LOG_LEVEL_INFO,PSTR("StopBLE - BLEStop->1"));
+ BLEStopAt = esp_timer_get_time();
+ // give a little time for it to stop.
+ vTaskDelay(1000/ portTICK_PERIOD_MS);
+ return 1;
+ }
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("StopBLE - wait as BLEStop==1"));
+ return 0;
+ } else {
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("StopBLE - was not running"));
+ return 1;
+ }
+}
+
+
+/*********************************************************************************************\
+ * Commands
+\*********************************************************************************************/
+
+static void CmndBLEPeriod(void) {
+ //ResponseCmndNumber(BLE.period);
+ ResponseCmndDone();
+}
+
+
+//////////////////////////////////////////////////////////////
+// Determine what to do with advertismaents
+// BLEAdv0 -> suppress MQTT about devices found
+// BLEAdv1 -> send MQTT about devices found after each scan
+void CmndBLEAdv(void){
+ switch(XdrvMailbox.index){
+ case 0:
+ BLEAdvertMode = BLE_ESP32::BLE_NO_ADV_SEND;
+ break;
+ case 1:
+ BLEAdvertMode = BLE_ESP32::BLE_ADV_TELE;
+ break;
+ /*case 2:
+ BLEAdvertMode = BLE_ADV_ALL;
+ break;*/
+ case 3:
+ break;
+ }
+
+ ResponseCmndNumber(BLEAdvertMode);
+}
+
+
+//////////////////////////////////////////////////////////////
+// Determine what to do with advertismaents
+// BLEAdv0 -> suppress MQTT about devices found
+// BLEAdv1 -> send MQTT about devices found after each scan
+void CmndBLEDebug(void){
+ BLEDebugMode = XdrvMailbox.index;
+ ResponseCmndNumber(BLEDebugMode);
+}
+
+void CmndBLEDevices(void){
+ switch(XdrvMailbox.index){
+ case 0:{
+ // clear devices delete
+ deleteSeenDevices();
+ } break;
+ case 1:{
+ BLEPublishDevices = 2; // mqtt publish as 'STAT'
+ } break;
+ }
+ ResponseCmndDone();
+}
+
+void CmndBLEMaxAge(void){
+ switch(XdrvMailbox.index){
+ case 1:{
+ if (XdrvMailbox.data_len > 0) {
+ BLEMaxAge = XdrvMailbox.payload;
+ }
+ } break;
+ }
+ ResponseCmndIdxNumber(BLEMaxAge);
+ if (BLEMaxAge) deleteSeenDevices(BLEMaxAge);
+}
+
+void CmndBLEAddrFilter(void){
+ switch(XdrvMailbox.index){
+ case 1:{
+ if (XdrvMailbox.data_len > 0) {
+ BLEAddressFilter = XdrvMailbox.payload;
+ }
+ } break;
+ }
+ ResponseCmndIdxNumber(BLEAddressFilter);
+}
+
+
+//////////////////////////////////////////////////////////////
+// Scan options
+// BLEScan0 -> do a scan now if BLEMode == BLEModeScanByCommand
+// BLEScan0 -> do a scan now if BLEMode == BLEModeScanByCommand for timesec seconds
+// BLEScan1 0 -> Scans are passive
+// BLEScan1 1 -> Scans are active
+// more options could be added...
+void CmndBLEScan(void){
+ switch(XdrvMailbox.index){
+ case 0:{
+ if (XdrvMailbox.data_len > 0) {
+ BLEScanActiveMode = XdrvMailbox.payload;
+ ResponseCmndNumber(BLEScanActiveMode);
+ } else {
+ ResponseCmndChar("Invalid");
+ }
+ } break;
+
+ case 1: // do a manual scan now
+ switch (BLEMode){
+ case BLEModeScanByCommand: {
+ int time = 20;
+ if (XdrvMailbox.data_len > 0) {
+ time = XdrvMailbox.payload;
+ if (time < 2) time = 2;
+ if (time > 40) time = 40;
+ }
+ BLETriggerScan = time;
+ ResponseCmndNumber(time); // -ve for fail for a few reasons
+ } break;
+ case BLEModeDisabled:
+ ResponseCmndChar("BLEDisabled");
+ break;
+ case BLEModeRegularScan:
+ ResponseCmndChar("BLEActive");
+ break;
+ }
+ break;
+ default:
+ ResponseCmndChar("Invalid");
+ break;
+ }
+}
+
+
+//////////////////////////////////////////////////////////////
+// Determine what to do with advertismaents
+// BLEMode0 -> kill BLE completely
+// BLEMode1 -> start BLE, scan by command
+// BLEMode2 -> start BLE, regular scan
+void CmndBLEMode(void){
+ int val = XdrvMailbox.index;
+ if (XdrvMailbox.data_len > 0) {
+ val = XdrvMailbox.payload;
+ }
+
+ switch(val){
+ case BLEModeDisabled:{
+ if (BLEMode != BLEModeDisabled){
+ BLEMode = BLEModeDisabled;
+ StopBLE();
+ ResponseCmndChar("StoppingBLE");
+ } else {
+ ResponseCmndChar("Disabled");
+ }
+ } break;
+ case BLEModeScanByCommand:{
+ uint64_t now = esp_timer_get_time();
+ switch(BLEMode){
+ // when changing from regular to by command,
+ // stop the scan next loop
+ case BLEModeRegularScan: {
+ BLEMode = BLEModeScanByCommand;
+ BLEStopScan = 1;
+ ResponseCmndChar("BLEStopScan");
+ } break;
+ case BLEModeDisabled: {
+ BLEMode = BLEModeScanByCommand;
+ StartBLE();
+ ResponseCmndChar("StartingBLE");
+ } break;
+ case BLEModeScanByCommand:{
+ ResponseCmndChar("BLERunning");
+ } break;
+ }
+ BLEScanLastAdvertismentAt = now; // note the time of the last advertisment
+ } break;
+ case BLEModeRegularScan:{
+ uint64_t now = esp_timer_get_time();
+ switch(BLEMode){
+ case BLEModeDisabled: {
+ BLEMode = BLEModeRegularScan;
+ StartBLE();
+ ResponseCmndChar("StartingBLE");
+ } break;
+ case BLEModeScanByCommand:{
+ BLEMode = BLEModeRegularScan;
+ ResponseCmndChar("BLEEnableScan");
+ } break;
+ case BLEModeRegularScan:{
+ BLEMode = BLEModeRegularScan;
+ ResponseCmndChar("BLERunning");
+ } break;
+ }
+ BLEScanLastAdvertismentAt = now; // note the time of the last advertisment
+ } break;
+ default:
+ ResponseCmndChar("InvalidIndex");
+ break;
+ }
+}
+
+
+//////////////////////////////////////////
+// get more drtails for a single MAC address
+// BLEDetails0 -> don;t send me anything
+// BLEDetails1 -> send me details for once
+// BLEDetails2 -> send me details for every advert if possible
+// example: BLEDetails1 001A22092C9A
+// details look like:
+// MQT: tele/tasmota_esp32/BLE = {"details":{"mac":"001A22092C9A","p":"0C0943432D52542D4D2D424C450CFF0000000000000000000000"}}
+// and incliude mac, complete advert payload, plus optional ,"lost":true if an advert was not captured because MQTT we already
+// had one waiting to be sent
+void CmndBLEDetails(void){
+ switch(XdrvMailbox.index){
+ case 0:
+ BLEDetailsRequest = 0;
+ ResponseCmndNumber(BLEDetailsRequest);
+ break;
+
+ case 1:
+ case 2:{
+ BLEDetailsRequest = 0;
+ if (getAddr(BLEDetailsMac, XdrvMailbox.data)){
+ BLEDetailsRequest = XdrvMailbox.index;
+ ResponseCmndIdxChar(XdrvMailbox.data);
+ } else {
+ ResponseCmndChar("InvalidMac");
+ }
+ } break;
+
+ case 3:{
+ BLEDetailsRequest = XdrvMailbox.index;
+ ResponseCmndNumber(BLEDetailsRequest);
+ } break;
+
+ default:
+ ResponseCmndChar("InvalidIndex");
+ break;
+ }
+}
+
+
+void CmndBLEAlias(void){
+#ifdef BLE_ESP32_ALIASES
+ int op = XdrvMailbox.index;
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("Alias %d %s"), op, XdrvMailbox.data);
+
+ int res = -1;
+ switch(op){
+ case 0:
+ case 1:{
+ char *p = strtok(XdrvMailbox.data, " ,=");
+ bool trigger = false;
+ int added = 0;
+
+ do {
+ if (!p || !(*p)){
+ break;
+ }
+
+ uint8_t addr[6];
+ char *mac = p;
+ int len = fromHex(addr, p, sizeof(addr));
+ if (len != 6){
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("Alias invalid mac %s"), p);
+ ResponseCmndChar("invalidmac");
+ return;
+ }
+
+ p = strtok(nullptr, " ,=");
+ char *name = p;
+ if (!p || !(*p)){
+ int i = 0;
+ for (i = 0; i < aliases.size(); i++){
+ BLE_ESP32::ble_alias_t *alias = aliases[i];
+ if (!memcmp(alias->addr, addr, 6)){
+ aliases.erase(aliases.begin() + i);
+ BLEAliasListResp();
+ return;
+ }
+ }
+ ResponseCmndChar("invalidmac");
+ return;
+ }
+
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("Add Alias mac %s = name %s"), mac, p);
+ if (addAlias( addr, name )){
+ added++;
+ }
+ p = strtok(nullptr, " ,=");
+ } while (p);
+
+ if (added){
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("Added %d Aliases"), added);
+ BLEAliasListResp();
+ } else {
+ BLEAliasListResp();
+ }
+ return;
+ } break;
+ case 2:{ // clear
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("BLEAlias clearing %d"), aliases.size());
+ for (int i = aliases.size()-1; i >= 0; i--){
+ BLE_ESP32::ble_alias_t *alias = aliases[i];
+ aliases.pop_back();
+ delete alias;
+ }
+ BLEAliasListResp();
+ return;
+ } break;
+ }
+ ResponseCmndChar("invalididx");
+#endif
+}
+
+
+// SET the BLE name for a device -
+// uses s:1800 c:2a00 and writes name to DEVICE
+void CmndBLEName(void) {
+ char *p = strtok(XdrvMailbox.data, " ");
+
+ if (!p || !(*p)){
+ ResponseCmndIdxChar(PSTR("invalid"));
+ return;
+ }
+
+ uint8_t addrbin[6];
+ int addrres = BLE_ESP32::getAddr(addrbin, p);
+ NimBLEAddress addr(addrbin);
+
+ if (addrres){
+ if (addrres == 2){
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("BLE addr used alias: %s"), p);
+ }
+
+//#ifdef EQ3_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_INFO,PSTR("BLE cmd addr: %s -> %s"), p, addr.toString().c_str());
+//#endif
+ } else {
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("BLE addr invalid: %s"), p);
+ ResponseCmndIdxChar(PSTR("invalidaddr"));
+ return;
+ }
+
+ BLE_ESP32::generic_sensor_t *op = nullptr;
+ // ALWAYS use this function to create a new one.
+ int res = BLE_ESP32::newOperation(&op);
+ if (!res){
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("Can't get a newOperation"));
+ ResponseCmndChar(PSTR("FAIL"));
+ return;
+ } else {
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("got a newOperation from BLE"));
+ }
+
+ op->addr = addr;
+ op->serviceUUID = NimBLEUUID("1800");
+ op->characteristicUUID = NimBLEUUID("2A00");
+
+ // get next part of cmd
+ char *name = strtok(nullptr, " ");
+ bool write = false;
+ if (name && *name){
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("write name %s"), name);
+ op->writelen = strlen(name);
+ memcpy(op->dataToWrite, name, op->writelen);
+ write = true;
+ } else {
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("read name"));
+ op->readlen = 1;
+ }
+
+ res = BLE_ESP32::extQueueOperation(&op);
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("queue res %d"), res);
+ if (!res){
+ // if it fails to add to the queue, do please delete it
+ BLE_ESP32::freeOperation(&op);
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("Failed to queue new operation - deleted"));
+ ResponseCmndChar(PSTR("QUEUEFAIL"));
+ return;
+ }
+
+ if (write){
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG, PSTR("BLE: will write name"));
+ } else {
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG, PSTR("BLE: will read name"));
+ }
+ ResponseCmndDone();
+ return;
+}
+
+
+
+//////////////////////////////////////////////////////////////////////////
+// Command to cause BLE read/write/notify operations to be run.
+//////////////////////////////////////////////////////////////////////////
+
+// we expect BLEOp0 - poll state
+// we expect BLEOp1 m:MAC s:svc
+// we expect BLEOp2 trigger queue of op. return is opid
+
+// returns: Done|FailCreate|FailNoOp|FailQueue|InvalidIndex|
+
+// BLEop0/1/2 will cause an MQTT send of ops currently known.
+// on op complete/op fail, a MQTT send is triggered of all known ops, and the completed/failed op removed.
+
+// example:
+// BLEOp1 M:001A22092CDB s:3e135142-654f-9090-134a-a6ff5bb77046 c:3fa4585a-ce4a-3bad-db4b-b8df8179ea09 w:03 n:d0e8434d-cd29-0996-af41-6c90f4e0eb2a go
+// requests write of 03, and request wait for notify.
+
+// You may queue up operations. they are currently processed serially.
+void CmndBLEOperation(void){
+
+ int op = XdrvMailbox.index;
+
+ //AddLog_P(LOG_LEVEL_INFO,PSTR("op %d"), op);
+
+ int res = -1;
+
+ // if in progress, only op 0 or 11 are allowed
+ switch(op) {
+ case 0:
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_INFO,PSTR("preview"));
+#endif
+ BLEPostMQTTTrigger = 1;
+ break;
+ case 1: {
+ if (prepOperation){
+ BLE_ESP32::freeOperation(&prepOperation);
+ }
+ int opres = BLE_ESP32::newOperation(&prepOperation);
+ if (!opres){
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("Could not create new operation"));
+#endif
+ ResponseCmndChar("FailCreate");
+ return;
+ }
+ // expect m:MAC s:svc
+ // < > are optional
+ char *p = strtok(XdrvMailbox.data, " ,");
+ bool trigger = false;
+
+ while (p){
+ switch(*p | 0x20){
+ case 'm':{
+ uint8_t addr[6];
+ if (getAddr(addr, p+2)){
+ prepOperation->addr = NimBLEAddress(addr);
+ } else {
+ prepOperation->addr = NimBLEAddress();
+ }
+ } break;
+ case 's':{
+ prepOperation->serviceUUID = NimBLEUUID(p+2);
+ } break;
+ case 'c':
+ prepOperation->characteristicUUID = NimBLEUUID(p+2);
+ //strncpy(prepOperation->characteristicStr, p+2, sizeof(prepOperation->characteristicStr)-1);
+ break;
+ case 'n':
+ prepOperation->notificationCharacteristicUUID = NimBLEUUID(p+2);
+ //strncpy(prepOperation->notificationCharacteristicStr, p+2, sizeof(prepOperation->notificationCharacteristicStr)-1);
+ break;
+ case 'w':
+ prepOperation->writelen = fromHex(prepOperation->dataToWrite, p+2, sizeof(prepOperation->dataToWrite));
+ break;
+ case 'u': // 'unique' context for this request
+ prepOperation->context = (void *)atoi(p+2);
+ break;
+ case 'r':
+ prepOperation->readlen = 1;
+ break;
+ case 'g':
+ if ((*(p+1))|0x20 == 'o'){
+ trigger = true;
+ }
+ break;
+ }
+
+ p = strtok(nullptr, " ,");
+ }
+
+ if (trigger){
+ int u = (int)prepOperation->context;
+ int opres = BLE_ESP32::extQueueOperation(&prepOperation);
+ if (!opres){
+ // NOTE: prepOperation will NOT have been deleted.
+ // this means you could retry with another BLEOp10.
+ // it WOULD be deleted if you sent another BELOP1
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("Could not queue new operation"));
+#endif
+ ResponseCmndChar("FailQueue");
+ return;
+ } else {
+ // NOTE: prepOperation has been set to null if we queued sucessfully.
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_INFO,PSTR("Operations queued:%d"), queuedOperations.size());
+#endif
+ char temp[40];
+ sprintf(temp, "{\"opid\":%d,\"u\":%d}", lastopid-1, u);
+ Response_P(S_JSON_COMMAND_XVALUE, XdrvMailbox.command, temp);
+ // don't do this here... overwrites response
+ //BLE_ESP32::BLEPostMQTT(false);
+ return;
+ }
+ } else {
+ ResponseCmndChar("Prepared");
+ //BLE_ESP32::BLEPostMQTT(false);
+ return;
+ }
+ } break;
+
+ case 2: {
+ if (!prepOperation) {
+ ResponseCmndChar("FailNoOp");
+ return;
+ }
+ //prepOperation->requestType = atoi(XdrvMailbox.data);
+ int u = (int)prepOperation->context;
+ int opres = BLE_ESP32::extQueueOperation(&prepOperation);
+ if (!opres){
+ // NOTE: prepOperation will NOT have been deleted.
+ // this means you could retry with another BLEOp10.
+ // it WOULD be deleted if you sent another BELOP1
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("Could not queue new operation"));
+#endif
+ ResponseCmndChar("FailQueue");
+ } else {
+ // NOTE: prepOperation has been set to null if we queued sucessfully.
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_INFO,PSTR("Operations queued:%d"), queuedOperations.size());
+#endif
+ char temp[40];
+ sprintf(temp, "{\"opid\":%d,\"u\":%d}", lastopid-1, u);
+ Response_P(S_JSON_COMMAND_XVALUE, XdrvMailbox.command, temp);
+ }
+ return;
+ } break;
+
+ default:
+ ResponseCmndChar("InvalidIndex");
+ return;
+ }
+
+ ResponseCmndDone();
+ return;
+}
+
+
+/*********************************************************************************************\
+ * Presentation
+\*********************************************************************************************/
+static void BLEPostMQTTSeenDevices(int type) {
+ int remains = 0;
+ nextSeenDev = 0;
+
+ memset(TasmotaGlobal.mqtt_data, 0, sizeof(TasmotaGlobal.mqtt_data));
+ ResponseTime_P(PSTR(""));
+ int timelen = strlen(TasmotaGlobal.mqtt_data);
+ char *dest = TasmotaGlobal.mqtt_data + timelen;
+ int maxlen = (sizeof(TasmotaGlobal.mqtt_data)-20) - timelen;
+
+// if (!TasmotaGlobal.ota_state_flag){
+ do {
+ remains = getSeenDevicesToJson(dest, maxlen);
+ // no retain - this is present devices, not historic
+ if (type == 1){
+ MqttPublishPrefixTopic_P(TELE, PSTR("BLE"), 0);
+ } else {
+ MqttPublishPrefixTopic_P(STAT, PSTR("BLE"), 0);
+ }
+ } while (remains);
+// }
+}
+
+static void BLEPostMQTT(bool onlycompleted) {
+// if (TasmotaGlobal.ota_state_flag) return;
+
+
+ if (prepOperation || completedOperations.size() || queuedOperations.size() || currentOperations.size()){
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_INFO,PSTR("some to show"));
+#endif
+ if (prepOperation && !onlycompleted){
+ std::string out = BLETriggerResponse(prepOperation);
+ snprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), PSTR("%s"), out.c_str());
+ MqttPublishPrefixTopic_P(TELE, PSTR("BLE"), Settings.flag.mqtt_sensor_retain);
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_INFO,PSTR("prep sent %s"), out.c_str());
+#endif
+ }
+
+ if (queuedOperations.size() && !onlycompleted){
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_INFO,PSTR("queued %d"), queuedOperations.size());
+#endif
+ for (int i = 0; i < queuedOperations.size(); i++){
+ TasAutoMutex localmutex(&BLEOperationsRecursiveMutex, "BLEPost1");
+
+ generic_sensor_t *toSend = queuedOperations[i];
+ if (!toSend) {
+ break;
+ } else {
+ std::string out = BLETriggerResponse(toSend);
+ localmutex.give();
+ snprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), PSTR("%s"), out.c_str());
+ MqttPublishPrefixTopic_P(TELE, PSTR("BLE"), Settings.flag.mqtt_sensor_retain);
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_INFO,PSTR("queued %d sent %s"), i, out.c_str());
+#endif
+ //break;
+ }
+ }
+ }
+
+ if (currentOperations.size() && !onlycompleted){
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_INFO,PSTR("current %d"), currentOperations.size());
+#endif
+ for (int i = 0; i < currentOperations.size(); i++){
+ TasAutoMutex localmutex(&BLEOperationsRecursiveMutex, "BLEPost2");
+ generic_sensor_t *toSend = currentOperations[i];
+ if (!toSend) {
+ break;
+ } else {
+ std::string out = BLETriggerResponse(toSend);
+ localmutex.give();
+ snprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), PSTR("%s"), out.c_str());
+ MqttPublishPrefixTopic_P(TELE, PSTR("BLE"), Settings.flag.mqtt_sensor_retain);
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_INFO,PSTR("curr %d sent %s"), i, out.c_str());
+#endif
+ //break;
+ }
+ }
+ }
+
+ if (completedOperations.size()){
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_INFO,PSTR("completed %d"), completedOperations.size());
+#endif
+ do {
+ generic_sensor_t *toSend = nextOperation(&completedOperations);
+ if (!toSend) {
+ break; // break from while loop
+ } else {
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("BLE:completedOperation removed"));
+#endif
+ std::string out = BLETriggerResponse(toSend);
+ snprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), PSTR("%s"), out.c_str());
+ MqttPublishPrefixTopic_P(TELE, PSTR("BLE"), Settings.flag.mqtt_sensor_retain);
+ // we alreayd removed this from the queues, so now delete
+ delete toSend;
+ //break;
+ }
+ //break;
+ } while (1);
+ }
+ } else {
+ snprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), PSTR("{\"BLEOperation\":{}}"));
+ MqttPublishPrefixTopic_P(TELE, PSTR("BLE"), Settings.flag.mqtt_sensor_retain);
+ }
+}
+
+static void mainThreadBLETimeouts() {
+ uint64_t now = esp_timer_get_time();
+
+ if (!BLERunning){
+ BLELastLoopTime = now; // initialise the time of the last advertisment
+ BLEScanLastAdvertismentAt = now; // initialise the time of the last advertisment
+ return;
+ }
+
+ if (BLEStop == 1){
+ if (BLEStopAt + 30L*1000L*1000L < now){ // if asked to stop > 30s ago...
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("BLE: Stop Timeout - restart Tasmota"));
+ BLERestartTasmota = 2;
+ BLEStopAt = now;
+ }
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("BLE: Awaiting BLEStop"));
+ return;
+ }
+
+ // if no adverts for 120s, and BLE is running, retsart NimBLE.
+ // belt and braces....
+ uint64_t adTimeout = ((uint64_t)BLEMaxTimeBetweenAdverts)*1000L*1000L;
+ if (BLEScanLastAdvertismentAt + adTimeout < now){
+ BLEScanLastAdvertismentAt = now; // initialise the time of the last advertisment
+ BLERestartNimBLE = 1;
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("BLE: scan stall? no adverts > 120s, restart BLE"));
+
+ BLERestartBLEReason = BLE_RESTART_BLE_REASON_ADVERT_BLE_TIMEOUT;
+ }
+
+ // if stuck and have not done task for 120s, something is seriously wrong.
+ // restart Tasmota completely. (belt and braces)
+ uint64_t bleLoopTimeout = ((uint64_t)BLEMaxTaskLoopTime)*1000L*1000L;
+ if (BLELastLoopTime + bleLoopTimeout < now){
+ BLELastLoopTime = now; // initialise the time of the last advertisment
+ BLERestartTasmota = 10;
+ AddLog_P(LOG_LEVEL_DEBUG,PSTR("BLE: BLETask stall > 120s, restart Tasmota in 10s"));
+ BLERestartTasmotaReason = BLE_RESTART_TEAMOTA_REASON_BLE_LOOP_STALLED;
+ }
+}
+
+
+static void mainThreadOpCallbacks() {
+ if (completedOperations.size()){
+ //AddLog_P(LOG_LEVEL_INFO,PSTR("completed %d"), completedOperations.size());
+ TasAutoMutex localmutex(&BLEOperationsRecursiveMutex, "BLEMainCB");
+
+ // find this operation in currentOperations, and remove it.
+ // in reverse so we can erase them safely.
+ for (int i = completedOperations.size()-1; i >= 0 ; i--){
+ generic_sensor_t *op = completedOperations[i];
+
+ bool callbackres = false;
+
+ if (op->completecallback){
+ try {
+ OPCOMPLETE_CALLBACK *pFn = (OPCOMPLETE_CALLBACK *)(op->completecallback);
+ callbackres = pFn(op);
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("op->completecallback %d"), callbackres);
+#endif
+ } catch(const std::exception& e){
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("exception in op->completecallback"));
+#endif
+ }
+ }
+
+ if (!callbackres){
+ for (int i = 0; i < operationsCallbacks.size(); i++){
+ try {
+ OPCOMPLETE_CALLBACK *pFn = operationsCallbacks[i];
+ callbackres = pFn(op);
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("operationsCallbacks %d %d"), i, callbackres);
+#endif
+ if (callbackres){
+ break; // this callback ate the op.
+ }
+ } catch(const std::exception& e){
+#ifdef BLE_ESP32_DEBUG
+ AddLog_P(LOG_LEVEL_ERROR,PSTR("exception in operationsCallbacks"));
+#endif
+ }
+ }
+ }
+
+ // if some callback told us not to send on MQTT, then remove from completed and delete the data
+ if (callbackres){
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("callbackres true -> delete op"));
+#endif
+ completedOperations.erase(completedOperations.begin() + i);
+ delete op;
+ }
+ }
+ }
+}
+
+
+static void BLEShow(bool json)
+{
+ if (json){
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_INFO,PSTR("show json %d"),json);
+#endif
+ uint32_t totalCount = BLEAdvertisment.totalCount;
+ uint32_t deviceCount = seenDevices.size();
+
+ ResponseAppend_P(PSTR(",\"BLE\":{\"scans\":%u,\"adverts\":%u,\"devices\":%u,\"resets\":%u}"), BLEScanCount, totalCount, deviceCount, BLEResets);
+ }
+#ifdef USE_WEBSERVER
+ else {
+ //WSContentSend_PD(HTTP_MI32, i+1,stemp,MIBLEsensors.size());
+ }
+#endif // USE_WEBSERVER
+
+}
+
+/*void BLEAliasMqttList(){
+ ResponseTime_P(PSTR(",\"BLEAlias\":["));
+ for (int i = 0; i < aliases.size(); i++){
+ if (i){
+ ResponseAppend_P(PSTR(","));
+ }
+ char tmp[20];
+ ToHex_P(aliases[i]->addr,6,tmp,20,0);
+ ResponseAppend_P(PSTR("{\"%s\":\"%s\"}"), tmp, aliases[i]->name);
+ }
+ ResponseAppend_P(PSTR("]}"));
+ MqttPublishPrefixTopic_P(TELE, PSTR("BLE"), Settings.flag.mqtt_sensor_retain);
+}*/
+
+void BLEAliasListResp(){
+ Response_P(PSTR("{\"BLEAlias\":{"));
+ for (int i = 0; i < aliases.size(); i++){
+ if (i){
+ ResponseAppend_P(PSTR(","));
+ }
+ char tmp[20];
+ ToHex_P(aliases[i]->addr,6,tmp,20,0);
+ ResponseAppend_P(PSTR("\"%s\":\"%s\""), tmp, aliases[i]->name);
+ }
+ ResponseAppend_P(PSTR("}}"));
+}
+
+
+static void BLEDiag()
+{
+ uint32_t totalCount = BLEAdvertisment.totalCount;
+ uint32_t deviceCount = seenDevices.size();
+#ifdef BLE_ESP32_DEBUG
+ if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_INFO,PSTR("BLE:scans:%u,advertisements:%u,devices:%u,resets:%u,BLEStop:%d,BLERunning:%d,BLERunningScan:%d,BLELoopCount:%u,BLEOpCount:%u"), BLEScanCount, totalCount, deviceCount, BLEResets, BLEStop, BLERunning, BLERunningScan, BLELoopCount, BLEOpCount);
+#endif
+}
+
+/**
+ * @brief creates a JSON representing a single operation.
+ *
+ */
+std::string BLETriggerResponse(generic_sensor_t *toSend){
+ char temp[100];
+ if (!toSend) return "";
+ std::string out = "{\"BLEOperation\":{\"opid\":\"";
+ sprintf(temp, "%d", toSend->opid); // note only 10 long!
+ out = out + temp;
+/* out = out + "\",\"state\":\"";
+ sprintf(t, "%d", toSend->state);
+ out = out + t;*/
+ out = out + "\",\"stat\":\"";
+ sprintf(temp, "%d", toSend->state);
+ out = out + temp;
+ out = out + "\",\"state\":\"";
+ out = out + getStateString(toSend->state);
+
+ if (toSend->addr != NimBLEAddress()){
+ out = out + "\",\"MAC\":\"";
+ uint8_t addrrev[6];
+ memcpy(addrrev, toSend->addr.getNative(), 6);
+ ReverseMAC(addrrev);
+ dump(temp, 13, addrrev, 6);
+ out = out + temp;
+ }
+ if (toSend->context){
+ out = out + "\",\"u\":\"";
+ sprintf(temp, "%d", (int32_t)toSend->context);
+ out = out + temp;
+ }
+ if (toSend->serviceUUID.bitSize()){
+ out = out + "\",\"svc\":\"";
+ out = out + toSend->serviceUUID.toString();
+ }
+ if (toSend->characteristicUUID.bitSize()){
+ out = out + "\",\"char\":\"";
+ out = out + toSend->characteristicUUID.toString();
+ }
+ if (toSend->notificationCharacteristicUUID.bitSize()){
+ out = out + "\",\"notifychar\":\"";
+ out = out + toSend->notificationCharacteristicUUID.toString();
+ }
+ out = out + "\"";
+ if (toSend->readlen){
+ dump(temp, 99, toSend->dataRead, toSend->readlen);
+ if (toSend->readtruncated){
+ strcat(temp, "+");
+ }
+ out = out + ",\"read\":\"";
+ out = out + temp;
+ out = out + "\"";
+ }
+ if (toSend->writelen){
+ dump(temp, 99, toSend->dataToWrite, toSend->writelen);
+ out = out + ",\"write\":\"";
+ out = out + temp;
+ out = out + "\"";
+ }
+ if (toSend->notifylen){
+ dump(temp, 99, toSend->dataNotify, toSend->notifylen);
+ if (toSend->notifytruncated){
+ strcat(temp, "+");
+ }
+ out = out + ",\"notify\":\"";
+ out = out + temp;
+ out = out + "\"";
+ }
+ out = out + "}}";
+ return out;
+}
+
+#ifdef USE_WEBSERVER
+
+#define WEB_HANDLE_BLE "ble"
+#define D_CONFIGURE_BLE "Configure BLE"
+#define D_BLE_PARAMETERS "Bluetooth Settings"
+#define D_MQTT_BLE_ENABLE "Enable Bluetooth"
+#define D_MQTT_BLE_ACTIVESCAN "Enable Active Scan(*)"
+#define D_BLE_DEVICES "Devices Seen"
+
+const char HTTP_BTN_MENU_BLE[] PROGMEM =
+ "
";
+
+const char HTTP_FORM_BLE[] PROGMEM =
+ "