7 Commits

Author SHA1 Message Date
Yurii
5e2f6c9cea refactor: optimization of the web portal layout 2024-12-16 00:43:32 +03:00
Yurii
f439f8c5ba feat: added OT cooling support flag
* refactoring OT settings struct
* renamed some OT settings
2024-12-15 14:24:05 +03:00
Yurii
bae7770371 chore: update pcb 2024-12-07 23:47:21 +03:00
Yurii
4e5a3e9da5 refactor: little changes 2024-12-07 23:10:50 +03:00
Yurii
412e1594e9 chore: update readme & assets 2024-12-07 23:10:27 +03:00
Yurii
9701e8c97b chore: update readme 2024-12-07 22:59:29 +03:00
Yurii
2fe546812c chore: update readme 2024-12-07 22:47:24 +03:00
25 changed files with 15885 additions and 7516 deletions

View File

@@ -1,40 +1,44 @@
<div align="center">
![logo](/assets/logo.svg)
<br>
[![GitHub version](https://img.shields.io/github/release/Laxilef/OTGateway.svg?include_prereleases)](https://github.com/Laxilef/OTGateway/releases)
[![GitHub download](https://img.shields.io/github/downloads/Laxilef/OTGateway/total.svg)](https://github.com/Laxilef/OTGateway/releases/latest)
[![License](https://img.shields.io/github/license/Laxilef/OTGateway.svg)](LICENSE.txt)
[![Telegram](https://img.shields.io/badge/Telegram-Channel-33A8E3)](https://t.me/otgateway)
</div>
<hr />
![Dashboard](/assets/poster-1.png)
![Configuration](/assets/poster-2.png)
![Integration with HomeAssistant](/assets/poster-3.png)
## Features
- Hot water temperature control
- DHW temperature control
- Heating temperature control
- Smart heating temperature control modes:
- PID
- Equithermic curves - adjusts the temperature based on indoor and outdoor temperatures
- Hysteresis setting (for accurate maintenance of room temperature)
- Ability to connect an external sensors to monitor outdoor and indoor temperature ([compatible sensors](https://github.com/Laxilef/OTGateway/wiki/Compatibility#temperature-sensors))
- Emergency mode. If the Wi-Fi connection is lost or the gateway cannot connect to the MQTT server, the mode will turn on. This mode will automatically maintain the set temperature and prevent your home from freezing. In this mode it is also possible to use equithermal curves (weather-compensated control).
- Automatic error reset (not with all boilers)
- Hysteresis setting _(for accurate maintenance of room temperature)_
- Ability to connect [additional (external) sensors](https://github.com/Laxilef/OTGateway/wiki/Compatibility#temperature-sensors): Dallas (1-wire), NTC 10k, Bluetooth (BLE). Makes it possible to monitor indoor and outdoor temperatures, temperatures on pipes/heat exchangers/etc.
- Emergency mode. In any dangerous situation _(loss of connection with Wifi, MQTT, sensors, etc)_ it will not let you and your home freeze.
- Ability of remote fault reset _(not with all boilers)_
- Diagnostics:
- The process of heating: works/does not work
- The process of heating water for hot water: working/not working
- Display of boiler errors
- Burner status (flame): on/off
- Burner modulation level in percent
- Pressure in the heating system
- Gateway status (depending on errors and connection status)
- Boiler connection status via OpenTherm interface
- The current temperature of the heat carrier (usually the return heat carrier)
- Set heat carrier temperature (depending on the selected mode)
- Current hot water temperature
- Displaying gateway status
- Displaying the connection status to the boiler via OpenTherm
- Displaying the fault status and fault code
- Displaying the diagnostic status & diagnostic code
- Display of the process of heating: works/does not work
- Display of burner (flame) status: on/off
- Display of burner modulation level in percent
- Display of pressure in the heating system
- Display of current temperature of the heat carrier
- Display of return temperature of the heat carrier
- Display of setpoint heat carrier temperature (useful when using PID or Equitherm)
- Display of the process of DHW: working/not working
- Display of current DHW temperature
- _And other information..._
- [Home Assistant](https://www.home-assistant.io/) integration via MQTT. The ability to create any automation for the boiler!
![logo](/assets/ha.png)
## Documentation
All available information and instructions can be found in the wiki:
@@ -52,6 +56,7 @@ All available information and instructions can be found in the wiki:
* [Ratios](https://github.com/Laxilef/OTGateway/wiki#ratios)
* [Fit coefficients](https://github.com/Laxilef/OTGateway/wiki#fit-coefficients)
* [PID mode](https://github.com/Laxilef/OTGateway/wiki#pid-mode)
* [Logs and debug](https://github.com/Laxilef/OTGateway/wiki#logs-and-debug)
* [Compatibility](https://github.com/Laxilef/OTGateway/wiki/Compatibility)
* [Boilers](https://github.com/Laxilef/OTGateway/wiki/Compatibility#boilers)
* [Boards](https://github.com/Laxilef/OTGateway/wiki/Compatibility#boards)
@@ -64,24 +69,5 @@ All available information and instructions can be found in the wiki:
* [Connection](https://github.com/Laxilef/OTGateway/wiki/OT-adapters#connection)
* [Leds on board](https://github.com/Laxilef/OTGateway/wiki/OT-adapters#leds-on-board)
## Dependencies
- [ESP8266Scheduler](https://github.com/nrwiersma/ESP8266Scheduler) (for ESP8266)
- [ESP32Scheduler](https://github.com/laxilef/ESP32Scheduler) (for ESP32)
- [ArduinoJson](https://github.com/bblanchon/ArduinoJson)
- [OpenTherm Library](https://github.com/ihormelnyk/opentherm_library)
- [ArduinoMqttClient](https://github.com/arduino-libraries/ArduinoMqttClient)
- [ESPTelnet](https://github.com/LennartHennigs/ESPTelnet)
- [FileData](https://github.com/GyverLibs/FileData)
- [GyverPID](https://github.com/GyverLibs/GyverPID)
- [GyverBlinker](https://github.com/GyverLibs/GyverBlinker)
- [DallasTemperature](https://github.com/milesburton/Arduino-Temperature-Control-Library)
- [TinyLogger](https://github.com/laxilef/TinyLogger)
## Debug
To display DEBUG messages you must enable debug in settings (switch is disabled by default).
You can connect via Telnet to read messages. IP: ESP8266 ip, port: 23
___
This project is tested with BrowserStack.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 261 KiB

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 KiB

After

Width:  |  Height:  |  Size: 380 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 684 KiB

After

Width:  |  Height:  |  Size: 849 KiB

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 KiB

BIN
assets/poster-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
assets/poster-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
assets/poster-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 973 KiB

View File

@@ -229,7 +229,7 @@ protected:
emergencyFlags |= 0b00000010;
}
if (settings.opentherm.nativeHeatingControl) {
if (settings.opentherm.options.nativeHeatingControl) {
emergencyFlags |= 0b00000100;
}
}

View File

@@ -532,13 +532,13 @@ protected:
bool publishNonStaticHaEntities(bool force = false) {
static byte _heatingMinTemp, _heatingMaxTemp, _dhwMinTemp, _dhwMaxTemp = 0;
static bool _indoorTempControl, _dhwPresent = false;
static bool _indoorTempControl, _dhwSupport = false;
bool published = false;
if (force || _dhwPresent != settings.opentherm.dhwPresent) {
_dhwPresent = settings.opentherm.dhwPresent;
if (force || _dhwSupport != settings.opentherm.options.dhwSupport) {
_dhwSupport = settings.opentherm.options.dhwSupport;
if (_dhwPresent) {
if (_dhwSupport) {
this->haHelper->publishInputDhwMinTemp(settings.system.unitSystem);
this->haHelper->publishInputDhwMaxTemp(settings.system.unitSystem);
this->haHelper->publishDhwState();
@@ -569,7 +569,7 @@ protected:
published = true;
}
if (_dhwPresent && (force || _dhwMinTemp != settings.dhw.minTemp || _dhwMaxTemp != settings.dhw.maxTemp)) {
if (_dhwSupport && (force || _dhwMinTemp != settings.dhw.minTemp || _dhwMaxTemp != settings.dhw.maxTemp)) {
_dhwMinTemp = settings.dhw.minTemp;
_dhwMaxTemp = settings.dhw.maxTemp;

View File

@@ -153,18 +153,18 @@ protected:
&& !vars.master.heating.blocking;
// DHW settings
vars.master.dhw.enabled = settings.opentherm.dhwPresent && settings.dhw.enabled;
vars.master.dhw.enabled = settings.opentherm.options.dhwSupport && settings.dhw.enabled;
vars.master.dhw.targetTemp = settings.dhw.target;
// CH2 settings
vars.master.ch2.enabled = settings.opentherm.heatingCh2Enabled
|| (settings.opentherm.heatingCh1ToCh2 && vars.master.heating.enabled)
|| (settings.opentherm.dhwToCh2 && settings.opentherm.dhwPresent && settings.dhw.enabled);
vars.master.ch2.enabled = settings.opentherm.options.heatingCh2Enabled
|| (settings.opentherm.options.heatingToCh2 && vars.master.heating.enabled)
|| (settings.opentherm.options.dhwToCh2 && settings.opentherm.options.dhwSupport && settings.dhw.enabled);
if (settings.opentherm.heatingCh1ToCh2) {
if (settings.opentherm.options.heatingToCh2) {
vars.master.ch2.targetTemp = vars.master.heating.setpointTemp;
} else if (settings.opentherm.dhwToCh2) {
} else if (settings.opentherm.options.dhwToCh2) {
vars.master.ch2.targetTemp = vars.master.dhw.targetTemp;
}
@@ -174,18 +174,18 @@ protected:
// Immergas fix
// https://arduino.ru/forum/programmirovanie/termostat-opentherm-na-esp8266?page=15#comment-649392
if (settings.opentherm.immergasFix) {
if (settings.opentherm.options.immergasFix) {
statusLb = 0xCA;
}
unsigned long response = this->instance->setBoilerStatus(
vars.master.heating.enabled,
vars.master.dhw.enabled,
false,
settings.opentherm.nativeHeatingControl,
settings.opentherm.options.coolingSupport,
settings.opentherm.options.nativeHeatingControl,
vars.master.ch2.enabled,
settings.opentherm.summerWinterMode,
settings.opentherm.dhwBlocking,
settings.opentherm.options.summerWinterMode,
settings.opentherm.options.dhwBlocking,
statusLb
);
@@ -265,15 +265,16 @@ protected:
}
vars.slave.heating.active = CustomOpenTherm::isCentralHeatingActive(response);
vars.slave.dhw.active = settings.opentherm.dhwPresent ? CustomOpenTherm::isHotWaterActive(response) : false;
vars.slave.dhw.active = settings.opentherm.options.dhwSupport ? CustomOpenTherm::isHotWaterActive(response) : false;
vars.slave.flame = CustomOpenTherm::isFlameOn(response);
vars.slave.cooling = CustomOpenTherm::isCoolingActive(response);
vars.slave.fault.active = CustomOpenTherm::isFault(response);
vars.slave.diag.active = CustomOpenTherm::isDiagnostic(response);
Log.snoticeln(
FPSTR(L_OT), F("Received boiler status. Heating: %hhu; DHW: %hhu; flame: %hhu; fault: %hhu; diag: %hhu"),
FPSTR(L_OT), F("Received boiler status. Heating: %hhu; DHW: %hhu; flame: %hhu; cooling: %hhu; fault: %hhu; diag: %hhu"),
vars.slave.heating.active, vars.slave.dhw.active,
vars.slave.flame, vars.slave.fault.active, vars.slave.diag.active
vars.slave.flame, vars.slave.cooling, vars.slave.fault.active, vars.slave.diag.active
);
// These parameters will be updated every minute
@@ -309,7 +310,7 @@ protected:
Log.swarningln(FPSTR(L_OT), F("Failed receive min modulation and max power"));
}
if (!vars.master.heating.enabled && settings.opentherm.modulationSyncWithHeating) {
if (!vars.master.heating.enabled && settings.opentherm.options.modulationSyncWithHeating) {
if (this->setMaxModulationLevel(0)) {
Log.snoticeln(FPSTR(L_OT), F("Set max modulation: 0% (response: %hhu%%)"), vars.slave.modulation.max);
@@ -334,7 +335,7 @@ protected:
// Get DHW min/max temp (if necessary)
if (settings.opentherm.dhwPresent && settings.opentherm.getMinMaxTemp) {
if (settings.opentherm.options.dhwSupport && settings.opentherm.options.getMinMaxTemp) {
if (this->updateMinMaxDhwTemp()) {
uint8_t convertedMinTemp = convertTemp(
vars.slave.dhw.minTemp,
@@ -380,7 +381,7 @@ protected:
// Get heating min/max temp
if (settings.opentherm.getMinMaxTemp) {
if (settings.opentherm.options.getMinMaxTemp) {
if (this->updateMinMaxHeatingTemp()) {
uint8_t convertedMinTemp = convertTemp(
vars.slave.heating.minTemp,
@@ -520,7 +521,7 @@ protected:
}
// Update DHW temp
if (settings.opentherm.dhwPresent && Sensors::getAmountByType(Sensors::Type::OT_DHW_TEMP, true)) {
if (settings.opentherm.options.dhwSupport && Sensors::getAmountByType(Sensors::Type::OT_DHW_TEMP, true)) {
bool result = this->updateDhwTemp();
if (result) {
@@ -546,7 +547,7 @@ protected:
}
// Update DHW temp 2
if (settings.opentherm.dhwPresent && Sensors::getAmountByType(Sensors::Type::OT_DHW_TEMP2, true)) {
if (settings.opentherm.options.dhwSupport && Sensors::getAmountByType(Sensors::Type::OT_DHW_TEMP2, true)) {
if (this->updateDhwTemp2()) {
float convertedDhwTemp2 = convertTemp(
vars.slave.dhw.currentTemp2,
@@ -570,7 +571,7 @@ protected:
}
// Update DHW flow rate
if (settings.opentherm.dhwPresent && Sensors::getAmountByType(Sensors::Type::OT_DHW_FLOW_RATE, true)) {
if (settings.opentherm.options.dhwSupport && Sensors::getAmountByType(Sensors::Type::OT_DHW_FLOW_RATE, true)) {
if (this->updateDhwFlowRate()) {
float convertedDhwFlowRate = convertVolume(
vars.slave.dhw.flowRate,
@@ -643,7 +644,7 @@ protected:
// Update CH2 temp
if (Sensors::getAmountByType(Sensors::Type::OT_CH2_TEMP, true)) {
if (vars.master.ch2.enabled && !settings.opentherm.nativeHeatingControl) {
if (vars.master.ch2.enabled && !settings.opentherm.options.nativeHeatingControl) {
if (this->updateCh2Temp()) {
float convertedCh2Temp = convertTemp(
vars.slave.ch2.currentTemp,
@@ -942,7 +943,7 @@ protected:
}
// Native heating control
if (settings.opentherm.nativeHeatingControl) {
if (settings.opentherm.options.nativeHeatingControl) {
// Converted current indoor temp
float convertedTemp = convertTemp(vars.master.heating.indoorTemp, settings.system.unitSystem, settings.opentherm.unitSystem);
@@ -958,7 +959,7 @@ protected:
}
// Set current CH2 indoor temp
if (settings.opentherm.heatingCh1ToCh2) {
if (settings.opentherm.options.heatingToCh2) {
if (this->setRoomTempCh2(convertedTemp)) {
Log.sinfoln(
FPSTR(L_OT_HEATING), F("Set current CH2 indoor temp: %.2f (converted: %.2f, response: %.2f)"),
@@ -990,7 +991,7 @@ protected:
}
// Set target CH2 temp
if (settings.opentherm.heatingCh1ToCh2 && this->needSetCh2Temp(convertedTemp)) {
if (settings.opentherm.options.heatingToCh2 && this->needSetCh2Temp(convertedTemp)) {
if (this->setRoomSetpointCh2(convertedTemp)) {
this->ch2SetTempTime = millis();
@@ -1006,7 +1007,7 @@ protected:
}
// Normal heating control
if (!settings.opentherm.nativeHeatingControl && vars.master.heating.enabled) {
if (!settings.opentherm.options.nativeHeatingControl && vars.master.heating.enabled) {
// Converted target heating temp
float convertedTemp = convertTemp(vars.master.heating.setpointTemp, settings.system.unitSystem, settings.opentherm.unitSystem);
@@ -1038,8 +1039,8 @@ protected:
}
// Set CH2 temp
if (!settings.opentherm.nativeHeatingControl && vars.master.ch2.enabled) {
if (settings.opentherm.heatingCh1ToCh2 || settings.opentherm.dhwToCh2) {
if (!settings.opentherm.options.nativeHeatingControl && vars.master.ch2.enabled) {
if (settings.opentherm.options.heatingToCh2 || settings.opentherm.options.dhwToCh2) {
// Converted target CH2 temp
float convertedTemp = convertTemp(
vars.master.ch2.targetTemp,

View File

@@ -39,7 +39,7 @@ protected:
this->indoorSensorsConnected = Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::INDOOR_TEMP);
//this->outdoorSensorsConnected = Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::OUTDOOR_TEMP);
if (settings.equitherm.enabled || settings.pid.enabled || settings.opentherm.nativeHeatingControl) {
if (settings.equitherm.enabled || settings.pid.enabled || settings.opentherm.options.nativeHeatingControl) {
vars.master.heating.indoorTempControl = true;
vars.master.heating.minTemp = THERMOSTAT_INDOOR_MIN_TEMP;
vars.master.heating.maxTemp = THERMOSTAT_INDOOR_MAX_TEMP;
@@ -93,7 +93,7 @@ protected:
void hysteresis() {
bool useHyst = false;
if (settings.heating.hysteresis > 0.01f && this->indoorSensorsConnected) {
useHyst = settings.equitherm.enabled || settings.pid.enabled || settings.opentherm.nativeHeatingControl;
useHyst = settings.equitherm.enabled || settings.pid.enabled || settings.opentherm.options.nativeHeatingControl;
}
if (useHyst) {
@@ -110,13 +110,13 @@ protected:
}
inline float getHeatingMinSetpointTemp() {
return settings.opentherm.nativeHeatingControl
return settings.opentherm.options.nativeHeatingControl
? vars.master.heating.minTemp
: settings.heating.minTemp;
}
inline float getHeatingMaxSetpointTemp() {
return settings.opentherm.nativeHeatingControl
return settings.opentherm.options.nativeHeatingControl
? vars.master.heating.maxTemp
: settings.heating.maxTemp;
}
@@ -137,7 +137,7 @@ protected:
if (vars.emergency.state) {
return settings.emergency.target;
} else if (settings.opentherm.nativeHeatingControl) {
} else if (settings.opentherm.options.nativeHeatingControl) {
return settings.heating.target;
} else if (!settings.equitherm.enabled && !settings.pid.enabled) {

View File

@@ -57,16 +57,19 @@ struct Settings {
float minPower = 0.0f;
float maxPower = 0.0f;
bool dhwPresent = true;
bool summerWinterMode = false;
bool heatingCh2Enabled = true;
bool heatingCh1ToCh2 = false;
bool dhwToCh2 = false;
bool dhwBlocking = false;
bool modulationSyncWithHeating = false;
bool getMinMaxTemp = true;
bool nativeHeatingControl = false;
bool immergasFix = false;
struct {
bool dhwSupport = true;
bool coolingSupport = false;
bool summerWinterMode = false;
bool heatingCh2Enabled = true;
bool heatingToCh2 = false;
bool dhwToCh2 = false;
bool dhwBlocking = false;
bool modulationSyncWithHeating = false;
bool getMinMaxTemp = true;
bool nativeHeatingControl = false;
bool immergasFix = false;
} options;
} opentherm;
struct {
@@ -287,6 +290,7 @@ struct Variables {
bool connected = false;
bool flame = false;
bool cooling = false;
float pressure = 0.0f;
float heatExchangerTemp = 0.0f;

View File

@@ -54,6 +54,8 @@ const char S_CHIP[] PROGMEM = "chip";
const char S_CODE[] PROGMEM = "code";
const char S_CONNECTED[] PROGMEM = "connected";
const char S_CONTINUES[] PROGMEM = "continues";
const char S_COOLING[] PROGMEM = "cooling";
const char S_COOLING_SUPPORT[] PROGMEM = "coolingSupport";
const char S_CORE[] PROGMEM = "core";
const char S_CORES[] PROGMEM = "cores";
const char S_CRASH[] PROGMEM = "crash";
@@ -62,7 +64,7 @@ const char S_DATA[] PROGMEM = "data";
const char S_DATE[] PROGMEM = "date";
const char S_DHW[] PROGMEM = "dhw";
const char S_DHW_BLOCKING[] PROGMEM = "dhwBlocking";
const char S_DHW_PRESENT[] PROGMEM = "dhwPresent";
const char S_DHW_SUPPORT[] PROGMEM = "dhwSupport";
const char S_DHW_TO_CH2[] PROGMEM = "dhwToCh2";
const char S_DIAG[] PROGMEM = "diag";
const char S_DNS[] PROGMEM = "dns";
@@ -88,7 +90,7 @@ const char S_GET_MIN_MAX_TEMP[] PROGMEM = "getMinMaxTemp";
const char S_GPIO[] PROGMEM = "gpio";
const char S_HEAP[] PROGMEM = "heap";
const char S_HEATING[] PROGMEM = "heating";
const char S_HEATING_CH1_TO_CH2[] PROGMEM = "heatingCh1ToCh2";
const char S_HEATING_TO_CH2[] PROGMEM = "heatingToCh2";
const char S_HEATING_CH2_ENABLED[] PROGMEM = "heatingCh2Enabled";
const char S_HIDDEN[] PROGMEM = "hidden";
const char S_HOME_ASSISTANT_DISCOVERY[] PROGMEM = "homeAssistantDiscovery";
@@ -134,6 +136,7 @@ const char S_ON_ENABLED_HEATING[] PROGMEM = "onEnabledHeating";
const char S_ON_FAULT[] PROGMEM = "onFault";
const char S_ON_LOSS_CONNECTION[] PROGMEM = "onLossConnection";
const char S_OPENTHERM[] PROGMEM = "opentherm";
const char S_OPTIONS[] PROGMEM = "options";
const char S_OUTDOOR_TEMP[] PROGMEM = "outdoorTemp";
const char S_OUT_GPIO[] PROGMEM = "outGpio";
const char S_OUTPUT[] PROGMEM = "output";

View File

@@ -377,16 +377,19 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
opentherm[FPSTR(S_MAX_MODULATION)] = src.opentherm.maxModulation;
opentherm[FPSTR(S_MIN_POWER)] = roundf(src.opentherm.minPower, 2);
opentherm[FPSTR(S_MAX_POWER)] = roundf(src.opentherm.maxPower, 2);
opentherm[FPSTR(S_DHW_PRESENT)] = src.opentherm.dhwPresent;
opentherm[FPSTR(S_SUMMER_WINTER_MODE)] = src.opentherm.summerWinterMode;
opentherm[FPSTR(S_HEATING_CH2_ENABLED)] = src.opentherm.heatingCh2Enabled;
opentherm[FPSTR(S_HEATING_CH1_TO_CH2)] = src.opentherm.heatingCh1ToCh2;
opentherm[FPSTR(S_DHW_TO_CH2)] = src.opentherm.dhwToCh2;
opentherm[FPSTR(S_DHW_BLOCKING)] = src.opentherm.dhwBlocking;
opentherm[FPSTR(S_MODULATION_SYNC_WITH_HEATING)] = src.opentherm.modulationSyncWithHeating;
opentherm[FPSTR(S_GET_MIN_MAX_TEMP)] = src.opentherm.getMinMaxTemp;
opentherm[FPSTR(S_NATIVE_HEATING_CONTROL)] = src.opentherm.nativeHeatingControl;
opentherm[FPSTR(S_IMMERGAS_FIX)] = src.opentherm.immergasFix;
auto otOptions = opentherm[FPSTR(S_OPTIONS)].to<JsonObject>();
otOptions[FPSTR(S_DHW_SUPPORT)] = src.opentherm.options.dhwSupport;
otOptions[FPSTR(S_COOLING_SUPPORT)] = src.opentherm.options.coolingSupport;
otOptions[FPSTR(S_SUMMER_WINTER_MODE)] = src.opentherm.options.summerWinterMode;
otOptions[FPSTR(S_HEATING_CH2_ENABLED)] = src.opentherm.options.heatingCh2Enabled;
otOptions[FPSTR(S_HEATING_TO_CH2)] = src.opentherm.options.heatingToCh2;
otOptions[FPSTR(S_DHW_TO_CH2)] = src.opentherm.options.dhwToCh2;
otOptions[FPSTR(S_DHW_BLOCKING)] = src.opentherm.options.dhwBlocking;
otOptions[FPSTR(S_MODULATION_SYNC_WITH_HEATING)] = src.opentherm.options.modulationSyncWithHeating;
otOptions[FPSTR(S_GET_MIN_MAX_TEMP)] = src.opentherm.options.getMinMaxTemp;
otOptions[FPSTR(S_NATIVE_HEATING_CONTROL)] = src.opentherm.options.nativeHeatingControl;
otOptions[FPSTR(S_IMMERGAS_FIX)] = src.opentherm.options.immergasFix;
auto mqtt = dst[FPSTR(S_MQTT)].to<JsonObject>();
mqtt[FPSTR(S_ENABLED)] = src.mqtt.enabled;
@@ -718,101 +721,110 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_DHW_PRESENT)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_DHW_PRESENT)].as<bool>();
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_DHW_SUPPORT)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_DHW_SUPPORT)].as<bool>();
if (value != dst.opentherm.dhwPresent) {
dst.opentherm.dhwPresent = value;
if (value != dst.opentherm.options.dhwSupport) {
dst.opentherm.options.dhwSupport = value;
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_SUMMER_WINTER_MODE)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_SUMMER_WINTER_MODE)].as<bool>();
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_COOLING_SUPPORT)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_COOLING_SUPPORT)].as<bool>();
if (value != dst.opentherm.summerWinterMode) {
dst.opentherm.summerWinterMode = value;
if (value != dst.opentherm.options.coolingSupport) {
dst.opentherm.options.coolingSupport = value;
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_HEATING_CH2_ENABLED)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_HEATING_CH2_ENABLED)].as<bool>();
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_SUMMER_WINTER_MODE)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_SUMMER_WINTER_MODE)].as<bool>();
if (value != dst.opentherm.heatingCh2Enabled) {
dst.opentherm.heatingCh2Enabled = value;
if (value != dst.opentherm.options.summerWinterMode) {
dst.opentherm.options.summerWinterMode = value;
changed = true;
}
}
if (dst.opentherm.heatingCh2Enabled) {
dst.opentherm.heatingCh1ToCh2 = false;
dst.opentherm.dhwToCh2 = false;
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_HEATING_CH2_ENABLED)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_HEATING_CH2_ENABLED)].as<bool>();
if (value != dst.opentherm.options.heatingCh2Enabled) {
dst.opentherm.options.heatingCh2Enabled = value;
if (dst.opentherm.options.heatingCh2Enabled) {
dst.opentherm.options.heatingToCh2 = false;
dst.opentherm.options.dhwToCh2 = false;
}
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_HEATING_CH1_TO_CH2)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_HEATING_CH1_TO_CH2)].as<bool>();
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_HEATING_TO_CH2)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_HEATING_TO_CH2)].as<bool>();
if (value != dst.opentherm.heatingCh1ToCh2) {
dst.opentherm.heatingCh1ToCh2 = value;
if (value != dst.opentherm.options.heatingToCh2) {
dst.opentherm.options.heatingToCh2 = value;
if (dst.opentherm.heatingCh1ToCh2) {
dst.opentherm.heatingCh2Enabled = false;
dst.opentherm.dhwToCh2 = false;
if (dst.opentherm.options.heatingToCh2) {
dst.opentherm.options.heatingCh2Enabled = false;
dst.opentherm.options.dhwToCh2 = false;
}
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_DHW_TO_CH2)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_DHW_TO_CH2)].as<bool>();
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_DHW_TO_CH2)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_DHW_TO_CH2)].as<bool>();
if (value != dst.opentherm.dhwToCh2) {
dst.opentherm.dhwToCh2 = value;
if (value != dst.opentherm.options.dhwToCh2) {
dst.opentherm.options.dhwToCh2 = value;
if (dst.opentherm.dhwToCh2) {
dst.opentherm.heatingCh2Enabled = false;
dst.opentherm.heatingCh1ToCh2 = false;
if (dst.opentherm.options.dhwToCh2) {
dst.opentherm.options.heatingCh2Enabled = false;
dst.opentherm.options.heatingToCh2 = false;
}
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_DHW_BLOCKING)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_DHW_BLOCKING)].as<bool>();
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_DHW_BLOCKING)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_DHW_BLOCKING)].as<bool>();
if (value != dst.opentherm.dhwBlocking) {
dst.opentherm.dhwBlocking = value;
if (value != dst.opentherm.options.dhwBlocking) {
dst.opentherm.options.dhwBlocking = value;
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_MODULATION_SYNC_WITH_HEATING)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_MODULATION_SYNC_WITH_HEATING)].as<bool>();
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_MODULATION_SYNC_WITH_HEATING)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_MODULATION_SYNC_WITH_HEATING)].as<bool>();
if (value != dst.opentherm.modulationSyncWithHeating) {
dst.opentherm.modulationSyncWithHeating = value;
if (value != dst.opentherm.options.modulationSyncWithHeating) {
dst.opentherm.options.modulationSyncWithHeating = value;
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_GET_MIN_MAX_TEMP)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_GET_MIN_MAX_TEMP)].as<bool>();
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_GET_MIN_MAX_TEMP)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_GET_MIN_MAX_TEMP)].as<bool>();
if (value != dst.opentherm.getMinMaxTemp) {
dst.opentherm.getMinMaxTemp = value;
if (value != dst.opentherm.options.getMinMaxTemp) {
dst.opentherm.options.getMinMaxTemp = value;
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_NATIVE_HEATING_CONTROL)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_NATIVE_HEATING_CONTROL)].as<bool>();
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_NATIVE_HEATING_CONTROL)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_NATIVE_HEATING_CONTROL)].as<bool>();
if (value != dst.opentherm.nativeHeatingControl) {
dst.opentherm.nativeHeatingControl = value;
if (value != dst.opentherm.options.nativeHeatingControl) {
dst.opentherm.options.nativeHeatingControl = value;
if (value) {
dst.equitherm.enabled = false;
@@ -823,11 +835,11 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_IMMERGAS_FIX)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_IMMERGAS_FIX)].as<bool>();
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_IMMERGAS_FIX)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_IMMERGAS_FIX)].as<bool>();
if (value != dst.opentherm.immergasFix) {
dst.opentherm.immergasFix = value;
if (value != dst.opentherm.options.immergasFix) {
dst.opentherm.options.immergasFix = value;
changed = true;
}
}
@@ -923,7 +935,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
if (src[FPSTR(S_EQUITHERM)][FPSTR(S_ENABLED)].is<bool>()) {
bool value = src[FPSTR(S_EQUITHERM)][FPSTR(S_ENABLED)].as<bool>();
if (!dst.opentherm.nativeHeatingControl) {
if (!dst.opentherm.options.nativeHeatingControl) {
if (value != dst.equitherm.enabled) {
dst.equitherm.enabled = value;
changed = true;
@@ -967,7 +979,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
if (src[FPSTR(S_PID)][FPSTR(S_ENABLED)].is<bool>()) {
bool value = src[FPSTR(S_PID)][FPSTR(S_ENABLED)].as<bool>();
if (!dst.opentherm.nativeHeatingControl) {
if (!dst.opentherm.options.nativeHeatingControl) {
if (value != dst.pid.enabled) {
dst.pid.enabled = value;
changed = true;
@@ -1326,7 +1338,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
// force check emergency target
{
float value = !src[FPSTR(S_EMERGENCY)][FPSTR(S_TARGET)].isNull() ? src[FPSTR(S_EMERGENCY)][FPSTR(S_TARGET)].as<float>() : dst.emergency.target;
bool noRegulators = !dst.opentherm.nativeHeatingControl;
bool noRegulators = !dst.opentherm.options.nativeHeatingControl;
bool valid = isValidTemp(
value,
dst.system.unitSystem,
@@ -1351,7 +1363,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
// force check heating target
{
bool indoorTempControl = dst.equitherm.enabled || dst.pid.enabled || dst.opentherm.nativeHeatingControl;
bool indoorTempControl = dst.equitherm.enabled || dst.pid.enabled || dst.opentherm.options.nativeHeatingControl;
float minTemp = indoorTempControl ? THERMOSTAT_INDOOR_MIN_TEMP : dst.heating.minTemp;
float maxTemp = indoorTempControl ? THERMOSTAT_INDOOR_MAX_TEMP : dst.heating.maxTemp;
@@ -1720,6 +1732,7 @@ void varsToJson(const Variables& src, JsonVariant dst) {
slave[FPSTR(S_PROTOCOL_VERSION)] = src.slave.appVersion;
slave[FPSTR(S_CONNECTED)] = src.slave.connected;
slave[FPSTR(S_FLAME)] = src.slave.flame;
slave[FPSTR(S_COOLING)] = src.slave.cooling;
auto sModulation = slave[FPSTR(S_MODULATION)].to<JsonObject>();
sModulation[FPSTR(S_MIN)] = src.slave.modulation.min;

View File

@@ -97,6 +97,7 @@
"sConnected": "OpenTherm connection",
"sFlame": "Flame",
"sCooling": "Cooling",
"sFaultActive": "Fault",
"sFaultCode": "Faul code",
"sDiagActive": "Diagnostic",
@@ -356,10 +357,11 @@
"options": {
"desc": "Options",
"dhwPresent": "DHW present",
"dhwSupport": "DHW support",
"coolingSupport": "Cooling support",
"summerWinterMode": "Summer/winter mode",
"heatingCh2Enabled": "Heating CH2 always enabled",
"heatingCh1ToCh2": "Duplicate heating CH1 to CH2",
"heatingToCh2": "Duplicate heating to CH2",
"dhwToCh2": "Duplicate DHW to CH2",
"dhwBlocking": "DHW blocking",
"modulationSyncWithHeating": "Sync modulation with heating",

View File

@@ -97,6 +97,7 @@
"sConnected": "Подключение к OpenTherm",
"sFlame": "Пламя",
"sCooling": "Охлаждение",
"sFaultActive": "Ошибка",
"sFaultCode": "Код ошибки",
"sDiagActive": "Диагностика",
@@ -356,10 +357,11 @@
"options": {
"desc": "Опции",
"dhwPresent": "Контур ГВС",
"dhwSupport": "Поддержка ГВС",
"coolingSupport": "Поддержка охлаждения",
"summerWinterMode": "Летний/зимний режим",
"heatingCh2Enabled": "Канал 2 отопления всегда вкл.",
"heatingCh1ToCh2": "Дублировать параметры отопления канала 1 в канал 2",
"heatingToCh2": "Дублировать параметры отопления в канал 2",
"dhwToCh2": "Дублировать параметры ГВС в канал 2",
"dhwBlocking": "DHW blocking",
"modulationSyncWithHeating": "Синхронизировать модуляцию с отоплением",

View File

@@ -113,6 +113,10 @@
<th scope="row" data-i18n>dashboard.states.sFlame</th>
<td><i class="sFlame"></i></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.sCooling</th>
<td><i class="sCooling"></i></td>
</tr>
<tr>
@@ -372,8 +376,8 @@
(prevSettings.heating.enabled != newSettings.heating.enabled)
|| (prevSettings.heating.turbo != newSettings.heating.turbo)
|| (prevSettings.heating.target != newSettings.heating.target)
|| (prevSettings.opentherm.dhwPresent && prevSettings.dhw.enabled != newSettings.dhw.enabled)
|| (prevSettings.opentherm.dhwPresent && prevSettings.dhw.target != newSettings.dhw.target)
|| (prevSettings.opentherm.options.dhwSupport && prevSettings.dhw.enabled != newSettings.dhw.enabled)
|| (prevSettings.opentherm.options.dhwSupport && prevSettings.dhw.target != newSettings.dhw.target)
);
if (modified) {
@@ -397,7 +401,7 @@
}
const result = await response.json();
noRegulators = !result.opentherm.nativeHeatingControl && !result.equitherm.enabled && !result.pid.enabled;
noRegulators = !result.opentherm.options.nativeHeatingControl && !result.equitherm.enabled && !result.pid.enabled;
prevSettings = result;
unitSystem = result.system.unitSystem;
newSettings.heating.enabled = result.heating.enabled;
@@ -406,7 +410,7 @@
newSettings.dhw.enabled = result.dhw.enabled;
newSettings.dhw.target = result.dhw.target;
if (result.opentherm.dhwPresent) {
if (result.opentherm.options.dhwSupport) {
show('#thermostat-dhw');
} else {
hide('#thermostat-dhw');
@@ -462,6 +466,7 @@
result.slave.connected ? "green" : "red"
);
setState('.sFlame', result.slave.flame);
setState('.sCooling', result.slave.cooling);
setValue('.sModMin', result.slave.modulation.min);
setValue('.sModMax', result.slave.modulation.max);

View File

@@ -38,37 +38,37 @@
<div id="network-settings-busy" aria-busy="true"></div>
<form action="/api/network/settings" id="network-settings" class="hidden">
<label for="network-hostname">
<label>
<span data-i18n>network.params.hostname</span>
<input type="text" id="network-hostname" name="hostname" maxlength="24" pattern="[A-Za-z0-9]+[A-Za-z0-9\-]+[A-Za-z0-9]+" required>
<input type="text" name="hostname" maxlength="24" pattern="[A-Za-z0-9]+[A-Za-z0-9\-]+[A-Za-z0-9]+" required>
</label>
<label for="network-use-dhcp">
<input type="checkbox" id="network-use-dhcp" name="useDhcp" value="true">
<label>
<input type="checkbox" name="useDhcp" value="true">
<span data-i18n>network.params.dhcp</span>
</label>
<br />
<hr />
<h4 data-i18n>network.section.static</h4>
<label for="network-static-ip">
<label>
<span data-i18n>network.params.ip</span>
<input type="text" id="network-static-ip" name="staticConfig[ip]" value="true" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
<input type="text" name="staticConfig[ip]" value="true" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
</label>
<label for="network-static-gateway">
<label>
<span data-i18n>network.params.gateway</span>
<input type="text" id="network-static-gateway" name="staticConfig[gateway]" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
<input type="text" name="staticConfig[gateway]" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
</label>
<label for="network-static-subnet">
<label>
<span data-i18n>network.params.subnet</span>
<input type="text" id="network-static-subnet" name="staticConfig[subnet]" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
<input type="text" name="staticConfig[subnet]" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
</label>
<label for="network-static-dns">
<label>
<span data-i18n>network.params.dns</span>
<input type="text" id="network-static-dns" name="staticConfig[dns]" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
<input type="text" name="staticConfig[dns]" maxlength="16" pattern="\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}" required>
</label>
<button type="submit" data-i18n>button.save</button>
@@ -109,19 +109,19 @@
<div id="sta-settings-busy" aria-busy="true"></div>
<form action="/api/network/settings" id="sta-settings" class="hidden">
<label for="sta-ssid">
<label>
<span data-i18n>network.wifi.ssid</span>
<input type="text" id="sta-ssid" name="sta[ssid]" maxlength="32" required>
<input type="text" name="sta[ssid]" maxlength="32" required>
</label>
<label for="sta-password">
<label>
<span data-i18n>network.wifi.password</span>
<input type="password" id="sta-password" name="sta[password]" maxlength="64" required>
<input type="password" name="sta[password]" maxlength="64" required>
</label>
<label for="sta-channel">
<label>
<span data-i18n>network.wifi.channel</span>
<input type="number" inputmode="numeric" id="sta-channel" name="sta[channel]" min="0" max="12" step="1" required>
<input type="number" inputmode="numeric" name="sta[channel]" min="0" max="12" step="1" required>
<small data-i18n>network.sta.channel.note</small>
</label>
@@ -140,19 +140,19 @@
<div id="ap-settings-busy" aria-busy="true"></div>
<form action="/api/network/settings" id="ap-settings" class="hidden">
<label for="ap-ssid">
<label>
<span data-i18n>network.wifi.ssid</span>
<input type="text" id="ap-ssid" name="ap[ssid]" maxlength="32" required>
<input type="text" name="ap[ssid]" maxlength="32" required>
</label>
<label for="ap-password">
<label>
<span data-i18n>network.wifi.password</span>
<input type="text" id="ap-password" name="ap[password]" maxlength="64" required>
<input type="text" name="ap[password]" maxlength="64" required>
</label>
<label for="ap-channel">
<label>
<span data-i18n>network.wifi.channel</span>
<input type="number" inputmode="numeric" id="ap-channel" name="ap[channel]" min="1" max="12" step="1" required>
<input type="number" inputmode="numeric" name="ap[channel]" min="1" max="12" step="1" required>
</label>
<button type="submit" data-i18n>button.save</button>
@@ -179,22 +179,22 @@
lang.build();
const fillData = (data) => {
setInputValue('#network-hostname', data.hostname);
setCheckboxValue('#network-use-dhcp', data.useDhcp);
setInputValue('#network-static-ip', data.staticConfig.ip);
setInputValue('#network-static-gateway', data.staticConfig.gateway);
setInputValue('#network-static-subnet', data.staticConfig.subnet);
setInputValue('#network-static-dns', data.staticConfig.dns);
setInputValue("[name='hostname']", data.hostname);
setCheckboxValue("[name='useDhcp']", data.useDhcp);
setInputValue("[name='staticConfig[ip]']", data.staticConfig.ip);
setInputValue("[name='staticConfig[gateway]']", data.staticConfig.gateway);
setInputValue("[name='staticConfig[subnet]']", data.staticConfig.subnet);
setInputValue("[name='staticConfig[dns]']", data.staticConfig.dns);
setBusy('#network-settings-busy', '#network-settings', false);
setInputValue('#sta-ssid', data.sta.ssid);
setInputValue('#sta-password', data.sta.password);
setInputValue('#sta-channel', data.sta.channel);
setInputValue("[name='sta[ssid]']", data.sta.ssid);
setInputValue("[name='sta[password]']", data.sta.password);
setInputValue("[name='sta[channel]']", data.sta.channel);
setBusy('#sta-settings-busy', '#sta-settings', false);
setInputValue('#ap-ssid', data.ap.ssid);
setInputValue('#ap-password', data.ap.password);
setInputValue('#ap-channel', data.ap.channel);
setInputValue("[name='ap[ssid]']", data.ap.ssid);
setInputValue("[name='ap[password]']", data.ap.password);
setInputValue("[name='ap[channel]']", data.ap.channel);
setBusy('#ap-settings-busy', '#ap-settings', false);
};

View File

@@ -41,19 +41,19 @@
<div id="portal-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="portal-settings" class="hidden">
<div class="grid">
<label for="portal-login">
<label>
<span data-i18n>settings.portal.login</span>
<input type="text" id="portal-login" name="portal[login]" maxlength="12" required>
<input type="text" name="portal[login]" maxlength="12" required>
</label>
<label for="portal-password">
<label>
<span data-i18n>settings.portal.password</span>
<input type="password" id="portal-password" name="portal[password]" maxlength="32" required>
<input type="password" name="portal[password]" maxlength="32" required>
</label>
</div>
<label for="portal-auth">
<input type="checkbox" id="portal-auth" name="portal[auth]" value="true">
<label>
<input type="checkbox" name="portal[auth]" value="true">
<span data-i18n>settings.portal.auth</span>
</label>
<br />
@@ -74,20 +74,20 @@
<legend data-i18n>settings.system.unit</legend>
<label>
<input type="radio" class="system-unit-system" name="system[unitSystem]" value="0" />
<input type="radio" name="system[unitSystem]" value="0" />
<span data-i18n>settings.system.metric</span>
</label>
<label>
<input type="radio" class="system-unit-system" name="system[unitSystem]" value="1" />
<input type="radio" name="system[unitSystem]" value="1" />
<span data-i18n>settings.system.imperial</span>
</label>
</fieldset>
<fieldset>
<label for="system-status-led-gpio">
<label>
<span data-i18n>settings.system.statusLedGpio</span>
<input type="number" inputmode="numeric" id="system-status-led-gpio" name="system[statusLedGpio]" min="0" max="254" step="1">
<input type="number" inputmode="numeric" name="system[statusLedGpio]" min="0" max="254" step="1">
<small data-i18n>settings.note.blankNotUse</small>
</label>
</fieldset>
@@ -95,19 +95,19 @@
<fieldset>
<legend data-i18n>settings.section.diag</legend>
<label for="system-serial-enable">
<input type="checkbox" id="system-serial-enable" name="system[serial][enabled]" value="true">
<label>
<input type="checkbox" name="system[serial][enabled]" value="true">
<span data-i18n>settings.system.serial.enable</span>
</label>
<label for="system-telnet-enable">
<input type="checkbox" id="system-telnet-enable" name="system[telnet][enabled]" value="true">
<label>
<input type="checkbox" name="system[telnet][enabled]" value="true">
<span data-i18n>settings.system.telnet.enable</span>
</label>
<label for="system-log-level">
<label>
<span data-i18n>settings.system.logLevel</span>
<select id="system-log-level" name="system[logLevel]">
<select name="system[logLevel]">
<option value="0">SILENT</option>
<option value="1">FATAL</option>
<option value="2">ERROR</option>
@@ -120,9 +120,9 @@
</label>
<div class="grid">
<label for="system-serial-baudrate">
<label>
<span data-i18n>settings.system.serial.baud</span>
<select id="system-serial-baudrate" name="system[serial][baudrate]" required>
<select name="system[serial][baudrate]" required>
<option value="9600">9600</option>
<option value="19200">19200</option>
<option value="38400">38400</option>
@@ -132,9 +132,9 @@
</select>
</label>
<label for="system-telnet-port">
<label>
<span data-i18n>settings.system.telnet.port.title</span>
<input type="number" inputmode="numeric" id="system-telnet-port" name="system[telnet][port]" min="1" max="65535" step="1" required>
<input type="number" inputmode="numeric" name="system[telnet][port]" min="1" max="65535" step="1" required>
<small data-i18n>settings.system.telnet.port.note</small>
</label>
</div>
@@ -156,26 +156,26 @@
<div id="heating-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="heating-settings" class="hidden">
<div class="grid">
<label for="heating-min-temp">
<label>
<span data-i18n>settings.temp.min</span>
<input type="number" inputmode="numeric" id="heating-min-temp" name="heating[minTemp]" min="0" max="0" step="1" required>
<input type="number" inputmode="numeric" name="heating[minTemp]" min="0" max="0" step="1" required>
</label>
<label for="heating-max-temp">
<label>
<span data-i18n>settings.temp.max</span>
<input type="number" inputmode="numeric" id="heating-max-temp" name="heating[maxTemp]" min="0" max="0" step="1" required>
<input type="number" inputmode="numeric" name="heating[maxTemp]" min="0" max="0" step="1" required>
</label>
</div>
<div class="grid">
<label for="heating-hysteresis">
<label>
<span data-i18n>settings.heating.hyst</span>
<input type="number" inputmode="numeric" id="heating-hysteresis" name="heating[hysteresis]" min="0" max="5" step="0.05" required>
<input type="number" inputmode="numeric" name="heating[hysteresis]" min="0" max="5" step="0.05" required>
</label>
<label for="heating-turbo-factor">
<label>
<span data-i18n>settings.heating.turboFactor</span>
<input type="number" inputmode="numeric" id="heating-turbo-factor" name="heating[turboFactor]" min="1.5" max="10" step="0.1" required>
<input type="number" inputmode="numeric" name="heating[turboFactor]" min="1.5" max="10" step="0.1" required>
</label>
</div>
@@ -192,14 +192,14 @@
<div id="dhw-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="dhw-settings" class="hidden">
<div class="grid">
<label for="dhw-min-temp">
<label>
<span data-i18n>settings.temp.min</span>
<input type="number" inputmode="numeric" id="dhw-min-temp" name="dhw[minTemp]" min="0" max="0" step="1" required>
<input type="number" inputmode="numeric" name="dhw[minTemp]" min="0" max="0" step="1" required>
</label>
<label for="dhw-max-temp">
<label>
<span data-i18n>settings.temp.max</span>
<input type="number" inputmode="numeric" id="dhw-max-temp" name="dhw[maxTemp]" min="0" max="0" step="1" required>
<input type="number" inputmode="numeric" name="dhw[maxTemp]" min="0" max="0" step="1" required>
</label>
</div>
@@ -220,15 +220,15 @@
</fieldset>
<div class="grid">
<label for="emergency-target">
<label>
<span data-i18n>settings.emergency.target.title</span>
<input type="number" inputmode="numeric" id="emergency-target" name="emergency[target]" min="0" max="0" step="1" required>
<input type="number" inputmode="numeric" name="emergency[target]" min="0" max="0" step="1" required>
<small data-i18n>settings.emergency.target.note</small>
</label>
<label for="emergency-treshold-time">
<label>
<span data-i18n>settings.emergency.treshold</span>
<input type="number" inputmode="numeric" id="emergency-treshold-time" name="emergency[tresholdTime]" min="60" max="1800" step="1" required>
<input type="number" inputmode="numeric" name="emergency[tresholdTime]" min="60" max="1800" step="1" required>
</label>
</div>
@@ -245,26 +245,26 @@
<div id="equitherm-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="equitherm-settings" class="hidden">
<fieldset>
<label for="equitherm-enable">
<input type="checkbox" id="equitherm-enable" name="equitherm[enabled]" value="true">
<label>
<input type="checkbox" name="equitherm[enabled]" value="true">
<span data-i18n>settings.enable</span>
</label>
</fieldset>
<div class="grid">
<label for="equitherm-n-factor">
<label>
<span data-i18n>settings.equitherm.n</span>
<input type="number" inputmode="numeric" id="equitherm-n-factor" name="equitherm[n_factor]" min="0.001" max="10" step="0.001" required>
<input type="number" inputmode="numeric" name="equitherm[n_factor]" min="0.001" max="10" step="0.001" required>
</label>
<label for="equitherm-k-factor">
<label>
<span data-i18n>settings.equitherm.k</span>
<input type="number" inputmode="numeric" id="equitherm-k-factor" name="equitherm[k_factor]" min="0" max="10" step="0.01" required>
<input type="number" inputmode="numeric" name="equitherm[k_factor]" min="0" max="10" step="0.01" required>
</label>
<label for="equitherm-t-factor">
<label>
<span data-i18n>settings.equitherm.t.title</span>
<input type="number" inputmode="numeric" id="equitherm-t-factor" name="equitherm[t_factor]" min="0" max="10" step="0.01" required>
<input type="number" inputmode="numeric" name="equitherm[t_factor]" min="0" max="10" step="0.01" required>
<small data-i18n>settings.equitherm.t.note</small>
</label>
</div>
@@ -282,45 +282,45 @@
<div id="pid-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="pid-settings" class="hidden">
<fieldset>
<label for="pid-enable">
<input type="checkbox" id="pid-enable" name="pid[enabled]" value="true">
<label>
<input type="checkbox" name="pid[enabled]" value="true">
<span data-i18n>settings.enable</span>
</label>
</fieldset>
<div class="grid">
<label for="pid-p-factor">
<label>
<span data-i18n>settings.pid.p</span>
<input type="number" inputmode="numeric" id="pid-p-factor" name="pid[p_factor]" min="0.1" max="1000" step="0.01" required>
<input type="number" inputmode="numeric" name="pid[p_factor]" min="0.1" max="1000" step="0.01" required>
</label>
<label for="pid-i-factor">
<label>
<span data-i18n>settings.pid.i</span>
<input type="number" inputmode="numeric" id="pid-i-factor" name="pid[i_factor]" min="0" max="100" step="0.0001" required>
<input type="number" inputmode="numeric" name="pid[i_factor]" min="0" max="100" step="0.0001" required>
</label>
<label for="pid-d-factor">
<label>
<span data-i18n>settings.pid.d</span>
<input type="number" inputmode="numeric" id="pid-d-factor" name="pid[d_factor]" min="0" max="100000" step="0.1" required>
<input type="number" inputmode="numeric" name="pid[d_factor]" min="0" max="100000" step="0.1" required>
</label>
</div>
<label for="pid-dt">
<label>
<span data-i18n>settings.pid.dt</span>
<input type="number" inputmode="numeric" id="pid-dt" name="pid[dt]" min="30" max="1800" step="1" required>
<input type="number" inputmode="numeric" name="pid[dt]" min="30" max="1800" step="1" required>
</label>
<hr />
<div class="grid">
<label for="pid-min-temp">
<label>
<span data-i18n>settings.temp.min</span>
<input type="number" inputmode="numeric" id="pid-min-temp" name="pid[minTemp]" min="0" max="0" step="1" required>
<input type="number" inputmode="numeric" name="pid[minTemp]" min="0" max="0" step="1" required>
</label>
<label for="pid-max-temp">
<label>
<span data-i18n>settings.temp.max</span>
<input type="number" inputmode="numeric" id="pid-max-temp" name="pid[maxTemp]" min="0" max="0" step="1" required>
<input type="number" inputmode="numeric" name="pid[maxTemp]" min="0" max="0" step="1" required>
</label>
</div>
@@ -336,67 +336,67 @@
<details>
<summary><b data-i18n>settings.section.ot</b></summary>
<div>
<div id="opentherm-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="opentherm-settings" class="hidden">
<div id="ot-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="ot-settings" class="hidden">
<fieldset>
<legend data-i18n>settings.system.unit</legend>
<label>
<input type="radio" class="opentherm-unit-system" name="opentherm[unitSystem]" value="0" />
<input type="radio" name="opentherm[unitSystem]" value="0" />
<span data-i18n>settings.system.metric</span>
</label>
<label>
<input type="radio" class="opentherm-unit-system" name="opentherm[unitSystem]" value="1" />
<input type="radio" name="opentherm[unitSystem]" value="1" />
<span data-i18n>settings.system.imperial</span>
</label>
</fieldset>
<div class="grid">
<label for="opentherm-in-gpio">
<label>
<span data-i18n>settings.ot.inGpio</span>
<input type="number" inputmode="numeric" id="opentherm-in-gpio" name="opentherm[inGpio]" min="0" max="254" step="1">
<input type="number" inputmode="numeric" name="opentherm[inGpio]" min="0" max="254" step="1">
</label>
<label for="opentherm-in-gpio">
<label>
<span data-i18n>settings.ot.outGpio</span>
<input type="number" inputmode="numeric" id="opentherm-out-gpio" name="opentherm[outGpio]" min="0" max="254" step="1">
<input type="number" inputmode="numeric" name="opentherm[outGpio]" min="0" max="254" step="1">
</label>
<label for="opentherm-rx-led-gpio">
<label>
<span data-i18n>settings.ot.ledGpio</span>
<input type="number" inputmode="numeric" id="opentherm-rx-led-gpio" name="opentherm[rxLedGpio]" min="0" max="254" step="1">
<input type="number" inputmode="numeric" name="opentherm[rxLedGpio]" min="0" max="254" step="1">
<small data-i18n>settings.note.blankNotUse</small>
</label>
</div>
<div class="grid">
<label for="opentherm-member-id-code">
<label>
<span data-i18n>settings.ot.memberId</span>
<input type="number" inputmode="numeric" id="opentherm-member-id" name="opentherm[memberId]" min="0" max="255" step="1" required>
<input type="number" inputmode="numeric" name="opentherm[memberId]" min="0" max="255" step="1" required>
</label>
<label for="opentherm-flags">
<label>
<span data-i18n>settings.ot.flags</span>
<input type="number" inputmode="numeric" id="opentherm-flags" name="opentherm[flags]" min="0" max="255" step="1" required>
<input type="number" inputmode="numeric" name="opentherm[flags]" min="0" max="255" step="1" required>
</label>
<label for="opentherm-max-modulation">
<label>
<span data-i18n>settings.ot.maxMod</span>
<input type="number" inputmode="numeric" id="opentherm-max-modulation" name="opentherm[maxModulation]" min="1" max="100" step="1" required>
<input type="number" inputmode="numeric" name="opentherm[maxModulation]" min="1" max="100" step="1" required>
</label>
</div>
<div class="grid">
<label for="opentherm-min-power">
<label>
<span data-i18n>settings.ot.minPower.title</span>
<input type="number" inputmode="numeric" id="opentherm-min-power" name="opentherm[minPower]" min="0" max="1000" step="0.1">
<input type="number" inputmode="numeric" name="opentherm[minPower]" min="0" max="1000" step="0.1">
<small data-i18n>settings.ot.minPower.note</small>
</label>
<label for="opentherm-max-power">
<label>
<span data-i18n>settings.ot.maxPower.title</span>
<input type="number" inputmode="numeric" id="opentherm-max-power" name="opentherm[maxPower]" min="0" max="1000" step="0.1">
<input type="number" inputmode="numeric" name="opentherm[maxPower]" min="0" max="1000" step="0.1">
<small data-i18n>settings.ot.maxPower.note</small>
</label>
</div>
@@ -404,54 +404,59 @@
<fieldset>
<legend data-i18n>settings.ot.options.desc</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>
<label>
<input type="checkbox" name="opentherm[options][dhwSupport]" value="true">
<span data-i18n>settings.ot.options.dhwSupport</span>
</label>
<label for="opentherm-sw-mode">
<input type="checkbox" id="opentherm-sw-mode" name="opentherm[summerWinterMode]" value="true">
<label>
<input type="checkbox" name="opentherm[options][coolingSupport]" value="true">
<span data-i18n>settings.ot.options.coolingSupport</span>
</label>
<label>
<input type="checkbox" name="opentherm[options][summerWinterMode]" value="true">
<span data-i18n>settings.ot.options.summerWinterMode</span>
</label>
<label for="opentherm-heating-ch2-enabled">
<input type="checkbox" id="opentherm-heating-ch2-enabled" name="opentherm[heatingCh2Enabled]" value="true">
<label>
<input type="checkbox" name="opentherm[options][heatingCh2Enabled]" value="true">
<span data-i18n>settings.ot.options.heatingCh2Enabled</span>
</label>
<label for="opentherm-heating-ch1-to-ch2">
<input type="checkbox" id="opentherm-heating-ch1-to-ch2" name="opentherm[heatingCh1ToCh2]" value="true">
<span data-i18n>settings.ot.options.heatingCh1ToCh2</span>
<label>
<input type="checkbox" name="opentherm[options][heatingToCh2]" value="true">
<span data-i18n>settings.ot.options.heatingToCh2</span>
</label>
<label for="opentherm-dhw-to-ch2">
<input type="checkbox" id="opentherm-dhw-to-ch2" name="opentherm[dhwToCh2]" value="true">
<label>
<input type="checkbox" name="opentherm[options][dhwToCh2]" value="true">
<span data-i18n>settings.ot.options.dhwToCh2</span>
</label>
<label for="opentherm-dhw-blocking">
<input type="checkbox" id="opentherm-dhw-blocking" name="opentherm[dhwBlocking]" value="true">
<label>
<input type="checkbox" name="opentherm[options][dhwBlocking]" value="true">
<span data-i18n>settings.ot.options.dhwBlocking</span>
</label>
<label for="opentherm-sync-modulation-with-heating">
<input type="checkbox" id="opentherm-sync-modulation-with-heating" name="opentherm[modulationSyncWithHeating]" value="true">
<label>
<input type="checkbox" name="opentherm[options][modulationSyncWithHeating]" value="true">
<span data-i18n>settings.ot.options.modulationSyncWithHeating</span>
</label>
<label for="opentherm-get-min-max-temp">
<input type="checkbox" id="opentherm-get-min-max-temp" name="opentherm[getMinMaxTemp]" value="true">
<label>
<input type="checkbox" name="opentherm[options][getMinMaxTemp]" value="true">
<span data-i18n>settings.ot.options.getMinMaxTemp</span>
</label>
<label for="opentherm-immergas-fix">
<input type="checkbox" id="opentherm-immergas-fix" name="opentherm[immergasFix]" value="true">
<label>
<input type="checkbox" name="opentherm[options][immergasFix]" value="true">
<span data-i18n>settings.ot.options.immergasFix</span>
</label>
<hr />
<label for="opentherm-native-heating-control">
<input type="checkbox" id="opentherm-native-heating-control" name="opentherm[nativeHeatingControl]" value="true">
<label>
<input type="checkbox" name="opentherm[options][nativeHeatingControl]" value="true">
<span data-i18n>settings.ot.nativeHeating.title</span><br />
<small data-i18n>settings.ot.nativeHeating.note</small>
</label>
@@ -470,50 +475,50 @@
<div id="mqtt-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="mqtt-settings" class="hidden">
<fieldset>
<label for="mqtt-enable">
<input type="checkbox" id="mqtt-enable" name="mqtt[enabled]" value="true">
<label>
<input type="checkbox" name="mqtt[enabled]" value="true">
<span data-i18n>settings.enable</span>
</label>
<label for="mqtt-ha-discovery">
<input type="checkbox" id="mqtt-ha-discovery" name="mqtt[homeAssistantDiscovery]" value="true">
<label>
<input type="checkbox" name="mqtt[homeAssistantDiscovery]" value="true">
<span data-i18n>settings.mqtt.homeAssistantDiscovery</span>
</label>
</fieldset>
<div class="grid">
<label for="mqtt-server">
<label>
<span data-i18n>settings.mqtt.server</span>
<input type="text" id="mqtt-server" name="mqtt[server]" maxlength="80" required>
<input type="text" name="mqtt[server]" maxlength="80" required>
</label>
<label for="mqtt-port">
<label>
<span data-i18n>settings.mqtt.port</span>
<input type="number" inputmode="numeric" id="mqtt-port" name="mqtt[port]" min="1" max="65535" step="1" required>
<input type="number" inputmode="numeric" name="mqtt[port]" min="1" max="65535" step="1" required>
</label>
</div>
<div class="grid">
<label for="mqtt-user">
<label>
<span data-i18n>settings.mqtt.user</span>
<input type="text" id="mqtt-user" name="mqtt[user]" maxlength="32" required>
<input type="text" name="mqtt[user]" maxlength="32" required>
</label>
<label for="mqtt-password">
<label>
<span data-i18n>settings.mqtt.password</span>
<input type="password" id="mqtt-password" name="mqtt[password]" maxlength="32">
<input type="password" name="mqtt[password]" maxlength="32">
</label>
</div>
<div class="grid">
<label for="mqtt-prefix">
<label>
<span data-i18n>settings.mqtt.prefix</span>
<input type="text" id="mqtt-prefix" name="mqtt[prefix]" maxlength="32" required>
<input type="text" name="mqtt[prefix]" maxlength="32" required>
</label>
<label for="mqtt-interval">
<label>
<span data-i18n>settings.mqtt.interval</span>
<input type="number" inputmode="numeric" id="mqtt-interval" name="mqtt[interval]" min="3" max="60" step="1" required>
<input type="number" inputmode="numeric" name="mqtt[interval]" min="3" max="60" step="1" required>
</label>
</div>
@@ -530,33 +535,33 @@
<div id="extpump-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="extpump-settings" class="hidden">
<fieldset>
<label for="extpump-use">
<input type="checkbox" id="extpump-use" name="externalPump[use]" value="true">
<label>
<input type="checkbox" name="externalPump[use]" value="true">
<span data-i18n>settings.extPump.use</span>
</label>
</fieldset>
<div class="grid">
<label for="extpump-gpio">
<label>
<span data-i18n>settings.extPump.gpio</span>
<input type="number" inputmode="numeric" id="extpump-gpio" name="externalPump[gpio]" min="0" max="254" step="1">
<input type="number" inputmode="numeric" name="externalPump[gpio]" min="0" max="254" step="1">
</label>
<label for="extpump-pc-time">
<label>
<span data-i18n>settings.extPump.postCirculationTime</span>
<input type="number" inputmode="numeric" id="extpump-pc-time" name="externalPump[postCirculationTime]" min="1" max="120" step="1" required>
<input type="number" inputmode="numeric" name="externalPump[postCirculationTime]" min="1" max="120" step="1" required>
</label>
</div>
<div class="grid">
<label for="extpump-as-interval">
<label>
<span data-i18n>settings.extPump.antiStuckInterval</span>
<input type="number" inputmode="numeric" id="extpump-as-interval" name="externalPump[antiStuckInterval]" min="1" max="366" step="1" required>
<input type="number" inputmode="numeric" name="externalPump[antiStuckInterval]" min="1" max="366" step="1" required>
</label>
<label for="extpump-as-time">
<label>
<span data-i18n>settings.extPump.antiStuckTime</span>
<input type="number" inputmode="numeric" id="extpump-as-time" name="externalPump[antiStuckTime]" min="1" max="20" step="1" required>
<input type="number" inputmode="numeric" name="externalPump[antiStuckTime]" min="1" max="20" step="1" required>
</label>
</div>
@@ -573,74 +578,74 @@
<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][enabled]" value="true">
<label>
<input type="checkbox" name="cascadeControl[input][enabled]" 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">
<label>
<input type="checkbox" name="cascadeControl[input][invertState]" value="true">
<span data-i18n>settings.cascadeControl.input.invertState</span>
</label>
</fieldset>
<div class="grid">
<label for="cc-input-gpio">
<label>
<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">
<input type="number" inputmode="numeric" name="cascadeControl[input][gpio]" min="0" max="254" step="1">
</label>
<label for="cc-input-tt">
<label>
<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>
<input type="number" inputmode="numeric" 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][enabled]" value="true">
<label>
<input type="checkbox" name="cascadeControl[output][enabled]" 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">
<label>
<input type="checkbox" name="cascadeControl[output][invertState]" value="true">
<span data-i18n>settings.cascadeControl.output.invertState</span>
</label>
</fieldset>
<div class="grid">
<label for="cc-output-gpio">
<label>
<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">
<input type="number" outputmode="numeric" name="cascadeControl[output][gpio]" min="0" max="254" step="1">
</label>
<label for="cc-output-tt">
<label>
<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>
<input type="number" outputmode="numeric" name="cascadeControl[output][thresholdTime]" min="5" max="600" step="1" required>
</label>
</div>
<fieldset>
<legend data-i18n>settings.cascadeControl.output.events.desc</legend>
<label for="cc-on-fault">
<input type="checkbox" id="cc-on-fault" name="cascadeControl[output][onFault]" value="true">
<label>
<input type="checkbox" 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">
<label>
<input type="checkbox" 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">
<label>
<input type="checkbox" name="cascadeControl[output][onEnabledHeating]" value="true">
<span data-i18n>settings.cascadeControl.output.events.onEnabledHeating</span>
</label>
</fieldset>
@@ -671,111 +676,112 @@
const fillData = (data) => {
// System
setSelectValue('#system-log-level', data.system.logLevel);
setCheckboxValue('#system-serial-enable', data.system.serial.enabled);
setSelectValue('#system-serial-baudrate', data.system.serial.baudrate);
setCheckboxValue('#system-telnet-enable', data.system.telnet.enabled);
setInputValue('#system-telnet-port', data.system.telnet.port);
setRadioValue('.system-unit-system', data.system.unitSystem);
setInputValue('#system-status-led-gpio', data.system.statusLedGpio < 255 ? data.system.statusLedGpio : '');
setSelectValue("[name='system[logLevel]']", data.system.logLevel);
setCheckboxValue("[name='system[serial][enabled]']", data.system.serial.enabled);
setSelectValue("[name='system[serial][baudrate]']", data.system.serial.baudrate);
setCheckboxValue("[name='system[telnet][enabled]']", data.system.telnet.enabled);
setInputValue("[name='system[telnet][port]']", data.system.telnet.port);
setRadioValue("[name='system[unitSystem]']", data.system.unitSystem);
setInputValue("[name='system[statusLedGpio]']", data.system.statusLedGpio < 255 ? data.system.statusLedGpio : '');
setBusy('#system-settings-busy', '#system-settings', false);
// Portal
setCheckboxValue('#portal-auth', data.portal.auth);
setInputValue('#portal-login', data.portal.login);
setInputValue('#portal-password', data.portal.password);
setCheckboxValue("[name='portal[auth]']", data.portal.auth);
setInputValue("[name='portal[login]']", data.portal.login);
setInputValue("[name='portal[password]']", data.portal.password);
setBusy('#portal-settings-busy', '#portal-settings', false);
// Opentherm
setRadioValue('.opentherm-unit-system', data.opentherm.unitSystem);
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-member-id', data.opentherm.memberId);
setInputValue('#opentherm-flags', data.opentherm.flags);
setInputValue('#opentherm-max-modulation', data.opentherm.maxModulation);
setInputValue('#opentherm-min-power', data.opentherm.minPower);
setInputValue('#opentherm-max-power', data.opentherm.maxPower);
setCheckboxValue('#opentherm-dhw-present', data.opentherm.dhwPresent);
setCheckboxValue('#opentherm-sw-mode', data.opentherm.summerWinterMode);
setCheckboxValue('#opentherm-heating-ch2-enabled', data.opentherm.heatingCh2Enabled);
setCheckboxValue('#opentherm-heating-ch1-to-ch2', data.opentherm.heatingCh1ToCh2);
setCheckboxValue('#opentherm-dhw-to-ch2', data.opentherm.dhwToCh2);
setCheckboxValue('#opentherm-dhw-blocking', data.opentherm.dhwBlocking);
setCheckboxValue('#opentherm-sync-modulation-with-heating', data.opentherm.modulationSyncWithHeating);
setCheckboxValue('#opentherm-get-min-max-temp', data.opentherm.getMinMaxTemp);
setCheckboxValue('#opentherm-native-heating-control', data.opentherm.nativeHeatingControl);
setCheckboxValue('#opentherm-immergas-fix', data.opentherm.immergasFix);
setBusy('#opentherm-settings-busy', '#opentherm-settings', false);
setRadioValue("[name='opentherm[unitSystem]']", data.opentherm.unitSystem);
setInputValue("[name='opentherm[inGpio]']", data.opentherm.inGpio < 255 ? data.opentherm.inGpio : '');
setInputValue("[name='opentherm[outGpio]']", data.opentherm.outGpio < 255 ? data.opentherm.outGpio : '');
setInputValue("[name='opentherm[rxLedGpio]']", data.opentherm.rxLedGpio < 255 ? data.opentherm.rxLedGpio : '');
setInputValue("[name='opentherm[memberId]']", data.opentherm.memberId);
setInputValue("[name='opentherm[flags]']", data.opentherm.flags);
setInputValue("[name='opentherm[maxModulation]']", data.opentherm.maxModulation);
setInputValue("[name='opentherm[minPower]']", data.opentherm.minPower);
setInputValue("[name='opentherm[maxPower]']", data.opentherm.maxPower);
setCheckboxValue("[name='opentherm[options][dhwSupport]']", data.opentherm.options.dhwSupport);
setCheckboxValue("[name='opentherm[options][coolingSupport]']", data.opentherm.options.coolingSupport);
setCheckboxValue("[name='opentherm[options][summerWinterMode]']", data.opentherm.options.summerWinterMode);
setCheckboxValue("[name='opentherm[options][heatingCh2Enabled]']", data.opentherm.options.heatingCh2Enabled);
setCheckboxValue("[name='opentherm[options][heatingToCh2]']", data.opentherm.options.heatingToCh2);
setCheckboxValue("[name='opentherm[options][dhwToCh2]']", data.opentherm.options.dhwToCh2);
setCheckboxValue("[name='opentherm[options][dhwBlocking]']", data.opentherm.options.dhwBlocking);
setCheckboxValue("[name='opentherm[options][modulationSyncWithHeating]']", data.opentherm.options.modulationSyncWithHeating);
setCheckboxValue("[name='opentherm[options][getMinMaxTemp]']", data.opentherm.options.getMinMaxTemp);
setCheckboxValue("[name='opentherm[options][nativeHeatingControl]']", data.opentherm.options.nativeHeatingControl);
setCheckboxValue("[name='opentherm[options][immergasFix]']", data.opentherm.options.immergasFix);
setBusy('#ot-settings-busy', '#ot-settings', false);
// MQTT
setCheckboxValue('#mqtt-enable', data.mqtt.enabled);
setCheckboxValue('#mqtt-ha-discovery', data.mqtt.homeAssistantDiscovery);
setInputValue('#mqtt-server', data.mqtt.server);
setInputValue('#mqtt-port', data.mqtt.port);
setInputValue('#mqtt-user', data.mqtt.user);
setInputValue('#mqtt-password', data.mqtt.password);
setInputValue('#mqtt-prefix', data.mqtt.prefix);
setInputValue('#mqtt-interval', data.mqtt.interval);
setCheckboxValue("[name='mqtt[enabled]']", data.mqtt.enabled);
setCheckboxValue("[name='mqtt[homeAssistantDiscovery]']", data.mqtt.homeAssistantDiscovery);
setInputValue("[name='mqtt[server]']", data.mqtt.server);
setInputValue("[name='mqtt[port]']", data.mqtt.port);
setInputValue("[name='mqtt[user]']", data.mqtt.user);
setInputValue("[name='mqtt[password]']", data.mqtt.password);
setInputValue("[name='mqtt[prefix]']", data.mqtt.prefix);
setInputValue("[name='mqtt[interval]']", data.mqtt.interval);
setBusy('#mqtt-settings-busy', '#mqtt-settings', false);
// Extpump
setCheckboxValue('#extpump-use', data.externalPump.use);
setInputValue('#extpump-gpio', data.externalPump.gpio < 255 ? data.externalPump.gpio : '');
setInputValue('#extpump-pc-time', data.externalPump.postCirculationTime);
setInputValue('#extpump-as-interval', data.externalPump.antiStuckInterval);
setInputValue('#extpump-as-time', data.externalPump.antiStuckTime);
setCheckboxValue("[name='externalPump[use]']", data.externalPump.use);
setInputValue("[name='externalPump[gpio]']", data.externalPump.gpio < 255 ? data.externalPump.gpio : '');
setInputValue("[name='externalPump[postCirculationTime]']", data.externalPump.postCirculationTime);
setInputValue("[name='externalPump[antiStuckInterval]']", data.externalPump.antiStuckInterval);
setInputValue("[name='externalPump[antiStuckTime]']", data.externalPump.antiStuckTime);
setBusy('#extpump-settings-busy', '#extpump-settings', false);
// Cascade control
setCheckboxValue('#cc-input-enable', data.cascadeControl.input.enabled);
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("[name='cascadeControl[input][enabled]']", data.cascadeControl.input.enabled);
setInputValue("[name='cascadeControl[input][gpio]']", data.cascadeControl.input.gpio < 255 ? data.cascadeControl.input.gpio : '');
setCheckboxValue("[name='cascadeControl[input][invertState]']", data.cascadeControl.input.invertState);
setInputValue("[name='cascadeControl[input][thresholdTime]']", data.cascadeControl.input.thresholdTime);
setCheckboxValue('#cc-output-enable', data.cascadeControl.output.enabled);
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);
setCheckboxValue("[name='cascadeControl[output][enabled]']", data.cascadeControl.output.enabled);
setInputValue("[name='cascadeControl[output][gpio]']", data.cascadeControl.output.gpio < 255 ? data.cascadeControl.output.gpio : '');
setCheckboxValue("[name='cascadeControl[output][invertState]']", data.cascadeControl.output.invertState);
setInputValue("[name='cascadeControl[output][thresholdTime]']", data.cascadeControl.output.thresholdTime);
setCheckboxValue("[name='cascadeControl[output][onFault]']", data.cascadeControl.output.onFault);
setCheckboxValue("[name='cascadeControl[output][onLossConnection]']", data.cascadeControl.output.onLossConnection);
setCheckboxValue("[name='cascadeControl[output][onEnabledHeating]']", data.cascadeControl.output.onEnabledHeating);
setBusy('#cc-settings-busy', '#cc-settings', false);
// Heating
setInputValue('#heating-min-temp', data.heating.minTemp, {
setInputValue("[name='heating[minTemp]']", data.heating.minTemp, {
"min": data.system.unitSystem == 0 ? 0 : 32,
"max": data.system.unitSystem == 0 ? 99 : 211
});
setInputValue('#heating-max-temp', data.heating.maxTemp, {
setInputValue("[name='heating[maxTemp]']", data.heating.maxTemp, {
"min": data.system.unitSystem == 0 ? 1 : 33,
"max": data.system.unitSystem == 0 ? 100 : 212
});
setInputValue('#heating-hysteresis', data.heating.hysteresis);
setInputValue('#heating-turbo-factor', data.heating.turboFactor);
setInputValue("[name='heating[hysteresis]']", data.heating.hysteresis);
setInputValue("[name='heating[turboFactor]']", data.heating.turboFactor);
setBusy('#heating-settings-busy', '#heating-settings', false);
// DHW
setInputValue('#dhw-min-temp', data.dhw.minTemp, {
setInputValue("[name='dhw[minTemp]']", data.dhw.minTemp, {
"min": data.system.unitSystem == 0 ? 0 : 32,
"max": data.system.unitSystem == 0 ? 99 : 211
});
setInputValue('#dhw-max-temp', data.dhw.maxTemp, {
setInputValue("[name='dhw[maxTemp]']", data.dhw.maxTemp, {
"min": data.system.unitSystem == 0 ? 1 : 33,
"max": data.system.unitSystem == 0 ? 100 : 212
});
setBusy('#dhw-settings-busy', '#dhw-settings', false);
// Emergency mode
setInputValue('#emergency-treshold-time', data.emergency.tresholdTime);
if (data.opentherm.nativeHeatingControl) {
setInputValue('#emergency-target', data.emergency.target, {
setInputValue("[name='emergency[tresholdTime]']", data.emergency.tresholdTime);
if (data.opentherm.options.nativeHeatingControl) {
setInputValue("[name='emergency[target]']", data.emergency.target, {
"min": data.system.unitSystem == 0 ? 5 : 41,
"max": data.system.unitSystem == 0 ? 30 : 86
});
} else {
setInputValue('#emergency-target', data.emergency.target, {
setInputValue("[name='emergency[target]']", data.emergency.target, {
"min": data.heating.minTemp,
"max": data.heating.maxTemp,
});
@@ -784,23 +790,23 @@
setBusy('#emergency-settings-busy', '#emergency-settings', false);
// Equitherm
setCheckboxValue('#equitherm-enable', data.equitherm.enabled);
setInputValue('#equitherm-n-factor', data.equitherm.n_factor);
setInputValue('#equitherm-k-factor', data.equitherm.k_factor);
setInputValue('#equitherm-t-factor', data.equitherm.t_factor);
setCheckboxValue("[name='equitherm[enabled]']", data.equitherm.enabled);
setInputValue("[name='equitherm[n_factor]']", data.equitherm.n_factor);
setInputValue("[name='equitherm[k_factor]']", data.equitherm.k_factor);
setInputValue("[name='equitherm[t_factor]']", data.equitherm.t_factor);
setBusy('#equitherm-settings-busy', '#equitherm-settings', false);
// PID
setCheckboxValue('#pid-enable', data.pid.enabled);
setInputValue('#pid-p-factor', data.pid.p_factor);
setInputValue('#pid-i-factor', data.pid.i_factor);
setInputValue('#pid-d-factor', data.pid.d_factor);
setInputValue('#pid-dt', data.pid.dt);
setInputValue('#pid-min-temp', data.pid.minTemp, {
setCheckboxValue("[name='pid[enabled]']", data.pid.enabled);
setInputValue("[name='pid[p_factor]']", data.pid.p_factor);
setInputValue("[name='pid[i_factor]']", data.pid.i_factor);
setInputValue("[name='pid[d_factor]']", data.pid.d_factor);
setInputValue("[name='pid[dt]']", data.pid.dt);
setInputValue("[name='pid[minTemp]']", data.pid.minTemp, {
"min": data.equitherm.enabled ? (data.system.unitSystem == 0 ? -100 : -146) : (data.system.unitSystem == 0 ? 0 : 32),
"max": (data.system.unitSystem == 0 ? 99 : 211)
});
setInputValue('#pid-max-temp', data.pid.maxTemp, {
setInputValue("[name='pid[maxTemp]']", data.pid.maxTemp, {
"min": (data.system.unitSystem == 0 ? 0 : 33),
"max": (data.system.unitSystem == 0 ? 100 : 212)
});
@@ -827,7 +833,7 @@
setupForm('#emergency-settings', fillData);
setupForm('#equitherm-settings', fillData);
setupForm('#pid-settings', fillData);
setupForm('#opentherm-settings', fillData);
setupForm('#ot-settings', fillData);
setupForm('#mqtt-settings', fillData, ['mqtt.user', 'mqtt.password', 'mqtt.prefix']);
setupForm('#extpump-settings', fillData);
setupForm('#cc-settings', fillData);

View File

@@ -42,7 +42,7 @@
<input type="file" name="settings" id="restore-file" accept=".json">
</label>
<div class="grid">
<div role="group">
<button type="submit" data-i18n>button.restore</button>
<button type="button" class="secondary" onclick="window.location='/api/backup/save';" data-i18n>button.backup</button>
</div>