From 38acae417d2fd91033301d19fcc95fefcbc30ba0 Mon Sep 17 00:00:00 2001 From: Yurii Date: Thu, 30 Jan 2025 01:46:55 +0300 Subject: [PATCH] refactor: reworked BLE sensors * add clean unused ble instances * moved subscribe to notify to another method * set date/time on BLE sensors --- src/SensorsTask.h | 260 +++++++++++++++++++++++++++++++++------------- src/utils.h | 38 +++++++ 2 files changed, 227 insertions(+), 71 deletions(-) diff --git a/src/SensorsTask.h b/src/SensorsTask.h index f5ebd47..18e9b3d 100644 --- a/src/SensorsTask.h +++ b/src/SensorsTask.h @@ -31,12 +31,19 @@ protected: const unsigned short dallasSearchInterval = 60000; const unsigned short dallasPollingInterval = 10000; const unsigned short globalPollingInterval = 15000; + #if USE_BLE + const unsigned int bleSetDtInterval = 7200000; + #endif std::unordered_map owInstances; std::unordered_map dallasInstances; std::unordered_map dallasSearchTime; std::unordered_map dallasPolling; std::unordered_map dallasLastPollingTime; + #if USE_BLE + std::unordered_map bleSubscribed; + std::unordered_map bleLastSetDtTime; + #endif unsigned long globalLastPollingTime = 0; #if defined(ARDUINO_ARCH_ESP32) @@ -83,8 +90,11 @@ protected: pollingNtcSensors(); this->yield(); + #if USE_BLE + cleanBleInstances(); pollingBleSensors(); this->yield(); + #endif this->globalLastPollingTime = millis(); } @@ -277,7 +287,7 @@ protected: unsigned long ts = millis(); if (this->dallasPolling[gpio]) { - auto minPollingTime = instance.millisToWaitForConversion(12) * 2; + unsigned long minPollingTime = instance.millisToWaitForConversion(12) * 2; unsigned long estimatePollingTime = ts - this->dallasLastPollingTime[gpio]; // check conversion time @@ -348,26 +358,6 @@ protected: } } - void pollingBleSensors() { - #if USE_BLE - if (!NimBLEDevice::isInitialized() && millis() > 5000) { - Log.sinfoln(FPSTR(L_SENSORS_BLE), F("Initialized")); - BLEDevice::init(""); - NimBLEDevice::setPower(9); - } - - for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) { - auto& sSensor = Sensors::settings[sensorId]; - - if (!sSensor.enabled || sSensor.type != Sensors::Type::BLUETOOTH || sSensor.purpose == Sensors::Purpose::NOT_CONFIGURED) { - continue; - } - - connectToBleDevice(sensorId); - } - #endif - } - void pollingNtcSensors() { for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) { auto& sSensor = Sensors::settings[sensorId]; @@ -413,17 +403,139 @@ protected: } } - bool connectToBleDevice(const uint8_t sensorId) { + #if USE_BLE + void cleanBleInstances() { #if USE_BLE if (!NimBLEDevice::isInitialized()) { - return false; + return; + } + + for (auto client : NimBLEDevice::getConnectedClients()) { + auto address = client->getPeerAddress(); + bool used = false; + + for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) { + auto& sSensor = Sensors::settings[sensorId]; + + if (!sSensor.enabled || sSensor.type != Sensors::Type::BLUETOOTH || sSensor.purpose == Sensors::Purpose::NOT_CONFIGURED) { + continue; + } + + auto pAddress = address.getVal(); + uint8_t addr[] = { + pAddress[5], pAddress[4], pAddress[3], + pAddress[2], pAddress[1], pAddress[0] + }; + + if (isEqualAddress(addr, sSensor.address, sizeof(addr))) { + used = true; + break; + } + } + + if (!used) { + Log.sinfoln( + FPSTR(L_SENSORS_BLE), F("Deleted unused client connected to %s"), + address.toString().c_str() + ); + + NimBLEDevice::deleteClient(client); + } + } + #endif + } + + void pollingBleSensors() { + if (!NimBLEDevice::isInitialized() && millis() > 5000) { + Log.sinfoln(FPSTR(L_SENSORS_BLE), F("Initialized")); + BLEDevice::init(""); + NimBLEDevice::setPower(9); + } + + for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) { + auto& sSensor = Sensors::settings[sensorId]; + auto& rSensor = Sensors::results[sensorId]; + + if (!sSensor.enabled || sSensor.type != Sensors::Type::BLUETOOTH || sSensor.purpose == Sensors::Purpose::NOT_CONFIGURED) { + continue; + } + + auto client = this->getBleClient(sensorId); + if (client == nullptr) { + continue; + } + + if (!client->isConnected()) { + this->bleSubscribed[sensorId] = false; + this->bleLastSetDtTime[sensorId] = 0; + + if (client->connect()) { + Log.sinfoln( + FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': connected to %s"), + sensorId, sSensor.name, client->getPeerAddress().toString().c_str() + ); + + } else { + Log.swarningln( + FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed connecting to %s"), + sensorId, sSensor.name, client->getPeerAddress().toString().c_str() + ); + + continue; + } + } + + if (!this->bleSubscribed[sensorId]) { + if (this->subscribeToBleDevice(sensorId, client)) { + this->bleSubscribed[sensorId] = true; + + } else { + this->bleSubscribed[sensorId] = false; + client->disconnect(); + continue; + } + } + + if (!rSensor.connected) { + rSensor.connected = true; + } + + if (!this->bleLastSetDtTime[sensorId] || millis() - this->bleLastSetDtTime[sensorId] > this->bleSetDtInterval) { + struct tm ti; + + if (getLocalTime(&ti)) { + if (this->setDateOnBleSensor(client, &ti)) { + Log.sinfoln( + FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s', successfully set date: %02d.%02d.%04d %02d:%02d:%02d"), + sensorId, sSensor.name, + ti.tm_mday, ti.tm_mon + 1, ti.tm_year + 1900, ti.tm_hour, ti.tm_min, ti.tm_sec + ); + + } else { + Log.swarningln( + FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s', failed set date: %02d.%02d.%04d %02d:%02d:%02d"), + sensorId, sSensor.name, + ti.tm_mday, ti.tm_mon + 1, ti.tm_year + 1900, ti.tm_hour, ti.tm_min, ti.tm_sec + ); + } + + this->bleLastSetDtTime[sensorId] = millis(); + } + + } + } + } + + NimBLEClient* getBleClient(const uint8_t sensorId) { + if (!NimBLEDevice::isInitialized()) { + return nullptr; } auto& sSensor = Sensors::settings[sensorId]; auto& rSensor = Sensors::results[sensorId]; if (!sSensor.enabled || sSensor.type != Sensors::Type::BLUETOOTH || sSensor.purpose == Sensors::Purpose::NOT_CONFIGURED) { - return false; + return nullptr; } uint8_t addr[6] = { @@ -432,19 +544,20 @@ protected: }; const auto address = NimBLEAddress(addr, 0); - NimBLEClient* pClient = nullptr; - pClient = NimBLEDevice::getClientByPeerAddress(address); - + NimBLEClient* pClient = NimBLEDevice::getClientByPeerAddress(address); if (pClient == nullptr) { pClient = NimBLEDevice::getDisconnectedClient(); } if (pClient == nullptr) { if (NimBLEDevice::getCreatedClientCount() >= NIMBLE_MAX_CONNECTIONS) { - return false; + return nullptr; } pClient = NimBLEDevice::createClient(); + if (pClient == nullptr) { + return nullptr; + } /** * Set initial connection parameters: @@ -457,28 +570,17 @@ protected: pClient->setSelfDelete(false, true); } - if(pClient->isConnected()) { - if (!rSensor.connected) { - rSensor.connected = true; - } - - return true; + if (!pClient->isConnected()) { + pClient->setPeerAddress(address); } - if (!pClient->connect(address)) { - Log.swarningln( - FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed connecting to %s"), - sensorId, sSensor.name, address.toString().c_str() - ); - - return false; - } - - Log.sinfoln( - FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': connected to %s"), - sensorId, sSensor.name, address.toString().c_str() - ); + return pClient; + } + bool subscribeToBleDevice(const uint8_t sensorId, NimBLEClient* pClient) { + auto& sSensor = Sensors::settings[sensorId]; + auto pAddress = pClient->getPeerAddress().toString().c_str(); + NimBLERemoteService* pService = nullptr; NimBLERemoteCharacteristic* pChar = nullptr; @@ -488,13 +590,13 @@ protected: if (!pService) { Log.straceln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to find env service (%s) on device %s"), - sensorId, sSensor.name, serviceUuid.toString().c_str(), address.toString().c_str() + sensorId, sSensor.name, serviceUuid.toString().c_str(), pAddress ); } else { Log.straceln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found env service (%s) on device %s"), - sensorId, sSensor.name, serviceUuid.toString().c_str(), address.toString().c_str() + sensorId, sSensor.name, serviceUuid.toString().c_str(), pAddress ); // 0x2A6E - Notify temperature x0.01C (pvvx) @@ -506,7 +608,7 @@ protected: if (pChar && (pChar->canNotify() || pChar->canIndicate())) { Log.straceln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found temp char (%s) in env service on device %s"), - sensorId, sSensor.name, charUuid.toString().c_str(), address.toString().c_str() + sensorId, sSensor.name, charUuid.toString().c_str(), pAddress ); pChar->unsubscribe(); @@ -561,14 +663,14 @@ protected: Log.straceln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': subscribed to temp char (%s) in env service on device %s"), sensorId, sSensor.name, - charUuid.toString().c_str(), address.toString().c_str() + charUuid.toString().c_str(), pAddress ); } else { Log.swarningln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to subscribe to temp char (%s) in env service on device %s"), sensorId, sSensor.name, - charUuid.toString().c_str(), address.toString().c_str() + charUuid.toString().c_str(), pAddress ); } } @@ -583,7 +685,7 @@ protected: if (pChar && (pChar->canNotify() || pChar->canIndicate())) { Log.straceln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found temp char (%s) in env service on device %s"), - sensorId, sSensor.name, charUuid.toString().c_str(), address.toString().c_str() + sensorId, sSensor.name, charUuid.toString().c_str(), pAddress ); pChar->unsubscribe(); @@ -638,14 +740,14 @@ protected: Log.straceln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': subscribed to temp char (%s) in env service on device %s"), sensorId, sSensor.name, - charUuid.toString().c_str(), address.toString().c_str() + charUuid.toString().c_str(), pAddress ); } else { Log.swarningln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to subscribe to temp char (%s) in env service on device %s"), sensorId, sSensor.name, - charUuid.toString().c_str(), address.toString().c_str() + charUuid.toString().c_str(), pAddress ); } } @@ -654,7 +756,7 @@ protected: if (!tempNotifyCreated) { Log.swarningln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': not found supported temp chars in env service on device %s"), - sensorId, sSensor.name, address.toString().c_str() + sensorId, sSensor.name, pAddress ); pClient->disconnect(); @@ -672,7 +774,7 @@ protected: if (pChar && (pChar->canNotify() || pChar->canIndicate())) { Log.straceln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found humidity char (%s) in env service on device %s"), - sensorId, sSensor.name, charUuid.toString().c_str(), address.toString().c_str() + sensorId, sSensor.name, charUuid.toString().c_str(), pAddress ); pChar->unsubscribe(); @@ -727,14 +829,14 @@ protected: Log.straceln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': subscribed to humidity char (%s) in env service on device %s"), sensorId, sSensor.name, - charUuid.toString().c_str(), address.toString().c_str() + charUuid.toString().c_str(), pAddress ); } else { Log.swarningln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to subscribe to humidity char (%s) in env service on device %s"), sensorId, sSensor.name, - charUuid.toString().c_str(), address.toString().c_str() + charUuid.toString().c_str(), pAddress ); } } @@ -743,7 +845,7 @@ protected: if (!humidityNotifyCreated) { Log.swarningln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': not found supported humidity chars in env service on device %s"), - sensorId, sSensor.name, address.toString().c_str() + sensorId, sSensor.name, pAddress ); } } @@ -757,13 +859,13 @@ protected: if (!pService) { Log.straceln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to find battery service (%s) on device %s"), - sensorId, sSensor.name, serviceUuid.toString().c_str(), address.toString().c_str() + sensorId, sSensor.name, serviceUuid.toString().c_str(), pAddress ); } else { Log.straceln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found battery service (%s) on device %s"), - sensorId, sSensor.name, serviceUuid.toString().c_str(), address.toString().c_str() + sensorId, sSensor.name, serviceUuid.toString().c_str(), pAddress ); // 0x2A19 - Notify the battery charge level 0..99% (pvvx) @@ -775,7 +877,7 @@ protected: if (pChar && (pChar->canNotify() || pChar->canIndicate())) { Log.straceln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found battery char (%s) in battery service on device %s"), - sensorId, sSensor.name, charUuid.toString().c_str(), address.toString().c_str() + sensorId, sSensor.name, charUuid.toString().c_str(), pAddress ); pChar->unsubscribe(); @@ -830,14 +932,14 @@ protected: Log.straceln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': subscribed to battery char (%s) in battery service on device %s"), sensorId, sSensor.name, - charUuid.toString().c_str(), address.toString().c_str() + charUuid.toString().c_str(), pAddress ); } else { Log.swarningln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to subscribe to battery char (%s) in battery service on device %s"), sensorId, sSensor.name, - charUuid.toString().c_str(), address.toString().c_str() + charUuid.toString().c_str(), pAddress ); } } @@ -846,18 +948,33 @@ protected: if (!batteryNotifyCreated) { Log.swarningln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': not found supported battery chars in battery service on device %s"), - sensorId, sSensor.name, address.toString().c_str() + sensorId, sSensor.name, pAddress ); } } } return true; - #else - return false; - #endif } + bool setDateOnBleSensor(NimBLEClient* pClient, const struct tm *ptm) { + auto ts = mkgmtime(ptm); + + uint8_t data[5] = {}; + data[0] = 0x23; + data[1] = ts & 0xff; + data[2] = (ts >> 8) & 0xff; + data[3] = (ts >> 16) & 0xff; + data[4] = (ts >> 24) & 0xff; + + return pClient->setValue( + NimBLEUUID((uint16_t) 0x1f10), + NimBLEUUID((uint16_t) 0x1f1f), + NimBLEAttValue(data, sizeof(data)) + ); + } + #endif + void updateConnectionStatus() { for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) { auto& sSensor = Sensors::settings[sensorId]; @@ -882,7 +999,8 @@ protected: } static bool isEqualAddress(const uint8_t *addr1, const uint8_t *addr2, const uint8_t length = 8) { - bool result = true; + return memcmp(addr1, addr2, length) == 0; + /*bool result = true; for (uint8_t i = 0; i < length; i++) { if (addr1[i] != addr2[i]) { @@ -891,7 +1009,7 @@ protected: } } - return result; + return result;*/ } static bool isEmptyAddress(const uint8_t *addr, const uint8_t length = 8) { diff --git a/src/utils.h b/src/utils.h index b51d1c2..7e2e46a 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,5 +1,43 @@ #include +bool isLeapYear(short year) { + if (year % 4 != 0) { + return false; + } + + if (year % 100 != 0) { + return true; + } + + return (year % 400) == 0; +} + +// convert UTC tm time to time_t epoch time +// source: https://github.com/cyberman54/ESP32-Paxcounter/blob/master/src/timekeeper.cpp +time_t mkgmtime(const struct tm *ptm) { + const int SecondsPerMinute = 60; + const int SecondsPerHour = 3600; + const int SecondsPerDay = 86400; + const int DaysOfMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + time_t secs = 0; + // tm_year is years since 1900 + int year = ptm->tm_year + 1900; + for (int y = 1970; y < year; ++y) { + secs += (isLeapYear(y) ? 366 : 365) * SecondsPerDay; + } + // tm_mon is month from 0..11 + for (int m = 0; m < ptm->tm_mon; ++m) { + secs += DaysOfMonth[m] * SecondsPerDay; + if (m == 1 && isLeapYear(year)) + secs += SecondsPerDay; + } + secs += (ptm->tm_mday - 1) * SecondsPerDay; + secs += ptm->tm_hour * SecondsPerHour; + secs += ptm->tm_min * SecondsPerMinute; + secs += ptm->tm_sec; + return secs; +} + inline bool isDigit(const char* ptr) { char* endPtr; strtol(ptr, &endPtr, 10);