mirror of
https://github.com/Laxilef/OTGateway.git
synced 2025-12-11 02:34:29 +05:00
feat: added feat use of BLE external sensor; added events onIndoorSensorDisconnect and onOutdoorSensorDisconnect for emergency mode; added polling of rssi, humidity, battery for BLE sensors
This commit is contained in:
101
src/MainTask.h
101
src/MainTask.h
@@ -29,7 +29,6 @@ protected:
|
||||
enum class PumpStartReason {NONE, HEATING, ANTISTUCK};
|
||||
|
||||
Blinker* blinker = nullptr;
|
||||
unsigned long firstFailConnect = 0;
|
||||
unsigned long lastHeapInfo = 0;
|
||||
unsigned int minFreeHeap = 0;
|
||||
unsigned int minMaxFreeBlockHeap = 0;
|
||||
@@ -39,6 +38,8 @@ protected:
|
||||
PumpStartReason extPumpStartReason = PumpStartReason::NONE;
|
||||
unsigned long externalPumpStartTime = 0;
|
||||
bool telnetStarted = false;
|
||||
bool emergencyDetected = false;
|
||||
unsigned long emergencyFlipTime = 0;
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
const char* getTaskName() override {
|
||||
@@ -85,10 +86,6 @@ protected:
|
||||
vars.states.mqtt = tMqtt->isConnected();
|
||||
vars.sensors.rssi = network->isConnected() ? WiFi.RSSI() : 0;
|
||||
|
||||
if (vars.states.emergency && !settings.emergency.enable) {
|
||||
vars.states.emergency = false;
|
||||
}
|
||||
|
||||
if (network->isConnected()) {
|
||||
if (!this->telnetStarted && telnetStream != nullptr) {
|
||||
telnetStream->begin(23, false);
|
||||
@@ -102,17 +99,6 @@ protected:
|
||||
tMqtt->disable();
|
||||
}
|
||||
|
||||
if (!vars.states.emergency && settings.emergency.enable && settings.emergency.onMqttFault && !tMqtt->isEnabled()) {
|
||||
vars.states.emergency = true;
|
||||
|
||||
} else if (vars.states.emergency && !settings.emergency.onMqttFault) {
|
||||
vars.states.emergency = false;
|
||||
}
|
||||
|
||||
if (this->firstFailConnect != 0) {
|
||||
this->firstFailConnect = 0;
|
||||
}
|
||||
|
||||
if ( Log.getLevel() != TinyLogger::Level::INFO && !settings.system.debug ) {
|
||||
Log.setLevel(TinyLogger::Level::INFO);
|
||||
|
||||
@@ -129,21 +115,10 @@ protected:
|
||||
if (tMqtt->isEnabled()) {
|
||||
tMqtt->disable();
|
||||
}
|
||||
|
||||
if (!vars.states.emergency && settings.emergency.enable && settings.emergency.onNetworkFault) {
|
||||
if (this->firstFailConnect == 0) {
|
||||
this->firstFailConnect = millis();
|
||||
}
|
||||
|
||||
if (millis() - this->firstFailConnect > (settings.emergency.tresholdTime * 1000)) {
|
||||
vars.states.emergency = true;
|
||||
Log.sinfoln(FPSTR(L_MAIN), F("Emergency mode enabled"));
|
||||
}
|
||||
}
|
||||
}
|
||||
this->yield();
|
||||
|
||||
|
||||
this->emergency();
|
||||
this->ledStatus();
|
||||
this->externalPump();
|
||||
this->yield();
|
||||
@@ -213,6 +188,76 @@ protected:
|
||||
}
|
||||
}
|
||||
|
||||
void emergency() {
|
||||
if (!settings.emergency.enable && vars.states.emergency) {
|
||||
this->emergencyDetected = false;
|
||||
vars.states.emergency = false;
|
||||
|
||||
Log.sinfoln(FPSTR(L_MAIN), F("Emergency mode disabled"));
|
||||
}
|
||||
|
||||
if (!settings.emergency.enable) {
|
||||
return;
|
||||
}
|
||||
|
||||
// flags
|
||||
uint8_t emergencyFlags = 0b00000000;
|
||||
|
||||
// set network flag
|
||||
if (settings.emergency.onNetworkFault && !network->isConnected()) {
|
||||
emergencyFlags |= 0b00000001;
|
||||
}
|
||||
|
||||
// set mqtt flag
|
||||
if (settings.emergency.onMqttFault && (!tMqtt->isEnabled() || !tMqtt->isConnected())) {
|
||||
emergencyFlags |= 0b00000010;
|
||||
}
|
||||
|
||||
// set outdoor sensor flag
|
||||
if (settings.sensors.outdoor.type == SensorType::DS18B20 || settings.sensors.outdoor.type == SensorType::BLUETOOTH) {
|
||||
if (settings.emergency.onOutdoorSensorDisconnect && !vars.sensors.outdoor.connected) {
|
||||
emergencyFlags |= 0b00000100;
|
||||
}
|
||||
}
|
||||
|
||||
// set indoor sensor flag
|
||||
if (settings.sensors.indoor.type == SensorType::DS18B20 || settings.sensors.indoor.type == SensorType::BLUETOOTH) {
|
||||
if (settings.emergency.onIndoorSensorDisconnect && !vars.sensors.indoor.connected) {
|
||||
emergencyFlags |= 0b00001000;
|
||||
}
|
||||
}
|
||||
|
||||
// if any flags is true
|
||||
if ((emergencyFlags & 0b00001111) != 0) {
|
||||
if (!this->emergencyDetected) {
|
||||
// flip flag
|
||||
this->emergencyDetected = true;
|
||||
this->emergencyFlipTime = millis();
|
||||
|
||||
} else if (this->emergencyDetected && !vars.states.emergency) {
|
||||
// enable emergency
|
||||
if (millis() - this->emergencyFlipTime > (settings.emergency.tresholdTime * 1000)) {
|
||||
vars.states.emergency = true;
|
||||
Log.sinfoln(FPSTR(L_MAIN), F("Emergency mode enabled (%hhu)"), emergencyFlags);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (this->emergencyDetected) {
|
||||
// flip flag
|
||||
this->emergencyDetected = false;
|
||||
this->emergencyFlipTime = millis();
|
||||
|
||||
} else if (!this->emergencyDetected && vars.states.emergency) {
|
||||
// disable emergency
|
||||
if (millis() - this->emergencyFlipTime > 30000) {
|
||||
vars.states.emergency = false;
|
||||
Log.sinfoln(FPSTR(L_MAIN), F("Emergency mode disabled"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ledStatus() {
|
||||
uint8_t errors[4];
|
||||
uint8_t errCount = 0;
|
||||
|
||||
@@ -198,17 +198,6 @@ protected:
|
||||
this->onConnect();
|
||||
}
|
||||
|
||||
if (settings.emergency.enable && settings.emergency.onMqttFault) {
|
||||
if (!this->connected && !vars.states.emergency && millis() - this->disconnectedTime > (settings.emergency.tresholdTime * 1000)) {
|
||||
vars.states.emergency = true;
|
||||
Log.sinfoln(FPSTR(L_MQTT), F("Emergency mode enabled"));
|
||||
|
||||
} else if (this->connected && vars.states.emergency && millis() - this->connectedTime > 10000) {
|
||||
vars.states.emergency = false;
|
||||
Log.sinfoln(FPSTR(L_MQTT), F("Emergency mode disabled"));
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -198,6 +198,10 @@ protected:
|
||||
etRegulator.Kt = 0;
|
||||
etRegulator.indoorTemp = 0;
|
||||
|
||||
} else if ((settings.sensors.indoor.type == SensorType::DS18B20 || settings.sensors.indoor.type == SensorType::BLUETOOTH) && !vars.sensors.indoor.connected) {
|
||||
etRegulator.Kt = 0;
|
||||
etRegulator.indoorTemp = 0;
|
||||
|
||||
} else {
|
||||
etRegulator.Kt = settings.equitherm.t_factor;
|
||||
etRegulator.indoorTemp = indoorTemp;
|
||||
|
||||
@@ -35,19 +35,18 @@ protected:
|
||||
unsigned long initOutdoorSensorTime = 0;
|
||||
unsigned long startOutdoorConversionTime = 0;
|
||||
float filteredOutdoorTemp = 0;
|
||||
bool emptyOutdoorTemp = true;
|
||||
float prevFilteredOutdoorTemp = 0;
|
||||
|
||||
bool initIndoorSensor = false;
|
||||
unsigned long initIndoorSensorTime = 0;
|
||||
unsigned long startIndoorConversionTime = 0;
|
||||
float filteredIndoorTemp = 0;
|
||||
bool emptyIndoorTemp = true;
|
||||
float prevFilteredIndoorTemp = 0;
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#if USE_BLE
|
||||
BLEClient* pBleClient = nullptr;
|
||||
bool initBleSensor = false;
|
||||
bool initBleNotify = false;
|
||||
unsigned long outdoorConnectedTime = 0;
|
||||
unsigned long indoorConnectedTime = 0;
|
||||
#endif
|
||||
|
||||
const char* getTaskName() override {
|
||||
@@ -69,26 +68,62 @@ protected:
|
||||
#endif
|
||||
|
||||
void loop() {
|
||||
bool indoorTempUpdated = false;
|
||||
bool outdoorTempUpdated = false;
|
||||
|
||||
if (settings.sensors.outdoor.type == SensorType::DS18B20 && GPIO_IS_VALID(settings.sensors.outdoor.gpio)) {
|
||||
outdoorTemperatureSensor();
|
||||
outdoorTempUpdated = true;
|
||||
}
|
||||
|
||||
if (settings.sensors.indoor.type == SensorType::DS18B20 && GPIO_IS_VALID(settings.sensors.indoor.gpio)) {
|
||||
indoorTemperatureSensor();
|
||||
indoorTempUpdated = true;
|
||||
}
|
||||
#if USE_BLE
|
||||
else if (settings.sensors.indoor.type == SensorType::BLUETOOTH) {
|
||||
indoorTemperatureBluetoothSensor();
|
||||
indoorTempUpdated = true;
|
||||
if (!NimBLEDevice::getInitialized() && millis() > 5000) {
|
||||
Log.sinfoln(FPSTR(L_SENSORS_BLE), F("Init BLE"));
|
||||
BLEDevice::init("");
|
||||
NimBLEDevice::setPower(ESP_PWR_LVL_P9);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (outdoorTempUpdated) {
|
||||
if (settings.sensors.outdoor.type == SensorType::DS18B20 && GPIO_IS_VALID(settings.sensors.outdoor.gpio)) {
|
||||
outdoorDallasSensor();
|
||||
}
|
||||
#if USE_BLE
|
||||
else if (settings.sensors.outdoor.type == SensorType::BLUETOOTH) {
|
||||
bool connected = this->bluetoothSensor(
|
||||
BLEAddress(settings.sensors.outdoor.bleAddress),
|
||||
&vars.sensors.outdoor.rssi,
|
||||
&this->filteredOutdoorTemp,
|
||||
&vars.sensors.outdoor.humidity,
|
||||
&vars.sensors.outdoor.battery
|
||||
);
|
||||
|
||||
if (connected) {
|
||||
this->outdoorConnectedTime = millis();
|
||||
vars.sensors.outdoor.connected = true;
|
||||
|
||||
} else if (millis() - this->outdoorConnectedTime > 60000) {
|
||||
vars.sensors.outdoor.connected = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (settings.sensors.indoor.type == SensorType::DS18B20 && GPIO_IS_VALID(settings.sensors.indoor.gpio)) {
|
||||
indoorDallasSensor();
|
||||
}
|
||||
#if USE_BLE
|
||||
else if (settings.sensors.indoor.type == SensorType::BLUETOOTH) {
|
||||
bool connected = this->bluetoothSensor(
|
||||
BLEAddress(settings.sensors.indoor.bleAddress),
|
||||
&vars.sensors.indoor.rssi,
|
||||
&this->filteredIndoorTemp,
|
||||
&vars.sensors.indoor.humidity,
|
||||
&vars.sensors.indoor.battery
|
||||
);
|
||||
|
||||
if (connected) {
|
||||
this->indoorConnectedTime = millis();
|
||||
vars.sensors.indoor.connected = true;
|
||||
|
||||
} else if (millis() - this->indoorConnectedTime > 60000) {
|
||||
vars.sensors.indoor.connected = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// convert
|
||||
if (fabs(this->prevFilteredOutdoorTemp - this->filteredOutdoorTemp) >= 0.1f) {
|
||||
float newTemp = settings.sensors.outdoor.offset;
|
||||
if (settings.system.unitSystem == UnitSystem::METRIC) {
|
||||
newTemp += this->filteredOutdoorTemp;
|
||||
@@ -101,9 +136,11 @@ protected:
|
||||
vars.temperatures.outdoor = newTemp;
|
||||
Log.sinfoln(FPSTR(L_SENSORS_OUTDOOR), F("New temp: %f"), vars.temperatures.outdoor);
|
||||
}
|
||||
|
||||
this->prevFilteredOutdoorTemp = this->filteredOutdoorTemp;
|
||||
}
|
||||
|
||||
if (indoorTempUpdated) {
|
||||
if (fabs(this->prevFilteredIndoorTemp - this->filteredIndoorTemp) > 0.1f) {
|
||||
float newTemp = settings.sensors.indoor.offset;
|
||||
if (settings.system.unitSystem == UnitSystem::METRIC) {
|
||||
newTemp += this->filteredIndoorTemp;
|
||||
@@ -116,127 +153,378 @@ protected:
|
||||
vars.temperatures.indoor = newTemp;
|
||||
Log.sinfoln(FPSTR(L_SENSORS_INDOOR), F("New temp: %f"), vars.temperatures.indoor);
|
||||
}
|
||||
|
||||
this->prevFilteredIndoorTemp = this->filteredIndoorTemp;
|
||||
}
|
||||
}
|
||||
|
||||
#if USE_BLE
|
||||
void indoorTemperatureBluetoothSensor() {
|
||||
static bool initBleNotify = false;
|
||||
if (!initBleSensor && millis() > 5000) {
|
||||
Log.sinfoln(FPSTR(L_SENSORS_BLE), F("Init BLE"));
|
||||
BLEDevice::init("");
|
||||
|
||||
pBleClient = BLEDevice::createClient();
|
||||
pBleClient->setConnectTimeout(5);
|
||||
|
||||
initBleSensor = true;
|
||||
bool bluetoothSensor(const BLEAddress& address, int8_t* const pRssi, float* const pTemperature, float* const pHumidity = nullptr, float* const pBattery = nullptr) {
|
||||
if (!NimBLEDevice::getInitialized()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!initBleSensor || pBleClient->isConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset init notify flag
|
||||
this->initBleNotify = false;
|
||||
NimBLEClient* pClient = nullptr;
|
||||
pClient = NimBLEDevice::getClientByPeerAddress(address);
|
||||
|
||||
// Connect to the remote BLE Server.
|
||||
BLEAddress bleServerAddress(settings.sensors.indoor.bleAddress);
|
||||
if (!pBleClient->connect(bleServerAddress)) {
|
||||
Log.swarningln(FPSTR(L_SENSORS_BLE), "Failed connecting to device at %s", bleServerAddress.toString().c_str());
|
||||
return;
|
||||
if (pClient == nullptr) {
|
||||
pClient = NimBLEDevice::getDisconnectedClient();
|
||||
}
|
||||
|
||||
Log.sinfoln(FPSTR(L_SENSORS_BLE), "Connected to device at %s", bleServerAddress.toString().c_str());
|
||||
if (pClient == nullptr) {
|
||||
if (NimBLEDevice::getClientListSize() >= NIMBLE_MAX_CONNECTIONS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
NimBLEUUID serviceUUID((uint16_t) 0x181AU);
|
||||
BLERemoteService* pRemoteService = pBleClient->getService(serviceUUID);
|
||||
if (!pRemoteService) {
|
||||
Log.straceln(FPSTR(L_SENSORS_BLE), F("Failed to find service UUID: %s"), serviceUUID.toString().c_str());
|
||||
return;
|
||||
pClient = NimBLEDevice::createClient();
|
||||
pClient->setConnectTimeout(5);
|
||||
}
|
||||
|
||||
Log.straceln(FPSTR(L_SENSORS_BLE), F("Found service UUID: %s"), serviceUUID.toString().c_str());
|
||||
if(pClient->isConnected()) {
|
||||
*pRssi = pClient->getRssi();
|
||||
return true;
|
||||
}
|
||||
|
||||
// 0x2A6E - Notify temperature x0.01C (pvvx)
|
||||
if (!this->initBleNotify) {
|
||||
NimBLEUUID charUUID((uint16_t) 0x2A6E);
|
||||
BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
|
||||
if (pRemoteCharacteristic && pRemoteCharacteristic->canNotify()) {
|
||||
Log.straceln(FPSTR(L_SENSORS_BLE), F("Found characteristic UUID: %s"), charUUID.toString().c_str());
|
||||
if (!pClient->connect(address)) {
|
||||
Log.swarningln(FPSTR(L_SENSORS_BLE), "Device %s: failed connecting", address.toString().c_str());
|
||||
|
||||
this->initBleNotify = pRemoteCharacteristic->subscribe(true, [this](NimBLERemoteCharacteristic*, uint8_t* pData, size_t length, bool isNotify) {
|
||||
if (length != 2) {
|
||||
Log.swarningln(FPSTR(L_SENSORS_BLE), F("Invalid notification data"));
|
||||
return;
|
||||
}
|
||||
NimBLEDevice::deleteClient(pClient);
|
||||
return false;
|
||||
}
|
||||
|
||||
float rawTemp = ((pData[0] | (pData[1] << 8)) * 0.01f);
|
||||
Log.straceln(FPSTR(L_SENSORS_INDOOR), F("Raw temp: %f"), rawTemp);
|
||||
Log.sinfoln(FPSTR(L_SENSORS_BLE), "Device %s: connected", address.toString().c_str());
|
||||
NimBLERemoteService* pService = nullptr;
|
||||
NimBLERemoteCharacteristic* pChar = nullptr;
|
||||
|
||||
if (this->emptyIndoorTemp) {
|
||||
this->filteredIndoorTemp = rawTemp;
|
||||
this->emptyIndoorTemp = false;
|
||||
// ENV Service (0x181A)
|
||||
pService = pClient->getService(NimBLEUUID((uint16_t) 0x181AU));
|
||||
if (!pService) {
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: failed to find env service (%s)"),
|
||||
address.toString().c_str(),
|
||||
pService->getUUID().toString().c_str()
|
||||
);
|
||||
|
||||
} else {
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: found env service (%s)"),
|
||||
address.toString().c_str(),
|
||||
pService->getUUID().toString().c_str()
|
||||
);
|
||||
|
||||
|
||||
// 0x2A6E - Notify temperature x0.01C (pvvx)
|
||||
bool tempNotifyCreated = false;
|
||||
if (!tempNotifyCreated) {
|
||||
pChar = pService->getCharacteristic(NimBLEUUID((uint16_t) 0x2A6E));
|
||||
|
||||
if (pChar && pChar->canNotify()) {
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: found temperature char (%s) in env service"),
|
||||
address.toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
|
||||
tempNotifyCreated = pChar->subscribe(true, [pTemperature](NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) {
|
||||
NimBLEClient* pClient = pChar->getRemoteService()->getClient();
|
||||
|
||||
if (length != 2) {
|
||||
Log.swarningln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: invalid notification data at temperature char (%s)"),
|
||||
pClient->getPeerAddress().toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
float rawTemp = ((pData[0] | (pData[1] << 8)) * 0.01f);
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_INDOOR),
|
||||
F("Device %s: raw temp %f"),
|
||||
pClient->getPeerAddress().toString().c_str(),
|
||||
rawTemp
|
||||
);
|
||||
|
||||
if (fabs(*pTemperature) < 0.1f) {
|
||||
*pTemperature = rawTemp;
|
||||
|
||||
} else {
|
||||
*pTemperature += (rawTemp - (*pTemperature)) * EXT_SENSORS_FILTER_K;
|
||||
}
|
||||
|
||||
*pTemperature = floor((*pTemperature) * 100) / 100;
|
||||
});
|
||||
|
||||
if (tempNotifyCreated) {
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: subscribed to temperature char (%s) in env service"),
|
||||
address.toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
|
||||
} else {
|
||||
this->filteredIndoorTemp += (rawTemp - this->filteredIndoorTemp) * EXT_SENSORS_FILTER_K;
|
||||
Log.swarningln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: failed to subscribe to temperature char (%s) in env service"),
|
||||
address.toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this->filteredIndoorTemp = floor(this->filteredIndoorTemp * 100) / 100;
|
||||
});
|
||||
|
||||
if (this->initBleNotify) {
|
||||
Log.straceln(FPSTR(L_SENSORS_BLE), F("Subscribed to characteristic UUID: %s"), charUUID.toString().c_str());
|
||||
// 0x2A1F - Notify temperature x0.1C (atc1441/pvvx)
|
||||
if (!tempNotifyCreated) {
|
||||
pChar = pService->getCharacteristic(NimBLEUUID((uint16_t) 0x2A1F));
|
||||
|
||||
} else {
|
||||
Log.swarningln(FPSTR(L_SENSORS_BLE), F("Failed to subscribe to characteristic UUID: %s"), charUUID.toString().c_str());
|
||||
if (pChar && pChar->canNotify()) {
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: found temperature char (%s) in env service"),
|
||||
address.toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
|
||||
tempNotifyCreated = pChar->subscribe(true, [pTemperature](NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) {
|
||||
NimBLEClient* pClient = pChar->getRemoteService()->getClient();
|
||||
|
||||
if (length != 2) {
|
||||
Log.swarningln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: invalid notification data at temperature char (%s)"),
|
||||
pClient->getPeerAddress().toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
float rawTemp = ((pData[0] | (pData[1] << 8)) * 0.1f);
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_INDOOR),
|
||||
F("Device %s: raw temp %f"),
|
||||
pClient->getPeerAddress().toString().c_str(),
|
||||
rawTemp
|
||||
);
|
||||
|
||||
if (fabs(*pTemperature) < 0.1f) {
|
||||
*pTemperature = rawTemp;
|
||||
|
||||
} else {
|
||||
*pTemperature += (rawTemp - (*pTemperature)) * EXT_SENSORS_FILTER_K;
|
||||
}
|
||||
|
||||
*pTemperature = floor((*pTemperature) * 100) / 100;
|
||||
});
|
||||
|
||||
if (tempNotifyCreated) {
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: subscribed to temperature char (%s) in env service"),
|
||||
address.toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
|
||||
} else {
|
||||
Log.swarningln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: failed to subscribe to temperature char (%s) in env service"),
|
||||
address.toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!tempNotifyCreated) {
|
||||
Log.swarningln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: not found supported temperature chars in env service"),
|
||||
address.toString().c_str()
|
||||
);
|
||||
|
||||
pClient->disconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// 0x2A6F - Notify about humidity x0.01% (pvvx)
|
||||
if (pHumidity != nullptr) {
|
||||
bool humidityNotifyCreated = false;
|
||||
if (!humidityNotifyCreated) {
|
||||
pChar = pService->getCharacteristic(NimBLEUUID((uint16_t) 0x2A6F));
|
||||
|
||||
if (pChar && pChar->canNotify()) {
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: found humidity char (%s) in env service"),
|
||||
address.toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
|
||||
humidityNotifyCreated = pChar->subscribe(true, [pHumidity](NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) {
|
||||
NimBLEClient* pClient = pChar->getRemoteService()->getClient();
|
||||
|
||||
if (length != 2) {
|
||||
Log.swarningln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: invalid notification data at humidity char (%s)"),
|
||||
pClient->getPeerAddress().toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
float rawHumidity = ((pData[0] | (pData[1] << 8)) * 0.01f);
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_INDOOR),
|
||||
F("Device %s: raw humidity %f"),
|
||||
pClient->getPeerAddress().toString().c_str(),
|
||||
rawHumidity
|
||||
);
|
||||
|
||||
if (fabs(*pHumidity) < 0.1f) {
|
||||
*pHumidity = rawHumidity;
|
||||
|
||||
} else {
|
||||
*pHumidity += (rawHumidity - (*pHumidity)) * EXT_SENSORS_FILTER_K;
|
||||
}
|
||||
|
||||
*pHumidity = floor((*pHumidity) * 100) / 100;
|
||||
});
|
||||
|
||||
if (humidityNotifyCreated) {
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: subscribed to humidity char (%s) in env service"),
|
||||
address.toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
|
||||
} else {
|
||||
Log.swarningln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: failed to subscribe to humidity char (%s) in env service"),
|
||||
address.toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!humidityNotifyCreated) {
|
||||
Log.swarningln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: not found supported humidity chars in env service"),
|
||||
address.toString().c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 0x2A1F - Notify temperature x0.1C (atc1441/pvvx)
|
||||
if (!this->initBleNotify) {
|
||||
NimBLEUUID charUUID((uint16_t) 0x2A1F);
|
||||
BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
|
||||
if (pRemoteCharacteristic && pRemoteCharacteristic->canNotify()) {
|
||||
Log.straceln(FPSTR(L_SENSORS_BLE), F("Found characteristic UUID: %s"), charUUID.toString().c_str());
|
||||
|
||||
this->initBleNotify = pRemoteCharacteristic->subscribe(true, [this](NimBLERemoteCharacteristic*, uint8_t* pData, size_t length, bool isNotify) {
|
||||
if (length != 2) {
|
||||
Log.swarningln(FPSTR(L_SENSORS_BLE), F("Invalid notification data"));
|
||||
return;
|
||||
// Battery Service (0x180F)
|
||||
if (pBattery != nullptr) {
|
||||
pService = pClient->getService(NimBLEUUID((uint16_t) 0x180F));
|
||||
if (!pService) {
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: failed to find battery service (%s)"),
|
||||
address.toString().c_str(),
|
||||
pService->getUUID().toString().c_str()
|
||||
);
|
||||
|
||||
} else {
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: found battery service (%s)"),
|
||||
address.toString().c_str(),
|
||||
pService->getUUID().toString().c_str()
|
||||
);
|
||||
|
||||
// 0x2A19 - Notify the battery charge level 0..99% (pvvx)
|
||||
bool batteryNotifyCreated = false;
|
||||
if (!batteryNotifyCreated) {
|
||||
pChar = pService->getCharacteristic(NimBLEUUID((uint16_t) 0x2A19));
|
||||
|
||||
if (pChar && pChar->canNotify()) {
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: found battery char (%s) in battery service"),
|
||||
address.toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
|
||||
batteryNotifyCreated = pChar->subscribe(true, [pBattery](NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) {
|
||||
NimBLEClient* pClient = pChar->getRemoteService()->getClient();
|
||||
|
||||
if (length != 1) {
|
||||
Log.swarningln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: invalid notification data at battery char (%s)"),
|
||||
pClient->getPeerAddress().toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t rawBattery = pData[0];
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_INDOOR),
|
||||
F("Device %s: raw battery %hhu"),
|
||||
pClient->getPeerAddress().toString().c_str(),
|
||||
rawBattery
|
||||
);
|
||||
|
||||
if (fabs(*pBattery) < 0.1f) {
|
||||
*pBattery = rawBattery;
|
||||
|
||||
} else {
|
||||
*pBattery += (rawBattery - (*pBattery)) * EXT_SENSORS_FILTER_K;
|
||||
}
|
||||
|
||||
*pBattery = floor((*pBattery) * 100) / 100;
|
||||
});
|
||||
|
||||
if (batteryNotifyCreated) {
|
||||
Log.straceln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: subscribed to battery char (%s) in battery service"),
|
||||
address.toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
|
||||
} else {
|
||||
Log.swarningln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: failed to subscribe to battery char (%s) in battery service"),
|
||||
address.toString().c_str(),
|
||||
pChar->getUUID().toString().c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float rawTemp = ((pData[0] | (pData[1] << 8)) * 0.1);
|
||||
Log.straceln(FPSTR(L_SENSORS_INDOOR), F("Raw temp: %f"), rawTemp);
|
||||
|
||||
if (this->emptyIndoorTemp) {
|
||||
this->filteredIndoorTemp = rawTemp;
|
||||
this->emptyIndoorTemp = false;
|
||||
|
||||
} else {
|
||||
this->filteredIndoorTemp += (rawTemp - this->filteredIndoorTemp) * EXT_SENSORS_FILTER_K;
|
||||
}
|
||||
|
||||
this->filteredIndoorTemp = floor(this->filteredIndoorTemp * 100) / 100;
|
||||
});
|
||||
|
||||
if (this->initBleNotify) {
|
||||
Log.straceln(FPSTR(L_SENSORS_BLE), F("Subscribed to characteristic UUID: %s"), charUUID.toString().c_str());
|
||||
|
||||
} else {
|
||||
Log.swarningln(FPSTR(L_SENSORS_BLE), F("Failed to subscribe to characteristic UUID: %s"), charUUID.toString().c_str());
|
||||
if (!batteryNotifyCreated) {
|
||||
Log.swarningln(
|
||||
FPSTR(L_SENSORS_BLE),
|
||||
F("Device %s: not found supported battery chars in battery service"),
|
||||
address.toString().c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->initBleNotify) {
|
||||
Log.swarningln(FPSTR(L_SENSORS_BLE), F("Not found supported characteristics"));
|
||||
pBleClient->disconnect();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void outdoorTemperatureSensor() {
|
||||
void outdoorDallasSensor() {
|
||||
if (!this->initOutdoorSensor) {
|
||||
if (this->initOutdoorSensorTime && millis() - this->initOutdoorSensorTime < EXT_SENSORS_INTERVAL * 10) {
|
||||
return;
|
||||
@@ -265,6 +553,10 @@ protected:
|
||||
Log.sinfoln(FPSTR(L_SENSORS_OUTDOOR), F("Started"));
|
||||
|
||||
} else {
|
||||
if (vars.sensors.outdoor.connected) {
|
||||
vars.sensors.outdoor.connected = false;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -294,9 +586,12 @@ protected:
|
||||
} else {
|
||||
Log.straceln(FPSTR(L_SENSORS_OUTDOOR), F("Raw temp: %f"), rawTemp);
|
||||
|
||||
if (this->emptyOutdoorTemp) {
|
||||
if (!vars.sensors.outdoor.connected) {
|
||||
vars.sensors.outdoor.connected = true;
|
||||
}
|
||||
|
||||
if (fabs(this->filteredOutdoorTemp) < 0.1f) {
|
||||
this->filteredOutdoorTemp = rawTemp;
|
||||
this->emptyOutdoorTemp = false;
|
||||
|
||||
} else {
|
||||
this->filteredOutdoorTemp += (rawTemp - this->filteredOutdoorTemp) * EXT_SENSORS_FILTER_K;
|
||||
@@ -308,7 +603,7 @@ protected:
|
||||
}
|
||||
}
|
||||
|
||||
void indoorTemperatureSensor() {
|
||||
void indoorDallasSensor() {
|
||||
if (!this->initIndoorSensor) {
|
||||
if (this->initIndoorSensorTime && millis() - this->initIndoorSensorTime < EXT_SENSORS_INTERVAL * 10) {
|
||||
return;
|
||||
@@ -337,6 +632,10 @@ protected:
|
||||
Log.sinfoln(FPSTR(L_SENSORS_INDOOR), F("Started"));
|
||||
|
||||
} else {
|
||||
if (vars.sensors.indoor.connected) {
|
||||
vars.sensors.indoor.connected = false;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -366,9 +665,12 @@ protected:
|
||||
} else {
|
||||
Log.straceln(FPSTR(L_SENSORS_INDOOR), F("Raw temp: %f"), rawTemp);
|
||||
|
||||
if (this->emptyIndoorTemp) {
|
||||
if (!vars.sensors.indoor.connected) {
|
||||
vars.sensors.indoor.connected = true;
|
||||
}
|
||||
|
||||
if (fabs(this->filteredIndoorTemp) < 0.1f) {
|
||||
this->filteredIndoorTemp = rawTemp;
|
||||
this->emptyIndoorTemp = false;
|
||||
|
||||
} else {
|
||||
this->filteredIndoorTemp += (rawTemp - this->filteredIndoorTemp) * EXT_SENSORS_FILTER_K;
|
||||
|
||||
@@ -77,13 +77,15 @@ struct Settings {
|
||||
} mqtt;
|
||||
|
||||
struct {
|
||||
bool enable = true;
|
||||
bool enable = false;
|
||||
float target = DEFAULT_HEATING_TARGET_TEMP;
|
||||
unsigned short tresholdTime = 120;
|
||||
bool useEquitherm = false;
|
||||
bool usePid = false;
|
||||
bool onNetworkFault = true;
|
||||
bool onMqttFault = true;
|
||||
bool onIndoorSensorDisconnect = false;
|
||||
bool onOutdoorSensorDisconnect = false;
|
||||
} emergency;
|
||||
|
||||
struct {
|
||||
@@ -124,6 +126,7 @@ struct Settings {
|
||||
struct {
|
||||
SensorType type = SensorType::BOILER;
|
||||
byte gpio = DEFAULT_SENSOR_OUTDOOR_GPIO;
|
||||
uint8_t bleAddress[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
float offset = 0.0f;
|
||||
} outdoor;
|
||||
|
||||
@@ -165,6 +168,20 @@ struct Variables {
|
||||
float dhwFlowRate = 0.0f;
|
||||
byte faultCode = 0;
|
||||
int8_t rssi = 0;
|
||||
|
||||
struct {
|
||||
bool connected = false;
|
||||
int8_t rssi = 0;
|
||||
float battery = 0.0f;
|
||||
float humidity = 0.0f;
|
||||
} outdoor;
|
||||
|
||||
struct {
|
||||
bool connected = false;
|
||||
int8_t rssi = 0;
|
||||
float battery = 0.0f;
|
||||
float humidity = 0.0f;
|
||||
} indoor;
|
||||
} sensors;
|
||||
|
||||
struct {
|
||||
|
||||
74
src/utils.h
74
src/utils.h
@@ -371,6 +371,8 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
|
||||
dst["emergency"]["usePid"] = src.emergency.usePid;
|
||||
dst["emergency"]["onNetworkFault"] = src.emergency.onNetworkFault;
|
||||
dst["emergency"]["onMqttFault"] = src.emergency.onMqttFault;
|
||||
dst["emergency"]["onIndoorSensorDisconnect"] = src.emergency.onIndoorSensorDisconnect;
|
||||
dst["emergency"]["onOutdoorSensorDisconnect"] = src.emergency.onOutdoorSensorDisconnect;
|
||||
}
|
||||
|
||||
dst["heating"]["enable"] = src.heating.enable;
|
||||
@@ -401,12 +403,24 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
|
||||
|
||||
dst["sensors"]["outdoor"]["type"] = static_cast<byte>(src.sensors.outdoor.type);
|
||||
dst["sensors"]["outdoor"]["gpio"] = src.sensors.outdoor.gpio;
|
||||
|
||||
char bleAddress[18];
|
||||
sprintf(
|
||||
bleAddress,
|
||||
"%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
src.sensors.outdoor.bleAddress[0],
|
||||
src.sensors.outdoor.bleAddress[1],
|
||||
src.sensors.outdoor.bleAddress[2],
|
||||
src.sensors.outdoor.bleAddress[3],
|
||||
src.sensors.outdoor.bleAddress[4],
|
||||
src.sensors.outdoor.bleAddress[5]
|
||||
);
|
||||
dst["sensors"]["outdoor"]["bleAddress"] = String(bleAddress);
|
||||
dst["sensors"]["outdoor"]["offset"] = roundd(src.sensors.outdoor.offset, 2);
|
||||
|
||||
dst["sensors"]["indoor"]["type"] = static_cast<byte>(src.sensors.indoor.type);
|
||||
dst["sensors"]["indoor"]["gpio"] = src.sensors.indoor.gpio;
|
||||
|
||||
char bleAddress[18];
|
||||
sprintf(
|
||||
bleAddress,
|
||||
"%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
@@ -883,7 +897,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
if (src["emergency"]["useEquitherm"].is<bool>()) {
|
||||
bool value = src["emergency"]["useEquitherm"].as<bool>();
|
||||
|
||||
if (!dst.opentherm.nativeHeatingControl && dst.sensors.outdoor.type != SensorType::MANUAL) {
|
||||
if (!dst.opentherm.nativeHeatingControl && dst.sensors.outdoor.type != SensorType::MANUAL && dst.sensors.outdoor.type != SensorType::BLUETOOTH) {
|
||||
if (value != dst.emergency.useEquitherm) {
|
||||
dst.emergency.useEquitherm = value;
|
||||
changed = true;
|
||||
@@ -903,7 +917,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
if (src["emergency"]["usePid"].is<bool>()) {
|
||||
bool value = src["emergency"]["usePid"].as<bool>();
|
||||
|
||||
if (!dst.opentherm.nativeHeatingControl && dst.sensors.indoor.type != SensorType::MANUAL) {
|
||||
if (!dst.opentherm.nativeHeatingControl && dst.sensors.indoor.type != SensorType::MANUAL && dst.sensors.indoor.type != SensorType::BLUETOOTH) {
|
||||
if (value != dst.emergency.usePid) {
|
||||
dst.emergency.usePid = value;
|
||||
changed = true;
|
||||
@@ -937,6 +951,26 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (src["emergency"]["onIndoorSensorDisconnect"].is<bool>()) {
|
||||
bool value = src["emergency"]["onIndoorSensorDisconnect"].as<bool>();
|
||||
|
||||
if (value != dst.emergency.onIndoorSensorDisconnect) {
|
||||
dst.emergency.onIndoorSensorDisconnect = value;
|
||||
dst.emergency.usePid = false;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (src["emergency"]["onOutdoorSensorDisconnect"].is<bool>()) {
|
||||
bool value = src["emergency"]["onOutdoorSensorDisconnect"].as<bool>();
|
||||
|
||||
if (value != dst.emergency.onOutdoorSensorDisconnect) {
|
||||
dst.emergency.onOutdoorSensorDisconnect = value;
|
||||
dst.emergency.useEquitherm = false;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1167,6 +1201,16 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
}
|
||||
break;
|
||||
|
||||
#if USE_BLE
|
||||
case static_cast<byte>(SensorType::BLUETOOTH):
|
||||
if (dst.sensors.outdoor.type != SensorType::BLUETOOTH) {
|
||||
dst.sensors.outdoor.type = SensorType::BLUETOOTH;
|
||||
dst.emergency.useEquitherm = false;
|
||||
changed = true;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -1189,6 +1233,21 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
}
|
||||
}
|
||||
|
||||
#if USE_BLE
|
||||
if (!src["sensors"]["outdoor"]["bleAddress"].isNull()) {
|
||||
String value = src["sensors"]["outdoor"]["bleAddress"].as<String>();
|
||||
int tmp[6];
|
||||
if(sscanf(value.c_str(), "%02x:%02x:%02x:%02x:%02x:%02x", &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4], &tmp[5]) == 6) {
|
||||
for(uint8_t i = 0; i < 6; i++) {
|
||||
if (dst.sensors.outdoor.bleAddress[i] != (uint8_t) tmp[i]) {
|
||||
dst.sensors.outdoor.bleAddress[i] = (uint8_t) tmp[i];
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!src["sensors"]["outdoor"]["offset"].isNull()) {
|
||||
float value = src["sensors"]["outdoor"]["offset"].as<float>();
|
||||
|
||||
@@ -1222,6 +1281,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
case static_cast<byte>(SensorType::BLUETOOTH):
|
||||
if (dst.sensors.indoor.type != SensorType::BLUETOOTH) {
|
||||
dst.sensors.indoor.type = SensorType::BLUETOOTH;
|
||||
dst.emergency.usePid = false;
|
||||
changed = true;
|
||||
}
|
||||
break;
|
||||
@@ -1439,6 +1499,14 @@ void varsToJson(const Variables& src, JsonVariant dst) {
|
||||
dst["sensors"]["faultCode"] = src.sensors.faultCode;
|
||||
dst["sensors"]["rssi"] = src.sensors.rssi;
|
||||
dst["sensors"]["uptime"] = millis() / 1000ul;
|
||||
dst["sensors"]["outdoor"]["connected"] = src.sensors.outdoor.connected;
|
||||
dst["sensors"]["outdoor"]["rssi"] = src.sensors.outdoor.rssi;
|
||||
dst["sensors"]["outdoor"]["battery"] = roundd(src.sensors.outdoor.battery, 2);
|
||||
dst["sensors"]["outdoor"]["humidity"] = roundd(src.sensors.outdoor.humidity, 2);
|
||||
dst["sensors"]["indoor"]["connected"] = src.sensors.indoor.connected;
|
||||
dst["sensors"]["indoor"]["rssi"] = src.sensors.indoor.rssi;
|
||||
dst["sensors"]["indoor"]["battery"] = roundd(src.sensors.indoor.battery, 2);
|
||||
dst["sensors"]["indoor"]["humidity"] = roundd(src.sensors.indoor.humidity, 2);
|
||||
|
||||
dst["temperatures"]["indoor"] = roundd(src.temperatures.indoor, 2);
|
||||
dst["temperatures"]["outdoor"] = roundd(src.temperatures.outdoor, 2);
|
||||
|
||||
Reference in New Issue
Block a user