diff --git a/main/ZgatewayBLEConnect.h b/main/ZgatewayBLEConnect.h new file mode 100644 index 0000000..ccf703c --- /dev/null +++ b/main/ZgatewayBLEConnect.h @@ -0,0 +1,38 @@ +#ifndef zBLEConnect_h +#define zBLEConnect_h + +#ifdef ESP32 +# include "ArduinoJson.h" +# include "NimBLEDevice.h" + +extern void pubBT(JsonObject& data); + +class zBLEConnect { +public: + NimBLEClient* m_pClient; + TaskHandle_t m_taskHandle = nullptr; + zBLEConnect(NimBLEAddress& addr) { m_pClient = NimBLEDevice::createClient(addr); } + virtual ~zBLEConnect() { NimBLEDevice::deleteClient(m_pClient); } + virtual void publishData() {} + virtual NimBLERemoteCharacteristic* getCharacteristic(const NimBLEUUID& service, const NimBLEUUID& characteristic); +}; + +class LYWSD03MMC_connect : public zBLEConnect { + void notifyCB(NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify); + +public: + LYWSD03MMC_connect(NimBLEAddress& addr) : zBLEConnect(addr) {} + void publishData() override; +}; + +class DT24_connect : public zBLEConnect { + std::vector m_data; + void notifyCB(NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify); + +public: + DT24_connect(NimBLEAddress& addr) : zBLEConnect(addr) {} + void publishData() override; +}; + +#endif //ESP32 +#endif //zBLEConnect_h diff --git a/main/ZgatewayBLEConnect.ino b/main/ZgatewayBLEConnect.ino new file mode 100644 index 0000000..dbef725 --- /dev/null +++ b/main/ZgatewayBLEConnect.ino @@ -0,0 +1,162 @@ +#ifdef ESP32 +# include "User_config.h" +# ifdef ZgatewayBT +# include "ArduinoJson.h" +# include "ArduinoLog.h" +# include "ZgatewayBLEConnect.h" +# include "config_BT.h" + +# define convertTemp_CtoF(c) ((c * 1.8) + 32) + +extern bool ProcessLock; +extern std::vector devices; + +NimBLERemoteCharacteristic* zBLEConnect::getCharacteristic(const NimBLEUUID& service, + const NimBLEUUID& characteristic) { + BLERemoteCharacteristic* pRemoteCharacteristic = nullptr; + if (!m_pClient) { + Log.error(F("No BLE client" CR)); + } else if (!m_pClient->isConnected() && !m_pClient->connect()) { + Log.error(F("Connect to: %s failed" CR), m_pClient->getPeerAddress().toString().c_str()); + } else { + BLERemoteService* pRemoteService = m_pClient->getService(service); + if (!pRemoteService) { + Log.notice(F("Failed to find service UUID: %s" CR), service.toString().c_str()); + } else { + Log.trace(F("Found service: %s" CR), service.toString().c_str()); + Log.trace(F("Client isConnected, freeHeap: %d" CR), ESP.getFreeHeap()); + pRemoteCharacteristic = pRemoteService->getCharacteristic(characteristic); + if (!pRemoteCharacteristic) { + Log.notice(F("Failed to find characteristic UUID: %s" CR), characteristic.toString().c_str()); + } + } + } + + return pRemoteCharacteristic; +} + +/*-----------------------LYWSD03MMC && MHO_C401 HANDLING-----------------------*/ +void LYWSD03MMC_connect::notifyCB(NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) { + if (m_taskHandle == nullptr) { + return; // unexpected notification + } + if (!ProcessLock) { + Log.trace(F("Callback from %s characteristic" CR), pChar->getUUID().toString().c_str()); + + if (length == 5) { + Log.trace(F("Device identified creating BLE buffer" CR)); + JsonObject& BLEdata = getBTJsonObject(); + String mac_address = m_pClient->getPeerAddress().toString().c_str(); + mac_address.toUpperCase(); + for (std::vector::iterator it = devices.begin(); it != devices.end(); ++it) { + BLEdevice* p = *it; + if ((strcmp(p->macAdr, (char*)mac_address.c_str()) == 0)) { + if (p->sensorModel == LYWSD03MMC) + BLEdata.set("model", "LYWSD03MMC"); + else if (p->sensorModel == MHO_C401) + BLEdata.set("model", "MHO_C401"); + } + } + BLEdata.set("id", (char*)mac_address.c_str()); + Log.trace(F("Device identified in CB: %s" CR), (char*)mac_address.c_str()); + BLEdata.set("tempc", (float)((pData[0] | (pData[1] << 8)) * 0.01)); + BLEdata.set("tempf", (float)(convertTemp_CtoF((pData[0] | (pData[1] << 8)) * 0.01))); + BLEdata.set("hum", (float)(pData[2])); + BLEdata.set("volt", (float)(((pData[4] * 256) + pData[3]) / 1000.0)); + BLEdata.set("batt", (float)(((((pData[4] * 256) + pData[3]) / 1000.0) - 2.1) * 100)); + + pubBT(BLEdata); + } else { + Log.notice(F("Invalid notification data" CR)); + return; + } + } else { + Log.trace(F("Callback process canceled by processLock" CR)); + } + + xTaskNotifyGive(m_taskHandle); +} + +void LYWSD03MMC_connect::publishData() { + NimBLEUUID serviceUUID("ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6"); + NimBLEUUID charUUID("ebe0ccc1-7a0a-4b0c-8a1a-6ff2997da3a6"); + NimBLERemoteCharacteristic* pChar = getCharacteristic(serviceUUID, charUUID); + + if (pChar && pChar->canNotify()) { + Log.trace(F("Registering notification" CR)); + if (pChar->subscribe(true, std::bind(&LYWSD03MMC_connect::notifyCB, this, + std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4))) { + m_taskHandle = xTaskGetCurrentTaskHandle(); + if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(BLE_CNCT_TIMEOUT)) == pdFALSE) { + m_taskHandle = nullptr; + } + } else { + Log.notice(F("Failed registering notification" CR)); + } + } +} + +/*-----------------------DT24 HANDLING-----------------------*/ +void DT24_connect::notifyCB(NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) { + if (m_taskHandle == nullptr) { + return; // unexpected notification + } + + if (!ProcessLock) { + Log.trace(F("Callback from %s characteristic" CR), pChar->getUUID().toString().c_str()); + if (length == 20) { + m_data.assign(pData, pData + length); + return; + } else if (m_data.size() == 20 && length == 16) { + m_data.insert(m_data.end(), pData, pData + length); + + // DT24-BLE data format + // https://github.com/NiceLabs/atorch-console/blob/master/docs/protocol-design.md#dc-meter-report + // Data comes as two packets ( 20 and 16 ), and am only processing first + Log.trace(F("Device identified creating BLE buffer" CR)); + JsonObject& BLEdata = getBTJsonObject(); + String mac_address = m_pClient->getPeerAddress().toString().c_str(); + mac_address.toUpperCase(); + BLEdata.set("model", "DT24"); + BLEdata.set("id", (char*)mac_address.c_str()); + Log.trace(F("Device identified in CB: %s" CR), (char*)mac_address.c_str()); + BLEdata.set("volt", (float)(((m_data[4] * 256 * 256) + (m_data[5] * 256) + m_data[6]) / 10.0)); + BLEdata.set("current", (float)(((m_data[7] * 256 * 256) + (m_data[8] * 256) + m_data[9]) / 1000.0)); + BLEdata.set("power", (float)(((m_data[10] * 256 * 256) + (m_data[11] * 256) + m_data[12]) / 10.0)); + BLEdata.set("energy", (float)(((m_data[13] * 256 * 256 * 256) + (m_data[14] * 256 * 256) + (m_data[15] * 256) + m_data[16]) / 100.0)); + BLEdata.set("price", (float)(((m_data[17] * 256 * 256) + (m_data[18] * 256) + m_data[19]) / 100.0)); + + pubBT(BLEdata); + } else { + Log.notice(F("Invalid notification data" CR)); + return; + } + } else { + Log.trace(F("Callback process canceled by processLock" CR)); + } + + xTaskNotifyGive(m_taskHandle); +} + +void DT24_connect::publishData() { + NimBLEUUID serviceUUID("ffe0"); + NimBLEUUID charUUID("ffe1"); + NimBLERemoteCharacteristic* pChar = getCharacteristic(serviceUUID, charUUID); + + if (pChar && pChar->canNotify()) { + Log.trace(F("Registering notification" CR)); + if (pChar->subscribe(true, std::bind(&DT24_connect::notifyCB, this, + std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4))) { + m_taskHandle = xTaskGetCurrentTaskHandle(); + if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(BLE_CNCT_TIMEOUT)) == pdFALSE) { + m_taskHandle = nullptr; + } + } else { + Log.notice(F("Failed registering notification" CR)); + } + } +} +# endif //ZgatewayBT +#endif //ESP32 diff --git a/main/ZgatewayBT.ino b/main/ZgatewayBT.ino index c266017..9ec3b83 100644 --- a/main/ZgatewayBT.ino +++ b/main/ZgatewayBT.ino @@ -46,14 +46,10 @@ FreeRTOS::Semaphore semaphoreCreateOrUpdateDevice = FreeRTOS::Semaphore("createO # include # include +# include "ZgatewayBLEConnect.h" # include "soc/timer_group_reg.h" # include "soc/timer_group_struct.h" -void notifyCB( - BLERemoteCharacteristic* pBLERemoteCharacteristic, - uint8_t* pData, - size_t length, - bool isNotify); # endif # if !defined(ESP32) && !defined(ESP8266) @@ -63,18 +59,11 @@ void notifyCB( # include using namespace std; -struct BLEdevice { - char macAdr[18]; - bool isDisc; - bool isWhtL; - bool isBlkL; - ble_sensor_model sensorModel; -}; - # define device_flags_init 0 << 0 # define device_flags_isDisc 1 << 0 # define device_flags_isWhiteL 1 << 1 # define device_flags_isBlackL 1 << 2 +# define device_flags_connect 1 << 3 struct decompose { int start; @@ -85,7 +74,7 @@ struct decompose { vector devices; int newDevices = 0; -static BLEdevice NO_DEVICE_FOUND = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, false, false, false, UNKNOWN_MODEL}; +static BLEdevice NO_DEVICE_FOUND = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, false, false, false, false, UNKNOWN_MODEL}; static bool oneWhite = false; int minRssi = abs(MinimumRSSI); //minimum rssi value @@ -134,7 +123,7 @@ int btQueueBlocked = 0; int btQueueLengthSum = 0; int btQueueLengthCount = 0; -JsonObject& getBTJsonObject(const char* json = NULL, bool haPresenceEnabled = true) { +JsonObject& getBTJsonObject(const char* json, bool haPresenceEnabled) { int next, last; for (bool blocked = false;;) { next = atomic_load_explicit(&jsonBTBufferQueueNext, ::memory_order_seq_cst); // use namespace std -> ambiguous error... @@ -178,7 +167,7 @@ void emptyBTQueue() { JsonBundle jsonBTBuffer; -JsonObject& getBTJsonObject(const char* json = NULL, bool haPresenceEnabled = true) { +JsonObject& getBTJsonObject(const char* json, bool haPresenceEnabled) { return jsonBTBuffer.createObject(); } @@ -235,6 +224,7 @@ void createOrUpdateDevice(const char* mac, uint8_t flags, ble_sensor_model model device->isDisc = flags & device_flags_isDisc; device->isWhtL = flags & device_flags_isWhiteL; device->isBlkL = flags & device_flags_isBlackL; + device->connect = flags & device_flags_connect; if (model != UNKNOWN_MODEL) device->sensorModel = model; devices.push_back(device); newDevices++; @@ -634,6 +624,13 @@ class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks { Log.notice(F("Device detected: %s" CR), (char*)mac_adress.c_str()); BLEdevice* device = getDeviceByMac(BLEdata["id"].as()); +# if BLE_FILTER_CONNECTABLE + if (device->connect) { + Log.notice(F("Filtered connectable device" CR)); + return; + } +# endif + if ((!oneWhite || isWhite(device)) && !isBlack(device)) { //if not black listed mac we go AND if we have no white mac or this mac is white we go out if (advertisedDevice->haveName()) BLEdata.set("name", (char*)advertisedDevice->getName().c_str()); @@ -716,82 +713,10 @@ void BLEscan() { pBLEScan->setWindow(BLEScanWindow); BLEScanResults foundDevices = pBLEScan->start(Scan_duration / 1000, false); scanCount++; - Log.notice(F("Found %d devices, scan number %d end deinit controller" CR), foundDevices.getCount(), scanCount); + Log.notice(F("Found %d devices, scan number %d end" CR), foundDevices.getCount(), scanCount); enableCore0WDT(); } -/** - * Callback method to retrieve data from devices characteristics - */ -void notifyCB( - BLERemoteCharacteristic* pBLERemoteCharacteristic, - uint8_t* pData, - size_t length, - bool isNotify) { - if (!ProcessLock) { - Log.trace(F("Callback from %s characteristic" CR), pBLERemoteCharacteristic->getUUID().toString().c_str()); - - // This logic needs refactoring and should be changed to a device/model approach based on the sensor macAdr lookup. Growing this beyond 2 devices will get too cumbersome - - if (length == 5) { - Log.trace(F("Device identified creating BLE buffer" CR)); - JsonObject& BLEdata = getBTJsonObject(); - String mac_adress = pBLERemoteCharacteristic->getRemoteService()->getClient()->getPeerAddress().toString().c_str(); - mac_adress.toUpperCase(); - for (vector::iterator it = devices.begin(); it != devices.end(); ++it) { - BLEdevice* p = *it; - if ((strcmp(p->macAdr, (char*)mac_adress.c_str()) == 0)) { - if (p->sensorModel == LYWSD03MMC) - BLEdata.set("model", "LYWSD03MMC"); - else if (p->sensorModel == MHO_C401) - BLEdata.set("model", "MHO_C401"); - } - } - BLEdata.set("id", (char*)mac_adress.c_str()); - Log.trace(F("Device identified in CB: %s" CR), (char*)mac_adress.c_str()); - BLEdata.set("tempc", (float)((pData[0] | (pData[1] << 8)) * 0.01)); - BLEdata.set("tempf", (float)(convertTemp_CtoF((pData[0] | (pData[1] << 8)) * 0.01))); - BLEdata.set("hum", (float)(pData[2])); - BLEdata.set("volt", (float)(((pData[4] * 256) + pData[3]) / 1000.0)); - BLEdata.set("batt", (float)(((((pData[4] * 256) + pData[3]) / 1000.0) - 2.1) * 100)); - - pubBT(BLEdata); - } else if (length == 20) { - // DT24-BLE data format - // https://github.com/NiceLabs/atorch-console/blob/master/docs/protocol-design.md#dc-meter-report - // Data comes as two packets ( 20 and 16 ), and am only processing first - Log.trace(F("Device identified creating BLE buffer" CR)); - JsonObject& BLEdata = getBTJsonObject(); - String mac_adress = pBLERemoteCharacteristic->getRemoteService()->getClient()->getPeerAddress().toString().c_str(); - mac_adress.toUpperCase(); - for (vector::iterator it = devices.begin(); it != devices.end(); ++it) { - BLEdevice* p = *it; - if ((strcmp(p->macAdr, (char*)mac_adress.c_str()) == 0)) { - if (p->sensorModel == LYWSD03MMC) - BLEdata.set("model", "LYWSD03MMC"); - else if (p->sensorModel == MHO_C401) - BLEdata.set("model", "MHO_C401"); - else if (p->sensorModel == DT24) - BLEdata.set("model", "DT24"); - } - } - BLEdata.set("id", (char*)mac_adress.c_str()); - Log.trace(F("Device identified in CB: %s" CR), (char*)mac_adress.c_str()); - BLEdata.set("volt", (float)(((pData[4] * 256 * 256) + (pData[5] * 256) + pData[6]) / 10.0)); - BLEdata.set("current", (float)(((pData[7] * 256 * 256) + (pData[8] * 256) + pData[9]) / 1000.0)); - BLEdata.set("power", (float)(((pData[10] * 256 * 256) + (pData[11] * 256) + pData[12]) / 10.0)); - BLEdata.set("energy", (float)(((pData[13] * 256 * 256 * 256) + (pData[14] * 256 * 256) + (pData[15] * 256) + pData[16]) / 100.0)); - BLEdata.set("price", (float)(((pData[17] * 256 * 256) + (pData[18] * 256) + pData[19]) / 100.0)); - - pubBT(BLEdata); - } else { - Log.notice(F("Device not identified" CR)); - } - } else { - Log.trace(F("Callback process canceled by processLock" CR)); - } -} - /** * Connect to BLE devices and initiate the callbacks with a service/characteristic request */ @@ -799,74 +724,24 @@ void BLEconnect() { Log.notice(F("BLE Connect begin" CR)); for (vector::iterator it = devices.begin(); it != devices.end(); ++it) { BLEdevice* p = *it; - if (p->sensorModel == LYWSD03MMC || p->sensorModel == MHO_C401) { - Log.trace(F("Model to connect found" CR)); - NimBLEClient* pClient; - pClient = BLEDevice::createClient(); - BLEUUID serviceUUID("ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6"); - BLEUUID charUUID("ebe0ccc1-7a0a-4b0c-8a1a-6ff2997da3a6"); - BLEAddress sensorAddress(p->macAdr); - if (!pClient->connect(sensorAddress)) { - Log.notice(F("Failed to find client: %s" CR), p->macAdr); - } else { - BLERemoteService* pRemoteService = pClient->getService(serviceUUID); - if (!pRemoteService) { - Log.notice(F("Failed to find service UUID: %s" CR), serviceUUID.toString().c_str()); - } else { - Log.trace(F("Found service: %s" CR), serviceUUID.toString().c_str()); - // Obtain a reference to the characteristic in the service of the remote BLE server. - if (pClient->isConnected()) { - Log.trace(F("Client isConnected, freeHeap: %d" CR), ESP.getFreeHeap()); - BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); - if (!pRemoteCharacteristic) { - Log.notice(F("Failed to find characteristic UUID: %s" CR), charUUID.toString().c_str()); - } else { - if (pRemoteCharacteristic->canNotify()) { - Log.trace(F("Registering notification" CR)); - pRemoteCharacteristic->subscribe(true, notifyCB); - delay(BLE_CNCT_TIMEOUT); - } else { - Log.notice(F("Failed registering notification" CR)); - } - } - } - } - } - NimBLEDevice::deleteClient(pClient); - } else if (p->sensorModel == DT24) { + if (p->connect) { Log.trace(F("Model to connect found: %s" CR), p->macAdr); - NimBLEClient* pClient; - pClient = BLEDevice::createClient(); - BLEUUID serviceUUID("ffe0"); - BLEUUID charUUID("ffe1"); - BLEAddress sensorAddress(p->macAdr); - if (!pClient->connect(sensorAddress)) { - Log.notice(F("Failed to find client: %s" CR), p->macAdr); - } else { - BLERemoteService* pRemoteService = pClient->getService(serviceUUID); - if (!pRemoteService) { - Log.notice(F("Failed to find service UUID: %s" CR), serviceUUID.toString().c_str()); - } else { - Log.trace(F("Found service: %s" CR), serviceUUID.toString().c_str()); - // Obtain a reference to the characteristic in the service of the remote BLE server. - if (pClient->isConnected()) { - Log.trace(F("Client isConnected, freeHeap: %d" CR), ESP.getFreeHeap()); - BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); - if (!pRemoteCharacteristic) { - Log.notice(F("Failed to find characteristic UUID: %s" CR), charUUID.toString().c_str()); - } else { - if (pRemoteCharacteristic->canNotify()) { - Log.trace(F("Registering notification" CR)); - pRemoteCharacteristic->subscribe(true, notifyCB); - delay(BLE_CNCT_TIMEOUT); - } else { - Log.notice(F("Failed registering notification" CR)); - } - } - } + NimBLEAddress addr(std::string(p->macAdr)); + switch (p->sensorModel) { + case LYWSD03MMC: + case MHO_C401: { + LYWSD03MMC_connect BLEclient(addr); + BLEclient.publishData(); + break; } + case DT24: { + DT24_connect BLEclient(addr); + BLEclient.publishData(); + break; + } + default: + break; } - NimBLEDevice::deleteClient(pClient); } } Log.notice(F("BLE Connect end" CR)); @@ -1314,13 +1189,13 @@ JsonObject& process_bledata(JsonObject& BLEdata) { if (strstr(service_data, "588703") != NULL) { Log.trace(F("MHO_C401 add to list for future connect" CR)); if (device->sensorModel == -1) - createOrUpdateDevice(mac, device_flags_init, MHO_C401); + createOrUpdateDevice(mac, device_flags_connect, MHO_C401); } Log.trace(F("Is it a LYWSD03MMC?" CR)); if (strstr(service_data, "585b05") != NULL) { Log.trace(F("LYWSD03MMC add to list for future connect" CR)); if (device->sensorModel == -1) - createOrUpdateDevice(mac, device_flags_init, LYWSD03MMC); + createOrUpdateDevice(mac, device_flags_connect, LYWSD03MMC); } Log.trace(F("Is it a custom (pvvx) LYWSD03MMC?" CR)); if (service_len >= 30 && strncmp(service_data + 6, "38c1a4", 6) == 0) { @@ -1421,7 +1296,7 @@ JsonObject& process_bledata(JsonObject& BLEdata) { Log.trace(F("DT24 data reading data reading" CR)); BLEdata.set("model", "DT24"); if (device->sensorModel == -1) - createOrUpdateDevice(mac, device_flags_init, DT24); + createOrUpdateDevice(mac, device_flags_connect, DT24); return BLEdata; } } diff --git a/main/config_BT.h b/main/config_BT.h index c675768..de60797 100644 --- a/main/config_BT.h +++ b/main/config_BT.h @@ -40,6 +40,12 @@ extern int btQueueLengthCount; # define AttemptBLECOnnect true //do we by default attempt a BLE connection to sensors with ESP32 # endif bool bleConnect = AttemptBLECOnnect; + +// Sets whether to filter publishing of scanned devices that require a connection. +// Default (1) prevents overwriting the publication of the device connection data with the advertised data. +# ifndef BLE_FILTER_CONNECTABLE +# define BLE_FILTER_CONNECTABLE 1 +# endif #endif /*----------------------BT topics & parameters-------------------------*/ @@ -161,4 +167,16 @@ enum ble_sensor_model { # endif #endif +/*---------------INTERNAL USE: DO NOT MODIFY--------------*/ +struct BLEdevice { + char macAdr[18]; + bool isDisc; + bool isWhtL; + bool isBlkL; + bool connect; + ble_sensor_model sensorModel; +}; + +JsonObject& getBTJsonObject(const char* json = NULL, bool haPresenceEnabled = true); + #endif