From 7f701a74e7acc9dfa60f100b192452c97fb242dd Mon Sep 17 00:00:00 2001 From: Yurii Date: Fri, 18 Oct 2024 06:14:09 +0300 Subject: [PATCH] feat: fault state gpio setting replaced with cascade control --- src/MainTask.h | 165 ++++++++++++++++++++++++++++++++++ src/OpenThermTask.h | 45 +--------- src/Settings.h | 26 +++++- src/strings.h | 4 +- src/utils.h | 165 ++++++++++++++++++++++++++++------ src_data/locales/en.json | 40 ++++++--- src_data/locales/ru.json | 40 ++++++--- src_data/pages/dashboard.html | 10 +++ src_data/pages/settings.html | 128 +++++++++++++++++++++----- 9 files changed, 506 insertions(+), 117 deletions(-) diff --git a/src/MainTask.h b/src/MainTask.h index 02d572e..62caac1 100644 --- a/src/MainTask.h +++ b/src/MainTask.h @@ -119,6 +119,7 @@ protected: this->emergency(); this->ledStatus(); + this->cascadeControl(); this->externalPump(); this->yield(); @@ -336,6 +337,170 @@ protected: this->blinker->tick(); } + void cascadeControl() { + static uint8_t configuredInputGpio = GPIO_IS_NOT_CONFIGURED; + static uint8_t configuredOutputGpio = GPIO_IS_NOT_CONFIGURED; + static bool inputTempValue = false; + static unsigned long inputChangedTs = 0; + static bool outputTempValue = false; + static unsigned long outputChangedTs = 0; + + // input + if (settings.cascadeControl.input.enable) { + if (settings.cascadeControl.input.gpio != configuredInputGpio) { + if (configuredInputGpio != GPIO_IS_NOT_CONFIGURED) { + pinMode(configuredInputGpio, OUTPUT); + digitalWrite(configuredInputGpio, LOW); + + Log.sinfoln(FPSTR(L_CASCADE_INPUT), F("Deinitialized on GPIO %hhu"), configuredInputGpio); + } + + if (GPIO_IS_VALID(settings.cascadeControl.input.gpio)) { + configuredInputGpio = settings.cascadeControl.input.gpio; + pinMode(configuredInputGpio, INPUT); + + Log.sinfoln(FPSTR(L_CASCADE_INPUT), F("Initialized on GPIO %hhu"), configuredInputGpio); + + } else if (configuredInputGpio != GPIO_IS_NOT_CONFIGURED) { + configuredInputGpio = GPIO_IS_NOT_CONFIGURED; + + Log.swarningln(FPSTR(L_CASCADE_INPUT), F("Failed initialize: GPIO %hhu is not valid!"), configuredInputGpio); + } + } + + if (configuredInputGpio != GPIO_IS_NOT_CONFIGURED) { + bool value; + if (digitalRead(configuredInputGpio) == HIGH) { + value = true ^ settings.cascadeControl.input.invertState; + } else { + value = false ^ settings.cascadeControl.input.invertState; + } + + if (value != vars.cascadeControl.input) { + if (value != inputTempValue) { + inputTempValue = value; + inputChangedTs = millis(); + + } else if (millis() - inputChangedTs >= settings.cascadeControl.input.thresholdTime * 1000u) { + vars.cascadeControl.input = value; + + Log.sinfoln( + FPSTR(L_CASCADE_INPUT), + F("State changed to %s"), + value ? F("TRUE") : F("FALSE") + ); + } + + } else if (value != inputTempValue) { + inputTempValue = value; + } + } + } + + if (!settings.cascadeControl.input.enable || configuredInputGpio == GPIO_IS_NOT_CONFIGURED) { + if (!vars.cascadeControl.input) { + vars.cascadeControl.input = true; + + Log.sinfoln( + FPSTR(L_CASCADE_INPUT), + F("Disabled, state changed to %s"), + vars.cascadeControl.input ? F("TRUE") : F("FALSE") + ); + } + } + + + // output + if (settings.cascadeControl.output.enable) { + if (settings.cascadeControl.output.gpio != configuredOutputGpio) { + if (configuredOutputGpio != GPIO_IS_NOT_CONFIGURED) { + pinMode(configuredOutputGpio, OUTPUT); + digitalWrite(configuredOutputGpio, LOW); + + Log.sinfoln(FPSTR(L_CASCADE_OUTPUT), F("Deinitialized on GPIO %hhu"), configuredOutputGpio); + } + + if (GPIO_IS_VALID(settings.cascadeControl.output.gpio)) { + configuredOutputGpio = settings.cascadeControl.output.gpio; + pinMode(configuredOutputGpio, OUTPUT); + digitalWrite( + configuredOutputGpio, + settings.cascadeControl.output.invertState + ? HIGH + : LOW + ); + + Log.sinfoln(FPSTR(L_CASCADE_OUTPUT), F("Initialized on GPIO %hhu"), configuredOutputGpio); + + } else if (configuredOutputGpio != GPIO_IS_NOT_CONFIGURED) { + configuredOutputGpio = GPIO_IS_NOT_CONFIGURED; + + Log.swarningln(FPSTR(L_CASCADE_OUTPUT), F("Failed initialize: GPIO %hhu is not valid!"), configuredOutputGpio); + } + } + + if (configuredOutputGpio != GPIO_IS_NOT_CONFIGURED) { + bool value = false; + if (settings.cascadeControl.output.onFault && vars.states.fault) { + value = true; + + } else if (settings.cascadeControl.output.onLossConnection && !vars.states.otStatus) { + value = true; + + } else if (settings.cascadeControl.output.onEnabledHeating && settings.heating.enable && vars.cascadeControl.input) { + value = true; + } + + if (value != vars.cascadeControl.output) { + if (value != outputTempValue) { + outputTempValue = value; + outputChangedTs = millis(); + + } else if (millis() - outputChangedTs >= settings.cascadeControl.output.thresholdTime * 1000u) { + vars.cascadeControl.output = value; + + digitalWrite( + configuredOutputGpio, + vars.cascadeControl.output ^ settings.cascadeControl.output.invertState + ? HIGH + : LOW + ); + + Log.sinfoln( + FPSTR(L_CASCADE_OUTPUT), + F("State changed to %s"), + value ? F("TRUE") : F("FALSE") + ); + } + + } else if (value != outputTempValue) { + outputTempValue = value; + } + } + } + + if (!settings.cascadeControl.output.enable || configuredOutputGpio == GPIO_IS_NOT_CONFIGURED) { + if (vars.cascadeControl.output) { + vars.cascadeControl.output = false; + + if (configuredOutputGpio != GPIO_IS_NOT_CONFIGURED) { + digitalWrite( + configuredOutputGpio, + vars.cascadeControl.output ^ settings.cascadeControl.output.invertState + ? HIGH + : LOW + ); + } + + Log.sinfoln( + FPSTR(L_CASCADE_OUTPUT), + F("Disabled, state changed to %s"), + vars.cascadeControl.output ? F("TRUE") : F("FALSE") + ); + } + } + } + void externalPump() { static uint8_t configuredGpio = GPIO_IS_NOT_CONFIGURED; diff --git a/src/OpenThermTask.h b/src/OpenThermTask.h index a42cfa2..5a3e8cf 100644 --- a/src/OpenThermTask.h +++ b/src/OpenThermTask.h @@ -28,8 +28,6 @@ protected: unsigned long dhwSetTempTime = 0; unsigned long heatingSetTempTime = 0; byte configuredRxLedGpio = GPIO_IS_NOT_CONFIGURED; - byte configuredFaultStateGpio = GPIO_IS_NOT_CONFIGURED; - bool faultState = false; #if defined(ARDUINO_ARCH_ESP32) const char* getTaskName() override { @@ -133,28 +131,7 @@ protected: } } - // Fault state setup - if (settings.opentherm.faultStateGpio != this->configuredFaultStateGpio) { - if (this->configuredFaultStateGpio != GPIO_IS_NOT_CONFIGURED) { - digitalWrite(this->configuredFaultStateGpio, LOW); - } - - if (GPIO_IS_VALID(settings.opentherm.faultStateGpio)) { - this->configuredFaultStateGpio = settings.opentherm.faultStateGpio; - this->faultState = false ^ settings.opentherm.invertFaultState ? HIGH : LOW; - - pinMode(this->configuredFaultStateGpio, OUTPUT); - digitalWrite( - this->configuredFaultStateGpio, - this->faultState - ); - - } else if (this->configuredFaultStateGpio != GPIO_IS_NOT_CONFIGURED) { - this->configuredFaultStateGpio = GPIO_IS_NOT_CONFIGURED; - } - } - - bool heatingEnabled = (vars.states.emergency || settings.heating.enable) && this->pump && this->isReady(); + bool heatingEnabled = (vars.states.emergency || settings.heating.enable) && this->pump && vars.cascadeControl.input && this->isReady(); bool heatingCh2Enabled = settings.opentherm.heatingCh2Enabled; if (settings.opentherm.heatingCh1ToCh2) { heatingCh2Enabled = heatingEnabled; @@ -212,16 +189,6 @@ protected: vars.states.fault = false; vars.states.diagnostic = false; - // Force fault state = on - if (this->configuredFaultStateGpio != GPIO_IS_NOT_CONFIGURED) { - bool fState = true ^ settings.opentherm.invertFaultState ? HIGH : LOW; - - if (fState != this->faultState) { - this->faultState = fState; - digitalWrite(this->configuredFaultStateGpio, this->faultState); - } - } - return; } @@ -251,16 +218,6 @@ protected: vars.states.heating, vars.states.dhw, vars.states.flame, vars.states.fault, vars.states.diagnostic ); - // Fault state - if (this->configuredFaultStateGpio != GPIO_IS_NOT_CONFIGURED) { - bool fState = vars.states.fault ^ settings.opentherm.invertFaultState ? HIGH : LOW; - - if (fState != this->faultState) { - this->faultState = fState; - digitalWrite(this->configuredFaultStateGpio, this->faultState); - } - } - // These parameters will be updated every minute if (millis() - this->prevUpdateNonEssentialVars > 60000) { if (this->updateMinModulationLevel()) { diff --git a/src/Settings.h b/src/Settings.h index ebc9515..6ca872e 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -51,8 +51,6 @@ struct Settings { byte inGpio = DEFAULT_OT_IN_GPIO; byte outGpio = DEFAULT_OT_OUT_GPIO; byte rxLedGpio = DEFAULT_OT_RX_LED_GPIO; - byte faultStateGpio = DEFAULT_OT_FAULT_STATE_GPIO; - byte invertFaultState = false; unsigned int memberIdCode = 0; uint8_t maxModulation = 100; float pressureFactor = 1.0f; @@ -156,6 +154,25 @@ struct Settings { unsigned short antiStuckTime = 300; } externalPump; + struct { + struct { + bool enable = false; + byte gpio = GPIO_IS_NOT_CONFIGURED; + byte invertState = false; + unsigned short thresholdTime = 60; + } input; + + struct { + bool enable = false; + byte gpio = GPIO_IS_NOT_CONFIGURED; + byte invertState = false; + unsigned short thresholdTime = 60; + bool onFault = true; + bool onLossConnection = true; + bool onEnabledHeating = false; + } output; + } cascadeControl; + char validationValue[8] = SETTINGS_VALID_VALUE; } settings; @@ -205,6 +222,11 @@ struct Variables { float exhaust = 0.0f; } temperatures; + struct { + bool input = false; + bool output = false; + } cascadeControl; + struct { bool heatingEnabled = false; byte heatingMinTemp = DEFAULT_HEATING_MIN_TEMP; diff --git a/src/strings.h b/src/strings.h index e688878..ba5e06b 100644 --- a/src/strings.h +++ b/src/strings.h @@ -24,4 +24,6 @@ const char L_SENSORS_INDOOR[] PROGMEM = "SENSORS.INDOOR"; const char L_SENSORS_BLE[] PROGMEM = "SENSORS.BLE"; const char L_REGULATOR[] PROGMEM = "REGULATOR"; const char L_REGULATOR_PID[] PROGMEM = "REGULATOR.PID"; -const char L_REGULATOR_EQUITHERM[] PROGMEM = "REGULATOR.EQUITHERM"; \ No newline at end of file +const char L_REGULATOR_EQUITHERM[] PROGMEM = "REGULATOR.EQUITHERM"; +const char L_CASCADE_INPUT[] PROGMEM = "CASCADE.INPUT"; +const char L_CASCADE_OUTPUT[] PROGMEM = "CASCADE.OUTPUT"; \ No newline at end of file diff --git a/src/utils.h b/src/utils.h index fcb56f3..89421ad 100644 --- a/src/utils.h +++ b/src/utils.h @@ -342,8 +342,6 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) { dst["opentherm"]["inGpio"] = src.opentherm.inGpio; dst["opentherm"]["outGpio"] = src.opentherm.outGpio; dst["opentherm"]["rxLedGpio"] = src.opentherm.rxLedGpio; - dst["opentherm"]["faultStateGpio"] = src.opentherm.faultStateGpio; - dst["opentherm"]["invertFaultState"] = src.opentherm.invertFaultState; dst["opentherm"]["memberIdCode"] = src.opentherm.memberIdCode; dst["opentherm"]["maxModulation"] = src.opentherm.maxModulation; dst["opentherm"]["pressureFactor"] = roundd(src.opentherm.pressureFactor, 2); @@ -447,6 +445,19 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) { dst["externalPump"]["postCirculationTime"] = roundd(src.externalPump.postCirculationTime / 60, 0); dst["externalPump"]["antiStuckInterval"] = roundd(src.externalPump.antiStuckInterval / 86400, 0); dst["externalPump"]["antiStuckTime"] = roundd(src.externalPump.antiStuckTime / 60, 0); + + dst["cascadeControl"]["input"]["enable"] = src.cascadeControl.input.enable; + dst["cascadeControl"]["input"]["gpio"] = src.cascadeControl.input.gpio; + dst["cascadeControl"]["input"]["invertState"] = src.cascadeControl.input.invertState; + dst["cascadeControl"]["input"]["thresholdTime"] = src.cascadeControl.input.thresholdTime; + + dst["cascadeControl"]["output"]["enable"] = src.cascadeControl.output.enable; + dst["cascadeControl"]["output"]["gpio"] = src.cascadeControl.output.gpio; + dst["cascadeControl"]["output"]["invertState"] = src.cascadeControl.output.invertState; + dst["cascadeControl"]["output"]["thresholdTime"] = src.cascadeControl.output.thresholdTime; + dst["cascadeControl"]["output"]["onFault"] = src.cascadeControl.output.onFault; + dst["cascadeControl"]["output"]["onLossConnection"] = src.cascadeControl.output.onLossConnection; + dst["cascadeControl"]["output"]["onEnabledHeating"] = src.cascadeControl.output.onEnabledHeating; } } @@ -665,32 +676,6 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["opentherm"]["faultStateGpio"].isNull()) { - if (src["opentherm"]["faultStateGpio"].is() && src["opentherm"]["faultStateGpio"].as().size() == 0) { - if (dst.opentherm.faultStateGpio != GPIO_IS_NOT_CONFIGURED) { - dst.opentherm.faultStateGpio = GPIO_IS_NOT_CONFIGURED; - changed = true; - } - - } else { - unsigned char value = src["opentherm"]["faultStateGpio"].as(); - - if (GPIO_IS_VALID(value) && value != dst.opentherm.faultStateGpio) { - dst.opentherm.faultStateGpio = value; - changed = true; - } - } - } - - if (src["opentherm"]["invertFaultState"].is()) { - bool value = src["opentherm"]["invertFaultState"].as(); - - if (value != dst.opentherm.invertFaultState) { - dst.opentherm.invertFaultState = value; - changed = true; - } - } - if (!src["opentherm"]["memberIdCode"].isNull()) { unsigned int value = src["opentherm"]["memberIdCode"].as(); @@ -1470,6 +1455,127 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } } + + + // cascade control + if (src["cascadeControl"]["input"]["enable"].is()) { + bool value = src["cascadeControl"]["input"]["enable"].as(); + + if (value != dst.cascadeControl.input.enable) { + dst.cascadeControl.input.enable = value; + changed = true; + } + } + + if (!src["cascadeControl"]["input"]["gpio"].isNull()) { + if (src["cascadeControl"]["input"]["gpio"].is() && src["cascadeControl"]["input"]["gpio"].as().size() == 0) { + if (dst.cascadeControl.input.gpio != GPIO_IS_NOT_CONFIGURED) { + dst.cascadeControl.input.gpio = GPIO_IS_NOT_CONFIGURED; + changed = true; + } + + } else { + unsigned char value = src["cascadeControl"]["input"]["gpio"].as(); + + if (GPIO_IS_VALID(value) && value != dst.cascadeControl.input.gpio) { + dst.cascadeControl.input.gpio = value; + changed = true; + } + } + } + + if (src["cascadeControl"]["input"]["invertState"].is()) { + bool value = src["cascadeControl"]["input"]["invertState"].as(); + + if (value != dst.cascadeControl.input.invertState) { + dst.cascadeControl.input.invertState = value; + changed = true; + } + } + + if (!src["cascadeControl"]["input"]["thresholdTime"].isNull()) { + unsigned short value = src["cascadeControl"]["input"]["thresholdTime"].as(); + + if (value >= 5 && value <= 600) { + if (value != dst.cascadeControl.input.thresholdTime) { + dst.cascadeControl.input.thresholdTime = value; + changed = true; + } + } + } + + if (src["cascadeControl"]["output"]["enable"].is()) { + bool value = src["cascadeControl"]["output"]["enable"].as(); + + if (value != dst.cascadeControl.output.enable) { + dst.cascadeControl.output.enable = value; + changed = true; + } + } + + if (!src["cascadeControl"]["output"]["gpio"].isNull()) { + if (src["cascadeControl"]["output"]["gpio"].is() && src["cascadeControl"]["output"]["gpio"].as().size() == 0) { + if (dst.cascadeControl.output.gpio != GPIO_IS_NOT_CONFIGURED) { + dst.cascadeControl.output.gpio = GPIO_IS_NOT_CONFIGURED; + changed = true; + } + + } else { + unsigned char value = src["cascadeControl"]["output"]["gpio"].as(); + + if (GPIO_IS_VALID(value) && value != dst.cascadeControl.output.gpio) { + dst.cascadeControl.output.gpio = value; + changed = true; + } + } + } + + if (src["cascadeControl"]["output"]["invertState"].is()) { + bool value = src["cascadeControl"]["output"]["invertState"].as(); + + if (value != dst.cascadeControl.output.invertState) { + dst.cascadeControl.output.invertState = value; + changed = true; + } + } + + if (!src["cascadeControl"]["output"]["thresholdTime"].isNull()) { + unsigned short value = src["cascadeControl"]["output"]["thresholdTime"].as(); + + if (value >= 5 && value <= 600) { + if (value != dst.cascadeControl.output.thresholdTime) { + dst.cascadeControl.output.thresholdTime = value; + changed = true; + } + } + } + + if (src["cascadeControl"]["output"]["onFault"].is()) { + bool value = src["cascadeControl"]["output"]["onFault"].as(); + + if (value != dst.cascadeControl.output.onFault) { + dst.cascadeControl.output.onFault = value; + changed = true; + } + } + + if (src["cascadeControl"]["output"]["onLossConnection"].is()) { + bool value = src["cascadeControl"]["output"]["onLossConnection"].as(); + + if (value != dst.cascadeControl.output.onLossConnection) { + dst.cascadeControl.output.onLossConnection = value; + changed = true; + } + } + + if (src["cascadeControl"]["output"]["onEnabledHeating"].is()) { + bool value = src["cascadeControl"]["output"]["onEnabledHeating"].as(); + + if (value != dst.cascadeControl.output.onEnabledHeating) { + dst.cascadeControl.output.onEnabledHeating = value; + changed = true; + } + } } // force check emergency target @@ -1587,6 +1693,9 @@ void varsToJson(const Variables& src, JsonVariant dst) { dst["temperatures"]["dhw"] = roundd(src.temperatures.dhw, 2); dst["temperatures"]["exhaust"] = roundd(src.temperatures.exhaust, 2); + dst["cascadeControl"]["input"] = src.cascadeControl.input; + dst["cascadeControl"]["output"] = src.cascadeControl.output; + dst["parameters"]["heatingEnabled"] = src.parameters.heatingEnabled; dst["parameters"]["heatingMinTemp"] = src.parameters.heatingMinTemp; dst["parameters"]["heatingMaxTemp"] = src.parameters.heatingMaxTemp; diff --git a/src_data/locales/en.json b/src_data/locales/en.json index 30dc17e..39f7adf 100644 --- a/src_data/locales/en.json +++ b/src_data/locales/en.json @@ -94,6 +94,8 @@ "outdoorSensorHumidity": "Outdoor sensor humidity", "outdoorSensorBattery": "Outdoor sensor battery", "indoorSensorConnected": "Indoor sensor connected", + "cascadeControlInput": "Cascade control (input)", + "cascadeControlOutput": "Cascade control (output)", "indoorSensorRssi": "Indoor sensor RSSI", "indoorSensorHumidity": "Indoor sensor humidity", "indoorSensorBattery": "Indoor sensor battery", @@ -163,16 +165,14 @@ "heating": "Heating settings", "dhw": "DHW settings", "emergency": "Emergency mode settings", - "emergency.events": "Events", - "emergency.regulators": "Using regulators", "equitherm": "Equitherm settings", "pid": "PID settings", "ot": "OpenTherm settings", - "ot.options": "Options", "mqtt": "MQTT settings", "outdorSensor": "Outdoor sensor settings", "indoorSensor": "Indoor sensor settings", - "extPump": "External pump settings" + "extPump": "External pump settings", + "cascadeControl": "Cascade control settings" }, "enable": "Enable", @@ -226,6 +226,7 @@ "treshold": "Treshold time (sec)", "events": { + "desc": "Events", "network": "On network fault", "mqtt": "On MQTT fault", "indoorSensorDisconnect": "On loss connection with indoor sensor", @@ -233,6 +234,7 @@ }, "regulators": { + "desc": "Using regulators", "equitherm": "Equitherm (requires at least an external (DS18B20) or boiler outdoor sensor)", "pid": "PID (requires at least an external (DS18B20) indoor sensor)" } @@ -290,6 +292,7 @@ }, "options": { + "desc": "Options", "dhwPresent": "DHW present", "summerWinterMode": "Summer/winter mode", "heatingCh2Enabled": "Heating CH2 always enabled", @@ -301,12 +304,6 @@ "immergasFix": "Fix for Immergas boilers" }, - "faultState": { - "gpio": "Fault state GPIO", - "note": "Can be useful to switch on another boiler via relay. Blank - not use.", - "invert": "Invert fault state" - }, - "nativeHeating": { "title": "Native heating control (boiler)", "note": "Works ONLY if the boiler requires the desired room temperature and regulates the temperature of the coolant itself. Not compatible with PID and Equitherm regulators and hysteresis in firmware." @@ -342,6 +339,29 @@ "postCirculationTime": "Post circulation time (min)", "antiStuckInterval": "Anti stuck interval (days)", "antiStuckTime": "Anti stuck time (min)" + }, + + "cascadeControl": { + "input": { + "desc": "Can be used to turn on the heating only if another boiler is faulty. The other boiler controller must change the state of the GPIO input in the event of a fault.", + "enable": "Enable input", + "gpio": "GPIO", + "invertState": "Invert GPIO state", + "thresholdTime": "State change threshold time (sec)" + }, + "output": { + "desc": "Can be used to switch on another boiler via relay.", + "enable": "Enable output", + "gpio": "GPIO", + "invertState": "Invert GPIO state", + "thresholdTime": "State change threshold time (sec)", + "events": { + "title": "Events", + "onFault": "If the fault state is active", + "onLossConnection": "If the connection via Opentherm is lost", + "onEnabledHeating": "If heating is enabled" + } + } } }, diff --git a/src_data/locales/ru.json b/src_data/locales/ru.json index 39faa98..09e9621 100644 --- a/src_data/locales/ru.json +++ b/src_data/locales/ru.json @@ -94,6 +94,8 @@ "outdoorSensorHumidity": "Влажность с наруж. датчика темп.", "outdoorSensorBattery": "Заряд наруж. датчика темп.", "indoorSensorConnected": "Датчик внутр. темп.", + "cascadeControlInput": "Каскадное управление (вход)", + "cascadeControlOutput": "Каскадное управление (выход)", "indoorSensorRssi": "RSSI датчика внутр. темп.", "indoorSensorHumidity": "Влажность с внутр. датчика темп.", "indoorSensorBattery": "Заряд внутр. датчика темп.", @@ -163,16 +165,14 @@ "heating": "Настройки отопления", "dhw": "Настройки ГВС", "emergency": "Настройки аварийного режима", - "emergency.events": "События", - "emergency.regulators": "Используемые регуляторы", "equitherm": "Настройки ПЗА", "pid": "Настройки ПИД", "ot": "Настройки OpenTherm", - "ot.options": "Опции", "mqtt": "Настройки MQTT", "outdorSensor": "Настройки наружного датчика температуры", "indoorSensor": "Настройки внутреннего датчика температуры", - "extPump": "Настройки дополнительного насоса" + "extPump": "Настройки дополнительного насоса", + "cascadeControl": "Настройки каскадного управления" }, "enable": "Вкл", @@ -226,6 +226,7 @@ "treshold": "Пороговое время включения (сек)", "events": { + "desc": "События", "network": "При отключении сети", "mqtt": "При отключении MQTT", "indoorSensorDisconnect": "При потере связи с датчиком внутренней темп.", @@ -233,6 +234,7 @@ }, "regulators": { + "desc": "Используемые регуляторы", "equitherm": "ПЗА (требуется внешний (DS18B20) или подключенный к котлу датчик наружной температуры)", "pid": "ПИД (требуется внешний (DS18B20) датчик внутренней температуры)" } @@ -290,6 +292,7 @@ }, "options": { + "desc": "Опции", "dhwPresent": "Контур ГВС", "summerWinterMode": "Летний/зимний режим", "heatingCh2Enabled": "Канал 2 отопления всегда вкл.", @@ -301,12 +304,6 @@ "immergasFix": "Фикс для котлов Immergas" }, - "faultState": { - "gpio": "Fault state GPIO", - "note": "Can be useful to switch on another boiler via relay. Blank - not use.", - "invert": "Invert fault state" - }, - "nativeHeating": { "title": "Передать управление отоплением котлу", "note": "Работает ТОЛЬКО если котел требует и принимает целевую температуру в помещении и сам регулирует температуру теплоносителя на основе встроенного режима кривых. Несовместимо с ПИД, ПЗА и гистерезисом." @@ -342,6 +339,29 @@ "postCirculationTime": "Время постциркуляции (в минутах)", "antiStuckInterval": "Интервал защиты от блокировки (в днях)", "antiStuckTime": "Время работы насоса (в минутах)" + }, + + "cascadeControl": { + "input": { + "desc": "Может использоваться для включения отопления только при неисправности другого котла. Контроллер другого котла должен изменить состояние входа GPIO в случае неисправности.", + "enable": "Включить вход", + "gpio": "GPIO", + "invertState": "Инвертировать состояние GPIO", + "thresholdTime": "Пороговое время изменения состояния (сек)" + }, + "output": { + "desc": "Может использоваться для включения другого котла через реле.", + "enable": "Включить выход", + "gpio": "GPIO", + "invertState": "Инвертировать состояние GPIO", + "thresholdTime": "Пороговое время изменения состояния (сек)", + "events": { + "title": "События", + "onFault": "Если состояние fault (ошибки) активно", + "onLossConnection": "Если соединение по OpenTherm потеряно", + "onEnabledHeating": "Если отопление включено" + } + } } }, diff --git a/src_data/pages/dashboard.html b/src_data/pages/dashboard.html index 1ef633e..554327e 100644 --- a/src_data/pages/dashboard.html +++ b/src_data/pages/dashboard.html @@ -134,6 +134,14 @@ dashboard.state.indoorSensorConnected + + dashboard.state.cascadeControlInput + + + + dashboard.state.cascadeControlOutput + + dashboard.state.indoorSensorRssi dbm @@ -427,6 +435,8 @@ setState('#ot-external-pump', result.states.externalPump); setState('#outdoor-sensor-connected', result.sensors.outdoor.connected); setState('#indoor-sensor-connected', result.sensors.indoor.connected); + setState('#cc-input', result.cascadeControl.input); + setState('#cc-output', result.cascadeControl.output); setValue('#outdoor-sensor-rssi', result.sensors.outdoor.rssi); setValue('#outdoor-sensor-humidity', result.sensors.outdoor.humidity); diff --git a/src_data/pages/settings.html b/src_data/pages/settings.html index 2fecf74..35a8c16 100644 --- a/src_data/pages/settings.html +++ b/src_data/pages/settings.html @@ -230,7 +230,7 @@
- settings.section.emergency.events + settings.emergency.events.title
- settings.section.emergency.regulators + settings.emergency.regulators.title