mirror of
https://github.com/Laxilef/OTGateway.git
synced 2025-12-10 18:24:27 +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);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"issues": "Issues & questions",
|
||||
"releases": "Releases"
|
||||
},
|
||||
"dbm": "dBm",
|
||||
|
||||
"button": {
|
||||
"upgrade": "Upgrade",
|
||||
@@ -87,6 +88,14 @@
|
||||
"fault": "Fault",
|
||||
"diag": "Diagnostic",
|
||||
"extpump": "External pump",
|
||||
"outdoorSensorConnected": "Outdoor sensor connected",
|
||||
"outdoorSensorRssi": "Outdoor sensor RSSI",
|
||||
"outdoorSensorHumidity": "Outdoor sensor humidity",
|
||||
"outdoorSensorBattery": "Outdoor sensor battery",
|
||||
"indoorSensorConnected": "Indoor sensor connected",
|
||||
"indoorSensorRssi": "Indoor sensor RSSI",
|
||||
"indoorSensorHumidity": "Indoor sensor humidity",
|
||||
"indoorSensorBattery": "Indoor sensor battery",
|
||||
"modulation": "Modulation",
|
||||
"pressure": "Pressure",
|
||||
"dhwFlowRate": "DHW flow rate",
|
||||
@@ -208,7 +217,7 @@
|
||||
},
|
||||
|
||||
"emergency": {
|
||||
"desc": "<b>!</b> Emergency mode can be useful <u>only</u> when using Equitherm and/or PID (when normal work) and when reporting indoor/outdoor temperature via MQTT or API. In this mode, sensor values that are reported via MQTT/API are not used.",
|
||||
"desc": "<b>!</b> Emergency mode can be useful <u>only</u> when using Equitherm and/or PID (when normal work) and when reporting indoor/outdoor temperature via MQTT/API/BLE. In this mode, sensor values that are reported via MQTT/API/BLE are not used.",
|
||||
|
||||
"target": {
|
||||
"title": "Target temperature",
|
||||
@@ -218,12 +227,14 @@
|
||||
|
||||
"events": {
|
||||
"network": "On network fault",
|
||||
"mqtt": "On MQTT fault"
|
||||
"mqtt": "On MQTT fault",
|
||||
"indoorSensorDisconnect": "On loss connection with indoor sensor",
|
||||
"outdoorSensorDisconnect": "On loss connection with outdoor sensor"
|
||||
},
|
||||
|
||||
"regulators": {
|
||||
"equitherm": "Equitherm <small>(requires at least an external/boiler <u>outdoor</u> sensor)</small>",
|
||||
"pid": "PID <small>(requires at least an external/BLE <u>indoor</u> sensor)</small>"
|
||||
"equitherm": "Equitherm <small>(requires at least an external (DS18B20) or boiler <u>outdoor</u> sensor)</small>",
|
||||
"pid": "PID <small>(requires at least an external (DS18B20) <u>indoor</u> sensor)</small>"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
"issues": "Проблемы и вопросы",
|
||||
"releases": "Релизы"
|
||||
},
|
||||
"dbm": "дБм",
|
||||
|
||||
"button": {
|
||||
"upgrade": "Обновить",
|
||||
@@ -87,6 +88,14 @@
|
||||
"fault": "Ошибка",
|
||||
"diag": "Диагностика",
|
||||
"extpump": "Внешний насос",
|
||||
"outdoorSensorConnected": "Датчик наруж. темп.",
|
||||
"outdoorSensorRssi": "RSSI датчика наруж. темп.",
|
||||
"outdoorSensorHumidity": "Влажность с наруж. датчика темп.",
|
||||
"outdoorSensorBattery": "Заряд наруж. датчика темп.",
|
||||
"indoorSensorConnected": "Датчик внутр. темп.",
|
||||
"indoorSensorRssi": "RSSI датчика внутр. темп.",
|
||||
"indoorSensorHumidity": "Влажность с внутр. датчика темп.",
|
||||
"indoorSensorBattery": "Заряд внутр. датчика темп.",
|
||||
"modulation": "Уровень модуляции",
|
||||
"pressure": "Давление",
|
||||
"dhwFlowRate": "Расход ГВС",
|
||||
@@ -208,7 +217,7 @@
|
||||
},
|
||||
|
||||
"emergency": {
|
||||
"desc": "<b>!</b> Аварийный режим может быть полезен <u>только</u> при использовании ПЗА и/или ПИД и при передачи наружной/внутренней температуры через MQTT или API. В этом режиме значения датчиков, передаваемые через MQTT/API, не используются.",
|
||||
"desc": "<b>!</b> Аварийный режим может быть полезен <u>только</u> при использовании ПЗА и/или ПИД и при передачи наружной/внутренней температуры через MQTT/API/BLE. В этом режиме значения датчиков, передаваемые через MQTT/API/BLE, не используются.",
|
||||
|
||||
"target": {
|
||||
"title": "Целевая температура",
|
||||
@@ -218,12 +227,14 @@
|
||||
|
||||
"events": {
|
||||
"network": "При отключении сети",
|
||||
"mqtt": "При отключении MQTT"
|
||||
"mqtt": "При отключении MQTT",
|
||||
"indoorSensorDisconnect": "При потере связи с датчиком внутренней темп.",
|
||||
"outdoorSensorDisconnect": "При потере связи с датчиком наружной темп."
|
||||
},
|
||||
|
||||
"regulators": {
|
||||
"equitherm": "ПЗА <small>(требуется внешний или подключенный к котлу датчик <u>наружной</u> температуры)</small>",
|
||||
"pid": "ПИД <small>(требуется внешний/BLE датчик <u>внутренней</u> температуры)</small>"
|
||||
"equitherm": "ПЗА <small>(требуется внешний (DS18B20) или подключенный к котлу датчик <u>наружной</u> температуры)</small>",
|
||||
"pid": "ПИД <small>(требуется внешний (DS18B20) датчик <u>внутренней</u> температуры)</small>"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -114,6 +114,38 @@
|
||||
<th scope="row" data-i18n>dashboard.state.extpump</th>
|
||||
<td><input type="radio" id="ot-external-pump" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.outdoorSensorConnected</th>
|
||||
<td><input type="radio" id="outdoor-sensor-connected" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.outdoorSensorRssi</th>
|
||||
<td><b id="outdoor-sensor-rssi"></b> <span data-i18n>dbm</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.outdoorSensorHumidity</th>
|
||||
<td><b id="outdoor-sensor-humidity"></b> %</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.outdoorSensorBattery</th>
|
||||
<td><b id="outdoor-sensor-battery"></b> %</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.indoorSensorConnected</th>
|
||||
<td><input type="radio" id="indoor-sensor-connected" aria-invalid="false" checked disabled /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.indoorSensorRssi</th>
|
||||
<td><b id="indoor-sensor-rssi"></b> <span data-i18n>dbm</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.indoorSensorHumidity</th>
|
||||
<td><b id="indoor-sensor-humidity"></b> %</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.indoorSensorBattery</th>
|
||||
<td><b id="indoor-sensor-battery"></b> %</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.modulation</th>
|
||||
<td><b id="ot-modulation"></b> %</td>
|
||||
@@ -377,6 +409,7 @@
|
||||
setValue('#thermostat-dhw-current', result.temperatures.dhw);
|
||||
|
||||
setState('#ot-connected', result.states.otStatus);
|
||||
setState('#mqtt-connected', result.states.mqtt);
|
||||
setState('#ot-emergency', result.states.emergency);
|
||||
setState('#ot-heating', result.states.heating);
|
||||
setState('#ot-dhw', result.states.dhw);
|
||||
@@ -384,7 +417,15 @@
|
||||
setState('#ot-fault', result.states.fault);
|
||||
setState('#ot-diagnostic', result.states.diagnostic);
|
||||
setState('#ot-external-pump', result.states.externalPump);
|
||||
setState('#mqtt-connected', result.states.mqtt);
|
||||
setState('#outdoor-sensor-connected', result.sensors.outdoor.connected);
|
||||
setState('#indoor-sensor-connected', result.sensors.indoor.connected);
|
||||
|
||||
setValue('#outdoor-sensor-rssi', result.sensors.outdoor.rssi);
|
||||
setValue('#outdoor-sensor-humidity', result.sensors.outdoor.humidity);
|
||||
setValue('#outdoor-sensor-battery', result.sensors.outdoor.battery);
|
||||
setValue('#indoor-sensor-rssi', result.sensors.indoor.rssi);
|
||||
setValue('#indoor-sensor-humidity', result.sensors.indoor.humidity);
|
||||
setValue('#indoor-sensor-battery', result.sensors.indoor.battery);
|
||||
|
||||
setValue('#ot-modulation', result.sensors.modulation);
|
||||
setValue('#ot-pressure', result.sensors.pressure);
|
||||
|
||||
@@ -233,6 +233,16 @@
|
||||
<input type="checkbox" id="emergency-on-mqtt-fault" name="emergency[onMqttFault]" value="true">
|
||||
<span data-i18n>settings.emergency.events.mqtt</span>
|
||||
</label>
|
||||
|
||||
<label for="emergency-on-indoor-sensor-disconnect">
|
||||
<input type="checkbox" id="emergency-on-indoor-sensor-disconnect" name="emergency[onIndoorSensorDisconnect]" value="true">
|
||||
<span data-i18n>settings.emergency.events.indoorSensorDisconnect</span>
|
||||
</label>
|
||||
|
||||
<label for="emergency-on-outdoor-sensor-disconnect">
|
||||
<input type="checkbox" id="emergency-on-outdoor-sensor-disconnect" name="emergency[onOutdoorSensorDisconnect]" value="true">
|
||||
<span data-i18n>settings.emergency.events.outdoorSensorDisconnect</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
@@ -544,6 +554,11 @@
|
||||
<input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="2" />
|
||||
<span data-i18n>settings.tempSensor.source.ext</span>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="3" />
|
||||
<span data-i18n>settings.tempSensor.source.ble</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<label for="outdoor-sensor-gpio">
|
||||
@@ -551,10 +566,18 @@
|
||||
<input type="number" inputmode="numeric" id="outdoor-sensor-gpio" name="sensors[outdoor][gpio]" min="0" max="254" step="1">
|
||||
</label>
|
||||
|
||||
<label for="outdoor-sensor-offset">
|
||||
<span data-i18n>settings.tempSensor.offset</span>
|
||||
<input type="number" inputmode="numeric" id="outdoor-sensor-offset" name="sensors[outdoor][offset]" min="-10" max="10" step="0.01" required>
|
||||
</label>
|
||||
<div class="grid">
|
||||
<label for="outdoor-sensor-offset">
|
||||
<span data-i18n>settings.tempSensor.offset</span>
|
||||
<input type="number" inputmode="numeric" id="outdoor-sensor-offset" name="sensors[outdoor][offset]" min="-10" max="10" step="0.01" required>
|
||||
</label>
|
||||
|
||||
<label for="outdoor-sensor-ble-addresss">
|
||||
<span data-i18n>settings.tempSensor.bleAddress.title</span>
|
||||
<input type="text" id="outdoor-sensor-ble-addresss" name="sensors[outdoor][bleAddress]" pattern="([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}">
|
||||
<small data-i18n>settings.tempSensor.bleAddress.note</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" data-i18n>button.save</button>
|
||||
</form>
|
||||
@@ -724,6 +747,7 @@
|
||||
setRadioValue('.outdoor-sensor-type', data.sensors.outdoor.type);
|
||||
setInputValue('#outdoor-sensor-gpio', data.sensors.outdoor.gpio < 255 ? data.sensors.outdoor.gpio : '');
|
||||
setInputValue('#outdoor-sensor-offset', data.sensors.outdoor.offset);
|
||||
setInputValue('#outdoor-sensor-ble-addresss', data.sensors.outdoor.bleAddress);
|
||||
setBusy('#outdoor-sensor-settings-busy', '#outdoor-sensor-settings', false);
|
||||
|
||||
// Indoor sensor
|
||||
@@ -772,6 +796,8 @@
|
||||
setCheckboxValue('#emergency-use-pid', data.emergency.usePid);
|
||||
setCheckboxValue('#emergency-on-network-fault', data.emergency.onNetworkFault);
|
||||
setCheckboxValue('#emergency-on-mqtt-fault', data.emergency.onMqttFault);
|
||||
setCheckboxValue('#emergency-on-indoor-sensor-disconnect', data.emergency.onIndoorSensorDisconnect);
|
||||
setCheckboxValue('#emergency-on-outdoor-sensor-disconnect', data.emergency.onOutdoorSensorDisconnect);
|
||||
setInputValue('#emergency-target', data.emergency.target, {
|
||||
"min": (!data.emergency.useEquitherm && !data.emergency.usePid) ? data.heating.minTemp : 10,
|
||||
"max": (!data.emergency.useEquitherm && !data.emergency.usePid) ? data.heating.maxTemp : 30,
|
||||
|
||||
Reference in New Issue
Block a user