feat: fault state gpio setting replaced with cascade control

This commit is contained in:
Yurii
2024-10-18 06:14:09 +03:00
parent 57cf98ca19
commit 7f701a74e7
9 changed files with 506 additions and 117 deletions

View File

@@ -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;

View File

@@ -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()) {

View File

@@ -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;

View File

@@ -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";
const char L_REGULATOR_EQUITHERM[] PROGMEM = "REGULATOR.EQUITHERM";
const char L_CASCADE_INPUT[] PROGMEM = "CASCADE.INPUT";
const char L_CASCADE_OUTPUT[] PROGMEM = "CASCADE.OUTPUT";

View File

@@ -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<JsonString>() && src["opentherm"]["faultStateGpio"].as<JsonString>().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<unsigned char>();
if (GPIO_IS_VALID(value) && value != dst.opentherm.faultStateGpio) {
dst.opentherm.faultStateGpio = value;
changed = true;
}
}
}
if (src["opentherm"]["invertFaultState"].is<bool>()) {
bool value = src["opentherm"]["invertFaultState"].as<bool>();
if (value != dst.opentherm.invertFaultState) {
dst.opentherm.invertFaultState = value;
changed = true;
}
}
if (!src["opentherm"]["memberIdCode"].isNull()) {
unsigned int value = src["opentherm"]["memberIdCode"].as<unsigned int>();
@@ -1470,6 +1455,127 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
}
}
}
// cascade control
if (src["cascadeControl"]["input"]["enable"].is<bool>()) {
bool value = src["cascadeControl"]["input"]["enable"].as<bool>();
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<JsonString>() && src["cascadeControl"]["input"]["gpio"].as<JsonString>().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<unsigned char>();
if (GPIO_IS_VALID(value) && value != dst.cascadeControl.input.gpio) {
dst.cascadeControl.input.gpio = value;
changed = true;
}
}
}
if (src["cascadeControl"]["input"]["invertState"].is<bool>()) {
bool value = src["cascadeControl"]["input"]["invertState"].as<bool>();
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<unsigned short>();
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>()) {
bool value = src["cascadeControl"]["output"]["enable"].as<bool>();
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<JsonString>() && src["cascadeControl"]["output"]["gpio"].as<JsonString>().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<unsigned char>();
if (GPIO_IS_VALID(value) && value != dst.cascadeControl.output.gpio) {
dst.cascadeControl.output.gpio = value;
changed = true;
}
}
}
if (src["cascadeControl"]["output"]["invertState"].is<bool>()) {
bool value = src["cascadeControl"]["output"]["invertState"].as<bool>();
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<unsigned short>();
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>()) {
bool value = src["cascadeControl"]["output"]["onFault"].as<bool>();
if (value != dst.cascadeControl.output.onFault) {
dst.cascadeControl.output.onFault = value;
changed = true;
}
}
if (src["cascadeControl"]["output"]["onLossConnection"].is<bool>()) {
bool value = src["cascadeControl"]["output"]["onLossConnection"].as<bool>();
if (value != dst.cascadeControl.output.onLossConnection) {
dst.cascadeControl.output.onLossConnection = value;
changed = true;
}
}
if (src["cascadeControl"]["output"]["onEnabledHeating"].is<bool>()) {
bool value = src["cascadeControl"]["output"]["onEnabledHeating"].as<bool>();
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;

View File

@@ -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 <small>(sec)</small>",
"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 <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>"
}
@@ -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 <u>via relay</u>. Blank - not use.",
"invert": "Invert fault state"
},
"nativeHeating": {
"title": "Native heating control (boiler)",
"note": "Works <u>ONLY</u> 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 <small>(min)</small>",
"antiStuckInterval": "Anti stuck interval <small>(days)</small>",
"antiStuckTime": "Anti stuck time <small>(min)</small>"
},
"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 <small>(sec)</small>"
},
"output": {
"desc": "Can be used to switch on another boiler <u>via relay</u>.",
"enable": "Enable output",
"gpio": "GPIO",
"invertState": "Invert GPIO state",
"thresholdTime": "State change threshold time <small>(sec)</small>",
"events": {
"title": "Events",
"onFault": "If the fault state is active",
"onLossConnection": "If the connection via Opentherm is lost",
"onEnabledHeating": "If heating is enabled"
}
}
}
},

View File

@@ -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": "Пороговое время включения <small>(сек)</small>",
"events": {
"desc": "События",
"network": "При отключении сети",
"mqtt": "При отключении MQTT",
"indoorSensorDisconnect": "При потере связи с датчиком внутренней темп.",
@@ -233,6 +234,7 @@
},
"regulators": {
"desc": "Используемые регуляторы",
"equitherm": "ПЗА <small>(требуется внешний (DS18B20) или подключенный к котлу датчик <u>наружной</u> температуры)</small>",
"pid": "ПИД <small>(требуется внешний (DS18B20) датчик <u>внутренней</u> температуры)</small>"
}
@@ -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 <u>via relay</u>. Blank - not use.",
"invert": "Invert fault state"
},
"nativeHeating": {
"title": "Передать управление отоплением котлу",
"note": "Работает <u>ТОЛЬКО</u> если котел требует и принимает целевую температуру в помещении и сам регулирует температуру теплоносителя на основе встроенного режима кривых. Несовместимо с ПИД, ПЗА и гистерезисом."
@@ -342,6 +339,29 @@
"postCirculationTime": "Время постциркуляции <small>(в минутах)</small>",
"antiStuckInterval": "Интервал защиты от блокировки <small>(в днях)</small>",
"antiStuckTime": "Время работы насоса <small>(в минутах)</small>"
},
"cascadeControl": {
"input": {
"desc": "Может использоваться для включения отопления только при неисправности другого котла. Контроллер другого котла должен изменить состояние входа GPIO в случае неисправности.",
"enable": "Включить вход",
"gpio": "GPIO",
"invertState": "Инвертировать состояние GPIO",
"thresholdTime": "Пороговое время изменения состояния <small>(сек)</small>"
},
"output": {
"desc": "Может использоваться для включения другого котла <u>через реле</u>.",
"enable": "Включить выход",
"gpio": "GPIO",
"invertState": "Инвертировать состояние GPIO",
"thresholdTime": "Пороговое время изменения состояния <small>(сек)</small>",
"events": {
"title": "События",
"onFault": "Если состояние fault (ошибки) активно",
"onLossConnection": "Если соединение по OpenTherm потеряно",
"onEnabledHeating": "Если отопление включено"
}
}
}
},

View File

@@ -134,6 +134,14 @@
<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.cascadeControlInput</th>
<td><input type="radio" id="cc-input" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.state.cascadeControlOutput</th>
<td><input type="radio" id="cc-output" 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>
@@ -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);

View File

@@ -230,7 +230,7 @@
</div>
<fieldset>
<legend data-i18n>settings.section.emergency.events</legend>
<legend data-i18n>settings.emergency.events.title</legend>
<label for="emergency-on-network-fault">
<input type="checkbox" id="emergency-on-network-fault" name="emergency[onNetworkFault]" value="true">
@@ -254,7 +254,7 @@
</fieldset>
<fieldset>
<legend data-i18n>settings.section.emergency.regulators</legend>
<legend data-i18n>settings.emergency.regulators.title</legend>
<label for="emergency-use-equitherm">
<input type="checkbox" id="emergency-use-equitherm" name="emergency[useEquitherm]" value="true">
@@ -429,7 +429,8 @@
</div>
<fieldset>
<legend data-i18n>settings.section.ot.options</legend>
<legend data-i18n>settings.ot.options.title</legend>
<label for="opentherm-dhw-present">
<input type="checkbox" id="opentherm-dhw-present" name="opentherm[dhwPresent]" value="true">
<span data-i18n>settings.ot.options.dhwPresent</span>
@@ -503,9 +504,7 @@
<hr />
<fieldset>
<legend>
<span data-i18n>settings.ot.fnv.title</span>
</legend>
<legend data-i18n>settings.ot.fnv.title</legend>
<label for="opentherm-fnv-enable">
<input type="checkbox" id="opentherm-fnv-enable" name="opentherm[filterNumValues][enable]" value="true">
@@ -520,20 +519,6 @@
<small data-i18n>settings.ot.fnv.factor.note</small>
</label>
</fieldset>
<hr />
<fieldset>
<label for="opentherm-fault-state-gpio">
<span data-i18n>settings.ot.faultState.gpio</span>
<input type="number" inputmode="numeric" id="opentherm-fault-state-gpio" name="opentherm[faultStateGpio]" min="0" max="254" step="1">
<small data-i18n>settings.ot.faultState.note</small>
</label>
<label for="opentherm-invert-fault-state">
<input type="checkbox" id="opentherm-invert-fault-state" name="opentherm[invertFaultState]" value="true">
<span data-i18n>settings.ot.faultState.invert</span>
</label>
</fieldset>
</div>
</details>
@@ -753,6 +738,91 @@
</form>
</div>
</details>
<hr />
<details>
<summary><b data-i18n>settings.section.cascadeControl</b></summary>
<div>
<div id="cc-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="cc-settings" class="hidden">
<fieldset>
<label for="cc-input-enable">
<input type="checkbox" id="cc-input-enable" name="cascadeControl[input][enable]" value="true">
<span data-i18n>settings.cascadeControl.input.enable</span>
<br>
<small data-i18n>settings.cascadeControl.input.desc</small>
</label>
<label for="cc-input-invert-state">
<input type="checkbox" id="cc-input-invert-state" name="cascadeControl[input][invertState]" value="true">
<span data-i18n>settings.cascadeControl.input.invertState</span>
</label>
</fieldset>
<div class="grid">
<label for="cc-input-gpio">
<span data-i18n>settings.cascadeControl.input.gpio</span>
<input type="number" inputmode="numeric" id="cc-input-gpio" name="cascadeControl[input][gpio]" min="0" max="254" step="1">
</label>
<label for="cc-input-tt">
<span data-i18n>settings.cascadeControl.input.thresholdTime</span>
<input type="number" inputmode="numeric" id="cc-input-tt" name="cascadeControl[input][thresholdTime]" min="5" max="600" step="1" required>
</label>
</div>
<hr />
<fieldset>
<label for="cc-output-enable">
<input type="checkbox" id="cc-output-enable" name="cascadeControl[output][enable]" value="true">
<span data-i18n>settings.cascadeControl.output.enable</span>
<br>
<small data-i18n>settings.cascadeControl.output.desc</small>
</label>
<label for="cc-output-invert-state">
<input type="checkbox" id="cc-output-invert-state" name="cascadeControl[output][invertState]" value="true">
<span data-i18n>settings.cascadeControl.output.invertState</span>
</label>
</fieldset>
<div class="grid">
<label for="cc-output-gpio">
<span data-i18n>settings.cascadeControl.output.gpio</span>
<input type="number" outputmode="numeric" id="cc-output-gpio" name="cascadeControl[output][gpio]" min="0" max="254" step="1">
</label>
<label for="cc-output-tt">
<span data-i18n>settings.cascadeControl.output.thresholdTime</span>
<input type="number" outputmode="numeric" id="cc-output-tt" name="cascadeControl[output][thresholdTime]" min="5" max="600" step="1" required>
</label>
</div>
<fieldset>
<legend data-i18n>settings.cascadeControl.output.events.title</legend>
<label for="cc-on-fault">
<input type="checkbox" id="cc-on-fault" name="cascadeControl[output][onFault]" value="true">
<span data-i18n>settings.cascadeControl.output.events.onFault</span>
</label>
<label for="cc-on-loss-conn">
<input type="checkbox" id="cc-on-loss-conn" name="cascadeControl[output][onLossConnection]" value="true">
<span data-i18n>settings.cascadeControl.output.events.onLossConnection</span>
</label>
<label for="cc-on-enabled-heating">
<input type="checkbox" id="cc-on-enabled-heating" name="cascadeControl[output][onEnabledHeating]" value="true">
<span data-i18n>settings.cascadeControl.output.events.onEnabledHeating</span>
</label>
</fieldset>
<button type="submit" data-i18n>button.save</button>
</form>
</div>
</details>
</article>
</main>
@@ -796,8 +866,6 @@
setInputValue('#opentherm-in-gpio', data.opentherm.inGpio < 255 ? data.opentherm.inGpio : '');
setInputValue('#opentherm-out-gpio', data.opentherm.outGpio < 255 ? data.opentherm.outGpio : '');
setInputValue('#opentherm-rx-led-gpio', data.opentherm.rxLedGpio < 255 ? data.opentherm.rxLedGpio : '');
setInputValue('#opentherm-fault-state-gpio', data.opentherm.faultStateGpio < 255 ? data.opentherm.faultStateGpio : '');
setCheckboxValue('#opentherm-invert-fault-state', data.opentherm.invertFaultState);
setInputValue('#opentherm-member-id-code', data.opentherm.memberIdCode);
setInputValue('#opentherm-max-modulation', data.opentherm.maxModulation);
setInputValue('#opentherm-pressure-factor', data.opentherm.pressureFactor);
@@ -851,6 +919,21 @@
setInputValue('#extpump-as-time', data.externalPump.antiStuckTime);
setBusy('#extpump-settings-busy', '#extpump-settings', false);
// Cascade control
setCheckboxValue('#cc-input-enable', data.cascadeControl.input.enable);
setInputValue('#cc-input-gpio', data.cascadeControl.input.gpio < 255 ? data.cascadeControl.input.gpio : '');
setCheckboxValue('#cc-input-invert-state', data.cascadeControl.input.invertState);
setInputValue('#cc-input-tt', data.cascadeControl.input.thresholdTime);
setCheckboxValue('#cc-output-enable', data.cascadeControl.output.enable);
setInputValue('#cc-output-gpio', data.cascadeControl.output.gpio < 255 ? data.cascadeControl.output.gpio : '');
setCheckboxValue('#cc-output-invert-state', data.cascadeControl.output.invertState);
setInputValue('#cc-output-tt', data.cascadeControl.output.thresholdTime);
setCheckboxValue('#cc-on-fault', data.cascadeControl.output.onFault);
setCheckboxValue('#cc-on-loss-conn', data.cascadeControl.output.onLossConnection);
setCheckboxValue('#cc-on-enabled-heating', data.cascadeControl.output.onEnabledHeating);
setBusy('#cc-settings-busy', '#cc-settings', false);
// Heating
setInputValue('#heating-min-temp', data.heating.minTemp, {
"min": data.system.unitSystem == 0 ? 0 : 32,
@@ -934,6 +1017,7 @@
setupForm('#outdoor-sensor-settings', fillData);
setupForm('#indoor-sensor-settings', fillData, ['sensors.indoor.bleAddress']);
setupForm('#extpump-settings', fillData);
setupForm('#cc-settings', fillData);
} catch (error) {
console.log(error);