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