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 = + "
 " D_BLE_PARAMETERS " " + "
" + "

" + "

" + "

items marked (*) are not stored in config

"; + + +const char HTTP_BLE_DEV_STYLE[] PROGMEM = "th, td { padding-left:5px; }"; +const char HTTP_BLE_DEV_START[] PROGMEM = + "
 " D_BLE_DEVICES " " + ""; +const char HTTP_BLE_DEV[] PROGMEM = + ""; +const char HTTP_BLE_DEV_END[] PROGMEM = + "
"; + +void HandleBleConfiguration(void) +{ + +#ifdef BLE_ESP32_DEBUG + AddLog_P(LOG_LEVEL_DEBUG, PSTR("HandleBleConfiguration")); +#endif + + if (!HttpCheckPriviledgedAccess()) { +#ifdef BLE_ESP32_DEBUG + AddLog_P(LOG_LEVEL_DEBUG, PSTR("!HttpCheckPriviledgedAccess()")); +#endif + return; + } + +#ifdef BLE_ESP32_DEBUG + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_CONFIGURE_BLE)); +#endif + + char tmp[20]; + WebGetArg("en", tmp, sizeof(tmp)); + +#ifdef BLE_ESP32_DEBUG + if (BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG, PSTR("arg en is %s"), tmp); +#endif + + if (Webserver->hasArg("save")) { +#ifdef BLE_ESP32_DEBUG + AddLog_P(LOG_LEVEL_DEBUG, PSTR("BLE SETTINGS SAVE")); +#endif + Settings.flag5.mi32_enable = Webserver->hasArg("e0"); // + BLEScanActiveMode = (Webserver->hasArg("e1")?1:0); // + + SettingsSaveAll(); + HandleConfiguration(); + return; + } +#ifdef BLE_ESP32_DEBUG + AddLog_P(LOG_LEVEL_DEBUG, PSTR("!SAVE")); +#endif + char str[TOPSZ]; + + WSContentStart_P(PSTR(D_CONFIGURE_BLE)); + WSContentSendStyle_P(HTTP_BLE_DEV_STYLE); + //WSContentSendStyle(); + WSContentSend_P(HTTP_FORM_BLE, + (Settings.flag5.mi32_enable) ? " checked" : "", + (BLEScanActiveMode) ? " checked" : "" + ); + WSContentSend_P(HTTP_FORM_END); + + + { + //TasAutoMutex localmutex(&BLEOperationsRecursiveMutex, "BLEConf"); + int number = seenDevices.size(); + if (number){ + WSContentSend_P(HTTP_BLE_DEV_START); + uint64_t now = esp_timer_get_time(); + now = now/1000L; + now = now/1000L; + uint32_t nowS = (uint32_t)now; + + for (int i = 0; i < number; i++){ + BLE_ESP32::BLE_simple_device_t* dev = seenDevices[i]; + char addr[20]; + dump(addr, 20, dev->mac, 6); + uint8_t addrtype = dev->addrtype; + const char *alias = getAlias(dev->mac); + uint64_t lastseen = dev->lastseen/1000L; + lastseen = lastseen/1000L; + uint32_t lastseenS = (uint32_t) lastseen; + uint32_t ageS = nowS-lastseenS; + + WSContentSend_P(HTTP_BLE_DEV, addr, addrtype, alias, dev->name, dev->RSSI, ageS, dev->maxAge); + } + WSContentSend_P(HTTP_BLE_DEV_END); + } + } + + WSContentSpaceButton(BUTTON_CONFIGURATION); + WSContentStop(); + +} +#endif + + +} // end namespace BLE_ESP32 + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +int ExtStopBLE(){ + AddLog_P(LOG_LEVEL_INFO, PSTR("Stopping BLE if active - upgrade starting?")); + BLE_ESP32::BLEMode = BLE_ESP32::BLEModeDisabled; + BLE_ESP32::StopBLE(); + return 0; +} + +bool Xdrv52(uint8_t function) +{ + //if (!Settings.flag5.mi32_enable) { return false; } // SetOption115 - Enable ESP32 BLE BLE + + bool result = false; + + if (FUNC_INIT == function){ + BLE_ESP32::BLEPreInit(); + } + + if (!BLE_ESP32::BLEInitState) { + if (function == FUNC_EVERY_250_MSECOND) { + BLE_ESP32::BLEInit(); + } + return result; + } + switch (function) { + case FUNC_EVERY_50_MSECOND: + BLE_ESP32::BLEEvery50mSecond(); + //############################# DEBUG + TasmotaGlobal.seriallog_timer = 0; + break; + case FUNC_EVERY_SECOND: + BLE_ESP32::BLEEverySecond(false); + break; + case FUNC_COMMAND: + result = DecodeCommand(BLE_ESP32::kBLE_Commands, BLE_ESP32::BLE_Commands); + break; + case FUNC_JSON_APPEND: + BLE_ESP32::BLEShow(1); + break; + + // next second, we will publish to our MQTT topic. + case FUNC_AFTER_TELEPERIOD: + BLE_ESP32::BLEPublishDevices = 1; // mqtt publish as 'TELE' + break; + +#ifdef USE_WEBSERVER + case FUNC_WEB_ADD_BUTTON: + WSContentSend_P(BLE_ESP32::HTTP_BTN_MENU_BLE); + break; + case FUNC_WEB_ADD_HANDLER: + WebServer_on(PSTR("/" WEB_HANDLE_BLE), BLE_ESP32::HandleBleConfiguration); + break; + + case FUNC_WEB_SENSOR: + BLE_ESP32::BLEShow(0); + break; +#endif // USE_WEBSERVER + } + return result; +} + + + +/*********************************************************************************************\ + * Example Advertisment callback +\*********************************************************************************************/ + +#ifdef EXAMPLE_ADVERTISMENT_CALLBACK + +// match ADVERTISMENT_CALLBACK +int myAdvertCallback(BLE_ESP32::ble_advertisment_t *pStruct) { + + // indicate others can also hear this + // to say 'I want this exclusively', return true. + return 0; + +} +#endif +/*********************************************************************************************\ + * End of Example Advertisment callback +\*********************************************************************************************/ + + +/*********************************************************************************************\ + * Example Operations callbacks +\*********************************************************************************************/ +#ifdef EXAMPLE_OPERATION_CALLBACK + +// this one is used to demonstrate processing ALL operations +int myOpCallback(BLE_ESP32::generic_sensor_t *pStruct){ + AddLog_P(LOG_LEVEL_INFO,PSTR("myOpCallback")); + return 0; // return true to block MQTT broadcast +} + +// this one is used to demonstrate processing of ONE specific operation +int myOpCallback2(BLE_ESP32::generic_sensor_t *pStruct){ + AddLog_P(LOG_LEVEL_INFO,PSTR("myOpCallback2")); + return 1; // return true to block MQTT broadcast +} +#endif +/*********************************************************************************************\ + * End of Example Operations callbacks +\*********************************************************************************************/ + +void installExamples(){ +#ifdef EXAMPLE_ADVERTISMENT_CALLBACK + BLE_ESP32::registerForAdvertismentCallbacks((const char *)"test myOpCallback", &myAdvertCallback); +#endif + +#ifdef EXAMPLE_OPERATION_CALLBACK + BLE_ESP32:registerForOpCallbacks((const char *)"test myOpCallback", &myOpCallback); +#endif +} + +void sendExample(){ +#ifdef EXAMPLE_OPERATION_CALLBACK + BLE_ESP32::generic_sensor_t *op = nullptr; + int res = BLE_ESP32::newOperation(&op); + if (!res){ + AddLog_P(LOG_LEVEL_ERROR,PSTR("Could not create new operation")); + return; + } + strncpy(op->MAC, "001A22092EE0", sizeof(op->MAC)); + strncpy(op->serviceStr, "3e135142-654f-9090-134a-a6ff5bb77046", sizeof(op->serviceStr)); + strncpy(op->characteristicStr, "3fa4585a-ce4a-3bad-db4b-b8df8179ea09", sizeof(op->characteristicStr)); + strncpy(op->notificationCharacteristicStr, "d0e8434d-cd29-0996-af41-6c90f4e0eb2a", sizeof(op->notificationCharacteristicStr)); + op->writelen = BLE_ESP32::fromHex(op->dataToWrite, (char *)"4040", sizeof(op->dataToWrite)); + + // this op will call us back on complete or failure. + op->completecallback = (void *)myOpCallback2; + res = BLE_ESP32::extQueueOperation(&op); + 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")); + return; + } + +#endif +} + + + +#endif +#endif // ESP32 + + diff --git a/tasmota/xsns_52_ibeacon.ino b/tasmota/xsns_52_ibeacon.ino index 723446700..8fb924d4e 100755 --- a/tasmota/xsns_52_ibeacon.ino +++ b/tasmota/xsns_52_ibeacon.ino @@ -17,6 +17,10 @@ along with this program. If not, see . */ +// for testing of BLE_ESP32, we remove this completely, and instead add the modified xsns_52_ibeacon_BLE_ESP32.ino +// in the future this may be more fine-grained, e.g. to allow hm17 for this, and BLE-ESP32 for other +#ifndef USE_BLE_ESP32 + #ifdef USE_IBEACON #define XSNS_52 52 @@ -279,13 +283,16 @@ void ESP32Init() { if (TasmotaGlobal.global_state.wifi_down) { return; } - TasmotaGlobal.wifi_stay_asleep = true; if (WiFi.getSleep() == false) { - AddLog_P(LOG_LEVEL_DEBUG,PSTR("%s: Put WiFi modem in sleep mode"),"BLE"); - WiFi.setSleep(true); // Sleep + if (0 == Settings.flag3.sleep_normal) { + AddLog_P(LOG_LEVEL_DEBUG,PSTR("%s: About to restart to put WiFi modem in sleep mode"),"BLE"); + Settings.flag3.sleep_normal = 1; // SetOption60 - Enable normal sleep instead of dynamic sleep + TasmotaGlobal.restart_flag = 2; + } + return; } - AddLog_P(LOG_LEVEL_DEBUG,PSTR("%s: Initializing Bluetooth..."),"BLE"); + AddLog_P(LOG_LEVEL_DEBUG,PSTR("%s: Initializing Blueetooth..."),"BLE"); if (!ESP32BLE.mode.init) { NimBLEDevice::init(""); @@ -1012,3 +1019,5 @@ bool Xsns52(byte function) } #endif // USE_IBEACON + +#endif \ No newline at end of file diff --git a/tasmota/xsns_52_ibeacon_BLE_ESP32.ino b/tasmota/xsns_52_ibeacon_BLE_ESP32.ino new file mode 100644 index 000000000..687bf05cf --- /dev/null +++ b/tasmota/xsns_52_ibeacon_BLE_ESP32.ino @@ -0,0 +1,952 @@ +/* + xsns_52_ibeacon.ino - Support for HM17 BLE Module + ibeacon reader on Tasmota + + Copyright (C) 2020 Gerhard Mutz and Theo Arends + + 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 . +*/ + +// for testing of BLE_ESP32, we remove xsns_52_ibeacon.ino completely, and instead add this modified xsns_52_ibeacon_BLE_ESP32.ino +// in the future this may be more fine-grained, e.g. to allow hm17 for this, and BLE-ESP32 for other +#ifdef USE_BLE_ESP32 + +#ifdef USE_IBEACON_ESP32 + +#ifdef USE_IBEACON + +#define XSNS_52 52 + +// keyfob expires after N seconds +#define IB_TIMEOUT_INTERVAL 30 +// does a passive scan every N seconds +#define IB_UPDATE_TIME_INTERVAL 10 + +// should be in Settings +#if 1 + uint8_t ib_upd_interval,ib_tout_interval; + #define IB_UPDATE_TIME ib_upd_interval + #define IB_TIMEOUT_TIME ib_tout_interval +#else + #undef IB_UPDATE_TIME + #undef IB_TIMEOUT_TIME + #define IB_UPDATE_TIME Settings.ib_upd_interval + #define IB_TIMEOUT_TIME Settings.ib_tout_interval +#endif + +char ib_mac[14]; + + + struct { + union { + struct { + uint32_t init:1; + }; + uint32_t all = 0; + } mode; + } ESP32BLE; + + void *beaconmutex = nullptr; + + #define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00) >> 8) + (((x)&0xFF) << 8)) + +#else + + #include + + #define TMSBSIZ52 512 + + #define HM17_BAUDRATE 9600 + + #define IBEACON_DEBUG + + // use this for Version 110 + #define HM17_V110 + + TasmotaSerial *IBEACON_Serial = nullptr; + + uint8_t hm17_found,hm17_cmd,hm17_flag; + + #ifdef IBEACON_DEBUG + uint8_t hm17_debug=0; + #endif + + // 78 is max serial response + #define HM17_BSIZ 128 + char hm17_sbuffer[HM17_BSIZ]; + uint8_t hm17_sindex,hm17_result,hm17_scanning,hm17_connecting; + uint32_t hm17_lastms; + + enum {HM17_TEST,HM17_ROLE,HM17_IMME,HM17_DISI,HM17_IBEA,HM17_SCAN,HM17_DISC,HM17_RESET,HM17_RENEW,HM17_CON}; + #define HM17_SUCESS 99 + +#endif + +struct IBEACON { + char FACID[8]; + char UID[32]; + char MAJOR[4]; + char MINOR[4]; + char PWR[2]; + char MAC[12]; + char RSSI[4]; +#ifdef USE_IBEACON_ESP32 + char NAME[16]; +#endif +}; + +#ifdef USE_IBEACON_ESP32 + #define MAX_IBEACONS 32 +#else + #define MAX_IBEACONS 16 +#endif + +struct IBEACON_UID { + char MAC[12]; + char RSSI[4]; + char UID[32]; + char MAJOR[4]; + char MINOR[4]; + uint8_t FLAGS; + uint8_t TIME; +#ifdef USE_IBEACON_ESP32 + uint8_t REPORTED; + uint8_t REPTIME; + char NAME[16]; +#endif +} ibeacons[MAX_IBEACONS]; + +#ifdef USE_IBEACON_ESP32 + +uint32_t ibeacon_add(struct IBEACON *ib); + +void ESP32BLE_ReverseStr(uint8_t _mac[], uint8_t len=6){ + uint8_t _reversedMAC[len]; + for (uint8_t i=0; i>4) & 0xF]; + pout[1] = hex[ pgm_read_byte(pin) & 0xF]; + } +} + +int advertismentCallback(BLE_ESP32::ble_advertisment_t *pStruct) +{ + struct IBEACON ib; + BLEAdvertisedDevice *advertisedDevice = pStruct->advertisedDevice; + + char sRSSI[6]; + itoa(pStruct->RSSI,sRSSI,10); + const uint8_t *MAC = pStruct->addr; + + int manufacturerDataLen = 0; + std::string data; + if (advertisedDevice->haveManufacturerData()){ + data = advertisedDevice->getManufacturerData(); + manufacturerDataLen = data.length(); + } + if (manufacturerDataLen){ + const uint8_t *manufacturerData = (const uint8_t *)data.data(); + DumpHex(manufacturerData, 2, ib.FACID); + if (manufacturerDataLen == 25 && + manufacturerData[0] == 0x4C && + manufacturerData[1] == 0x00) + { + BLEBeacon oBeacon = BLEBeacon(); + oBeacon.setData(std::string((char *)manufacturerData, manufacturerDataLen)); + uint8_t UUID[16]; + memcpy(UUID,oBeacon.getProximityUUID().getNative()->u128.value,16); + ESP32BLE_ReverseStr(UUID,16); + + uint16_t Major = ENDIAN_CHANGE_U16(oBeacon.getMajor()); + uint16_t Minor = ENDIAN_CHANGE_U16(oBeacon.getMinor()); + uint8_t PWR = oBeacon.getSignalPower(); + + DumpHex((const unsigned char*)&UUID,16,ib.UID); + DumpHex((const unsigned char*)&Major,2,ib.MAJOR); + DumpHex((const unsigned char*)&Minor,2,ib.MINOR); + DumpHex((const unsigned char*)&PWR,1,ib.PWR); + DumpHex((const unsigned char*)MAC,6,ib.MAC); + memcpy(ib.RSSI,sRSSI,4); + memset(ib.NAME,0x0,16); + + // if we added it + if (ibeacon_add(&ib) == 1){ + AddLog_P(LOG_LEVEL_DEBUG, PSTR("%s: MAC: %s Major: %d Minor: %d UUID: %s Power: %d RSSI: %d"), + "iBeacon", + advertisedDevice->getAddress().toString().c_str(), + Major, Minor, + oBeacon.getProximityUUID().toString().c_str(), + PWR, pStruct->RSSI); + } + return 0; + } + } + + // no manufacturer data, or not recognised. + // still have an RSSi.... + memset(ib.UID,'0',32); + memset(ib.MAJOR,'0',4); + memset(ib.MINOR,'0',4); + memset(ib.PWR,'0',2); + DumpHex((const unsigned char*)MAC,6,ib.MAC); + memcpy(ib.RSSI,sRSSI,4); + + if (advertisedDevice->haveName()) { + strncpy(ib.NAME,advertisedDevice->getName().c_str(),16); + } else { + memset(ib.NAME,0x0,16); + } + + ibeacon_add(&ib); + return 0; +} + +void ESP32Init() { + + if (!ESP32BLE.mode.init) { + ESP32BLE.mode.init = 1; + IB_UPDATE_TIME=IB_UPDATE_TIME_INTERVAL; + IB_TIMEOUT_TIME=IB_TIMEOUT_INTERVAL; + } +} + + +#endif + +void IBEACON_Init() { + + +#ifdef USE_IBEACON_ESP32 + BLE_ESP32::registerForAdvertismentCallbacks((const char *)"iBeacon", advertismentCallback); +#else + + hm17_found=0; + +// actually doesnt work reliably with software serial + if (PinUsed(GPIO_IBEACON_RX) && PinUsed(GPIO_IBEACON_TX)) { + IBEACON_Serial = new TasmotaSerial(Pin(GPIO_IBEACON_RX), Pin(GPIO_IBEACON_TX),1,0,TMSBSIZ52); + if (IBEACON_Serial->begin(HM17_BAUDRATE)) { + if (IBEACON_Serial->hardwareSerial()) { + ClaimSerial(); + } + hm17_sendcmd(HM17_TEST); + hm17_lastms=millis(); + // in case of using Settings this has to be moved + IB_UPDATE_TIME=IB_UPDATE_TIME_INTERVAL; + IB_TIMEOUT_TIME=IB_TIMEOUT_INTERVAL; + } + } + +#endif + +} + +#ifdef USE_IBEACON_ESP32 + +void esp32_every_second(void) { + for (uint32_t cnt=0; cnt < MAX_IBEACONS; cnt++) { + if (ibeacons[cnt].FLAGS) { + uint8_t mac[6]; + char tmp[13]; + memcpy(tmp, ibeacons[cnt].MAC, 12); + tmp[12] = 0; + BLE_ESP32::fromHex(mac, tmp, 6); + // use global device timeouts from BLE_ESP32. + + uint32_t ageS = BLE_ESP32::devicePresent(mac); + + // if device not present at all. + if (!ageS){ + //AddLog_P(LOG_LEVEL_INFO, PSTR("iBeacon no device %s %02x%02x%02x%02x%02x%02x"),tmp, mac[0],mac[1], mac[2],mac[3], mac[4],mac[5]); + ibeacons[cnt].FLAGS=0; + ibeacon_mqtt(ibeacons[cnt].MAC,"0000",ibeacons[cnt].UID,ibeacons[cnt].MAJOR,ibeacons[cnt].MINOR,ibeacons[cnt].NAME); + } else { + //AddLog_P(LOG_LEVEL_INFO, PSTR("iBeacon device %s %02x%02x%02x%02x%02x%02x"),tmp, mac[0],mac[1], mac[2],mac[3], mac[4],mac[5]); + } + //ibeacons[cnt].TIME++; + ibeacons[cnt].REPTIME++; // counter used to send mqtt for a dev regularly + } + } +} + +#else + +void hm17_every_second(void) { + if (!IBEACON_Serial) return; + + if (hm17_found) { + if (IB_UPDATE_TIME && (TasmotaGlobal.uptime%IB_UPDATE_TIME==0)) { + if (hm17_cmd!=99) { + if (hm17_flag&2) { + ib_sendbeep(); + } else { + if (!hm17_connecting) { + hm17_sendcmd(HM17_DISI); + } + } + } + } + for (uint32_t cnt=0;cntIB_TIMEOUT_TIME) { + ibeacons[cnt].FLAGS=0; + ibeacon_mqtt(ibeacons[cnt].MAC,"0000",ibeacons[cnt].UID,ibeacons[cnt].MAJOR,ibeacons[cnt].MINOR); + } + } + } + } else { + if (TasmotaGlobal.uptime%20==0) { + hm17_sendcmd(HM17_TEST); + } + } +} + +void hm17_sbclr(void) { + memset(hm17_sbuffer,0,HM17_BSIZ); + hm17_sindex=0; + //IBEACON_Serial->flush(); +} + +void hm17_sendcmd(uint8_t cmd) { + hm17_sbclr(); + hm17_cmd=cmd; +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("hm17cmd %d"),cmd); +#endif + switch (cmd) { + case HM17_TEST: + IBEACON_Serial->write("AT"); + break; + case HM17_ROLE: + IBEACON_Serial->write("AT+ROLE1"); + break; + case HM17_IMME: + IBEACON_Serial->write("AT+IMME1"); + break; + case HM17_DISI: + IBEACON_Serial->write("AT+DISI?"); + hm17_scanning=1; + break; + case HM17_IBEA: + IBEACON_Serial->write("AT+IBEA1"); + break; + case HM17_RESET: + IBEACON_Serial->write("AT+RESET"); + break; + case HM17_RENEW: + IBEACON_Serial->write("AT+RENEW"); + break; + case HM17_SCAN: + IBEACON_Serial->write("AT+SCAN5"); + break; + case HM17_DISC: + IBEACON_Serial->write("AT+DISC?"); + hm17_scanning=1; + break; + case HM17_CON: + IBEACON_Serial->write((const uint8_t*)"AT+CON",6); + IBEACON_Serial->write((const uint8_t*)ib_mac,12); + hm17_connecting=1; + break; + } +} + +#endif + +uint32_t ibeacon_add(struct IBEACON *ib) { +/* if (!strncmp(ib->MAJOR,"4B1C",4)) { + return 0; + } + */ + if (!strncmp(ib->RSSI,"0",1)) { + return 0; + } + + // don't bother protecting this. + //TasAutoMutex localmutex(&beaconmutex, "iBeacAdd"); + + // keyfob starts with ffff, ibeacon has valid facid + if (!strncmp(ib->MAC,"FFFF",4) || strncmp(ib->FACID,"00000000",8)) { + for (uint32_t cnt=0;cntUID,PSTR("00000000000000000000000000000000"),32)) { + if (!strncmp(ibeacons[cnt].MAC,ib->MAC,12)) { + // exists + memcpy(ibeacons[cnt].RSSI,ib->RSSI,4); + ibeacons[cnt].TIME=0; +#ifdef USE_IBEACON_ESP32 + if (ibeacons[cnt].REPTIME >= IB_UPDATE_TIME) { + ibeacons[cnt].REPTIME = 0; + ibeacons[cnt].REPORTED = 0; + } +#endif + return 2; + } + } else { + if (!strncmp(ibeacons[cnt].UID,ib->UID,32)) { + // exists + memcpy(ibeacons[cnt].RSSI,ib->RSSI,4); + ibeacons[cnt].TIME=0; +#ifdef USE_IBEACON_ESP32 + if (ibeacons[cnt].REPTIME >= IB_UPDATE_TIME) { + ibeacons[cnt].REPTIME = 0; + ibeacons[cnt].REPORTED = 0; + } +#endif + return 2; + } + } + } + } + for (uint32_t cnt=0;cntMAC,12); + memcpy(ibeacons[cnt].RSSI,ib->RSSI,4); + memcpy(ibeacons[cnt].UID,ib->UID,32); + memcpy(ibeacons[cnt].MAJOR,ib->MAJOR,4); + memcpy(ibeacons[cnt].MINOR,ib->MINOR,4); + ibeacons[cnt].FLAGS=1; + ibeacons[cnt].TIME=0; +#ifdef USE_IBEACON_ESP32 + memcpy(ibeacons[cnt].NAME,ib->NAME,16); + ibeacons[cnt].REPTIME = 0; + ibeacons[cnt].REPORTED = 0; +#endif + return 1; + } + } + } + return 0; +} + +#ifndef USE_IBEACON_ESP32 + +void hm17_decode(void) { + struct IBEACON ib; + switch (hm17_cmd) { + case HM17_TEST: + if (!strncmp(hm17_sbuffer,"OK",2)) { +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("AT OK")); +#endif + hm17_sbclr(); + hm17_result=HM17_SUCESS; + hm17_found=1; + } + break; + case HM17_ROLE: + if (!strncmp(hm17_sbuffer,"OK+Set:1",8)) { +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("ROLE OK")); +#endif + hm17_sbclr(); + hm17_result=HM17_SUCESS; + } + break; + case HM17_IMME: + if (!strncmp(hm17_sbuffer,"OK+Set:1",8)) { +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("IMME OK")); +#endif + hm17_sbclr(); + hm17_result=HM17_SUCESS; + } + break; + case HM17_IBEA: + if (!strncmp(hm17_sbuffer,"OK+Set:1",8)) { +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("IBEA OK")); +#endif + hm17_sbclr(); + hm17_result=HM17_SUCESS; + } + break; + case HM17_SCAN: + if (!strncmp(hm17_sbuffer,"OK+Set:5",8)) { +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("SCAN OK")); +#endif + hm17_sbclr(); + hm17_result=HM17_SUCESS; + } + break; + case HM17_RESET: + if (!strncmp(hm17_sbuffer,"OK+RESET",8)) { +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("RESET OK")); +#endif + hm17_sbclr(); + hm17_result=HM17_SUCESS; + } + break; + case HM17_RENEW: + if (!strncmp(hm17_sbuffer,"OK+RENEW",8)) { +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("RENEW OK")); +#endif + hm17_sbclr(); + hm17_result=HM17_SUCESS; + } + break; + case HM17_CON: + if (!strncmp(hm17_sbuffer,"OK+CONNA",8)) { + hm17_sbclr(); +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("CONNA OK")); +#endif + hm17_connecting=2; + break; + } + if (!strncmp(hm17_sbuffer,"OK+CONNE",8)) { + hm17_sbclr(); +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("CONNE ERROR")); +#endif + break; + } + if (!strncmp(hm17_sbuffer,"OK+CONNF",8)) { + hm17_sbclr(); +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("CONNF ERROR")); +#endif + break; + } + if (hm17_connecting==2 && !strncmp(hm17_sbuffer,"OK+CONN",7)) { + hm17_sbclr(); +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("CONN OK")); +#endif + hm17_connecting=3; + hm17_sendcmd(HM17_TEST); + hm17_connecting=0; + break; + } + break; + + case HM17_DISI: + case HM17_DISC: + if (!strncmp(hm17_sbuffer,"OK+DISCS",8)) { + hm17_sbclr(); + hm17_result=1; +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("DISCS OK")); +#endif + break; + } + if (!strncmp(hm17_sbuffer,"OK+DISIS",8)) { + hm17_sbclr(); + hm17_result=1; +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("DISIS OK")); +#endif + break; + } + if (!strncmp(hm17_sbuffer,"OK+DISCE",8)) { + hm17_sbclr(); + hm17_result=HM17_SUCESS; +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR("DISCE OK")); +#endif + hm17_scanning=0; + break; + } + if (!strncmp(hm17_sbuffer,"OK+NAME:",8)) { + if (hm17_sbuffer[hm17_sindex-1]=='\n') { + hm17_result=HM17_SUCESS; +#ifdef IBEACON_DEBUG + if (hm17_debug) { + AddLog_P(LOG_LEVEL_INFO, PSTR("NAME OK")); + AddLog_P(LOG_LEVEL_INFO, PSTR(">>%s"),&hm17_sbuffer[8]); + } +#endif + hm17_sbclr(); + } + break; + } + if (!strncmp(hm17_sbuffer,"OK+DIS0:",8)) { + if (hm17_cmd==HM17_DISI) { +#ifdef HM17_V110 + goto hm17_v110; +#endif + } else { + if (hm17_sindex==20) { + hm17_result=HM17_SUCESS; +#ifdef IBEACON_DEBUG + if (hm17_debug) { + AddLog_P(LOG_LEVEL_INFO, PSTR("DIS0 OK")); + AddLog_P(LOG_LEVEL_INFO, PSTR(">>%s"),&hm17_sbuffer[8]); + } +#endif + hm17_sbclr(); + } + } + break; + } + if (!strncmp(hm17_sbuffer,"OK+DISC:",8)) { +hm17_v110: + if (hm17_cmd==HM17_DISI) { + if (hm17_sindex==78) { +#ifdef IBEACON_DEBUG + if (hm17_debug) { + AddLog_P(LOG_LEVEL_INFO, PSTR("DISC: OK")); + //OK+DISC:4C 000C0E:003 A9144081A8 3B16849611 862EC1005: 0B1CE7485D :4DB4E940F C0E:-078 + AddLog_P(LOG_LEVEL_INFO, PSTR(">>%s"),&hm17_sbuffer[8]); + } +#endif + memcpy(ib.FACID,&hm17_sbuffer[8],8); + memcpy(ib.UID,&hm17_sbuffer[8+8+1],32); + memcpy(ib.MAJOR,&hm17_sbuffer[8+8+1+32+1],4); + memcpy(ib.MINOR,&hm17_sbuffer[8+8+1+32+1+4],4); + memcpy(ib.PWR,&hm17_sbuffer[8+8+1+32+1+4+4],2); + memcpy(ib.MAC,&hm17_sbuffer[8+8+1+32+1+4+4+2+1],12); + memcpy(ib.RSSI,&hm17_sbuffer[8+8+1+32+1+4+4+2+1+12+1],4); + + if (ibeacon_add(&ib)) { + ibeacon_mqtt(ib.MAC,ib.RSSI,ib.UID,ib.MAJOR,ib.MINOR); + } + hm17_sbclr(); + hm17_result=1; + } + } else { +#ifdef IBEACON_DEBUG + if (hm17_debug) AddLog_P(LOG_LEVEL_INFO, PSTR(">->%s"),&hm17_sbuffer[8]); +#endif + } + break; + } + } +} + +#endif + +void IBEACON_loop() { + +#ifdef USE_IBEACON_ESP32 + //TasAutoMutex localmutex(&beaconmutex, "iBeacLoop"); + for (uint32_t cnt=0;cntavailable()) { + hm17_lastms=millis(); + // shift in + if (hm17_sindexread(); + hm17_sindex++; + hm17_decode(); + } else { + hm17_sindex=0; + break; + } + } + + if (hm17_cmd==99) { + if (hm17_sindex>=HM17_BSIZ-2 || (hm17_sindex && (difftime>100))) { + AddLog_P(LOG_LEVEL_INFO, PSTR("%s"),hm17_sbuffer); + hm17_sbclr(); + } + } + +#endif + +} + +#ifdef USE_WEBSERVER +const char HTTP_IBEACON_HL[] PROGMEM = "{s}
{m}
{e}"; +const char HTTP_IBEACON_mac[] PROGMEM = + "{s}IBEACON-MAC : %s" " {m} RSSI : %s" "{e}"; +const char HTTP_IBEACON_uid[] PROGMEM = + "{s}IBEACON-UID : %s" " {m} RSSI : %s" "{e}"; +#ifdef USE_IBEACON_ESP32 +const char HTTP_IBEACON_name[] PROGMEM = + "{s}IBEACON-NAME : %s (%s)" " {m} RSSI : %s" "{e}"; +#endif +void IBEACON_Show(void) { + char mac[14]; + char rssi[6]; + char uid[34]; +#ifdef USE_IBEACON_ESP32 + char name[18]; + //TasAutoMutex localmutex(&beaconmutex, "iBeacShow"); +#endif + int total = 0; + + for (uint32_t cnt=0;cnt 0) { + char *cp=XdrvMailbox.data; + if (*cp=='u') { + cp++; + if (*cp) IB_UPDATE_TIME=atoi(cp); + Response_P(S_JSON_IBEACON, XSNS_52,"uintv",IB_UPDATE_TIME); + } else if (*cp=='t') { + cp++; + if (*cp) IB_TIMEOUT_TIME=atoi(cp); + Response_P(S_JSON_IBEACON, XSNS_52,"lintv",IB_TIMEOUT_TIME); + } else if (*cp=='c') { + for (uint32_t cnt=0;cnt='0' && *cp<='8') { + hm17_sendcmd(*cp&7); + Response_P(S_JSON_IBEACON, XSNS_52,"hm17cmd",*cp&7); + } else if (*cp=='s') { + cp++; + len--; + while (*cp==' ') { + len--; + cp++; + } + IBEACON_Serial->write((uint8_t*)cp,len); + hm17_cmd=99; + Response_P(S_JSON_IBEACON1, XSNS_52,"hm17cmd",cp); + } +#endif +#ifdef IBEACON_DEBUG + else if (*cp=='d') { + cp++; + hm17_debug=atoi(cp); + Response_P(S_JSON_IBEACON, XSNS_52,"debug",hm17_debug); + } +#endif + } else { + serviced=false; + } + return serviced; +} + +#define D_CMND_IBEACON "IBEACON" + +#ifndef USE_IBEACON_ESP32 +//"IBEACON_FFFF3D1B1E9D_RSSI", Data "99" causes TAG to beep +bool ibeacon_cmd(void) { + ib_mac[0]=0; + int16_t rssi=0; + const char S_JSON_IBEACON[] = "{\"" D_CMND_IBEACON "_%s_RSSI\":%d}"; + uint8_t cmd_len = strlen(D_CMND_IBEACON); + if (!strncasecmp_P(XdrvMailbox.topic, PSTR(D_CMND_IBEACON), cmd_len)) { + // IBEACON prefix + rssi = XdrvMailbox.payload; + if (rssi==99) { + memcpy(ib_mac,XdrvMailbox.topic+cmd_len+1,12); + ib_mac[12]=0; + if (hm17_scanning) { + // postpone sendbeep + hm17_flag|=2; + } else { + ib_sendbeep(); + } + } + Response_P(S_JSON_IBEACON,ib_mac,rssi); + return true; + } + return false; +} + +void ib_sendbeep(void) { + hm17_flag=0; + hm17_sendcmd(HM17_CON); +} + +#endif + +#ifdef USE_IBEACON_ESP32 +void ibeacon_mqtt(const char *mac,const char *rssi,const char *uid,const char *major,const char *minor, const char *name) { +#else +void ibeacon_mqtt(const char *mac,const char *rssi,const char *uid,const char *major,const char *minor) { +#endif + char s_mac[14]; + char s_uid[34]; + char s_major[6]; + char s_minor[6]; + char s_rssi[6]; +#ifdef USE_IBEACON_ESP32 + char *s_state; +#endif + char s_name[18]; + memcpy(s_mac,mac,12); + s_mac[12]=0; + memcpy(s_uid,uid,32); + s_uid[32]=0; + memcpy(s_major,major,4); + s_major[4]=0; + memcpy(s_minor,minor,4); + s_minor[4]=0; + memcpy(s_rssi,rssi,4); + s_rssi[4]=0; + int16_t n_rssi=atoi(s_rssi); +#ifdef USE_IBEACON_ESP32 + if (n_rssi) { + s_state=(char *)"ON"; + } else { + s_state=(char *)"OFF"; + } +#endif + // if uid == all zeros, take mac + if (!strncmp_P(s_uid,PSTR("00000000000000000000000000000000"),32)) { +#ifdef USE_IBEACON_ESP32 + if (name[0]) { + memcpy(s_name,name,16); + s_name[16]=0; + ResponseTime_P(PSTR(",\"" D_CMND_IBEACON "\":{\"MAC\":\"%s\",\"NAME\":\"%s\",\"RSSI\":%d,\"STATE\":\"%s\"}}"),s_mac,s_name,n_rssi,s_state); + } else { + ResponseTime_P(PSTR(",\"" D_CMND_IBEACON "\":{\"MAC\":\"%s\",\"RSSI\":%d,\"STATE\":\"%s\"}}"),s_mac,n_rssi,s_state); + } +#else + ResponseTime_P(PSTR(",\"" D_CMND_IBEACON "\":{\"MAC\":\"%s\",\"UID\":\"%s\",\"MAJOR\":\"%s\",\"MINOR\":\"%s\",\"RSSI\":%d}}"),s_mac,s_uid,s_major,s_minor,n_rssi); +#endif + } else { +#ifdef USE_IBEACON_ESP32 + ResponseTime_P(PSTR(",\"" D_CMND_IBEACON "\":{\"UID\":\"%s\",\"MAJOR\":\"%s\",\"MINOR\":\"%s\",\"MAC\":\"%s\",\"RSSI\":%d,\"STATE\":\"%s\"}}"),s_uid,s_major,s_minor,s_mac,n_rssi,s_state); +#else + ResponseTime_P(PSTR(",\"" D_CMND_IBEACON "\":{\"UID\":\"%s\",\"MAJOR\":\"%s\",\"MINOR\":\"%s\",\"MAC\":\"%s\",\"RSSI\":%d}}"),s_uid,s_major,s_minor,s_mac,n_rssi); +#endif + } + + MqttPublishTeleSensor(); +} + + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +bool Xsns52(byte function) +{ + bool result = false; + + switch (function) { + case FUNC_INIT: + IBEACON_Init(); + break; +#ifdef USE_IBEACON_ESP32 + case FUNC_EVERY_250_MSECOND: + if (!ESP32BLE.mode.init) { + ESP32Init(); + } + break; +#endif + case FUNC_LOOP: + IBEACON_loop(); + break; + case FUNC_EVERY_SECOND: +#ifdef USE_IBEACON_ESP32 + esp32_every_second(); +#else + hm17_every_second(); +#endif + break; + case FUNC_COMMAND_SENSOR: + if (XSNS_52 == XdrvMailbox.index) { + result = xsns52_cmd(); + } + break; +#ifndef USE_IBEACON_ESP32 + case FUNC_COMMAND: + result=ibeacon_cmd(); + break; +#endif +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: +#ifndef USE_IBEACON_ESP32 + if (hm17_found) IBEACON_Show(); +#else + IBEACON_Show(); +#endif + break; +#endif // USE_WEBSERVER + } + return result; +} + +#endif // USE_IBEACON + +#endif \ No newline at end of file diff --git a/tasmota/xsns_62_MI_ESP32.ino b/tasmota/xsns_62_MI_ESP32.ino index 689b5a72a..af24ec083 100644 --- a/tasmota/xsns_62_MI_ESP32.ino +++ b/tasmota/xsns_62_MI_ESP32.ino @@ -45,6 +45,7 @@ forked - from arendst/tasmota - https://github.com/arendst/Tasmota */ +#ifndef USE_BLE_ESP32 #ifdef ESP32 // ESP32 only. Use define USE_HM10 for ESP8266 support #ifdef USE_MI_ESP32 @@ -589,17 +590,17 @@ int MI32_decryptPacket(char *_buf, uint16_t _bufSize, uint32_t _type){ MI32_ReverseMAC(packet->MAC); uint8_t _bindkey[16] = {0x0}; bool foundNoKey = true; - AddLog_P(LOG_LEVEL_DEBUG,PSTR("M32: Search key for MAC: %02x %02x %02x %02x %02x %02x"), packet->MAC[0], packet->MAC[1], packet->MAC[2], packet->MAC[3], packet->MAC[4], packet->MAC[5]); + AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI32: search key for MAC: %02x %02x %02x %02x %02x %02x"), packet->MAC[0], packet->MAC[1], packet->MAC[2], packet->MAC[3], packet->MAC[4], packet->MAC[5]); for(uint32_t i=0; iMAC,MIBLEbindKeys[i].MAC,sizeof(packet->MAC))==0){ memcpy(_bindkey,MIBLEbindKeys[i].key,sizeof(_bindkey)); - AddLog_P(LOG_LEVEL_DEBUG,PSTR("M32: Decryption Key found")); + AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI32: decryption Key found")); foundNoKey = false; break; } } if(foundNoKey){ - AddLog_P(LOG_LEVEL_DEBUG,PSTR("M32: No Key found !!")); + AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI32: no Key found !!")); return -2; } @@ -619,7 +620,7 @@ int MI32_decryptPacket(char *_buf, uint16_t _bufSize, uint32_t _type){ ret = br_ccm_check_tag(&ctx, &tag); - AddLog_P(LOG_LEVEL_DEBUG,PSTR("M32: Err:%i, Decrypted : %02x %02x %02x %02x %02x "), ret, packet->payload[1],packet->payload[2],packet->payload[3],packet->payload[4],packet->payload[5]); + AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI32: Err:%i, Decrypted : %02x %02x %02x %02x %02x "), ret, packet->payload[1],packet->payload[2],packet->payload[3],packet->payload[4],packet->payload[5]); return ret-1; } #endif // USE_MI_DECRYPTION @@ -660,7 +661,7 @@ uint32_t MIBLEgetSensorSlot(uint8_t (&_MAC)[6], uint16_t _type, uint8_t counter) bool _success = false; for (uint32_t i=0;i19) { - AddLog_P(LOG_LEVEL_INFO,PSTR("M32: Scan buffer full")); + AddLog_P(LOG_LEVEL_INFO,PSTR("MI32: Scan buffer full")); MI32.state.beaconScanCounter = 1; return; } for(auto _scanResult : MIBLEscanResult){ if(memcmp(addr,_scanResult.MAC,6)==0){ - // AddLog_P(LOG_LEVEL_INFO,PSTR("M32: known device")); + // AddLog_P(LOG_LEVEL_INFO,PSTR("MI32: known device")); return; } } @@ -1582,12 +1586,12 @@ void MI32addBeacon(uint8_t index, char* data){ _new.time = 0; if(memcmp(_empty,_new.MAC,6) == 0){ _new.active = false; - AddLog_P(LOG_LEVEL_INFO,PSTR("M32: Beacon%u deactivated"), index); + AddLog_P(LOG_LEVEL_INFO,PSTR("MI32: beacon%u deactivated"), index); } else{ _new.active = true; MI32.mode.activeBeacon = 1; - AddLog_P(LOG_LEVEL_INFO,PSTR("M32: Beacon added with MAC: %s"), _MAC); + AddLog_P(LOG_LEVEL_INFO,PSTR("MI32: beacon added with MAC: %s"), _MAC); } } @@ -1842,7 +1846,7 @@ void CmndMi32Time(void) { if (XdrvMailbox.data_len > 0) { if (MIBLEsensors.size() > XdrvMailbox.payload) { if ((LYWSD02 == MIBLEsensors[XdrvMailbox.payload].type) || (MHOC303 == MIBLEsensors[XdrvMailbox.payload].type)) { - AddLog_P(LOG_LEVEL_DEBUG, PSTR("M32: Will set Time")); + AddLog_P(LOG_LEVEL_DEBUG, PSTR("MI32: will set Time")); MI32.state.sensor = XdrvMailbox.payload; MI32.mode.canScan = 0; MI32.mode.canConnect = 0; @@ -1872,7 +1876,7 @@ void CmndMi32Unit(void) { if (XdrvMailbox.data_len > 0) { if (MIBLEsensors.size() > XdrvMailbox.payload) { if ((LYWSD02 == MIBLEsensors[XdrvMailbox.payload].type) || (MHOC303 == MIBLEsensors[XdrvMailbox.payload].type)) { - AddLog_P(LOG_LEVEL_DEBUG,PSTR("M32: Will set Unit")); + AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI32: will set Unit")); MI32.state.sensor = XdrvMailbox.payload; MI32.mode.canScan = 0; MI32.mode.canConnect = 0; @@ -1922,11 +1926,11 @@ void CmndMi32Block(void){ switch (XdrvMailbox.index) { case 0: MIBLEBlockList.clear(); - // AddLog_P(LOG_LEVEL_INFO,PSTR("M32: Size of ilist: %u"), MIBLEBlockList.size()); - ResponseCmndIdxChar(PSTR("Block list cleared")); + // AddLog_P(LOG_LEVEL_INFO,PSTR("MI32: size of ilist: %u"), MIBLEBlockList.size()); + ResponseCmndIdxChar(PSTR("block list cleared")); break; case 1: - ResponseCmndIdxChar(PSTR("Show block list")); + ResponseCmndIdxChar(PSTR("show block list")); break; } } @@ -1952,7 +1956,7 @@ void CmndMi32Block(void){ ResponseCmndIdxChar(XdrvMailbox.data); MI32removeMIBLEsensor(_MACasBytes.buf); } - // AddLog_P(LOG_LEVEL_INFO,PSTR("M32: Size of ilist: %u"), MIBLEBlockList.size()); + // AddLog_P(LOG_LEVEL_INFO,PSTR("MI32: size of ilist: %u"), MIBLEBlockList.size()); break; } } @@ -2319,3 +2323,4 @@ bool Xsns62(uint8_t function) } #endif // USE_MI_ESP32 #endif // ESP32 +#endif \ No newline at end of file diff --git a/tasmota/xsns_62_MI_ESP32_BLE_ESP32.ino b/tasmota/xsns_62_MI_ESP32_BLE_ESP32.ino new file mode 100644 index 000000000..832aae2e7 --- /dev/null +++ b/tasmota/xsns_62_MI_ESP32_BLE_ESP32.ino @@ -0,0 +1,2742 @@ +/* + xsns_62_MI_ESP32.ino - MI-BLE-sensors via ESP32 support for Tasmota + + Copyright (C) 2020 Christian Baars and Theo Arends + + 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 + -------------------------------------------------------------------------------------------- + 0.9.1.9 20201226 changed - All change now. + ------- + 0.9.1.7 20201116 changed - small bugfixes, add BLOCK and OPTION command, send BLE scan via MQTT + ------- + 0.9.1.6 20201022 changed - Beacon support, RSSI at TELEPERIOD, refactoring + ------- + 0.9.1.5 20201021 changed - HASS related ('null', hold back discovery), number of found sensors for RULES + ------- + 0.9.1.4 20201020 changed - use BearSSL for decryption, revert to old TELEPERIOD-cycle as default + ------- + 0.9.1.3 20200926 changed - Improve HA discovery, make key+MAC case insensitive + ------- + 0.9.1.3 20200916 changed - add ATC (custom FW for LYWSD03MMC), API adaption for NimBLE-Arduino 1.0.2 + ------- + 0.9.1.2 20200802 changed - add MHO-C303 + ------- + 0.9.1.1 20200715 changed - add MHO-C401, refactoring + ------- + 0.9.1.0 20200712 changed - add lights and yeerc, add pure passive mode with decryption, + lots of refactoring + ------- + 0.9.0.1 20200706 changed - adapt to new NimBLE-API, tweak scan process + ------- + 0.9.0.0 20200413 started - initial development by Christian Baars + forked - from arendst/tasmota - https://github.com/arendst/Tasmota + +*/ +//#define VSCODE_DEV + +/* +#ifdef VSCODE_DEV +#define ESP32 +#define USE_BLE_ESP32 +#define USE_MI_ESP32 +#endif +*/ +//#undef USE_MI_ESP32 + +// for testing of BLE_ESP32, we remove xsns_62_MI_ESP32.ino completely, and instead add this modified xsns_52_ibeacon_BLE_ESP32.ino +#ifdef USE_BLE_ESP32 + +#ifdef ESP32 // ESP32 only. Use define USE_HM10 for ESP8266 support + +#ifdef USE_MI_ESP32 + +#define XSNS_62 62 +#define USE_MI_DECRYPTION + +#include +#ifdef USE_MI_DECRYPTION +#include +#endif //USE_MI_DECRYPTION + +void MI32scanEndedCB(NimBLEScanResults results); +void MI32notifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify); + +struct { + uint16_t perPage = 4; + uint8_t mqttCurrentSlot = 0; + uint32_t period; // set manually in addition to TELE-period, is set to TELE-period after start + int secondsCounter = 0; // counts up in MI32EverySecond to period + int secondsCounter2 = 0; // counts up in MI32EverySecond to period + union { + struct { + uint32_t init:1; + uint32_t shallClearResults:1; // BLE scan results + uint32_t shallShowStatusInfo:1; // react to amount of found sensors via RULES + uint32_t firstAutodiscoveryDone:1; + uint32_t shallTriggerTele:1; + uint32_t triggeredTele:1; + }; + uint32_t all = 0; + } mode; + + struct { + // the slot currently having it's battery read + // set to 0 to start a battery read cycle + uint8_t slot = 255; + uint8_t active = 0; + } batteryreader; + + struct { + // the slot currently having it's battery read + // set to 0 to start a battery read cycle + uint8_t slot = 255; + uint8_t active = 0; + } sensorreader; + + struct { + uint32_t allwaysAggregate:1; // always show all known values of one sensor in brdigemode + uint32_t noSummary:1; // no sensor values at TELE-period + uint32_t directBridgeMode:1; // send every received BLE-packet as a MQTT-message in real-time + uint32_t holdBackFirstAutodiscovery:1; // allows to trigger it later + uint32_t showRSSI:1; + uint32_t ignoreBogusBattery:1; + uint32_t minimalSummary:1; // DEPRECATED!! + } option; +} MI32; + +#pragma pack(1) // byte-aligned structures to read the sensor data + + struct { + int16_t temp; + uint8_t hum; + uint16_t volt; // LYWSD03 only + } LYWSD0x_HT; + struct { + uint8_t spare; + int16_t temp; + uint16_t hum; + } CGD1_HT; + struct { + int16_t temp; + uint8_t spare; + uint32_t lux; + uint8_t moist; + uint16_t fert; + } Flora_TLMF; // temperature, lux, moisture, fertility + + +//////////////////////////////////////////////////////////// +// from https://github.com/Magalex2x14/LYWSD03MMC-info +struct mi_beacon_frame_data_t{ + // data from byte 0 - e.g. 30 + uint8_t meshflag; //Byte 0: x....... + uint8_t dataflag; //Byte 0: .x...... + uint8_t compatibilityflag; //Byte 0: ..x..... - indicates compatibility data present + uint8_t MACFlag; //Byte 0: ...x.... + uint8_t isencrypted; //Byte 0: ....x... + uint8_t reserved; //Byte 0: .....xxx + + // data from byte 1 - e.g. 58 + uint8_t version; //Byte 0: xxxx.... + uint8_t authMode; //Byte 0: ....xx.. // e.g. 2 + uint8_t bindingvalidreq; //Byte 0: ......x. + uint8_t registeredflag; //Byte 0: .......x +}; + +struct mi_beacon_compatibility_data_t{ // e.g. 28/08 + uint8_t reserved; //Byte 0: xx...... + uint8_t IOcap; //Byte 0: ..x..... + uint8_t bondability; //Byte 0: ...xx... + uint8_t unused; //Byte 0: .....xxx + uint16_t IOCapability; // bytes 1-2, e.g. 01 00 -> 0001 +}; +struct mi_beacon_mac_data_t{ // e.g. 28/08 + uint8_t mac[6]; +}; +struct mi_beacon_payload_data_t{ // + uint8_t type; + uint8_t ten; + uint8_t size; + uint8_t data[16]; +}; + +struct mi_beacon_data_t { // + mi_beacon_frame_data_t framedata; + uint16_t devicetype; + uint8_t framecnt; + mi_beacon_mac_data_t macdata; + mi_beacon_compatibility_data_t compatibility; + uint8_t payloadpresent; + uint8_t needkey; // we need a (new) encryption key? + + mi_beacon_payload_data_t payload; +}; + +struct mi_beacon_data_payload_data_t { // + union { + struct{ //01 + uint16_t num; + uint8_t longPress; + } Btn; + + int16_t temp; //04 + uint16_t hum; //06 + uint32_t lux; //07 + uint8_t moist; //08 + uint16_t fert; //09 + uint8_t bat; //0a + struct{ //0d + int16_t temp; + uint16_t hum; + } HT; + uint32_t NMT; //17 + }; +}; + + + +/////////////////////////////////////////////////////////// + + + +union mi_bindKey_t{ + struct{ + uint8_t key[16]; + uint8_t MAC[6]; + }; + uint8_t buf[22]; +}; + +struct ATCPacket_t{ + //uint8_t size; // = 16? + //uint8_t uid; // = 0x16, 16-bit UUID + //uint16_t UUID; // = 0x181A, GATT Service 0x181A Environmental Sensing + uint8_t MAC[6]; // [0] - hi, .. [6] - lo digits + uint16_t temp; //sadly this is in wrong endianess + uint8_t hum; + uint8_t batPer; + uint16_t batMV; + uint8_t frameCnt; +}; + +// GATT Service 0x181A Environmental Sensing +// All data little-endian +struct PVVXPacket_t { + //uint8_t size; // = 19 + //uint8_t uid; // = 0x16, 16-bit UUID + //uint16_t UUID; // = 0x181A, GATT Service 0x181A Environmental Sensing + uint8_t MAC[6]; // [0] - lo, .. [6] - hi digits + int16_t temperature; // x 0.1 degree + uint16_t humidity; // x 0.01 % + uint16_t battery_mv; // mV + uint8_t battery_level; // 0..100 % + uint8_t counter; // measurement count + uint8_t flags; +}; + +#pragma pack(0) + +struct mi_sensor_t{ + uint8_t type; //MI_Flora = 1; MI_MI-HT_V1=2; MI_LYWSD02=3; MI_LYWSD03=4; MI_CGG1=5; MI_CGD1=6 + uint8_t needkey; // tells http to display needkey message with link + uint8_t lastCnt; //device generated counter of the packet + uint8_t shallSendMQTT; + uint8_t MAC[6]; + union { + struct { + uint32_t temp:1; + uint32_t hum:1; + uint32_t tempHum:1; //every hum sensor has temp too, easier to use Tasmota dew point functions + uint32_t lux:1; + uint32_t moist:1; + uint32_t fert:1; + uint32_t bat:1; + uint32_t NMT:1; + uint32_t PIR:1; + uint32_t Btn:1; + }; + uint32_t raw; + } feature; + union { + struct { + uint32_t temp:1; + uint32_t hum:1; + uint32_t tempHum:1; //can be combined from the sensor + uint32_t lux:1; + uint32_t moist:1; + uint32_t fert:1; + uint32_t bat:1; + uint32_t NMT:1; + uint32_t motion:1; + uint32_t noMotion:1; + uint32_t Btn:1; + uint32_t PairBtn:1; + }; + uint32_t raw; + } eventType; + + int RSSI; + uint8_t pairing; + uint32_t lastTime; + uint32_t lux; + float temp; //Flora, MJ_HT_V1, LYWSD0x, CGx + union { + struct { + uint8_t moisture; + uint16_t fertility; + char firmware[6]; // actually only for FLORA but hopefully we can add for more devices + }; // Flora + struct { + float hum; + }; // MJ_HT_V1, LYWSD0x + struct { + uint16_t events; //"alarms" since boot + uint32_t NMT; // no motion time in seconds for the MJYD2S + }; + uint16_t Btn; + }; + union { + uint8_t bat; // many values seem to be hard-coded garbage (LYWSD0x, GCD1) + }; +}; + +struct MAC_t { + uint8_t buf[6]; +}; + +std::vector MIBLEsensors; +std::vector MIBLEbindKeys; +std::vector MIBLEBlockList; + +void *slotmutex = nullptr; + +/*********************************************************************************************\ + * constants +\*********************************************************************************************/ + +#define D_CMND_MI32 "MI32" + +const char kMI32_Commands[] PROGMEM = D_CMND_MI32 "|" +#ifdef USE_MI_DECRYPTION + "Key|" + "Keys|" +#endif // USE_MI_DECRYPTION + "Period|Time|Page|Battery|Unit|Block|Option"; + +void (*const MI32_Commands[])(void) PROGMEM = { +#ifdef USE_MI_DECRYPTION + &CmndMi32Key, + &CmndMi32Keys, +#endif // USE_MI_DECRYPTION + &CmndMi32Period, &CmndMi32Time, &CmndMi32Page, &CmndMi32Battery, &CmndMi32Unit, &CmndMi32Block, &CmndMi32Option }; + + +#define MI_UNKOWN 1 +#define MI_FLORA 2 +#define MI_MJ_HT_V1 3 +#define MI_LYWSD02 4 +#define MI_LYWSD03MMC 5 +#define MI_CGG1 6 +#define MI_CGD1 7 +#define MI_NLIGHT 8 +#define MI_MJYD2S 9 +#define MI_YEERC 10 +#define MI_MHOC401 11 +#define MI_MHOC303 12 +#define MI_ATC 13 + +#define MI_MI32_TYPES 13 //count this manually + +const uint16_t kMI32DeviceID[MI_MI32_TYPES]={ + 0x0000, // Unkown + 0x0098, // Flora + 0x01aa, // MJ_HT_V1 + 0x045b, // LYWSD02 + 0x055b, // LYWSD03 + 0x0347, // CGG1 + 0x0576, // CGD1 + 0x03dd, // NLIGHT + 0x07f6, // MJYD2S + 0x0153, // yee-rc + 0x0387, // MHO-C401 + 0x06d3, // MHO-C303 + 0x0a1c // ATC -> this is a fake ID +}; + +const char kMI32DeviceType0[] PROGMEM = "Unknown"; +const char kMI32DeviceType1[] PROGMEM = "Flora"; +const char kMI32DeviceType2[] PROGMEM = "MJ_HT_V1"; +const char kMI32DeviceType3[] PROGMEM = "LYWSD02"; +const char kMI32DeviceType4[] PROGMEM = "LYWSD03"; +const char kMI32DeviceType5[] PROGMEM = "CGG1"; +const char kMI32DeviceType6[] PROGMEM = "CGD1"; +const char kMI32DeviceType7[] PROGMEM = "NLIGHT"; +const char kMI32DeviceType8[] PROGMEM = "MJYD2S"; +const char kMI32DeviceType9[] PROGMEM = "YEERC"; +const char kMI32DeviceType10[] PROGMEM ="MHOC401"; +const char kMI32DeviceType11[] PROGMEM ="MHOC303"; +const char kMI32DeviceType12[] PROGMEM ="ATC"; +const char * kMI32DeviceType[] PROGMEM = {kMI32DeviceType0,kMI32DeviceType1,kMI32DeviceType2,kMI32DeviceType3,kMI32DeviceType4,kMI32DeviceType5,kMI32DeviceType6,kMI32DeviceType7,kMI32DeviceType8,kMI32DeviceType9,kMI32DeviceType10,kMI32DeviceType11,kMI32DeviceType12}; + +typedef int BATREAD_FUNCTION(int slot); +typedef int UNITWRITE_FUNCTION(int slot, int unit); +typedef int TIMEWRITE_FUNCTION(int slot); + +int genericOpCompleteFn(BLE_ESP32::generic_sensor_t *pStruct); +int genericBatReadFn(int slot); +int genericUnitWriteFn(int slot, int unit); +int genericTimeWriteFn(int slot); +int MI32scanCompleteCallback(NimBLEScanResults results); + +const char LYWSD02_Svc[] PROGMEM = "EBE0CCB0-7A0A-4B0C-8A1A-6FF2997DA3A6"; +const char LYWSD02_BattChar[] PROGMEM = "EBE0CCC4-7A0A-4B0C-8A1A-6FF2997DA3A6"; +const char LYWSD02_UnitChar[] PROGMEM = "EBE0CCBE-7A0A-4B0C-8A1A-6FF2997DA3A6"; +const char LYWSD02_TimeChar[] PROGMEM = "EBE0CCB7-7A0A-4B0C-8A1A-6FF2997DA3A6"; +const char LYWSD02_BattNotifyChar[] PROGMEM = "EBE0CCC1-7A0A-4B0C-8A1A-6FF2997DA3A6"; + +const char *LYWSD03_Svc = LYWSD02_Svc; +const char *LYWSD03_BattNotifyChar = LYWSD02_BattNotifyChar; + +const char *MHOC303_Svc = LYWSD02_Svc; +const char *MHOC303_UnitChar = LYWSD02_UnitChar; +const char *MHOC303_TimeChar = LYWSD02_TimeChar; + +const char *MHOC401_Svc = LYWSD02_Svc; +const char *MHOC401_BattNotifyChar = LYWSD02_BattNotifyChar; + +const char CGD1_Svc[] PROGMEM = "180F"; +const char CGD1_BattChar[] PROGMEM = "2A19"; + +const char FLORA_Svc[] PROGMEM = "00001204-0000-1000-8000-00805F9B34FB"; +const char FLORA_BattChar[] PROGMEM = "00001A02-0000-1000-8000-00805F9B34FB"; + + + +/*********************************************************************************************\ + * enumerations +\*********************************************************************************************/ + +// types of operation performed, included in context +enum MI32_MI_OP_TYPES { + OP_TIME_WRITE = 0, + OP_BATT_READ = 1, + OP_UNIT_WRITE = 2, + OP_UNIT_READ = 3, + OP_UNIT_TOGGLE = 4, + OP_READ_HT_LY = 5, +}; + + +enum MI32_MI_KEY_REQ { + KEY_REQUIREMENT_UNKNOWN = 0, // we don't know if a key is needed + KEY_NOT_REQUIRED = 1, // we got an unencrypted payload + KEY_REQUIRED_BUT_NOT_FOUND = 2, // we got an encrypted packet, but had not key + KEY_REQUIRED_AND_FOUND = 3, // we got an encrypted packet, and could decrypt + KEY_REQUIRED_AND_INVALID = 4, // we got an encrypted packet, and could not decrypt +}; + +/*********************************************************************************************\ + * Classes +\*********************************************************************************************/ + + +// fn type READ_CALLBACK +// 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. +int toggleUnit(BLE_ESP32::generic_sensor_t *op){ + uint32_t context = (uint32_t) op->context; + int opType = context >> 24; + // we only need to op type + int devType = (context >> 16) & 0xff; + int slot = (context) & 0xff; + switch (opType){ + case OP_UNIT_TOGGLE:{ + uint8_t curUnit = 0; + if( op->dataRead[0] != 0 && op->dataRead[0] < 101 ){ + curUnit = op->dataRead[0]; + } + + curUnit = curUnit == 0x01?0xFF:0x01; // C/F + // copy in ALL of the data, because we don't know how long this is from the existing src code. + memcpy(op->dataToWrite, op->dataRead, op->readlen); + op->writelen = op->readlen; + op->dataToWrite[0] = curUnit; + } break; + case OP_UNIT_WRITE:{ + uint8_t curUnit = op->dataToWrite[0]; + // copy in ALL of the data, because we don't know how long this is from the existing src code. + memcpy(op->dataToWrite, op->dataRead, op->readlen); + op->writelen = op->readlen; + op->dataToWrite[0] = curUnit; + } break; + } + return 0; +} + +bool MI32Operation(int slot, int optype, const char *svc, const char *charactistic, const char *notifychar = nullptr, const uint8_t *data = nullptr, int datalen = 0, uint8_t *addr = nullptr ) { + if (!svc || !svc[0]){ + AddLog_P(LOG_LEVEL_ERROR,PSTR("MI32Op: inv svc")); + return 0; + } + + 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 from BLE")); + return 0; + } else { + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("got a newOperation from BLE")); + } + + if (slot >= 0){ + op->addr = NimBLEAddress(MIBLEsensors[slot].MAC); + } else { + if (!addr){ + AddLog_P(LOG_LEVEL_ERROR,PSTR("no addr")); + BLE_ESP32::freeOperation(&op); + return 0; + } + op->addr = NimBLEAddress(addr); + } + + bool havechar = false; + op->serviceUUID = NimBLEUUID(svc); + + if (!op->serviceUUID.bitSize()){ + BLE_ESP32::freeOperation(&op); + AddLog_P(LOG_LEVEL_ERROR,PSTR("MI: Bad service string %s"), svc); + return 0; + } + + + if (charactistic && charactistic[0]){ + havechar = true; + op->characteristicUUID = NimBLEUUID(charactistic); + if (!op->characteristicUUID.bitSize()){ + BLE_ESP32::freeOperation(&op); + AddLog_P(LOG_LEVEL_ERROR,PSTR("MI: Bad characteristic string %s"), charactistic); + return 0; + } + } + if (notifychar && notifychar[0]){ + op->notificationCharacteristicUUID = NimBLEUUID(notifychar); + if (!op->notificationCharacteristicUUID.bitSize()){ + BLE_ESP32::freeOperation(&op); + AddLog_P(LOG_LEVEL_ERROR,PSTR("MI: Bad notifycharacteristic string %s"), notifychar); + return 0; + } + } + + if (data && datalen) { + op->writelen = datalen; + memcpy(op->dataToWrite, data, datalen); + } else { + if (!datalen && havechar){ + op->readlen = 1; // if we don't set readlen, then it won't read + } + } + + // the only times we intercept between read abnd write + if ((optype == OP_UNIT_WRITE) || (optype == OP_UNIT_TOGGLE)){ + op->readlen = 1; // if we don't set readlen, then it won't read + op->readmodifywritecallback = (void *)toggleUnit; + } + + // this op will call us back on complete or failure. + op->completecallback = (void *)genericOpCompleteFn; + uint32_t context = (optype << 24) | (MIBLEsensors[slot].type << 16) | slot; + op->context = (void *)context; + + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI s:%d op:%s"), slot, BLE_ESP32::BLETriggerResponse(op).c_str()); + + res = BLE_ESP32::extQueueOperation(&op); + 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")); + } + + return res; +} + + + +int genericBatReadFn(int slot){ + int res = 0; + + switch(MIBLEsensors[slot].type) { + // these use notify for battery read, and it comes in the temp packet + case MI_LYWSD03MMC: + res = MI32Operation(slot, OP_BATT_READ, LYWSD03_Svc, nullptr, LYWSD03_BattNotifyChar); + break; + case MI_MHOC401: + res = MI32Operation(slot, OP_BATT_READ, MHOC401_Svc, nullptr, MHOC401_BattNotifyChar); + break; + + // these read a characteristic + case MI_FLORA: + res = MI32Operation(slot, OP_BATT_READ, FLORA_Svc, FLORA_BattChar); + break; + case MI_LYWSD02: + res = MI32Operation(slot, OP_BATT_READ, LYWSD02_Svc, LYWSD02_BattChar); + break; + case MI_CGD1: + res = MI32Operation(slot, OP_BATT_READ, CGD1_Svc, CGD1_BattChar); + break; + +// this was for testing only - it does work, but no need to read as we get good bat in advert +// case MI_MJ_HT_V1: +// res = MI32Operation(slot, OP_BATT_READ, CGD1_Svc, CGD1_BattChar); +// break; + + default: + res = -10; // no need to read + break; + } + if (res > 0){ + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_INFO,PSTR("Req batt read slot %d type %d queued"), slot, MIBLEsensors[slot].type); + } else { + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_INFO,PSTR("Req batt read slot %d type %d non-queued res %d"), slot, MIBLEsensors[slot].type, res); + } + return res; +} + + + +int genericSensorReadFn(int slot, int force){ + int res = 0; + switch(MIBLEsensors[slot].type) { +/* seen notify timeout consistently with MI_LYWSD02, + so although the characteristic seems to exist, it does not work? + further dev required with sensor to hand. + case MI_LYWSD02: + // don't read if key present and we've decoded at least one advert + if (MIBLEsensors[slot].needkey == KEY_REQUIRED_AND_FOUND) return -2; + res = MI32Operation(slot, OP_READ_HT_LY, LYWSD02_Svc, nullptr, LYWSD02_BattNotifyChar); + break;*/ + case MI_LYWSD03MMC: + // don't read if key present and we've decoded at least one advert + if (MIBLEsensors[slot].needkey == KEY_REQUIRED_AND_FOUND && !force) return -2; + res = MI32Operation(slot, OP_READ_HT_LY, LYWSD03_Svc, nullptr, LYWSD03_BattNotifyChar); + break; + case MI_MHOC401: + // don't read if key present and we've decoded at least one advert + if (MIBLEsensors[slot].needkey == KEY_REQUIRED_AND_FOUND && !force) return -2; + res = MI32Operation(slot, OP_READ_HT_LY, MHOC401_Svc, nullptr, MHOC401_BattNotifyChar); + break; + + default: + res = -1; + break; + } + return res; +} + + +// called once per second +int readOneSensor(){ + if (MI32.sensorreader.active){ + AddLog_P(LOG_LEVEL_DEBUG,PSTR("readOneSensor - already active reading %d"), MI32.sensorreader.slot-1); + return 0; + } + + // loop if the sensor at the slot does not need to be read + // i.e. drop out of loop when we start a read, or hit the end + int res = -1; + do { + // MI32.sensorreader.slot is reset to zero to trigger a read sequence + if (MI32.sensorreader.slot >= MIBLEsensors.size()){ + //AddLog_P(LOG_LEVEL_DEBUG,PSTR("readOneSensor past end of slots - %d > %d"), MI32.sensorreader.slot, MIBLEsensors.size()); + return 0; + } + + res = genericSensorReadFn(MI32.sensorreader.slot, 0); + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("genericSensorReadFn slot %d res %d"), MI32.sensorreader.slot, res); + + // if this sensor in this slot does not need to be read via notify, just move on top the next one + if (res < 0){ + MI32.sensorreader.slot++; + } else { + break; + } + } while (1); + + if (res == 0){ + // can't read at the moment (no operations available?) + AddLog_P(LOG_LEVEL_DEBUG,PSTR("readOneSensor no ops available slot %d res %d"), MI32.sensorreader.slot, res); + return 0; + } + + // setup next slot to read + MI32.sensorreader.slot++; + // and make it wait until the read/notify is complete + // this is cleared in the response callback. + MI32.sensorreader.active = 1; + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("readOneSensor reading for slot %d res %d"), MI32.sensorreader.slot-1, res); + + // started one + return 1; +} + + + +// called once per second +int readOneBat(){ + if (MI32.batteryreader.active){ + return 0; + } + + //MI32.batteryreader.slot is rest to zero to trigger a read... + if (MI32.batteryreader.slot >= MIBLEsensors.size()){ + return 0; + } + + int res = genericBatReadFn(MI32.batteryreader.slot); + + // if this sensor in this slot does not support battery read, just move on top the next one + if (res < 0){ + MI32.batteryreader.slot++; + if (MI32.batteryreader.slot >= MIBLEsensors.size()){ + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_INFO,PSTR("Batt loop complete at %d"), MI32.batteryreader.slot); + } + return 0; + } + + if (res == 0){ + // can't read at the moment (no operations available?) + return 0; + } + + // setup next slot to read + MI32.batteryreader.slot++; + // and make it wait until the read/notify is complete + // this is cleared in the response callback. + MI32.batteryreader.active = 1; + if (MI32.batteryreader.slot >= MIBLEsensors.size()){ + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_INFO,PSTR("Batt loop will complete at %d"), MI32.batteryreader.slot); + } + // started one + return 1; +} + + + +///////////////////////////////////////////////////// +// change the unit of measurement? +// call with unit == -1 to cause the unit to be toggled. +int genericUnitWriteFn(int slot, int unit){ + int res = 0; + int op = OP_UNIT_WRITE; + if (unit == -1){ + op = OP_UNIT_TOGGLE; + } + uint8_t writeData[1]; + writeData[0] = unit; + switch (MIBLEsensors[slot].type){ + case MI_LYWSD02: + res = MI32Operation(slot, op, LYWSD02_Svc, LYWSD02_UnitChar, nullptr, writeData, 1); + break; + case MI_MHOC303: // actually, EXACTLY the same as above, including the sevice and characteristic... + res = MI32Operation(slot, op, MHOC303_Svc, MHOC303_UnitChar, nullptr, writeData, 1); + break; + default: + res = -1; + break; + } + return res; +} + +///////////////////////////////////////////////////// +// read the unit of measurement. genericOpCompleteFn +int genericUnitReadFn(int slot){ + int res = 0; + switch (MIBLEsensors[slot].type){ + case MI_LYWSD02: + res = MI32Operation(slot, OP_UNIT_READ, LYWSD02_Svc, LYWSD02_UnitChar); + break; + case MI_MHOC303: // actually, EXACTLY the same as above, including the sevice and characteristic... + res = MI32Operation(slot, OP_UNIT_READ, MHOC303_Svc, MHOC303_UnitChar); + break; + default: + res = -1; + break; + } + return res; +} + + +///////////////////////////////////////////////////// +// write time to a device. genericOpCompleteFn +int genericTimeWriteFn(int slot){ + int res = 0; + switch (MIBLEsensors[slot].type){ + case MI_LYWSD02: { + union { + uint8_t buf[5]; + uint32_t time; + } _utc; + _utc.time = Rtc.utc_time; + _utc.buf[4] = Rtc.time_timezone / 60; + res = MI32Operation(slot, OP_TIME_WRITE, LYWSD02_Svc, LYWSD02_TimeChar, nullptr, _utc.buf, sizeof(_utc.buf)); + } break; + case MI_MHOC303: // actually, EXACTLY the same as above, including the sevice and characteristic... + union { + uint8_t buf[5]; + uint32_t time; + } _utc; + _utc.time = Rtc.utc_time; + _utc.buf[4] = Rtc.time_timezone / 60; + res = MI32Operation(slot, OP_TIME_WRITE, MHOC303_Svc, MHOC303_TimeChar, nullptr, _utc.buf, sizeof(_utc.buf)); + break; + default: + res = -1; + break; + } + return res; +} + + +int genericOpCompleteFn(BLE_ESP32::generic_sensor_t *op){ + uint32_t context = (uint32_t) op->context; + + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI op complete context %x"), context); + + int opType = context >> 24; + int devType = (context >> 16) & 0xff; + int slot = (context) & 0xff; + + char slotMAC[13]; + BLE_ESP32::dump(slotMAC, sizeof(slotMAC), MIBLEsensors[slot].MAC, 6) ; + uint8_t addrrev[6]; + memcpy(addrrev, MIBLEsensors[slot].MAC, 6); + //BLE_ESP32::ReverseMAC(addrrev); + NimBLEAddress addr(addrrev); + + bool fail = false; + if (op->addr != addr){ + // slot changed during operation? + AddLog_P(LOG_LEVEL_ERROR,PSTR("Slot mac changed during an operation")); + fail = true; + } + + if (op->state <= GEN_STATE_FAILED){ + AddLog_P(LOG_LEVEL_ERROR,PSTR("operation failed %d for %s"), op->state, slotMAC); + fail = true; + } + + if (fail){ + switch(opType){ + case OP_BATT_READ:{ + // allow another... + MI32.batteryreader.active = 0; + } break; + case OP_READ_HT_LY: { + // allow another... + MI32.sensorreader.active = 0; + } break; + } + return 0; + } + + switch(opType){ + case OP_TIME_WRITE: + AddLog_P(LOG_LEVEL_DEBUG,PSTR("Time write for %s complete"), slotMAC); + return 0; // nothing to do + case OP_BATT_READ:{ + uint8_t *data = nullptr; + int len = 0; + if (op->notifylen){ + data = op->dataNotify; + len = op->notifylen; + // note: the only thingas that have battery in notify FOR THE MOMENT read it like this. + MI32notifyHT_LY(slot, (char*)op->dataNotify, op->notifylen); + } + if (op->readlen){ + data = op->dataRead; + len = op->readlen; + MIParseBatt(slot, data, len); + } + + // allow another... + MI32.batteryreader.active = 0; + AddLog_P(LOG_LEVEL_INFO,PSTR("batt read slot %d done state %x"), slot, op->state); + + } return 0; + + case OP_UNIT_WRITE: // nothing more to do? + AddLog_P(LOG_LEVEL_DEBUG,PSTR("Unit write for %s complete"), slotMAC); + return 0; + + case OP_UNIT_READ: { + uint8_t currUnit = op->dataRead[0]; + AddLog_P(LOG_LEVEL_DEBUG,PSTR("Unit read for %s complete %d"), slotMAC, currUnit); + } return 0; + + case OP_UNIT_TOGGLE: { + uint8_t currUnit = op->dataToWrite[0]; + AddLog_P(LOG_LEVEL_DEBUG,PSTR("Unit toggle for %s complete %d->%d; datasize was %d"), slotMAC, op->dataRead[0], op->dataToWrite[0], op->readlen); + } return 0; + + case OP_READ_HT_LY: { + // allow another... + MI32.sensorreader.active = 0; + MI32notifyHT_LY(slot, (char*)op->dataNotify, op->notifylen); + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("HT_LY notify for %s complete"), slotMAC); + } return 0; + + default: + AddLog_P(LOG_LEVEL_ERROR,PSTR("OpType %d not recognised?"), opType); + return 0; + } + + return 0; +} + +int MI32advertismentCallback(BLE_ESP32::ble_advertisment_t *pStruct) +{ + // we will try not to use this... + BLEAdvertisedDevice *advertisedDevice = pStruct->advertisedDevice; + + // AddLog_P(LOG_LEVEL_DEBUG,PSTR("Advertised Device: %s Buffer: %u"),advertisedDevice->getAddress().toString().c_str(),advertisedDevice->getServiceData(0).length()); + int RSSI = pStruct->RSSI; + const uint8_t *addr = pStruct->addr; + if(MI32isInBlockList(addr) == true) return 0; + + int svcdataCount = advertisedDevice->getServiceDataCount(); + + if (svcdataCount == 0) { + return 0; + } + + NimBLEUUID UUIDBig = advertisedDevice->getServiceDataUUID(0);//.getNative()->u16.value; + + const ble_uuid_any_t* native = UUIDBig.getNative(); + if (native->u.type != 16){ + //not interested in 128 bit; + return 0; + } + uint16_t UUID = native->u16.value; + + char temp[60]; + BLE_ESP32::dump(temp, 13, addr, 6); + + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("MI:%s svc[0] UUID (%x)"), temp, UUID); + std::string ServiceDataStr = advertisedDevice->getServiceData(0); + + uint32_t ServiceDataLength = ServiceDataStr.length(); + const uint8_t *ServiceData = (const uint8_t *)ServiceDataStr.data(); + BLE_ESP32::dump(temp, 60, ServiceData, ServiceDataLength); + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("MI:%s"), temp); + + + if (UUID){ + // this will take and keep the mutex until the function is over + TasAutoMutex localmutex(&slotmutex, "Mi32AdCB2"); + switch(UUID){ + case 0xfe95: // std MI? + case 0xfdcd: // CGD1? + { + MI32ParseResponse(ServiceData, ServiceDataLength, addr, RSSI); + } break; + case 0x181a: { //ATC + MI32ParseATCPacket(ServiceData, ServiceDataLength, addr, RSSI); + } break; + + default:{ + } break; + } + } + return 0; +} + + +/*********************************************************************************************\ + * Helper functions +\*********************************************************************************************/ + +/** + * @brief Remove all colons from null terminated char array + * + * @param _string Typically representing a MAC-address like AA:BB:CC:DD:EE:FF + */ +void MI32stripColon(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; +} + +/** + * @brief Convert string that repesents a hexadecimal number to a byte array + * + * @param _string input string in format: AABBCCDDEEFF or AA:BB:CC:DD:EE:FF, caseinsensitive + * @param _mac target byte array must match the correct size (i.e. AA:BB -> uint8_t bytes[2]) + */ + +void MI32HexStringToBytes(char* _string, uint8_t* _byteArray) { + MI32stripColon(_string); + UpperCase(_string,_string); + uint32_t index = 0; + uint32_t _end = strlen(_string); + memset(_byteArray,0,_end/2); + while (index < _end) { + char c = _string[index]; + uint8_t value = 0; + if(c >= '0' && c <= '9') + value = (c - '0'); + else if (c >= 'A' && c <= 'F') + value = (10 + (c - 'A')); + _byteArray[(index/2)] += value << (((index + 1) % 2) * 4); + index++; + } +} + +/** + * @brief Reverse an array of 6 bytes + * + * @param _mac a byte array of size 6 (typicalliy representing a MAC address) + */ +void MI32_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)); +} + +#ifdef USE_MI_DECRYPTION +int MI32AddKey(char* payload, char* key = nullptr){ + mi_bindKey_t keyMAC; + + if (!key){ + MI32HexStringToBytes(payload,keyMAC.buf); + } else { + MI32HexStringToBytes(payload,keyMAC.MAC); + MI32HexStringToBytes(key,keyMAC.key); + } + + bool unknownKey = true; + for(uint32_t i=0; i 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI32: search key for MAC: %02x%02x%02x%02x%02x%02x"), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + for(uint32_t i=0; i 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI32: decryption Key found")); + foundNoKey = false; + break; + } + } + if(foundNoKey){ + AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI32: no Key found !!")); + return -2; // indicates needs key + } + + br_aes_small_ctrcbc_keys keyCtx; + br_aes_small_ctrcbc_init(&keyCtx, _bindkey, 16); + + br_ccm_context ctx; + br_ccm_init(&ctx, &keyCtx.vtable); + br_ccm_reset(&ctx, nonce, 12, 1, len, 4); + br_ccm_aad_inject(&ctx, authData, 1); + br_ccm_flip(&ctx); + + memcpy(payload, data, len); //we want to be sure about 4-byte alignement + br_ccm_run(&ctx, 0, payload, len); + memcpy(data, payload, len); //back to the packet + + + // crashed in here - why?, so give it more space to work with? + // returns 1 if matched, else 0 + int ret = br_ccm_check_tag(&ctx, &tag); + + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI32: Err:%i, Decrypted : %02x %02x %02x %02x %02x"), ret, payload[1],payload[2],payload[3],payload[4],payload[5]); + return ret-1; // -> -1=fail, 0=success +} + +#endif // USE_MI_DECRYPTION + + +// packet examples: +// MJ_HT_V1 +// 5020 AA01 41 3AF4DAA8654C 0A100109 +// 5020 AA01 43 3AF4DAA8654C 061002E901 +// 5020 AA01 48 3AF4DAA8654C 041002BF00 +// 5020 AA01 4A 3AF4DAA8654C 0D1004BF00E901 +// 7122 AA01 15 3AF4DAA8654C 0D 0200020D10 + +// LYWSD03 encrypted data: +// 5858 5B05 2F B3E30838C1A4 [69A9FBDF67] ,060000 0791C39A - 23bytes +// 23-9 = 14 +// -> nonce B3E30838C1A4|5B02|2F|060000 +// 23-6 = 17 +// -> tag 0791C39A +// datalen = 23 - 9 - 4 - 3 - 1 - 1 = 5 + +// CGD1 reconstructed from src: (svcdata on fdcd) +// xxyy FFEEDDCCBBAA MMMM TTTTHHHH|BB +// xxyy FFEEDDCCBBAA 0104 TTTTHHHH +// xxyy FFEEDDCCBBAA 0201 BB + + +int MIParsePacket(const uint8_t* slotmac, struct mi_beacon_data_t *parsed, const uint8_t *datain, int len){ + uint8_t data[32]; + memcpy(data, datain, len); + if (!parsed){ + return 0; + } + if (len < 5){ + return 0; + } + + int byteindex = 0; + + // 58 58 = 0x5858 = data|comp|mac|enc, v5|auth2 + // 30 58 = 0x5830 = comp|mac, v5|auth2 + // 30 50 = 0x5030 = comp|mac, v5|auth0 + // 48 59 = 0x5948 = data|enc, v5|auth2|registered + // 10 59 = 0x5910 = mac, v5|auth2|registered + // 71 22 = 0x2271 = data|comp|mac v2|bind + // 50 20 = 0x2050 = data|mac v2 - MJ_HT_V1 data + // 71 22 = 0x2271 = data|comp|mac|reserved1 v2|bind - MJ_HT_V1 pair + + // data from byte 0 - e.g. 30 + parsed->framedata.meshflag = (data[byteindex] & 0x80)>>7; //Byte 0: x....... + parsed->framedata.dataflag = (data[byteindex] & 0x40)>>6; //Byte 0: .x...... + parsed->framedata.compatibilityflag = (data[byteindex] & 0x20)>>5; //Byte 0: ..x..... - indicates compatibility data present + parsed->framedata.MACFlag = (data[byteindex] & 0x10)>>4; //Byte 0: ...x.... + parsed->framedata.isencrypted = (data[byteindex] & 0x08)>>3; //Byte 0: ....x... + parsed->framedata.reserved = (data[byteindex] & 0x03)>>6; //Byte 0: .....xxx + + // data from byte 1 - e.g. 58 + byteindex++; + parsed->framedata.version = (data[byteindex] & 0xf0)>>4; //Byte 0: xxxx.... + parsed->framedata.authMode = (data[byteindex] & 0x0C)>>6; //Byte 0: ....xx.. // e.g. 2 + parsed->framedata.bindingvalidreq = (data[byteindex] & 0x02)>>1; //Byte 0: ......x. + parsed->framedata.registeredflag = (data[byteindex] & 0x01); //Byte 0: .......x + + byteindex++; + + parsed->devicetype = *((uint16_t *)(data + byteindex)); + byteindex += 2; + parsed->framecnt = data[byteindex]; + //if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI frame %d"), parsed->framecnt); + byteindex++; + + + if (parsed->framedata.version <= 3){ + // e.g. MJ_HT_V1 + } + + if (parsed->framedata.MACFlag){ + if (len < byteindex + 6){ + return 0; + } + memcpy(parsed->macdata.mac, &data[byteindex], 6); + byteindex += 6; + } + + int decres = 1; + // everything after MAC is encrypted if specified? + if (parsed->framedata.isencrypted){ + if (len < byteindex + 3+4+1){ + return 0; + } + const uint8_t* mac = slotmac; + if (parsed->framedata.MACFlag){ + mac = parsed->macdata.mac; + } + uint8_t nonce[12]; + uint8_t *p = nonce; + memcpy(p, mac, 6); + p += 6; + memcpy(p, &parsed->devicetype, 2); + p += 2; + *(p++) = parsed->framecnt; + uint8_t *extCnt = data +(len-7); + memcpy(p, extCnt, 3); + p += 3; + uint32_t tag = *(uint32_t *)(data + (len-4)); + + // decrypt the data in place + decres = MIDecryptPayload(mac, nonce, tag, data + byteindex, len - byteindex - 7); + // no longer need the nonce data. + len -= 7; + } + + switch(decres){ + case 1: // decrypt not requested + break; + case 0: // suceeded + parsed->needkey = KEY_REQUIRED_AND_FOUND; + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI payload decrypted")); + break; + case -1: // key failed to work + parsed->needkey = KEY_REQUIRED_AND_INVALID; + AddLog_P(LOG_LEVEL_ERROR,PSTR("MI payload decrypt failed")); + parsed->payloadpresent = 0; + return 0; + break; + case -2: // key not present + parsed->needkey = KEY_REQUIRED_BUT_NOT_FOUND; + AddLog_P(LOG_LEVEL_ERROR,PSTR("MI payload encrypted but no key")); + parsed->payloadpresent = 0; + return 0; + break; + } + + // if set, there could be 1 or 3 bytes here. + if (parsed->framedata.compatibilityflag) { + if (len < byteindex + 1){ + return 0; + } + // e.g. in pair: 7122 AA01 15 3AF4DAA8654C [0D] 0200020D10 -> bond|unused2 + parsed->compatibility.reserved = (data[byteindex] & 0xc0) >> 6; //Byte 0: xx...... + parsed->compatibility.IOcap = (data[byteindex] & 0x20) >> 5; //Byte 0: ..x..... + parsed->compatibility.bondability = (data[byteindex] & 0x18) >> 3; //Byte 0: ...xx... + parsed->compatibility.unused = (data[byteindex] & 0x07) >> 0; //Byte 0: .....xxx + byteindex ++; + + if (parsed->compatibility.IOcap) { + if (len < byteindex + 2){ + return 0; + } + parsed->compatibility.IOCapability = *((uint16_t *)(data + byteindex)); // bytes 1-2, e.g. 01 00 -> 0001 + byteindex += 2; + } + } + + // rest is payload + int rem = (len - byteindex); + if (rem > sizeof(parsed->payload)){ + rem = sizeof(parsed->payload); + return 0; + } + + if ((len - byteindex) == 0){ + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI no payload")); + parsed->payload.size = 0; + parsed->payloadpresent = 0; + return 0; + } + + // we have payload which did not need decrypt. + if (decres == 1){ + parsed->needkey = KEY_NOT_REQUIRED; + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI payload unencrypted")); + } + + // already decrypted if required + parsed->payloadpresent = 1; + memcpy(&parsed->payload, (data + byteindex), (len - byteindex)); + if (parsed->payload.size != (len - byteindex) - 3){ + AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI payload length mismatch")); + } + + return 1; +} + + + + +#ifdef USE_HOME_ASSISTANT +/** + * @brief For HASS only, changes last entry of JSON in mqtt_data to 'null' + */ + +void MI32nullifyEndOfMQTT_DATA(){ + char *p = TasmotaGlobal.mqtt_data + strlen(TasmotaGlobal.mqtt_data); + while(true){ + *p--; + if(p[0]==':'){ + p[1] = 0; + break; + } + } + ResponseAppend_P(PSTR("null")); +} +#endif // USE_HOME_ASSISTANT + +/*********************************************************************************************\ + * common functions +\*********************************************************************************************/ + + +/** + * @brief Return the slot number of a known sensor or return create new sensor slot + * + * @param _MAC BLE address of the sensor + * @param _type Type number of the sensor + * @return uint32_t Known or new slot in the sensors-vector + */ +uint32_t MIBLEgetSensorSlot(const uint8_t *mac, uint16_t _type, uint8_t counter){ + + //AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("%s: will test ID-type: %x"),D_CMND_MI32, _type); + bool _success = false; + for (uint32_t i=0; i < MI_MI32_TYPES; i++){ // i < sizeof(kMI32DeviceID) gives compiler warning + if(_type == kMI32DeviceID[i]){ + _type = i+1; + _success = true; + break; + } + else { + //AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("%s: ID-type is not: %x"),D_CMND_MI32,kMI32DeviceID[i]); + } + } + if(!_success) { + _type = 1; // unknown + } + + //AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("%s: vector size %u"),D_CMND_MI32, MIBLEsensors.size()); + for(uint32_t i=0; i 0) AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("%s: slot: %u/%u - ign repeat"),D_CMND_MI32, i, MIBLEsensors.size()); + //return 0xff; // packet received before, stop here + } + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI frame %d, last %d"), counter, MIBLEsensors[i].lastCnt); + MIBLEsensors[i].lastCnt = counter; + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("%s: slot: %u/%u"),D_CMND_MI32, i, MIBLEsensors.size()); + + if (MIBLEsensors[i].type != _type){ + // this happens on incorrectly configured pvvx ATC firmware + AddLog_P(LOG_LEVEL_ERROR,PSTR("%s: slot: %u - device type 0x%04x(%s) -> 0x%04x(%s) - check device is only sending one type of advert."),D_CMND_MI32, i, + kMI32DeviceID[MIBLEsensors[i].type-1], kMI32DeviceType[MIBLEsensors[i].type-1], kMI32DeviceID[_type-1], kMI32DeviceType[_type-1]); + MIBLEsensors[i].type = _type; + } + + return i; + } + //AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("%s: i: %x %x %x %x %x %x"),D_CMND_MI32, MIBLEsensors[i].MAC[5], MIBLEsensors[i].MAC[4],MIBLEsensors[i].MAC[3],MIBLEsensors[i].MAC[2],MIBLEsensors[i].MAC[1],MIBLEsensors[i].MAC[0]); + //AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("%s: n: %x %x %x %x %x %x"),D_CMND_MI32, mac[5], mac[4], mac[3],mac[2],mac[1],mac[0]); + } + //AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("%s: new sensor -> slot: %u"),D_CMND_MI32, MIBLEsensors.size()); + //AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("%s: found new sensor"),D_CMND_MI32); + mi_sensor_t _newSensor; + memset(&_newSensor, 0 , sizeof(_newSensor)); + memcpy(_newSensor.MAC, mac, 6); + _newSensor.type = _type; + _newSensor.eventType.raw = 0; + _newSensor.feature.raw = 0; + _newSensor.temp = NAN; + _newSensor.needkey = KEY_REQUIREMENT_UNKNOWN; + _newSensor.bat = 0x00; + _newSensor.RSSI = 0xffff; + _newSensor.lux = 0x00ffffff; + + switch (_type) + { + case MI_FLORA: + _newSensor.moisture =0xff; + _newSensor.fertility =0xffff; + _newSensor.firmware[0]='\0'; + _newSensor.feature.temp=1; + _newSensor.feature.moist=1; + _newSensor.feature.fert=1; + _newSensor.feature.lux=1; + _newSensor.feature.bat=1; + break; + case MI_NLIGHT: + _newSensor.events=0x00; + _newSensor.feature.PIR=1; + _newSensor.feature.NMT=1; + break; + case MI_MJYD2S: + _newSensor.NMT=0; + _newSensor.events=0x00; + _newSensor.feature.PIR=1; + _newSensor.feature.NMT=1; + _newSensor.feature.lux=1; + _newSensor.feature.bat=1; + break; + case MI_YEERC: + _newSensor.feature.Btn=1; + break; + default: + _newSensor.hum=NAN; + _newSensor.feature.temp=1; + _newSensor.feature.hum=1; + _newSensor.feature.tempHum=1; + _newSensor.feature.bat=1; + break; + } + MIBLEsensors.push_back(_newSensor); + AddLog_P(LOG_LEVEL_DEBUG,PSTR("%s: new %s at slot: %u"),D_CMND_MI32, kMI32DeviceType[_type-1],MIBLEsensors.size()-1); + MI32.mode.shallShowStatusInfo = 1; + return MIBLEsensors.size()-1; +}; + +/** + * @brief trigger real-time message for PIR or RC + * + */ +void MI32triggerTele(void){ + MI32.mode.triggeredTele = 1; + MI32ShowTriggeredSensors(); + MI32.mode.triggeredTele = 0; +} + +/** + * @brief Is called after every finding of new BLE sensor + * + */ +void MI32StatusInfo() { + MI32.mode.shallShowStatusInfo = 0; + Response_P(PSTR("{\"%s\":{\"found\":%u}}"), D_CMND_MI32, MIBLEsensors.size()); + XdrvRulesProcess(); +} + +/*********************************************************************************************\ + * BLE callbacks section + * These are called from main thread only. +\*********************************************************************************************/ + + +int MI32scanCompleteCallback(NimBLEScanResults results){ + // we actually don't need to do anything here.... + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI32: scancomplete")); + return 0; +} + + +/*********************************************************************************************\ + * init BLE_32 +\*********************************************************************************************/ + + +void MI32Init(void) { + MIBLEsensors.reserve(10); + MIBLEbindKeys.reserve(10); + MI32.mode.init = false; + + //test section for options + MI32.option.allwaysAggregate = 1; + MI32.option.noSummary = 0; + MI32.option.minimalSummary = 0; + MI32.option.directBridgeMode = 0; + MI32.option.showRSSI = 1; + MI32.option.ignoreBogusBattery = 1; // from advertisements + MI32.option.holdBackFirstAutodiscovery = 1; + + BLE_ESP32::registerForAdvertismentCallbacks((const char *)"MI32", MI32advertismentCallback); + BLE_ESP32::registerForScanCallbacks((const char *)"MI32", MI32scanCompleteCallback); + // note: for operations, we will set individual callbacks in the operations we request + //void registerForOpCallbacks(const char *tag, BLE_ESP32::OPCOMPLETE_CALLBACK* pFn); + + AddLog_P(LOG_LEVEL_INFO,PSTR("MI32: init: request callbacks")); + MI32.period = Settings.tele_period; + MI32.mode.init = 1; + return; +} + + +/*********************************************************************************************\ + * Task section +\*********************************************************************************************/ + + + + +int MIParseBatt(int slot, uint8_t *data, int len){ + int value = data[0]; + char slotMAC[13]; + BLE_ESP32::dump(slotMAC, sizeof(slotMAC), MIBLEsensors[slot].MAC, 6) ; + + if ((value != 0) && (value < 101)){ + MIBLEsensors[slot].bat = value; + if(MIBLEsensors[slot].type==MI_FLORA){ + if (len < 7){ + AddLog_P(LOG_LEVEL_ERROR,PSTR("FLORA: not enough bytes read for firmware?")); + } else { + memcpy(MIBLEsensors[slot].firmware, data+2, 5); + MIBLEsensors[slot].firmware[5] = '\0'; + AddLog_P(LOG_LEVEL_DEBUG,PSTR("%s: FLORA Firmware: %s"),D_CMND_MI32,MIBLEsensors[slot].firmware); + } + } + MIBLEsensors[slot].eventType.bat = 1; + MIBLEsensors[slot].shallSendMQTT = 1; + MI32.mode.shallTriggerTele = 1; + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("Batt read for %s complete %d"), slotMAC, value); + } else { + AddLog_P(LOG_LEVEL_ERROR,PSTR("Batt read for %s complete but out of range 1-101 (%d)"), slotMAC, value); + } + + return 0; +} + +/*********************************************************************************************\ + * parse the response from advertisements +\*********************************************************************************************/ + + +void MI32ParseATCPacket(const uint8_t * _buf, uint32_t length, const uint8_t *addr, int RSSI){ + ATCPacket_t *_packet = (ATCPacket_t*)_buf; + PVVXPacket_t *ppv_packet = (PVVXPacket_t*)_buf; + + + if (length == 15){ // 19-1-1-2 + uint8_t addrrev[6]; + memcpy(addrrev, addr, 6); + MI32_ReverseMAC(addrrev); + if (!memcmp(addrrev, ppv_packet->MAC, 6)){ + //int16_t temperature; // x 0.1 degree + //uint16_t humidity; // x 0.01 % + //uint16_t battery_mv; // mV + //uint8_t battery_level; // 0..100 % + //uint8_t counter; // measurement count + //uint8_t flags; + + uint32_t _slot = MIBLEgetSensorSlot(addr, 0x0a1c, ppv_packet->counter); // This must be a hard-coded fake ID + if(_slot==0xff) return; + + if ((_slot >= 0) && (_slot < MIBLEsensors.size())){ + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("%s:pvvx at slot %u"), kMI32DeviceType[MIBLEsensors[_slot].type-1],_slot); + MIBLEsensors[_slot].RSSI=RSSI; + MIBLEsensors[_slot].needkey=KEY_NOT_REQUIRED; + + MIBLEsensors[_slot].temp = (float)(ppv_packet->temperature)/100.0f; + MIBLEsensors[_slot].hum = (float)(ppv_packet->humidity)/100.0f; + MIBLEsensors[_slot].eventType.tempHum = 1; + MIBLEsensors[_slot].bat = ppv_packet->battery_level; + MIBLEsensors[_slot].eventType.bat = 1; + + if(MI32.option.directBridgeMode) { + MIBLEsensors[_slot].shallSendMQTT = 1; + MI32.mode.shallTriggerTele = 1; + } + } + return; + } else { + AddLog_P(LOG_LEVEL_ERROR, PSTR("PVVX packet mac mismatch - ignored?")); + return; + } + } + + + uint8_t addrrev[6]; + memcpy(addrrev, addr, 6); + //MI32_ReverseMAC(addrrev); + + // if packet tell a different address to origin, use the different address + if (memcmp(addrrev, _packet->MAC, 6)){ + MI32_ReverseMAC(_packet->MAC); + if (!memcmp(addrrev, _packet->MAC, 6)){ + AddLog_P(LOG_LEVEL_ERROR, PSTR("ATC packet with reversed MAC addr?")); + } else { + AddLog_P(LOG_LEVEL_ERROR, PSTR("ATC packet with MAC addr mismatch - is this mesh?")); + memcpy(addrrev, _packet->MAC, 6); + } + addr = addrrev; + } + + uint32_t _slot = MIBLEgetSensorSlot(addr, 0x0a1c, _packet->frameCnt); // This must be a hard-coded fake ID + + if(_slot==0xff) return; + + if ((_slot >= 0) && (_slot < MIBLEsensors.size())){ + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("%s at slot %u"), kMI32DeviceType[MIBLEsensors[_slot].type-1],_slot); + MIBLEsensors[_slot].RSSI=RSSI; + MIBLEsensors[_slot].needkey=KEY_NOT_REQUIRED; + + MIBLEsensors[_slot].temp = (float)(int16_t(__builtin_bswap16(_packet->temp)))/10.0f; + MIBLEsensors[_slot].hum = (float)_packet->hum; + MIBLEsensors[_slot].eventType.tempHum = 1; + MIBLEsensors[_slot].bat = _packet->batPer; + MIBLEsensors[_slot].eventType.bat = 1; + + if(MI32.option.directBridgeMode) { + MIBLEsensors[_slot].shallSendMQTT = 1; + MI32.mode.shallTriggerTele = 1; + } + } else { + + } +} + +//////////////////////////////////////////////////////////// +// this SHOULD parse any MI payload. +int MI32parseMiPayload(int _slot, struct mi_beacon_data_t *parsed){ + struct mi_beacon_data_payload_data_t *pld = + (struct mi_beacon_data_payload_data_t *) &parsed->payload.data; + int res = 1; + + if (!parsed->payloadpresent){ + return 0; + } + + char tmp[20]; + BLE_ESP32::dump(tmp, 20, (uint8_t*)&(parsed->payload), parsed->payload.size+3); + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("MI%d payload %s"), _slot, tmp); + + switch(parsed->payload.type){ + case 0x01: // button press + MIBLEsensors[_slot].Btn = pld->Btn.num + (pld->Btn.longPress/2)*6; + MIBLEsensors[_slot].eventType.Btn = 1; + MI32.mode.shallTriggerTele = 1; + break; + case 0x02: + res = 0; + break; + case 0x03: {// motion? 1 byte + uint8_t motion = parsed->payload.data[0]; + res = 0; + }break; + case 0x04:{ + float _tempFloat=(float)(pld->temp)/10.0f; + if(_tempFloat<60){ + MIBLEsensors[_slot].temp=_tempFloat; + MIBLEsensors[_slot].eventType.temp = 1; + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("Mode 4: temp updated")); + } else { + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("Mode 4: temp ignored > 60 (%f)"), _tempFloat); + } + // AddLog_P(LOG_LEVEL_DEBUG,PSTR("Mode 4: U16: %u Temp"), _beacon.temp ); + } break; + case 0x06: { + float _tempFloat=(float)(pld->hum)/10.0f; + if(_tempFloat<101){ + MIBLEsensors[_slot].hum=_tempFloat; + MIBLEsensors[_slot].eventType.hum = 1; + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("Mode 6: hum updated")); + } else { + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("Mode 6: hum ignored > 101 (%f)"), _tempFloat); + } + // AddLog_P(LOG_LEVEL_DEBUG,PSTR("Mode 6: U16: %u Hum"), _beacon.hum); + } break; + case 0x07: + MIBLEsensors[_slot].lux=pld->lux & 0x00ffffff; + if(MIBLEsensors[_slot].type==MI_MJYD2S){ + MIBLEsensors[_slot].eventType.noMotion = 1; + } + MIBLEsensors[_slot].eventType.lux = 1; + // AddLog_P(LOG_LEVEL_DEBUG,PSTR("Mode 7: U24: %u Lux"), _beacon.lux & 0x00ffffff); + break; + case 0x08: + MIBLEsensors[_slot].moisture=pld->moist; + MIBLEsensors[_slot].eventType.moist = 1; + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("Mode 8: moisture updated")); + // AddLog_P(LOG_LEVEL_DEBUG,PSTR("Mode 8: U8: %u Moisture"), _beacon.moist); + break; + case 0x09: // 'conductivity' + MIBLEsensors[_slot].fertility=pld->fert; + MIBLEsensors[_slot].eventType.fert = 1; + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("Mode 9: fertility updated")); + // AddLog_P(LOG_LEVEL_DEBUG,PSTR("Mode 9: U16: %u Fertility"), _beacon.fert); + break; + case 0x0a: + if(MI32.option.ignoreBogusBattery){ + if(MIBLEsensors[_slot].type==MI_LYWSD03MMC || MIBLEsensors[_slot].type==MI_MHOC401){ + res = 0; + break; + } + } + if(pld->bat<101){ + MIBLEsensors[_slot].bat = pld->bat; + MIBLEsensors[_slot].eventType.bat = 1; + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("Mode a: bat updated")); + } else { + MIBLEsensors[_slot].bat = 100; + MIBLEsensors[_slot].eventType.bat = 1; + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("Mode a: bat > 100 (%d)"), pld->bat); + } + // AddLog_P(LOG_LEVEL_DEBUG,PSTR("Mode a: U8: %u %%"), _beacon.bat); + break; + case 0x0d:{ + float _tempFloat=(float)(pld->HT.temp)/10.0f; + if(_tempFloat < 60){ + MIBLEsensors[_slot].temp = _tempFloat; + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("Mode d: temp updated")); + } else { + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("Mode d: temp ignored > 60 (%f)"), _tempFloat); + } + _tempFloat=(float)(pld->HT.hum)/10.0f; + if(_tempFloat < 100){ + MIBLEsensors[_slot].hum = _tempFloat; + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("Mode d: hum updated")); + } else { + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("Mode d: hum ignored > 100 (%f)"), _tempFloat); + } + MIBLEsensors[_slot].eventType.tempHum = 1; + // AddLog_P(LOG_LEVEL_DEBUG,PSTR("Mode d: U16: %x Temp U16: %x Hum"), _beacon.HT.temp, _beacon.HT.hum); + } break; + case 0x0f: + if (parsed->payload.ten != 0) break; + MIBLEsensors[_slot].eventType.motion = 1; + MIBLEsensors[_slot].lastTime = millis(); + MIBLEsensors[_slot].events++; + MIBLEsensors[_slot].lux = pld->lux; + MIBLEsensors[_slot].eventType.lux = 1; + MIBLEsensors[_slot].NMT = 0; + MI32.mode.shallTriggerTele = 1; + // AddLog_P(LOG_LEVEL_DEBUG,PSTR("PIR: primary"),MIBLEsensors[_slot].lux ); + break; + case 0x10:{ // 'formaldehide' + const uint16_t f = uint16_t(parsed->payload.data[0]) | (uint16_t(parsed->payload.data[1]) << 8); + float formaldehyde = (float)f / 100.0f; + res = 0; + } break; + case 0x12:{ // 'active' + int active = parsed->payload.data[0]; + res = 0; + } break; + case 0x13:{ //mosquito tablet + int tablet = parsed->payload.data[0]; + res = 0; + } break; + case 0x17:{ + const uint32_t idle_time = + uint32_t(parsed->payload.data[0]) | (uint32_t(parsed->payload.data[1]) << 8) | (uint32_t(parsed->payload.data[2]) << 16) | (uint32_t(parsed->payload.data[2]) << 24); + float idlemins = (float)idle_time / 60.0f; + int has_motion = (idle_time) ? 0 : 0; + + MIBLEsensors[_slot].NMT = pld->NMT; + MIBLEsensors[_slot].eventType.NMT = 1; + MI32.mode.shallTriggerTele = 1; + // AddLog_P(LOG_LEVEL_DEBUG,PSTR("Mode 17: NMT: %u seconds"), _beacon.NMT); + } break; + + default: { + AddLog_P(LOG_LEVEL_DEBUG,PSTR("Unknown MI pld")); + res = 0; + } break; + } + + if(res && MI32.option.directBridgeMode) { + MIBLEsensors[_slot].shallSendMQTT = 1; + MI32.mode.shallTriggerTele = 1; + } + + return res; +} + +//////////////////////////////////////////////////////////// +// this SHOULD parse any MI packet, including encrypted. +void MI32ParseResponse(const uint8_t *buf, uint16_t bufsize, const uint8_t* addr, int RSSI) { + struct mi_beacon_data_t parsed; + memset(&parsed, 0, sizeof(parsed)); + int res = MIParsePacket(addr, &parsed, buf, bufsize); + + uint8_t addrrev[6]; + memcpy(addrrev, addr, 6); + MI32_ReverseMAC(addrrev); + + if (memcmp(addrrev, parsed.macdata.mac, 6)){ + AddLog_P(LOG_LEVEL_ERROR, PSTR("MI packet with MAC addr mismatch - is this mesh?")); + memcpy(addrrev, parsed.macdata.mac, 6); + MI32_ReverseMAC(addrrev); + addr = addrrev; + } + + uint16_t _slot = MIBLEgetSensorSlot( addr, parsed.devicetype, parsed.framecnt ); + if(_slot==0xff) return; + if ((_slot >= 0) && (_slot < MIBLEsensors.size())){ + if (parsed.needkey != KEY_REQUIREMENT_UNKNOWN){ + MIBLEsensors[_slot].needkey = parsed.needkey; + } + MIBLEsensors[_slot].RSSI=RSSI; + if (!res){ // - if the payload is not valid + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG, PSTR("MIParsePacket returned %d"), res); + return; + } else { + } + MI32parseMiPayload(_slot, &parsed); + } +} + +bool MI32isInBlockList(const uint8_t* MAC){ + bool isBlocked = false; + for(auto &_blockedMAC : MIBLEBlockList){ + if(memcmp(_blockedMAC.buf,MAC,6) == 0) isBlocked = true; + } + return isBlocked; +} + +void MI32removeMIBLEsensor(uint8_t* MAC){ + // this will take and keep the mutex until the function is over + TasAutoMutex localmutex(&slotmutex, "Mi32Rem"); + + MIBLEsensors.erase( std::remove_if( MIBLEsensors.begin() , MIBLEsensors.end(), [MAC]( mi_sensor_t _sensor )->bool + { return (memcmp(_sensor.MAC,MAC,6) == 0); } + ), end( MIBLEsensors ) ); +} +/***********************************************************************\ + * Read data from connections +\***********************************************************************/ + +void MI32notifyHT_LY(int slot, char *_buf, int len){ + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("%s: raw data: %x%x%x%x%x%x%x"),D_CMND_MI32,_buf[0],_buf[1],_buf[2],_buf[3],_buf[4],_buf[5],_buf[6]); + // the value 0b00 is 28.16 C? + if(_buf[0] != 0 || _buf[1] != 0){ + memcpy(&LYWSD0x_HT,(void *)_buf,sizeof(LYWSD0x_HT)); + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG, PSTR("%s: T * 100: %u, H: %u, V: %u"),D_CMND_MI32,LYWSD0x_HT.temp,LYWSD0x_HT.hum, LYWSD0x_HT.volt); + uint32_t _slot = slot; + + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("MIBLE: Sensor slot: %u"), _slot); + static float _tempFloat; + _tempFloat=(float)(LYWSD0x_HT.temp)/100.0f; + if(_tempFloat<60){ + MIBLEsensors[_slot].temp=_tempFloat; + // MIBLEsensors[_slot].showedUp=255; // this sensor is real + } + _tempFloat=(float)LYWSD0x_HT.hum; + if(_tempFloat<100){ + MIBLEsensors[_slot].hum = _tempFloat; + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG_MORE,PSTR("LYWSD0x: hum updated")); + } + MIBLEsensors[_slot].eventType.tempHum = 1; + if (MIBLEsensors[_slot].type == MI_LYWSD03MMC || MIBLEsensors[_slot].type == MI_MHOC401){ + // ok, so CR2032 is 3.0v, but drops immediately to ~2.9. + // so we'll go with the 2.1 min, 2.95 max. + float minVolts = 2100.0; + //float maxVolts = 2950.0; + //float range = maxVolts - minVolts; + //float divisor = range/100; // = 8.5 + float percent = (((float)LYWSD0x_HT.volt) - minVolts)/ 8.5; //divisor; + if (percent > 100) percent = 100; + + MIBLEsensors[_slot].bat = (int) percent; + MIBLEsensors[_slot].eventType.bat = 1; + } + if(MI32.option.directBridgeMode) { + MIBLEsensors[_slot].shallSendMQTT = 1; + MI32.mode.shallTriggerTele = 1; + } + } +} + + +/** + * @brief Launch functions from Core 1 to make race conditions less likely + * + */ + +void MI32Every50mSecond(){ + + if(MI32.mode.shallTriggerTele){ + MI32.mode.shallTriggerTele = 0; + MI32triggerTele(); + } +} + +/** + * @brief Main loop of the driver, "high level"-loop + * + */ + +void MI32EverySecond(bool restart){ + +// AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR("MI32: onesec")); + MI32TimeoutSensors(); + + MI32ShowSomeSensors(); + + // read a battery if + // MI32.batteryreader.slot < filled and !MI32.batteryreader.active + readOneBat(); + + + // read a sensor if + // MI32.sensorreader.slot < filled and !MI32.sensorreader.active + // for sensors which need to get data through notify... + readOneSensor(); + + if (MI32.secondsCounter >= MI32.period){ + // only if we finished the last read + if (MI32.sensorreader.slot >= MIBLEsensors.size()){ + AddLog_P(LOG_LEVEL_DEBUG,PSTR("kick off readOneSensor")); + // kick off notification sensor reading every period. + MI32.sensorreader.slot = 0; + MI32.secondsCounter = 0; + } + } + MI32.secondsCounter ++; + + if (MI32.secondsCounter2 >= MI32.period){ + if (MI32.mqttCurrentSlot >= MIBLEsensors.size()){ + AddLog_P(LOG_LEVEL_DEBUG,PSTR("kick off tele sending")); + MI32.mqttCurrentSlot = 0; + MI32.secondsCounter2 = 0; + } else { + AddLog_P(LOG_LEVEL_DEBUG,PSTR("hit tele time, restarted but not finished last - lost from slot %d")+MI32.mqttCurrentSlot); + MI32.mqttCurrentSlot = 0; + MI32.secondsCounter2 = 0; + } + } + MI32.secondsCounter2++; + + static uint32_t _counter = MI32.period - 15; + static uint32_t _nextSensorSlot = 0; + uint32_t _idx = 0; + + int numsensors = MIBLEsensors.size(); + for (uint32_t i = 0; i < numsensors; i++) { + if(MIBLEsensors[i].type==MI_NLIGHT || MIBLEsensors[i].type==MI_MJYD2S){ + MIBLEsensors[i].NMT++; + } + } + + if(MI32.mode.shallShowStatusInfo == 1){ + MI32StatusInfo(); + } +} + +/*********************************************************************************************\ + * Commands +\*********************************************************************************************/ + +void CmndMi32Period(void) { + if (XdrvMailbox.data_len > 0) { + if (1 == XdrvMailbox.payload) { + MI32EverySecond(true); + } else { + MI32.period = XdrvMailbox.payload; + } + } + ResponseCmndNumber(MI32.period); +} + +int findSlot(char *addrOrAlias){ + uint8_t mac[6]; + int res = BLE_ESP32::getAddr(mac, addrOrAlias); + if (!res) return -1; + + for (int i = MIBLEsensors.size()-1; i >= 0 ; i--) { + if (!memcmp(MIBLEsensors[i].MAC, mac, 6)){ + return i; + } + } + return -1; +} + + +void CmndMi32Time(void) { + if (XdrvMailbox.data_len > 0) { + int slot = findSlot(XdrvMailbox.data); + if (slot < 0) { + slot = XdrvMailbox.payload; + } + if (MIBLEsensors.size() > slot) { + int res = genericTimeWriteFn(slot); + if (res > 0){ + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG, PSTR("MI32: will set Time")); + ResponseCmndNumber(slot); + return; + } + if (res < 0) { + AddLog_P(LOG_LEVEL_ERROR, PSTR("MI32: cannot set Time on sensor type")); + } + if (res == 0) { + AddLog_P(LOG_LEVEL_ERROR, PSTR("MI32: cannot set Time right now")); + } + } + } + ResponseCmndChar_P("fail"); +} + +void CmndMi32Page(void) { + if (XdrvMailbox.payload > 0) { + MI32.perPage = XdrvMailbox.payload; + } + ResponseCmndNumber(MI32.perPage); +} + +// read ALL battery values where we can? +void CmndMi32Battery(void) { + // trigger a read cycle + MI32.batteryreader.slot = 0; + ResponseCmndDone(); +} + + +void CmndMi32Unit(void) { + if (XdrvMailbox.data_len > 0) { + int slot = findSlot(XdrvMailbox.data); + if (slot < 0) { + slot = XdrvMailbox.payload; + } + + if (MIBLEsensors.size() > slot) { + // TOGGLE unit? + int res = genericUnitWriteFn(slot, -1); + if (res > 0){ + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG, PSTR("MI32: will toggle Unit")); + ResponseCmndNumber(slot); + return; + } + if (res < 0) { + AddLog_P(LOG_LEVEL_ERROR, PSTR("MI32: cannot toggle Unit on sensor type")); + } + if (res == 0) { + AddLog_P(LOG_LEVEL_ERROR, PSTR("MI32: cannot toggle Unit right now")); + } + } + } + ResponseCmndIdxChar(PSTR("Invalid")); +} + +#ifdef USE_MI_DECRYPTION +void CmndMi32Key(void) { + if (44 == XdrvMailbox.data_len) { // a KEY-MAC-string + MI32AddKey(XdrvMailbox.data, nullptr); + MI32KeyListResp(); + } else { + ResponseCmndIdxChar(PSTR("Invalid")); + } +} +#endif // USE_MI_DECRYPTION + +void MI32BlockListResp(){ + Response_P(PSTR("{\"MI32Block\":{")); + for (int i = 0; i < MIBLEBlockList.size(); i++){ + if (i){ + ResponseAppend_P(PSTR(",")); + } + char tmp[20]; + ToHex_P(MIBLEBlockList[i].buf,6,tmp,20,0); + ResponseAppend_P(PSTR("\"%s\":1"), tmp); + } + ResponseAppend_P(PSTR("}}")); +} + + +void CmndMi32Block(void){ + if (XdrvMailbox.data_len == 0) { + switch (XdrvMailbox.index) { + case 0: { + //TasAutoMutex localmutex(&slotmutex, "Mi32Block1"); + MIBLEBlockList.clear(); + } break; + default: + case 1: + break; + } + MI32BlockListResp(); + return; + } + + MAC_t _MACasBytes; + int res = BLE_ESP32::getAddr(_MACasBytes.buf, XdrvMailbox.data); + if (!res){ + ResponseCmndIdxChar(PSTR("Addr invalid")); + return; + } + + //MI32HexStringToBytes(XdrvMailbox.data,_MACasBytes.buf); + switch (XdrvMailbox.index) { + case 0: { + //TasAutoMutex localmutex(&slotmutex, "Mi32Block2"); + MIBLEBlockList.erase( std::remove_if( begin( MIBLEBlockList ), end( MIBLEBlockList ), [_MACasBytes]( MAC_t& _entry )->bool + { return (memcmp(_entry.buf,_MACasBytes.buf,6) == 0); } + ), end( MIBLEBlockList ) ); + } break; + case 1: { + //TasAutoMutex localmutex(&slotmutex, "Mi32Block3"); + bool _notYetInList = true; + for (auto &_entry : MIBLEBlockList) { + if (memcmp(_entry.buf,_MACasBytes.buf,6) == 0){ + _notYetInList = false; + } + } + if (_notYetInList) { + MIBLEBlockList.push_back(_MACasBytes); + MI32removeMIBLEsensor(_MACasBytes.buf); + } + // AddLog_P(LOG_LEVEL_INFO,PSTR("MI32: size of ilist: %u"), MIBLEBlockList.size()); + } break; + } + MI32BlockListResp(); +} + +void CmndMi32Option(void){ + bool onOff = atoi(XdrvMailbox.data); + switch(XdrvMailbox.index) { + case 0: + MI32.option.allwaysAggregate = onOff; + break; + case 1: + MI32.option.noSummary = onOff; + break; + case 2: + MI32.option.directBridgeMode = onOff; + break; + case 4:{ + MI32.option.ignoreBogusBattery = onOff; + } break; + } + ResponseCmndDone(); +} + +void MI32KeyListResp(){ + Response_P(PSTR("{\"MIKeys\":{")); + for (int i = 0; i < MIBLEbindKeys.size(); i++){ + if (i){ + ResponseAppend_P(PSTR(",")); + } + char tmp[20]; + ToHex_P(MIBLEbindKeys[i].MAC,6,tmp,20,0); + char key[16*2+1]; + ToHex_P(MIBLEbindKeys[i].key,16,key,33,0); + + ResponseAppend_P(PSTR("\"%s\":\"%s\""), tmp, key); + } + ResponseAppend_P(PSTR("}}")); +} + + +void CmndMi32Keys(void){ +#ifdef BLE_ESP32_ALIASES + int op = XdrvMailbox.index; + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("key %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 addrres = BLE_ESP32::getAddr(addr, p); + if (!addrres){ + ResponseCmndChar("invalidmac"); + return; + } + + p = strtok(nullptr, " ,="); + char *key = p; + if (!p || !(*p)){ + int i = 0; + for (i = 0; i < MIBLEbindKeys.size(); i++){ + mi_bindKey_t *key = &MIBLEbindKeys[i]; + if (!memcmp(key->MAC, addr, 6)){ + MIBLEbindKeys.erase(MIBLEbindKeys.begin() + i); + MI32KeyListResp(); + return; + } + } + ResponseCmndChar("invalidmac"); + return; + } + + AddLog_P(LOG_LEVEL_ERROR,PSTR("Add key mac %s = key %s"), mac, key); + char tmp[20]; + // convert mac back to string + ToHex_P(addr,6,tmp,20,0); + if (MI32AddKey(tmp, key)){ + added++; + } + p = strtok(nullptr, " ,="); + } while (p); + + if (added){ + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("Added %d Keys"), added); + MI32KeyListResp(); + } else { + MI32KeyListResp(); + } + return; + } break; + case 2:{ // clear + if (BLE_ESP32::BLEDebugMode > 0) AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI32Keys clearing %d"), MIBLEbindKeys.size()); + for (int i = MIBLEbindKeys.size()-1; i >= 0; i--){ + MIBLEbindKeys.pop_back(); + } + MI32KeyListResp(); + return; + } break; + } + ResponseCmndChar("invalididx"); +#endif +} + + +/*********************************************************************************************\ + * Presentation +\*********************************************************************************************/ + +const char HTTP_MI32[] PROGMEM = "{s}MI ESP32 v0918{m}%u%s / %u{e}"; +const char HTTP_MI32_ALIAS[] PROGMEM = "{s}%s Alias {m}%s{e}"; +const char HTTP_MI32_MAC[] PROGMEM = "{s}%s %s{m}%s{e}"; +const char HTTP_RSSI[] PROGMEM = "{s}%s " D_RSSI "{m}%d dBm{e}"; +const char HTTP_BATTERY[] PROGMEM = "{s}%s" " Battery" "{m}%u %%{e}"; +const char HTTP_LASTBUTTON[] PROGMEM = "{s}%s Last Button{m}%u {e}"; +const char HTTP_EVENTS[] PROGMEM = "{s}%s Events{m}%u {e}"; +const char HTTP_NMT[] PROGMEM = "{s}%s No motion{m}> %u seconds{e}"; +const char HTTP_MI32_FLORA_DATA[] PROGMEM = "{s}%s" " Fertility" "{m}%u us/cm{e}"; +const char HTTP_MI32_HL[] PROGMEM = "{s}
{m}
{e}"; + +//const char HTTP_NEEDKEY[] PROGMEM = "{s}%s %s{m} {e}"; + +//const char HTTP_NEEDKEY[] PROGMEM = "{s}%s %s{m} {e}"; +const char HTTP_NEEDKEY[] PROGMEM = "{s}%s %s{m} {e}"; + + +const char HTTP_PAIRING[] PROGMEM = "{s}%s Pair Button Pressed{m} {e}"; + + +const char HTTP_KEY_ERROR[] PROGMEM = "Key error %s"; +const char HTTP_MAC_ERROR[] PROGMEM = "MAC error %s"; +const char HTTP_KEY_ADDED[] PROGMEM = "Cmnd: MI32Keys %s=%s"; +const char HTTP_MI_KEY_STYLE[] PROGMEM = ""; + + +#define D_MI32_KEY "MI32 Set Key" + +void HandleMI32Key(){ + AddLog_P(LOG_LEVEL_DEBUG, PSTR("HandleMI32Key hit")); + if (!HttpCheckPriviledgedAccess()) { + AddLog_P(LOG_LEVEL_DEBUG, PSTR("!HttpCheckPriviledgedAccess()")); + return; + } + WSContentStart_P(PSTR(D_MI32_KEY)); + WSContentSendStyle_P(HTTP_MI_KEY_STYLE); + + char key[64] = {0}; + WebGetArg("key", key, sizeof(key)); + + if (strlen(key) != 16*2){ + WSContentSend_P(HTTP_KEY_ERROR, key); + WSContentStop(); + return; + } + + char mac[13] = {0}; + WebGetArg("mac", mac, sizeof(mac)); + if (strlen(mac) != 12){ + WSContentSend_P(HTTP_MAC_ERROR, mac); + WSContentStop(); + return; + } + + WSContentSend_P(HTTP_KEY_ADDED, mac, key); + + strncat(key, mac, sizeof(key)); + MI32AddKey(key, nullptr); + +// WSContentSpaceButton(BUTTON_CONFIGURATION); + WSContentStop(); +} + + +void MI32TimeoutSensors(){ + // whatever, this function access all the arrays.... + // so block for as long as it takes. + + // PROBLEM: when we take this, it hangs the BLE loop. + // BUT, devicePresent uses the + // remove devices for which the adverts have timed out + for (int i = MIBLEsensors.size()-1; i >= 0 ; i--) { + //if (MIBLEsensors[i].MAC[2] || MIBLEsensors[i].MAC[3] || MIBLEsensors[i].MAC[4] || MIBLEsensors[i].MAC[5]){ + if (!BLE_ESP32::devicePresent(MIBLEsensors[i].MAC)){ + uint8_t *mac = MIBLEsensors[i].MAC; + AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI32: dev no longer present MAC: %02x%02x%02x%02x%02x%02x"), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + TasAutoMutex localmutex(&slotmutex, "Mi32Timeout"); + MIBLEsensors.erase(MIBLEsensors.begin() + i); + } + //} + } +} + + +// this assumes that we're adding to a ResponseTime_P +void MI32GetOneSensorJson(int slot){ + mi_sensor_t *p; + p = &MIBLEsensors[slot]; + + ResponseAppend_P(PSTR(",\"%s-%02x%02x%02x\":{"), + kMI32DeviceType[p->type-1], + p->MAC[3], p->MAC[4], p->MAC[5]); + + ResponseAppend_P(PSTR("\"MAC\":\"%02x%02x%02x%02x%02x%02x\""), + p->MAC[0], p->MAC[1], p->MAC[2], + p->MAC[3], p->MAC[4], p->MAC[5]); + + if((!MI32.mode.triggeredTele && !MI32.option.minimalSummary)||MI32.mode.triggeredTele){ + bool tempHumSended = false; + if(p->feature.tempHum){ + if(p->eventType.tempHum || !MI32.mode.triggeredTele || MI32.option.allwaysAggregate){ + if (!isnan(p->hum) && !isnan(p->temp) +#ifdef USE_HOME_ASSISTANT + ||(hass_mode!=-1) +#endif //USE_HOME_ASSISTANT + ) { + ResponseAppend_P(PSTR(",")); + ResponseAppendTHD(p->temp, p->hum); + tempHumSended = true; + } + } + } + if(p->feature.temp && !tempHumSended){ + if(p->eventType.temp || !MI32.mode.triggeredTele || MI32.option.allwaysAggregate) { + if (!isnan(p->temp) +#ifdef USE_HOME_ASSISTANT + ||(hass_mode!=-1) +#endif //USE_HOME_ASSISTANT + ) { + char temperature[FLOATSZ]; + dtostrfd(p->temp, Settings.flag2.temperature_resolution, temperature); + ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%s"), temperature); + } + } + } + if(p->feature.hum && !tempHumSended){ + if(p->eventType.hum || !MI32.mode.triggeredTele || MI32.option.allwaysAggregate) { + if (!isnan(p->hum) +#ifdef USE_HOME_ASSISTANT + ||(hass_mode!=-1) +#endif //USE_HOME_ASSISTANT + ) { + char hum[FLOATSZ]; + dtostrfd(p->hum, Settings.flag2.humidity_resolution, hum); + ResponseAppend_P(PSTR(",\"" D_JSON_HUMIDITY "\":%s"), hum); + } + } + } + if (p->feature.lux){ + if(p->eventType.lux || !MI32.mode.triggeredTele || MI32.option.allwaysAggregate){ + if (p->lux!=0x0ffffff +#ifdef USE_HOME_ASSISTANT + ||(hass_mode!=-1) +#endif //USE_HOME_ASSISTANT + ) { // this is the error code -> no lux + ResponseAppend_P(PSTR(",\"" D_JSON_ILLUMINANCE "\":%u"), p->lux); +#ifdef USE_HOME_ASSISTANT + if (p->lux==0x0ffffff) MI32nullifyEndOfMQTT_DATA(); +#endif //USE_HOME_ASSISTANT + } + } + } + if (p->feature.moist){ + if(p->eventType.moist || !MI32.mode.triggeredTele || MI32.option.allwaysAggregate){ + if (p->moisture!=0xff +#ifdef USE_HOME_ASSISTANT + ||(hass_mode!=-1) +#endif //USE_HOME_ASSISTANT + ) { + ResponseAppend_P(PSTR(",\"" D_JSON_MOISTURE "\":%u"), p->moisture); +#ifdef USE_HOME_ASSISTANT + if (p->moisture==0xff) MI32nullifyEndOfMQTT_DATA(); +#endif //USE_HOME_ASSISTANT + } + } + } + if (p->feature.fert){ + if(p->eventType.fert || !MI32.mode.triggeredTele || MI32.option.allwaysAggregate){ + if (p->fertility!=0xffff +#ifdef USE_HOME_ASSISTANT + ||(hass_mode!=-1) +#endif //USE_HOME_ASSISTANT + ) { + ResponseAppend_P(PSTR(",\"Fertility\":%u"), p->fertility); +#ifdef USE_HOME_ASSISTANT + if (p->fertility==0xffff) MI32nullifyEndOfMQTT_DATA(); +#endif //USE_HOME_ASSISTANT + } + } + } + if (p->feature.Btn){ + if(p->eventType.Btn +#ifdef USE_HOME_ASSISTANT + ||(hass_mode==2) +#endif //USE_HOME_ASSISTANT + ){ + ResponseAppend_P(PSTR(",\"Btn\":%u"),p->Btn); + } + } + if(p->eventType.PairBtn && p->pairing){ + ResponseAppend_P(PSTR(",\"Pair\":%u"),p->pairing); + } + } // minimal summary + + + if (p->feature.PIR){ + if(p->eventType.motion || !MI32.mode.triggeredTele){ + if(MI32.mode.triggeredTele) ResponseAppend_P(PSTR(",\"PIR\":1")); // only real-time + ResponseAppend_P(PSTR(",\"Events\":%u"),p->events); + } + else if(p->eventType.noMotion && MI32.mode.triggeredTele){ + ResponseAppend_P(PSTR(",\"PIR\":0")); + } + } + + if (p->type == MI_FLORA && !MI32.mode.triggeredTele) { + if (p->firmware[0] != '\0') { // this is the error code -> no firmware + ResponseAppend_P(PSTR(",\"Firmware\":\"%s\""), p->firmware); + } + } + + if (p->feature.NMT || !MI32.mode.triggeredTele){ + if(p->eventType.NMT){ + ResponseAppend_P(PSTR(",\"NMT\":%u"), p->NMT); + } + } + if (p->feature.bat){ + if(p->eventType.bat || !MI32.mode.triggeredTele || MI32.option.allwaysAggregate){ + if (p->bat != 0x00 +#ifdef USE_HOME_ASSISTANT + ||(hass_mode!=-1) +#endif //USE_HOME_ASSISTANT + ) { // this is the error code -> no battery + ResponseAppend_P(PSTR(",\"Battery\":%u"), p->bat); +#ifdef USE_HOME_ASSISTANT + if (p->bat == 0x00) MI32nullifyEndOfMQTT_DATA(); +#endif //USE_HOME_ASSISTANT + } + } + } + if (MI32.option.showRSSI) ResponseAppend_P(PSTR(",\"RSSI\":%d"), p->RSSI); + + ResponseAppend_P(PSTR("}")); + p->eventType.raw = 0; + p->shallSendMQTT = 0; + +} + + +/////////////////////////////////////////////// +// starts a completely fresh MQTT message. +// sends up to 4 sensors +// triggered by setting MI32.mqttCurrentSlot = 0 +void MI32ShowSomeSensors(){ + // don't detect half-added ones here + int numsensors = MIBLEsensors.size(); + if (MI32.mqttCurrentSlot >= numsensors){ + // if we got to the end of the sensors, then don't send more + return; + } + +#ifdef USE_HOME_ASSISTANT + bool _noSummarySave = MI32.option.noSummary; + bool _minimalSummarySave = MI32.option.minimalSummary; + if(hass_mode==2){ + if(MI32.option.holdBackFirstAutodiscovery){ + if(!MI32.mode.firstAutodiscoveryDone){ + MI32.mode.firstAutodiscoveryDone = 1; + return; + } + } + MI32.option.noSummary = false; + MI32.option.minimalSummary = false; + } +#endif //USE_HOME_ASSISTANT + + ResponseTime_P(PSTR("")); + int cnt = 0; + for (; (MI32.mqttCurrentSlot < numsensors) && (cnt < 4); MI32.mqttCurrentSlot++, cnt++) { + MI32GetOneSensorJson(MI32.mqttCurrentSlot); + int mlen = strlen(TasmotaGlobal.mqtt_data); + + // if we ran out of room, leave here. + if (sizeof(TasmotaGlobal.mqtt_data) - mlen < 100){ + MI32.mqttCurrentSlot++; + break; + } + } + ResponseAppend_P(PSTR("}")); + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain); + //AddLog_P(LOG_LEVEL_DEBUG,PSTR("%s: show some %d %s"),D_CMND_MI32, MI32.mqttCurrentSlot, TasmotaGlobal.mqtt_data); + +#ifdef USE_RULES + RulesTeleperiod(); // Allow rule based HA messages +#endif // USE_RULES + +#ifdef USE_HOME_ASSISTANT + if(hass_mode==2){ + MI32.option.noSummary = _noSummarySave; + MI32.option.minimalSummary = _minimalSummarySave; + } +#endif //USE_HOME_ASSISTANT +} + +/////////////////////////////////////////////// +// starts a completely fresh MQTT message. +// sends up to 4 sensors pe5r msg +// sends only those which are raw and triggered. +// triggered by setting MI32.mode.triggeredTele = 1 +void MI32ShowTriggeredSensors(){ + if (!MI32.mode.triggeredTele) return; // none to show + MI32.mode.triggeredTele = 0; + + // don't detect half-added ones here + int numsensors = MIBLEsensors.size(); + + int sensor = 0; + + do { + ResponseTime_P(PSTR("")); + int cnt = 0; + for (; (sensor < numsensors) && (cnt < 4); sensor++) { + mi_sensor_t *p; + p = &MIBLEsensors[sensor]; + if(p->eventType.raw == 0) continue; + if(p->shallSendMQTT==0) continue; + + cnt++; + MI32GetOneSensorJson(sensor); + int mlen = strlen(TasmotaGlobal.mqtt_data); + + // if we ran out of room, leave here. + if (sizeof(TasmotaGlobal.mqtt_data) - mlen < 100){ + sensor++; + break; + } + } + if (cnt){ // if we got one, then publish + ResponseAppend_P(PSTR("}")); + MqttPublishPrefixTopic_P(STAT, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain); + AddLog_P(LOG_LEVEL_DEBUG,PSTR("%s: triggered %d %s"),D_CMND_MI32, sensor, TasmotaGlobal.mqtt_data); + +#ifdef USE_RULES + RulesTeleperiod(); // Allow rule based HA messages +#endif // USE_RULES + + } else { // else don't and clear + ResponseClear(); + } + } while (sensor < numsensors); +} + + +void MI32Show(bool json) +{ + // don't detect half-added ones here + int numsensors = MIBLEsensors.size(); + + if (json) { + // TELE JSON messages now do nothing here, apart from set MI32.mqttCurrentSlot + // which will trigger send next second of up to 4 sensors, then the next four in the next second, etc. + //MI32.mqttCurrentSlot = 0; + +#ifdef USE_WEBSERVER + } else { + static uint16_t _page = 0; + static uint16_t _counter = 0; + int32_t i = _page * MI32.perPage; + uint32_t j = i + MI32.perPage; + + if (j+1 > numsensors){ + j = numsensors; + } + char stemp[5] ={0}; + if (numsensors-(_page*MI32.perPage)>1 && MI32.perPage!=1) { + sprintf_P(stemp,"-%u",j); + } + if (numsensors==0) i=-1; // only for the GUI + + WSContentSend_PD(HTTP_MI32, i+1,stemp,numsensors); + for (i; itype-1]; + const char *alias = BLE_ESP32::getAlias(p->MAC); + if (alias && *alias){ + WSContentSend_PD(HTTP_MI32_ALIAS, typeName, alias); + } + char _MAC[18]; + ToHex_P(p->MAC,6,_MAC,18);//,':'); + WSContentSend_PD(HTTP_MI32_MAC, typeName, D_MAC_ADDRESS, _MAC); + WSContentSend_PD(HTTP_RSSI, typeName, p->RSSI); + + + // for some reason, display flora differently + switch(p->type){ + case MI_FLORA:{ + if (!isnan(p->temp)) { + char temperature[FLOATSZ]; + dtostrfd(p->temp, Settings.flag2.temperature_resolution, temperature); + WSContentSend_PD(HTTP_SNS_TEMP, typeName, temperature, TempUnit()); + } + if (p->moisture!=0xff) { + WSContentSend_PD(HTTP_SNS_MOISTURE, typeName, p->moisture); + } + if (p->fertility!=0xffff) { + WSContentSend_PD(HTTP_MI32_FLORA_DATA, typeName, p->fertility); + } + } break; + default:{ + if (!isnan(p->hum) && !isnan(p->temp)) { + WSContentSend_THD(typeName, p->temp, p->hum); + } + } + } + +#ifdef USE_MI_DECRYPTION + bool showkey = false; + char tmp[40]; + strcpy(tmp, PSTR("KeyRqd")); + switch(p->needkey) { + default:{ + snprintf(tmp, 39, PSTR("?%d?"), p->needkey ); + showkey = true; + } break; + case KEY_REQUIREMENT_UNKNOWN: { + strcpy(tmp, PSTR("WAIT")); + showkey = true; + } break; + case KEY_NOT_REQUIRED: { + strcpy(tmp, PSTR("NOTKEY")); + //showkey = true; + } break; + case KEY_REQUIRED_BUT_NOT_FOUND: { + strcpy(tmp, PSTR("NoKey")); + showkey = true; + } break; + case KEY_REQUIRED_AND_FOUND: { + strcpy(tmp, PSTR("KeyOk")); + showkey = true; + } break; + case KEY_REQUIRED_AND_INVALID: { + strcpy(tmp, PSTR("KeyInv")); + showkey = true; + } break; + } + + // adds the link to get the key. + // provides mac and callback address to receive the key, if we had a website which did this + // (future work) + if (showkey){ + BLE_ESP32::dump(_MAC, 13, p->MAC,6); + WSContentSend_PD(HTTP_NEEDKEY, typeName, _MAC, Webserver->client().localIP().toString().c_str(), tmp ); + } + + if (p->type==MI_NLIGHT || p->type==MI_MJYD2S) { +#else + if (p->type==MI_NLIGHT) { +#endif //USE_MI_DECRYPTION + WSContentSend_PD(HTTP_EVENTS, typeName, p->events); + if(p->NMT>0) WSContentSend_PD(HTTP_NMT, typeName, p->NMT); + } + + if (p->lux!=0x00ffffff) { // this is the error code -> no valid value + WSContentSend_PD(HTTP_SNS_ILLUMINANCE, typeName, p->lux); + } + if(p->bat!=0x00){ + WSContentSend_PD(HTTP_BATTERY, typeName, p->bat); + } + if (p->type==MI_YEERC){ + WSContentSend_PD(HTTP_LASTBUTTON, typeName, p->Btn); + } + if (p->pairing){ + WSContentSend_PD(HTTP_PAIRING, typeName); + } + } + _counter++; + if(_counter>3) { + _page++; + _counter=0; + } + if (MIBLEsensors.size()%MI32.perPage==0 && _page==MIBLEsensors.size()/MI32.perPage) { _page = 0; } + if (_page>MIBLEsensors.size()/MI32.perPage) { _page = 0; } +#endif // USE_WEBSERVER + } +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ +#define WEB_HANDLE_MI32 "mikey" + +bool Xsns62(uint8_t function) +{ +// if (!Settings.flag5.mi32_enable) { return false; } // SetOption115 - Enable ESP32 MI32 BLE +// return false; + + bool result = false; + + switch (function) { + case FUNC_INIT: + MI32Init(); + break; + case FUNC_EVERY_50_MSECOND: + MI32Every50mSecond(); + break; + case FUNC_EVERY_SECOND: + MI32EverySecond(false); + break; + case FUNC_COMMAND: + result = DecodeCommand(kMI32_Commands, MI32_Commands); + break; + case FUNC_JSON_APPEND: + // we are not in control of when this is called... + //MI32Show(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_ADD_HANDLER: + WebServer_on(PSTR("/" WEB_HANDLE_MI32), HandleMI32Key); + break; + case FUNC_WEB_SENSOR: + MI32Show(0); + break; +#endif // USE_WEBSERVER + } + return result; +} +#endif // USE_MI_ESP32 +#endif // ESP32 + +#endif \ No newline at end of file