mirror of
https://github.com/Laxilef/OTGateway.git
synced 2025-12-12 19:24:27 +05:00
Compare commits
11 Commits
e573ce582f
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09c50d5df8 | ||
|
|
348fab39bb | ||
|
|
f9cb421893 | ||
|
|
eafb33cb6a | ||
|
|
0038a200ca | ||
|
|
1112a25172 | ||
|
|
69b3cdf05a | ||
|
|
01192a59f5 | ||
|
|
cb8251dd40 | ||
|
|
00baf10b9f | ||
|
|
6f8c8567a0 |
22
.github/workflows/pio-dependabot.yaml
vendored
22
.github/workflows/pio-dependabot.yaml
vendored
@@ -1,22 +0,0 @@
|
||||
name: PlatformIO Dependabot
|
||||
on:
|
||||
workflow_dispatch: # option to manually trigger the workflow
|
||||
schedule:
|
||||
# Runs every day at 00:00
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
dependabot:
|
||||
runs-on: ubuntu-latest
|
||||
name: run PlatformIO Dependabot
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
- name: run PlatformIO Dependabot
|
||||
uses: peterus/platformio_dependabot@v1.2.0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -27,6 +27,9 @@ let paths = {
|
||||
'src_data/scripts/i18n.min.js',
|
||||
'src_data/scripts/lang.js',
|
||||
'src_data/scripts/utils.js'
|
||||
],
|
||||
'chart.js': [
|
||||
'src_data/scripts/chart.js'
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
#if defined(EQUITHERM_INTEGER)
|
||||
// расчёты с целыми числами
|
||||
typedef int datatype;
|
||||
#else
|
||||
// расчёты с float числами
|
||||
typedef float datatype;
|
||||
#endif
|
||||
|
||||
class Equitherm {
|
||||
public:
|
||||
datatype targetTemp = 0;
|
||||
datatype indoorTemp = 0;
|
||||
datatype outdoorTemp = 0;
|
||||
float Kn = 0.0;
|
||||
float Kk = 0.0;
|
||||
float Kt = 0.0;
|
||||
|
||||
Equitherm() = default;
|
||||
|
||||
// kn, kk, kt
|
||||
Equitherm(float new_kn, float new_kk, float new_kt) {
|
||||
Kn = new_kn;
|
||||
Kk = new_kk;
|
||||
Kt = new_kt;
|
||||
}
|
||||
|
||||
// лимит выходной величины
|
||||
void setLimits(unsigned short min_output, unsigned short max_output) {
|
||||
_minOut = min_output;
|
||||
_maxOut = max_output;
|
||||
}
|
||||
|
||||
// возвращает новое значение при вызове
|
||||
datatype getResult() {
|
||||
datatype output = getResultN() + getResultK() + getResultT();
|
||||
output = constrain(output, _minOut, _maxOut); // ограничиваем выход
|
||||
return output;
|
||||
}
|
||||
|
||||
private:
|
||||
unsigned short _minOut = 20, _maxOut = 90;
|
||||
|
||||
// температура контура отопления в зависимости от наружной температуры
|
||||
datatype getResultN() {
|
||||
float a = (-0.21 * Kn) - 0.06; // a = -0,21k — 0,06
|
||||
float b = (6.04 * Kn) + 1.98; // b = 6,04k + 1,98
|
||||
float c = (-5.06 * Kn) + 18.06; // с = -5,06k + 18,06
|
||||
float x = (-0.2 * outdoorTemp) + 5; // x = -0.2*t1 + 5
|
||||
return (a * x * x) + (b * x) + c; // Tn = ax2 + bx + c
|
||||
}
|
||||
|
||||
// поправка на желаемую комнатную температуру
|
||||
datatype getResultK() {
|
||||
return (targetTemp - 20) * Kk;
|
||||
}
|
||||
|
||||
// Расчет поправки (ошибки) термостата
|
||||
datatype getResultT() {
|
||||
return constrain((targetTemp - indoorTemp), -3, 3) * Kt;
|
||||
}
|
||||
};
|
||||
@@ -92,13 +92,13 @@ check_flags = ${env.check_flags}
|
||||
;platform_packages =
|
||||
; framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.5
|
||||
; framework-arduinoespressif32-libs @ https://github.com/espressif/esp32-arduino-lib-builder/releases/download/idf-release_v5.1/esp32-arduino-libs-idf-release_v5.1-33fbade6.zip
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.32/platform-espressif32.zip
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.34/platform-espressif32.zip
|
||||
platform_packages = ${env.platform_packages}
|
||||
board_build.partitions = esp32_partitions.csv
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
laxilef/ESP32Scheduler@^1.0.1
|
||||
nimble_lib = h2zero/NimBLE-Arduino@2.3.3
|
||||
nimble_lib = h2zero/NimBLE-Arduino@2.3.7
|
||||
lib_ignore =
|
||||
extra_scripts =
|
||||
post:tools/esp32.py
|
||||
@@ -234,7 +234,7 @@ build_flags =
|
||||
${esp32_defaults.build_flags}
|
||||
-D ARDUINO_USB_MODE=0
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||
-D CONFIG_BT_NIMBLE_EXT_ADV=1
|
||||
-D MYNEWT_VAL_BLE_EXT_ADV=1
|
||||
-D USE_BLE=1
|
||||
-D DEFAULT_OT_IN_GPIO=35
|
||||
-D DEFAULT_OT_OUT_GPIO=36
|
||||
@@ -260,7 +260,7 @@ build_unflags =
|
||||
build_type = ${esp32_defaults.build_type}
|
||||
build_flags =
|
||||
${esp32_defaults.build_flags}
|
||||
-D CONFIG_BT_NIMBLE_EXT_ADV=1
|
||||
-D MYNEWT_VAL_BLE_EXT_ADV=1
|
||||
-D USE_BLE=1
|
||||
-D DEFAULT_OT_IN_GPIO=8
|
||||
-D DEFAULT_OT_OUT_GPIO=10
|
||||
@@ -366,7 +366,7 @@ build_unflags =
|
||||
build_type = ${esp32_defaults.build_type}
|
||||
build_flags =
|
||||
${esp32_defaults.build_flags}
|
||||
-D CONFIG_BT_NIMBLE_EXT_ADV=1
|
||||
-D MYNEWT_VAL_BLE_EXT_ADV=1
|
||||
-D USE_BLE=1
|
||||
-D DEFAULT_OT_IN_GPIO=3
|
||||
-D DEFAULT_OT_OUT_GPIO=1
|
||||
|
||||
104
src/HaHelper.h
104
src/HaHelper.h
@@ -443,6 +443,28 @@ public:
|
||||
return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_SWITCH), F("heating_turbo")).c_str(), doc);
|
||||
}
|
||||
|
||||
bool publishSwitchHeatingHysteresis(bool enabledByDefault = true) {
|
||||
JsonDocument doc;
|
||||
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
|
||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
||||
doc[FPSTR(HA_UNIQUE_ID)] = this->getUniqueIdWithPrefix(F("heating_hysteresis"));
|
||||
doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = this->getEntityIdWithPrefix(FPSTR(HA_ENTITY_SWITCH), F("heating_hysteresis"));
|
||||
doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG);
|
||||
doc[FPSTR(HA_NAME)] = F("Use heating hysteresis");
|
||||
doc[FPSTR(HA_ICON)] = F("mdi:altimeter");
|
||||
doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str();
|
||||
doc[FPSTR(HA_STATE_ON)] = true;
|
||||
doc[FPSTR(HA_STATE_OFF)] = false;
|
||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.heating.hysteresis.enabled }}");
|
||||
doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str();
|
||||
doc[FPSTR(HA_PAYLOAD_ON)] = F("{\"heating\": {\"hysteresis\" : {\"enabled\" : true}}}");
|
||||
doc[FPSTR(HA_PAYLOAD_OFF)] = F("{\"heating\": {\"hysteresis\" : {\"enabled\" : false}}}");
|
||||
doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter;
|
||||
doc.shrinkToFit();
|
||||
|
||||
return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_SWITCH), F("heating_hysteresis")).c_str(), doc);
|
||||
}
|
||||
|
||||
bool publishInputHeatingHysteresis(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
|
||||
JsonDocument doc;
|
||||
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
|
||||
@@ -462,9 +484,9 @@ public:
|
||||
doc[FPSTR(HA_NAME)] = F("Heating hysteresis");
|
||||
doc[FPSTR(HA_ICON)] = F("mdi:altimeter");
|
||||
doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str();
|
||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.heating.hysteresis|float(0)|round(2) }}");
|
||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.heating.hysteresis.value|float(0)|round(2) }}");
|
||||
doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str();
|
||||
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"heating\": {\"hysteresis\" : {{ value }}}}");
|
||||
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"heating\": {\"hysteresis\" : {\"value\" : {{ value }}}}}");
|
||||
doc[FPSTR(HA_MIN)] = 0;
|
||||
doc[FPSTR(HA_MAX)] = 15;
|
||||
doc[FPSTR(HA_STEP)] = 0.01f;
|
||||
@@ -844,19 +866,19 @@ public:
|
||||
return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_SWITCH), F("equitherm")).c_str(), doc);
|
||||
}
|
||||
|
||||
bool publishInputEquithermFactorN(bool enabledByDefault = true) {
|
||||
bool publishInputEquithermSlope(bool enabledByDefault = true) {
|
||||
JsonDocument doc;
|
||||
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
|
||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
||||
doc[FPSTR(HA_UNIQUE_ID)] = this->getUniqueIdWithPrefix(F("equitherm_n"));
|
||||
doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = this->getEntityIdWithPrefix(FPSTR(HA_ENTITY_NUMBER), F("equitherm_n"));
|
||||
doc[FPSTR(HA_UNIQUE_ID)] = this->getUniqueIdWithPrefix(F("equitherm_slope"));
|
||||
doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = this->getEntityIdWithPrefix(FPSTR(HA_ENTITY_NUMBER), F("equitherm_slope"));
|
||||
doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG);
|
||||
doc[FPSTR(HA_NAME)] = F("Equitherm factor N");
|
||||
doc[FPSTR(HA_ICON)] = F("mdi:alpha-n-circle-outline");
|
||||
doc[FPSTR(HA_NAME)] = F("Equitherm slope");
|
||||
doc[FPSTR(HA_ICON)] = F("mdi:slope-uphill");
|
||||
doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str();
|
||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.n_factor|float(0)|round(3) }}");
|
||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.slope|float(0)|round(3) }}");
|
||||
doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str();
|
||||
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"n_factor\" : {{ value }}}}");
|
||||
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"slope\" : {{ value }}}}");
|
||||
doc[FPSTR(HA_MIN)] = 0.001f;
|
||||
doc[FPSTR(HA_MAX)] = 10;
|
||||
doc[FPSTR(HA_STEP)] = 0.001f;
|
||||
@@ -864,56 +886,80 @@ public:
|
||||
doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter;
|
||||
doc.shrinkToFit();
|
||||
|
||||
return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_n_factor")).c_str(), doc);
|
||||
return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_slope")).c_str(), doc);
|
||||
}
|
||||
|
||||
bool publishInputEquithermFactorK(bool enabledByDefault = true) {
|
||||
bool publishInputEquithermExponent(bool enabledByDefault = true) {
|
||||
JsonDocument doc;
|
||||
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
|
||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
||||
doc[FPSTR(HA_UNIQUE_ID)] = this->getUniqueIdWithPrefix(F("equitherm_k"));
|
||||
doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = this->getEntityIdWithPrefix(FPSTR(HA_ENTITY_NUMBER), F("equitherm_k"));
|
||||
doc[FPSTR(HA_UNIQUE_ID)] = this->getUniqueIdWithPrefix(F("equitherm_exponent"));
|
||||
doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = this->getEntityIdWithPrefix(FPSTR(HA_ENTITY_NUMBER), F("equitherm_exponent"));
|
||||
doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG);
|
||||
doc[FPSTR(HA_NAME)] = F("Equitherm factor K");
|
||||
doc[FPSTR(HA_ICON)] = F("mdi:alpha-k-circle-outline");
|
||||
doc[FPSTR(HA_NAME)] = F("Equitherm exponent");
|
||||
doc[FPSTR(HA_ICON)] = F("mdi:exponent");
|
||||
doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str();
|
||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.k_factor|float(0)|round(2) }}");
|
||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.exponent|float(0)|round(3) }}");
|
||||
doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str();
|
||||
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"k_factor\" : {{ value }}}}");
|
||||
doc[FPSTR(HA_MIN)] = 0;
|
||||
doc[FPSTR(HA_MAX)] = 10;
|
||||
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"exponent\" : {{ value }}}}");
|
||||
doc[FPSTR(HA_MIN)] = 0.1;
|
||||
doc[FPSTR(HA_MAX)] = 2;
|
||||
doc[FPSTR(HA_STEP)] = 0.001f;
|
||||
doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX);
|
||||
doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter;
|
||||
doc.shrinkToFit();
|
||||
|
||||
return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_exponent")).c_str(), doc);
|
||||
}
|
||||
|
||||
bool publishInputEquithermShift(bool enabledByDefault = true) {
|
||||
JsonDocument doc;
|
||||
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
|
||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
||||
doc[FPSTR(HA_UNIQUE_ID)] = this->getUniqueIdWithPrefix(F("equitherm_shift"));
|
||||
doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = this->getEntityIdWithPrefix(FPSTR(HA_ENTITY_NUMBER), F("equitherm_shift"));
|
||||
doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG);
|
||||
doc[FPSTR(HA_DEVICE_CLASS)] = FPSTR(S_TEMPERATURE);
|
||||
doc[FPSTR(HA_NAME)] = F("Equitherm shift");
|
||||
doc[FPSTR(HA_ICON)] = F("mdi:chart-areaspline");
|
||||
doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str();
|
||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.shift|float(0)|round(2) }}");
|
||||
doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str();
|
||||
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"shift\" : {{ value }}}}");
|
||||
doc[FPSTR(HA_MIN)] = -15;
|
||||
doc[FPSTR(HA_MAX)] = 15;
|
||||
doc[FPSTR(HA_STEP)] = 0.01f;
|
||||
doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX);
|
||||
doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter;
|
||||
doc.shrinkToFit();
|
||||
|
||||
return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_k_factor")).c_str(), doc);
|
||||
return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_shift")).c_str(), doc);
|
||||
}
|
||||
|
||||
bool publishInputEquithermFactorT(bool enabledByDefault = true) {
|
||||
bool publishInputEquithermTargetDiffFactor(bool enabledByDefault = true) {
|
||||
JsonDocument doc;
|
||||
doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
|
||||
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->settingsTopic.c_str();
|
||||
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.pid.enabled, 'offline', 'online') }}");
|
||||
doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all");
|
||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
||||
doc[FPSTR(HA_UNIQUE_ID)] = this->getUniqueIdWithPrefix(F("equitherm_t"));
|
||||
doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = this->getEntityIdWithPrefix(FPSTR(HA_ENTITY_NUMBER), F("equitherm_t"));
|
||||
doc[FPSTR(HA_UNIQUE_ID)] = this->getUniqueIdWithPrefix(F("equitherm_target_diff_factor"));
|
||||
doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = this->getEntityIdWithPrefix(FPSTR(HA_ENTITY_NUMBER), F("equitherm_target_diff_factor"));
|
||||
doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG);
|
||||
doc[FPSTR(HA_NAME)] = F("Equitherm factor T");
|
||||
doc[FPSTR(HA_ICON)] = F("mdi:alpha-t-circle-outline");
|
||||
doc[FPSTR(HA_NAME)] = F("Equitherm target diff factor");
|
||||
doc[FPSTR(HA_ICON)] = F("mdi:chart-timeline-variant-shimmer");
|
||||
doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str();
|
||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.t_factor|float(0)|round(2) }}");
|
||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.targetDiffFactor|float(0)|round(3) }}");
|
||||
doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str();
|
||||
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"t_factor\" : {{ value }}}}");
|
||||
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"targetDiffFactor\" : {{ value }}}}");
|
||||
doc[FPSTR(HA_MIN)] = 0;
|
||||
doc[FPSTR(HA_MAX)] = 10;
|
||||
doc[FPSTR(HA_STEP)] = 0.01f;
|
||||
doc[FPSTR(HA_STEP)] = 0.001f;
|
||||
doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX);
|
||||
doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter;
|
||||
doc.shrinkToFit();
|
||||
|
||||
return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_t_factor")).c_str(), doc);
|
||||
return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_target_diff_factor")).c_str(), doc);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -319,7 +319,7 @@ protected:
|
||||
emergencyFlags |= 0b00000010;
|
||||
}
|
||||
|
||||
if (settings.opentherm.options.nativeHeatingControl) {
|
||||
if (settings.opentherm.options.nativeOTC) {
|
||||
emergencyFlags |= 0b00000100;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,6 +486,7 @@ protected:
|
||||
void publishHaEntities() {
|
||||
// heating
|
||||
this->haHelper->publishSwitchHeatingTurbo(false);
|
||||
this->haHelper->publishSwitchHeatingHysteresis();
|
||||
this->haHelper->publishInputHeatingHysteresis(settings.system.unitSystem);
|
||||
this->haHelper->publishInputHeatingTurboFactor(false);
|
||||
this->haHelper->publishInputHeatingMinTemp(settings.system.unitSystem);
|
||||
@@ -502,9 +503,10 @@ protected:
|
||||
|
||||
// equitherm
|
||||
this->haHelper->publishSwitchEquitherm();
|
||||
this->haHelper->publishInputEquithermFactorN(false);
|
||||
this->haHelper->publishInputEquithermFactorK(false);
|
||||
this->haHelper->publishInputEquithermFactorT(false);
|
||||
this->haHelper->publishInputEquithermSlope(false);
|
||||
this->haHelper->publishInputEquithermExponent(false);
|
||||
this->haHelper->publishInputEquithermShift(false);
|
||||
this->haHelper->publishInputEquithermTargetDiffFactor(false);
|
||||
|
||||
// states
|
||||
this->haHelper->publishStatusState();
|
||||
|
||||
@@ -171,7 +171,7 @@ protected:
|
||||
vars.master.heating.enabled = this->isReady()
|
||||
&& settings.heating.enabled
|
||||
&& vars.cascadeControl.input
|
||||
&& !vars.master.heating.blocking
|
||||
&& (!vars.master.heating.blocking || settings.heating.hysteresis.action != HysteresisAction::DISABLE_HEATING)
|
||||
&& !vars.master.heating.overheat;
|
||||
|
||||
// DHW settings
|
||||
@@ -218,7 +218,7 @@ protected:
|
||||
vars.master.heating.enabled,
|
||||
vars.master.dhw.enabled,
|
||||
settings.opentherm.options.coolingSupport,
|
||||
settings.opentherm.options.nativeHeatingControl,
|
||||
settings.opentherm.options.nativeOTC,
|
||||
vars.master.ch2.enabled,
|
||||
summerWinterMode,
|
||||
dhwBlocking,
|
||||
@@ -911,7 +911,7 @@ protected:
|
||||
|
||||
// Update CH2 temp
|
||||
if (Sensors::getAmountByType(Sensors::Type::OT_CH2_TEMP, true)) {
|
||||
if (vars.master.ch2.enabled && !settings.opentherm.options.nativeHeatingControl) {
|
||||
if (vars.master.ch2.enabled && !settings.opentherm.options.nativeOTC) {
|
||||
if (this->updateCh2Temp()) {
|
||||
float convertedCh2Temp = convertTemp(
|
||||
vars.slave.ch2.currentTemp,
|
||||
@@ -1209,8 +1209,8 @@ protected:
|
||||
}
|
||||
}
|
||||
|
||||
// Native heating control
|
||||
if (settings.opentherm.options.nativeHeatingControl) {
|
||||
// Send indoor temp if AlwaysSendIndoorTemp option is enabled.
|
||||
if (settings.opentherm.options.nativeOTC || settings.opentherm.options.alwaysSendIndoorTemp) {
|
||||
// Converted current indoor temp
|
||||
float convertedTemp = convertTemp(vars.master.heating.indoorTemp, settings.system.unitSystem, settings.opentherm.unitSystem);
|
||||
|
||||
@@ -1237,10 +1237,12 @@ protected:
|
||||
Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set current CH2 indoor temp"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// NativeOTC
|
||||
if (settings.opentherm.options.nativeOTC) {
|
||||
// Converted target indoor temp
|
||||
convertedTemp = convertTemp(vars.master.heating.targetTemp, settings.system.unitSystem, settings.opentherm.unitSystem);
|
||||
float convertedTemp = convertTemp(vars.master.heating.targetTemp, settings.system.unitSystem, settings.opentherm.unitSystem);
|
||||
|
||||
// Set target indoor temp
|
||||
if (this->needSetHeatingTemp(convertedTemp)) {
|
||||
@@ -1274,7 +1276,7 @@ protected:
|
||||
}
|
||||
|
||||
// Normal heating control
|
||||
if (!settings.opentherm.options.nativeHeatingControl && vars.master.heating.enabled) {
|
||||
if (!settings.opentherm.options.nativeOTC && vars.master.heating.enabled) {
|
||||
// Converted target heating temp
|
||||
float convertedTemp = convertTemp(vars.master.heating.setpointTemp, settings.system.unitSystem, settings.opentherm.unitSystem);
|
||||
|
||||
@@ -1311,7 +1313,7 @@ protected:
|
||||
}
|
||||
|
||||
// Set CH2 temp
|
||||
if (!settings.opentherm.options.nativeHeatingControl && vars.master.ch2.enabled) {
|
||||
if (!settings.opentherm.options.nativeOTC && vars.master.ch2.enabled) {
|
||||
if (settings.opentherm.options.heatingToCh2 || settings.opentherm.options.dhwToCh2) {
|
||||
// Converted target CH2 temp
|
||||
float convertedTemp = convertTemp(
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include <Equitherm.h>
|
||||
#include <GyverPID.h>
|
||||
|
||||
Equitherm etRegulator;
|
||||
GyverPID pidRegulator(0, 0, 0);
|
||||
|
||||
|
||||
@@ -39,7 +37,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.options.nativeHeatingControl) {
|
||||
if (settings.equitherm.enabled || settings.pid.enabled || settings.opentherm.options.nativeOTC) {
|
||||
vars.master.heating.indoorTempControl = true;
|
||||
vars.master.heating.minTemp = THERMOSTAT_INDOOR_MIN_TEMP;
|
||||
vars.master.heating.maxTemp = THERMOSTAT_INDOOR_MAX_TEMP;
|
||||
@@ -59,12 +57,23 @@ protected:
|
||||
this->turbo();
|
||||
this->hysteresis();
|
||||
|
||||
vars.master.heating.targetTemp = settings.heating.target;
|
||||
vars.master.heating.setpointTemp = roundf(constrain(
|
||||
this->getHeatingSetpointTemp(),
|
||||
this->getHeatingMinSetpointTemp(),
|
||||
this->getHeatingMaxSetpointTemp()
|
||||
), 0);
|
||||
if (vars.master.heating.blocking && settings.heating.hysteresis.action == HysteresisAction::SET_ZERO_TARGET) {
|
||||
vars.master.heating.targetTemp = 0.0f;
|
||||
vars.master.heating.setpointTemp = 0.0f;
|
||||
|
||||
// tick if PID enabled
|
||||
if (settings.pid.enabled) {
|
||||
this->getHeatingSetpointTemp();
|
||||
}
|
||||
|
||||
} else {
|
||||
vars.master.heating.targetTemp = settings.heating.target;
|
||||
vars.master.heating.setpointTemp = roundf(constrain(
|
||||
this->getHeatingSetpointTemp(),
|
||||
this->getHeatingMinSetpointTemp(),
|
||||
this->getHeatingMaxSetpointTemp()
|
||||
), 0);
|
||||
}
|
||||
|
||||
Sensors::setValueByType(
|
||||
Sensors::Type::HEATING_SETPOINT_TEMP, vars.master.heating.setpointTemp,
|
||||
@@ -92,15 +101,15 @@ protected:
|
||||
|
||||
void hysteresis() {
|
||||
bool useHyst = false;
|
||||
if (settings.heating.hysteresis > 0.01f && this->indoorSensorsConnected) {
|
||||
useHyst = settings.equitherm.enabled || settings.pid.enabled || settings.opentherm.options.nativeHeatingControl;
|
||||
if (settings.heating.hysteresis.enabled && this->indoorSensorsConnected) {
|
||||
useHyst = settings.equitherm.enabled || settings.pid.enabled || settings.opentherm.options.nativeOTC;
|
||||
}
|
||||
|
||||
if (useHyst) {
|
||||
if (!vars.master.heating.blocking && vars.master.heating.indoorTemp - settings.heating.target + 0.0001f >= settings.heating.hysteresis) {
|
||||
if (!vars.master.heating.blocking && vars.master.heating.indoorTemp - settings.heating.target + 0.0001f >= settings.heating.hysteresis.value) {
|
||||
vars.master.heating.blocking = true;
|
||||
|
||||
} else if (vars.master.heating.blocking && vars.master.heating.indoorTemp - settings.heating.target - 0.0001f <= -(settings.heating.hysteresis)) {
|
||||
} else if (vars.master.heating.blocking && vars.master.heating.indoorTemp - settings.heating.target - 0.0001f <= -(settings.heating.hysteresis.value)) {
|
||||
vars.master.heating.blocking = false;
|
||||
}
|
||||
|
||||
@@ -110,13 +119,13 @@ protected:
|
||||
}
|
||||
|
||||
inline float getHeatingMinSetpointTemp() {
|
||||
return settings.opentherm.options.nativeHeatingControl
|
||||
return settings.opentherm.options.nativeOTC
|
||||
? vars.master.heating.minTemp
|
||||
: settings.heating.minTemp;
|
||||
}
|
||||
|
||||
inline float getHeatingMaxSetpointTemp() {
|
||||
return settings.opentherm.options.nativeHeatingControl
|
||||
return settings.opentherm.options.nativeOTC
|
||||
? vars.master.heating.maxTemp
|
||||
: settings.heating.maxTemp;
|
||||
}
|
||||
@@ -137,7 +146,7 @@ protected:
|
||||
if (vars.emergency.state) {
|
||||
return settings.emergency.target;
|
||||
|
||||
} else if (settings.opentherm.options.nativeHeatingControl) {
|
||||
} else if (settings.opentherm.options.nativeOTC) {
|
||||
return settings.heating.target;
|
||||
|
||||
} else if (!settings.equitherm.enabled && !settings.pid.enabled) {
|
||||
@@ -146,39 +155,32 @@ protected:
|
||||
|
||||
// if use equitherm
|
||||
if (settings.equitherm.enabled) {
|
||||
unsigned short minTemp = settings.heating.minTemp;
|
||||
unsigned short maxTemp = settings.heating.maxTemp;
|
||||
float targetTemp = settings.heating.target;
|
||||
float indoorTemp = vars.master.heating.indoorTemp;
|
||||
float outdoorTemp = vars.master.heating.outdoorTemp;
|
||||
float tempDelta = settings.heating.target - vars.master.heating.outdoorTemp;
|
||||
float maxPoint = settings.heating.target - (
|
||||
settings.heating.maxTemp - settings.heating.target
|
||||
) / settings.equitherm.slope;
|
||||
|
||||
if (settings.system.unitSystem == UnitSystem::IMPERIAL) {
|
||||
minTemp = f2c(minTemp);
|
||||
maxTemp = f2c(maxTemp);
|
||||
targetTemp = f2c(targetTemp);
|
||||
indoorTemp = f2c(indoorTemp);
|
||||
outdoorTemp = f2c(outdoorTemp);
|
||||
float sf = (settings.heating.maxTemp - settings.heating.target) / pow(
|
||||
settings.heating.target - maxPoint,
|
||||
1.0f / settings.equitherm.exponent
|
||||
);
|
||||
float etResult = settings.heating.target + settings.equitherm.shift + sf * (
|
||||
tempDelta >= 0
|
||||
? pow(tempDelta, 1.0f / settings.equitherm.exponent)
|
||||
: -(pow(-(tempDelta), 1.0f / settings.equitherm.exponent))
|
||||
);
|
||||
|
||||
// add diff
|
||||
if (this->indoorSensorsConnected && !settings.pid.enabled && !settings.heating.turbo) {
|
||||
etResult += constrain(
|
||||
settings.heating.target - vars.master.heating.indoorTemp,
|
||||
-3.0f,
|
||||
3.0f
|
||||
) * settings.equitherm.targetDiffFactor;
|
||||
}
|
||||
|
||||
if (!this->indoorSensorsConnected || settings.pid.enabled) {
|
||||
etRegulator.Kt = 0.0f;
|
||||
etRegulator.indoorTemp = 0.0f;
|
||||
|
||||
} else {
|
||||
etRegulator.Kt = settings.heating.turbo ? 0.0f : settings.equitherm.t_factor;
|
||||
etRegulator.indoorTemp = indoorTemp;
|
||||
}
|
||||
|
||||
etRegulator.setLimits(minTemp, maxTemp);
|
||||
etRegulator.Kn = settings.equitherm.n_factor;
|
||||
etRegulator.Kk = settings.equitherm.k_factor;
|
||||
etRegulator.targetTemp = targetTemp;
|
||||
etRegulator.outdoorTemp = outdoorTemp;
|
||||
float etResult = etRegulator.getResult();
|
||||
|
||||
if (settings.system.unitSystem == UnitSystem::IMPERIAL) {
|
||||
etResult = c2f(etResult);
|
||||
}
|
||||
// limit
|
||||
etResult = constrain(etResult, settings.heating.minTemp, settings.heating.maxTemp);
|
||||
|
||||
if (fabsf(prevEtResult - etResult) > 0.09f) {
|
||||
prevEtResult = etResult;
|
||||
|
||||
@@ -149,7 +149,7 @@ public:
|
||||
|
||||
static int16_t getIdByName(const char* name) {
|
||||
if (settings == nullptr) {
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (uint8_t id = 0; id <= getMaxSensorId(); id++) {
|
||||
@@ -163,7 +163,7 @@ public:
|
||||
|
||||
static int16_t getIdByObjectId(const char* objectId) {
|
||||
if (settings == nullptr) {
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
String refObjectId;
|
||||
@@ -329,12 +329,12 @@ public:
|
||||
|
||||
static float getMeanValueByPurpose(Purpose purpose, const ValueType valueType, bool onlyConnected = true) {
|
||||
if (settings == nullptr || results == nullptr) {
|
||||
return 0;
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
uint8_t valueId = (uint8_t) valueType;
|
||||
if (!isValidValueId(valueId)) {
|
||||
return 0;
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float value = 0.0f;
|
||||
@@ -363,7 +363,7 @@ public:
|
||||
|
||||
static bool existsConnectedSensorsByPurpose(Purpose purpose) {
|
||||
if (settings == nullptr || results == nullptr) {
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint8_t id = 0; id <= getMaxSensorId(); id++) {
|
||||
|
||||
@@ -78,7 +78,8 @@ struct Settings {
|
||||
bool autoFaultReset = false;
|
||||
bool autoDiagReset = false;
|
||||
bool setDateAndTime = false;
|
||||
bool nativeHeatingControl = false;
|
||||
bool alwaysSendIndoorTemp = true;
|
||||
bool nativeOTC = false;
|
||||
bool immergasFix = false;
|
||||
} options;
|
||||
} opentherm;
|
||||
@@ -103,12 +104,17 @@ struct Settings {
|
||||
bool enabled = true;
|
||||
bool turbo = false;
|
||||
float target = DEFAULT_HEATING_TARGET_TEMP;
|
||||
float hysteresis = 0.5f;
|
||||
float turboFactor = 7.5f;
|
||||
uint8_t minTemp = DEFAULT_HEATING_MIN_TEMP;
|
||||
uint8_t maxTemp = DEFAULT_HEATING_MAX_TEMP;
|
||||
uint8_t maxModulation = 100;
|
||||
|
||||
struct {
|
||||
bool enabled = true;
|
||||
float value = 0.5f;
|
||||
HysteresisAction action = HysteresisAction::DISABLE_HEATING;
|
||||
} hysteresis;
|
||||
|
||||
struct {
|
||||
uint8_t highTemp = 95;
|
||||
uint8_t lowTemp = 90;
|
||||
@@ -154,9 +160,10 @@ struct Settings {
|
||||
|
||||
struct {
|
||||
bool enabled = false;
|
||||
float n_factor = 0.7f;
|
||||
float k_factor = 3.0f;
|
||||
float t_factor = 2.0f;
|
||||
float slope = 0.7f;
|
||||
float exponent = 1.3f;
|
||||
float shift = 0.0f;
|
||||
float targetDiffFactor = 2.0f;
|
||||
} equitherm;
|
||||
|
||||
struct {
|
||||
|
||||
@@ -163,4 +163,9 @@ enum class UnitSystem : uint8_t {
|
||||
IMPERIAL = 1
|
||||
};
|
||||
|
||||
enum class HysteresisAction : uint8_t {
|
||||
DISABLE_HEATING = 0,
|
||||
SET_ZERO_TARGET = 1
|
||||
};
|
||||
|
||||
char buffer[255];
|
||||
@@ -34,6 +34,7 @@ const char L_CASCADE_OUTPUT[] PROGMEM = "CASCADE.OUTPUT";
|
||||
const char L_EXTPUMP[] PROGMEM = "EXTPUMP";
|
||||
|
||||
|
||||
const char S_ACTION[] PROGMEM = "action";
|
||||
const char S_ACTIONS[] PROGMEM = "actions";
|
||||
const char S_ACTIVE[] PROGMEM = "active";
|
||||
const char S_ADDRESS[] PROGMEM = "address";
|
||||
@@ -81,6 +82,7 @@ const char S_ENABLED[] PROGMEM = "enabled";
|
||||
const char S_ENV[] PROGMEM = "env";
|
||||
const char S_EPC[] PROGMEM = "epc";
|
||||
const char S_EQUITHERM[] PROGMEM = "equitherm";
|
||||
const char S_EXPONENT[] PROGMEM = "exponent";
|
||||
const char S_EXTERNAL_PUMP[] PROGMEM = "externalPump";
|
||||
const char S_FACTOR[] PROGMEM = "factor";
|
||||
const char S_FAULT[] PROGMEM = "fault";
|
||||
@@ -108,6 +110,7 @@ const char S_HYSTERESIS[] PROGMEM = "hysteresis";
|
||||
const char S_ID[] PROGMEM = "id";
|
||||
const char S_IGNORE_DIAG_STATE[] PROGMEM = "ignoreDiagState";
|
||||
const char S_IMMERGAS_FIX[] PROGMEM = "immergasFix";
|
||||
const char S_ALWAYS_SEND_INDOOR_TEMP[] PROGMEM = "alwaysSendIndoorTemp";
|
||||
const char S_INDOOR_TEMP[] PROGMEM = "indoorTemp";
|
||||
const char S_INDOOR_TEMP_CONTROL[] PROGMEM = "indoorTempControl";
|
||||
const char S_IN_GPIO[] PROGMEM = "inGpio";
|
||||
@@ -117,7 +120,6 @@ const char S_INVERT_STATE[] PROGMEM = "invertState";
|
||||
const char S_IP[] PROGMEM = "ip";
|
||||
const char S_I_FACTOR[] PROGMEM = "i_factor";
|
||||
const char S_I_MULTIPLIER[] PROGMEM = "i_multiplier";
|
||||
const char S_K_FACTOR[] PROGMEM = "k_factor";
|
||||
const char S_LOGIN[] PROGMEM = "login";
|
||||
const char S_LOG_LEVEL[] PROGMEM = "logLevel";
|
||||
const char S_LOW_TEMP[] PROGMEM = "lowTemp";
|
||||
@@ -140,10 +142,9 @@ const char S_MODEL[] PROGMEM = "model";
|
||||
const char S_MODULATION[] PROGMEM = "modulation";
|
||||
const char S_MQTT[] PROGMEM = "mqtt";
|
||||
const char S_NAME[] PROGMEM = "name";
|
||||
const char S_NATIVE_HEATING_CONTROL[] PROGMEM = "nativeHeatingControl";
|
||||
const char S_NATIVE_OTC[] PROGMEM = "nativeOTC";
|
||||
const char S_NETWORK[] PROGMEM = "network";
|
||||
const char S_NTP[] PROGMEM = "ntp";
|
||||
const char S_N_FACTOR[] PROGMEM = "n_factor";
|
||||
const char S_OFFSET[] PROGMEM = "offset";
|
||||
const char S_ON_ENABLED_HEATING[] PROGMEM = "onEnabledHeating";
|
||||
const char S_ON_FAULT[] PROGMEM = "onFault";
|
||||
@@ -182,9 +183,11 @@ const char S_SERIAL[] PROGMEM = "serial";
|
||||
const char S_SERVER[] PROGMEM = "server";
|
||||
const char S_SETTINGS[] PROGMEM = "settings";
|
||||
const char S_SET_DATE_AND_TIME[] PROGMEM = "setDateAndTime";
|
||||
const char S_SHIFT[] PROGMEM = "shift";
|
||||
const char S_SIGNAL_QUALITY[] PROGMEM = "signalQuality";
|
||||
const char S_SIZE[] PROGMEM = "size";
|
||||
const char S_SLAVE[] PROGMEM = "slave";
|
||||
const char S_SLOPE[] PROGMEM = "slope";
|
||||
const char S_SSID[] PROGMEM = "ssid";
|
||||
const char S_STA[] PROGMEM = "sta";
|
||||
const char S_STATE[] PROGMEM = "state";
|
||||
@@ -196,6 +199,7 @@ const char S_SUBNET[] PROGMEM = "subnet";
|
||||
const char S_SUMMER_WINTER_MODE[] PROGMEM = "summerWinterMode";
|
||||
const char S_SYSTEM[] PROGMEM = "system";
|
||||
const char S_TARGET[] PROGMEM = "target";
|
||||
const char S_TARGET_DIFF_FACTOR[] PROGMEM = "targetDiffFactor";
|
||||
const char S_TARGET_TEMP[] PROGMEM = "targetTemp";
|
||||
const char S_TELNET[] PROGMEM = "telnet";
|
||||
const char S_TEMPERATURE[] PROGMEM = "temperature";
|
||||
@@ -208,7 +212,6 @@ const char S_TRESHOLD_TIME[] PROGMEM = "tresholdTime";
|
||||
const char S_TURBO[] PROGMEM = "turbo";
|
||||
const char S_TURBO_FACTOR[] PROGMEM = "turboFactor";
|
||||
const char S_TYPE[] PROGMEM = "type";
|
||||
const char S_T_FACTOR[] PROGMEM = "t_factor";
|
||||
const char S_UNIT_SYSTEM[] PROGMEM = "unitSystem";
|
||||
const char S_UPTIME[] PROGMEM = "uptime";
|
||||
const char S_USE[] PROGMEM = "use";
|
||||
|
||||
108
src/utils.h
108
src/utils.h
@@ -468,9 +468,11 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
|
||||
otOptions[FPSTR(S_AUTO_FAULT_RESET)] = src.opentherm.options.autoFaultReset;
|
||||
otOptions[FPSTR(S_AUTO_DIAG_RESET)] = src.opentherm.options.autoDiagReset;
|
||||
otOptions[FPSTR(S_SET_DATE_AND_TIME)] = src.opentherm.options.setDateAndTime;
|
||||
otOptions[FPSTR(S_NATIVE_HEATING_CONTROL)] = src.opentherm.options.nativeHeatingControl;
|
||||
otOptions[FPSTR(S_ALWAYS_SEND_INDOOR_TEMP)] = src.opentherm.options.alwaysSendIndoorTemp;
|
||||
otOptions[FPSTR(S_NATIVE_OTC)] = src.opentherm.options.nativeOTC;
|
||||
otOptions[FPSTR(S_IMMERGAS_FIX)] = src.opentherm.options.immergasFix;
|
||||
|
||||
|
||||
auto mqtt = dst[FPSTR(S_MQTT)].to<JsonObject>();
|
||||
mqtt[FPSTR(S_ENABLED)] = src.mqtt.enabled;
|
||||
mqtt[FPSTR(S_SERVER)] = src.mqtt.server;
|
||||
@@ -490,7 +492,9 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
|
||||
heating[FPSTR(S_ENABLED)] = src.heating.enabled;
|
||||
heating[FPSTR(S_TURBO)] = src.heating.turbo;
|
||||
heating[FPSTR(S_TARGET)] = roundf(src.heating.target, 2);
|
||||
heating[FPSTR(S_HYSTERESIS)] = roundf(src.heating.hysteresis, 3);
|
||||
heating[FPSTR(S_HYSTERESIS)][FPSTR(S_ENABLED)] = src.heating.hysteresis.enabled;
|
||||
heating[FPSTR(S_HYSTERESIS)][FPSTR(S_VALUE)] = roundf(src.heating.hysteresis.value, 3);
|
||||
heating[FPSTR(S_HYSTERESIS)][FPSTR(S_ACTION)] = static_cast<uint8_t>(src.heating.hysteresis.action);
|
||||
heating[FPSTR(S_TURBO_FACTOR)] = roundf(src.heating.turboFactor, 3);
|
||||
heating[FPSTR(S_MIN_TEMP)] = src.heating.minTemp;
|
||||
heating[FPSTR(S_MAX_TEMP)] = src.heating.maxTemp;
|
||||
@@ -517,9 +521,10 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
|
||||
|
||||
auto equitherm = dst[FPSTR(S_EQUITHERM)].to<JsonObject>();
|
||||
equitherm[FPSTR(S_ENABLED)] = src.equitherm.enabled;
|
||||
equitherm[FPSTR(S_N_FACTOR)] = roundf(src.equitherm.n_factor, 3);
|
||||
equitherm[FPSTR(S_K_FACTOR)] = roundf(src.equitherm.k_factor, 3);
|
||||
equitherm[FPSTR(S_T_FACTOR)] = roundf(src.equitherm.t_factor, 3);
|
||||
equitherm[FPSTR(S_SLOPE)] = roundf(src.equitherm.slope, 3);
|
||||
equitherm[FPSTR(S_EXPONENT)] = roundf(src.equitherm.exponent, 3);
|
||||
equitherm[FPSTR(S_SHIFT)] = roundf(src.equitherm.shift, 2);
|
||||
equitherm[FPSTR(S_TARGET_DIFF_FACTOR)] = roundf(src.equitherm.targetDiffFactor, 3);
|
||||
|
||||
auto pid = dst[FPSTR(S_PID)].to<JsonObject>();
|
||||
pid[FPSTR(S_ENABLED)] = src.pid.enabled;
|
||||
@@ -1000,11 +1005,20 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
}
|
||||
}
|
||||
|
||||
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 (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_ALWAYS_SEND_INDOOR_TEMP)].is<bool>()) {
|
||||
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_ALWAYS_SEND_INDOOR_TEMP)].as<bool>();
|
||||
|
||||
if (value != dst.opentherm.options.nativeHeatingControl) {
|
||||
dst.opentherm.options.nativeHeatingControl = value;
|
||||
if (value != dst.opentherm.options.alwaysSendIndoorTemp) {
|
||||
dst.opentherm.options.alwaysSendIndoorTemp = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_NATIVE_OTC)].is<bool>()) {
|
||||
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_NATIVE_OTC)].as<bool>();
|
||||
|
||||
if (value != dst.opentherm.options.nativeOTC) {
|
||||
dst.opentherm.options.nativeOTC = value;
|
||||
|
||||
if (value) {
|
||||
dst.equitherm.enabled = false;
|
||||
@@ -1024,7 +1038,6 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// mqtt
|
||||
if (src[FPSTR(S_MQTT)][FPSTR(S_ENABLED)].is<bool>()) {
|
||||
bool value = src[FPSTR(S_MQTT)][FPSTR(S_ENABLED)].as<bool>();
|
||||
@@ -1115,7 +1128,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.options.nativeHeatingControl) {
|
||||
if (!dst.opentherm.options.nativeOTC) {
|
||||
if (value != dst.equitherm.enabled) {
|
||||
dst.equitherm.enabled = value;
|
||||
changed = true;
|
||||
@@ -1127,29 +1140,38 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
}
|
||||
}
|
||||
|
||||
if (!src[FPSTR(S_EQUITHERM)][FPSTR(S_N_FACTOR)].isNull()) {
|
||||
float value = src[FPSTR(S_EQUITHERM)][FPSTR(S_N_FACTOR)].as<float>();
|
||||
if (!src[FPSTR(S_EQUITHERM)][FPSTR(S_SLOPE)].isNull()) {
|
||||
float value = src[FPSTR(S_EQUITHERM)][FPSTR(S_SLOPE)].as<float>();
|
||||
|
||||
if (value > 0 && value <= 10 && fabsf(value - dst.equitherm.n_factor) > 0.0001f) {
|
||||
dst.equitherm.n_factor = roundf(value, 3);
|
||||
if (value > 0.0f && value <= 10.0f && fabsf(value - dst.equitherm.slope) > 0.0001f) {
|
||||
dst.equitherm.slope = roundf(value, 3);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!src[FPSTR(S_EQUITHERM)][FPSTR(S_K_FACTOR)].isNull()) {
|
||||
float value = src[FPSTR(S_EQUITHERM)][FPSTR(S_K_FACTOR)].as<float>();
|
||||
if (!src[FPSTR(S_EQUITHERM)][FPSTR(S_EXPONENT)].isNull()) {
|
||||
float value = src[FPSTR(S_EQUITHERM)][FPSTR(S_EXPONENT)].as<float>();
|
||||
|
||||
if (value >= 0 && value <= 10 && fabsf(value - dst.equitherm.k_factor) > 0.0001f) {
|
||||
dst.equitherm.k_factor = roundf(value, 3);
|
||||
if (value > 0.0f && value <= 2.0f && fabsf(value - dst.equitherm.exponent) > 0.0001f) {
|
||||
dst.equitherm.exponent = roundf(value, 3);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!src[FPSTR(S_EQUITHERM)][FPSTR(S_T_FACTOR)].isNull()) {
|
||||
float value = src[FPSTR(S_EQUITHERM)][FPSTR(S_T_FACTOR)].as<float>();
|
||||
if (!src[FPSTR(S_EQUITHERM)][FPSTR(S_SHIFT)].isNull()) {
|
||||
float value = src[FPSTR(S_EQUITHERM)][FPSTR(S_SHIFT)].as<float>();
|
||||
|
||||
if (value >= 0 && value <= 10 && fabsf(value - dst.equitherm.t_factor) > 0.0001f) {
|
||||
dst.equitherm.t_factor = roundf(value, 3);
|
||||
if (value >= -15.0f && value <= 15.0f && fabsf(value - dst.equitherm.shift) > 0.0001f) {
|
||||
dst.equitherm.shift = roundf(value, 2);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!src[FPSTR(S_EQUITHERM)][FPSTR(S_TARGET_DIFF_FACTOR)].isNull()) {
|
||||
float value = src[FPSTR(S_EQUITHERM)][FPSTR(S_TARGET_DIFF_FACTOR)].as<float>();
|
||||
|
||||
if (value >= 0.0f && value <= 10.0f && fabsf(value - dst.equitherm.targetDiffFactor) > 0.0001f) {
|
||||
dst.equitherm.targetDiffFactor = roundf(value, 3);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
@@ -1159,7 +1181,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.options.nativeHeatingControl) {
|
||||
if (!dst.opentherm.options.nativeOTC) {
|
||||
if (value != dst.pid.enabled) {
|
||||
dst.pid.enabled = value;
|
||||
changed = true;
|
||||
@@ -1304,15 +1326,41 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
||||
}
|
||||
}
|
||||
|
||||
if (!src[FPSTR(S_HEATING)][FPSTR(S_HYSTERESIS)].isNull()) {
|
||||
float value = src[FPSTR(S_HEATING)][FPSTR(S_HYSTERESIS)].as<float>();
|
||||
if (src[FPSTR(S_HEATING)][FPSTR(S_HYSTERESIS)][FPSTR(S_ENABLED)].is<bool>()) {
|
||||
bool value = src[FPSTR(S_HEATING)][FPSTR(S_HYSTERESIS)][FPSTR(S_ENABLED)].as<bool>();
|
||||
|
||||
if (value >= 0.0f && value <= 15.0f && fabsf(value - dst.heating.hysteresis) > 0.0001f) {
|
||||
dst.heating.hysteresis = roundf(value, 2);
|
||||
if (value != dst.heating.hysteresis.enabled) {
|
||||
dst.heating.hysteresis.enabled = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!src[FPSTR(S_HEATING)][FPSTR(S_HYSTERESIS)][FPSTR(S_VALUE)].isNull()) {
|
||||
float value = src[FPSTR(S_HEATING)][FPSTR(S_HYSTERESIS)][FPSTR(S_VALUE)].as<float>();
|
||||
|
||||
if (value >= 0.0f && value <= 15.0f && fabsf(value - dst.heating.hysteresis.value) > 0.0001f) {
|
||||
dst.heating.hysteresis.value = roundf(value, 2);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!src[FPSTR(S_HEATING)][FPSTR(S_HYSTERESIS)][FPSTR(S_ACTION)].isNull()) {
|
||||
uint8_t value = src[FPSTR(S_HEATING)][FPSTR(S_HYSTERESIS)][FPSTR(S_ACTION)].as<uint8_t>();
|
||||
|
||||
switch (value) {
|
||||
case static_cast<uint8_t>(HysteresisAction::DISABLE_HEATING):
|
||||
case static_cast<uint8_t>(HysteresisAction::SET_ZERO_TARGET):
|
||||
if (static_cast<uint8_t>(dst.heating.hysteresis.action) != value) {
|
||||
dst.heating.hysteresis.action = static_cast<HysteresisAction>(value);
|
||||
changed = true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!src[FPSTR(S_HEATING)][FPSTR(S_TURBO_FACTOR)].isNull()) {
|
||||
float value = src[FPSTR(S_HEATING)][FPSTR(S_TURBO_FACTOR)].as<float>();
|
||||
|
||||
@@ -1666,7 +1714,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.options.nativeHeatingControl;
|
||||
bool noRegulators = !dst.opentherm.options.nativeOTC;
|
||||
bool valid = isValidTemp(
|
||||
value,
|
||||
dst.system.unitSystem,
|
||||
@@ -1691,7 +1739,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.options.nativeHeatingControl;
|
||||
bool indoorTempControl = dst.equitherm.enabled || dst.pid.enabled || dst.opentherm.options.nativeOTC;
|
||||
float minTemp = indoorTempControl ? THERMOSTAT_INDOOR_MIN_TEMP : dst.heating.minTemp;
|
||||
float maxTemp = indoorTempControl ? THERMOSTAT_INDOOR_MAX_TEMP : dst.heating.maxTemp;
|
||||
|
||||
|
||||
@@ -356,7 +356,16 @@
|
||||
},
|
||||
|
||||
"heating": {
|
||||
"hyst": "滞后值<small>(单位:度)</small>",
|
||||
"hyst": {
|
||||
"title": "滞回",
|
||||
"desc": "滞回有助于维持设定的室内温度(在使用«Equitherm»和/或«PID»时)。强制禁用加热当<code>current indoor > target + value</code>,启用加热当<code>current indoor < (target - value)</code>。",
|
||||
"value": "值 <small>(以度为单位)</small>",
|
||||
"action": {
|
||||
"title": "行动",
|
||||
"disableHeating": "禁用加热",
|
||||
"set0target": "设置空目标"
|
||||
}
|
||||
},
|
||||
"turboFactor": "Turbo 模式系数"
|
||||
},
|
||||
|
||||
@@ -371,11 +380,26 @@
|
||||
},
|
||||
|
||||
"equitherm": {
|
||||
"n": "N 系数",
|
||||
"k": "K 系数",
|
||||
"t": {
|
||||
"title": "T 系数",
|
||||
"note": "启用PID时此参数无效"
|
||||
"slope": {
|
||||
"title": "斜率",
|
||||
"note": "热损失补偿。主要调谐参数。"
|
||||
},
|
||||
"exponent": {
|
||||
"title": "指数",
|
||||
"note": "散热器效率。典型值:<code>1.1</code> - 地板采暖,<code>1.2</code> - 铸铁,<code>1.3</code> - 面板散热器,<code>1.4</code> - 对流器。"
|
||||
},
|
||||
"shift": {
|
||||
"title": "偏移",
|
||||
"note": "补偿额外热损失(例如,在管道中)或额外热源。"
|
||||
},
|
||||
"targetDiffFactor": {
|
||||
"title": "T 因子",
|
||||
"note": "如果启用 PID,则不使用。将目标和当前室内温度之间的差值添加到设定点:<code>setpoint = setpoint + ((target - indoor) * T)</code>。"
|
||||
},
|
||||
"chart": {
|
||||
"targetTemp": "目标室内温度",
|
||||
"setpointTemp": "热载体温度",
|
||||
"outdoorTemp": "室外温度"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -433,12 +457,13 @@
|
||||
"autoFaultReset": "自动报警复位 <small>(不推荐!)</small>",
|
||||
"autoDiagReset": "自动诊断复位 <small>(不推荐!)</small>",
|
||||
"setDateAndTime": "同步设置锅炉日期与时间",
|
||||
"immergasFix": "针对Immergas锅炉的兼容性修复"
|
||||
"immergasFix": "针对Immergas锅炉的兼容性修复",
|
||||
"alwaysSendIndoorTemp": "向锅炉发送当前室内温度"
|
||||
},
|
||||
|
||||
"nativeHeating": {
|
||||
"title": "原生锅炉供暖控制",
|
||||
"note": "<u>注意:</u> 仅适用于锅炉需接收目标室温并自主调节载热介质温度的场景,与固件中的PID及Equithermq气候补偿功能不兼容。"
|
||||
"nativeOTC": {
|
||||
"title": "原生热载体温度计算模式",
|
||||
"note": "仅在锅炉处于 OTC 模式时<u>才</u>工作:需要并接受目标室内温度,并基于内置曲线模式自行调节热载体温度。与 PID 和 Equitherm 不兼容。"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -356,7 +356,16 @@
|
||||
},
|
||||
|
||||
"heating": {
|
||||
"hyst": "Hysteresis <small>(in degrees)</small>",
|
||||
"hyst": {
|
||||
"title": "Hysteresis",
|
||||
"desc": "Hysteresis is useful for maintaining a set indoor temp (when using «Equitherm» and/or «PID»). Forces disable heating when <code>current indoor > target + value</code> and enable heating when <code>current indoor < (target - value)</code>.",
|
||||
"value": "Value <small>(in degrees)</small>",
|
||||
"action": {
|
||||
"title": "Action",
|
||||
"disableHeating": "Disable heating",
|
||||
"set0target": "Set null target"
|
||||
}
|
||||
},
|
||||
"turboFactor": "Turbo mode coeff."
|
||||
},
|
||||
|
||||
@@ -371,11 +380,26 @@
|
||||
},
|
||||
|
||||
"equitherm": {
|
||||
"n": "N factor",
|
||||
"k": "K factor",
|
||||
"t": {
|
||||
"slope": {
|
||||
"title": "Slope",
|
||||
"note": "Heat loss compensation. Main tuning parameter."
|
||||
},
|
||||
"exponent": {
|
||||
"title": "Exponent",
|
||||
"note": "Radiator efficiency. Typical values: <code>1.1</code> - Floor heating, <code>1.2</code> - Cast iron, <code>1.3</code> - Panel radiators, <code>1.4</code> - Convectors."
|
||||
},
|
||||
"shift": {
|
||||
"title": "Shift",
|
||||
"note": "Compensates for additional heat losses (e.g., in pipes) or extra heat sources."
|
||||
},
|
||||
"targetDiffFactor": {
|
||||
"title": "T factor",
|
||||
"note": "Not used if PID is enabled"
|
||||
"note": "Not used if PID is enabled. Adds to the setpoint the difference between the target and current indoor temp: <code>setpoint = setpoint + ((target - indoor) * T)</code>."
|
||||
},
|
||||
"chart": {
|
||||
"targetTemp": "Target indoor temperature",
|
||||
"setpointTemp": "Heat carrier temperature",
|
||||
"outdoorTemp": "Outdoor temperature"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -433,12 +457,13 @@
|
||||
"autoFaultReset": "Auto fault reset <small>(not recommended!)</small>",
|
||||
"autoDiagReset": "Auto diag reset <small>(not recommended!)</small>",
|
||||
"setDateAndTime": "Set date & time on boiler",
|
||||
"immergasFix": "Fix for Immergas boilers"
|
||||
"immergasFix": "Fix for Immergas boilers",
|
||||
"alwaysSendIndoorTemp": "Send current indoor temp to boiler"
|
||||
},
|
||||
|
||||
"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 in firmware."
|
||||
"nativeOTC": {
|
||||
"title": "Native OTC mode",
|
||||
"note": "Works <u>ONLY</u> if the boiler is in OTC mode: requires and accepts the target indoor temperature and self-regulates the heat carrier temperature based on the built-in curves mode. Incompatible with PID and Equitherm."
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -356,7 +356,16 @@
|
||||
},
|
||||
|
||||
"heating": {
|
||||
"hyst": "Isteresi <small>(in gradi)</small>",
|
||||
"hyst": {
|
||||
"title": "Isteresi",
|
||||
"desc": "L'isteresi è utile per mantenere una temperatura interna impostata (quando si utilizza «Equitherm» e/o «PID»). Forza la disabilitazione del riscaldamento quando <code>current indoor > target + value</code> e abilita il riscaldamento quando <code>current indoor < (target - value)</code>.",
|
||||
"value": "Valore <small>(in gradi)</small>",
|
||||
"action": {
|
||||
"title": "Azione",
|
||||
"disableHeating": "Disabilita riscaldamento",
|
||||
"set0target": "Imposta target nullo"
|
||||
}
|
||||
},
|
||||
"turboFactor": "Turbo mode coeff."
|
||||
},
|
||||
|
||||
@@ -371,11 +380,26 @@
|
||||
},
|
||||
|
||||
"equitherm": {
|
||||
"n": "Fattore N",
|
||||
"k": "Fattore K",
|
||||
"t": {
|
||||
"slope": {
|
||||
"title": "Pendenza",
|
||||
"note": "Compensazione della perdita di calore. Parametro di regolazione principale."
|
||||
},
|
||||
"exponent": {
|
||||
"title": "Esponente",
|
||||
"note": "Efficienza del radiatore. Valori tipici: <code>1.1</code> - Riscaldamento a pavimento, <code>1.2</code> - Ghisa, <code>1.3</code> - Radiatori a pannello, <code>1.4</code> - Convettori."
|
||||
},
|
||||
"shift": {
|
||||
"title": "Spostamento",
|
||||
"note": "Compensa perdite di calore aggiuntive (ad es., nelle tubature) o fonti di calore extra."
|
||||
},
|
||||
"targetDiffFactor": {
|
||||
"title": "Fattore T",
|
||||
"note": "Non usato se PID è attivato"
|
||||
"note": "Non utilizzato se PID è abilitato. Aggiunge al setpoint la differenza tra la temperatura target e quella interna attuale: <code>setpoint = setpoint + ((target - indoor) * T)</code>."
|
||||
},
|
||||
"chart": {
|
||||
"targetTemp": "Temperatura interna target",
|
||||
"setpointTemp": "Temperatura del vettore termico",
|
||||
"outdoorTemp": "Temperatura esterna"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -433,12 +457,13 @@
|
||||
"autoFaultReset": "Ripristino automatico degli errori <small>(sconsigliato!)</small>",
|
||||
"autoDiagReset": "Ripristino diagnostico automatica <small>(sconsigliato!)</small>",
|
||||
"setDateAndTime": "Imposta data e ora sulla caldaia",
|
||||
"immergasFix": "Fix per caldiaie Immergas"
|
||||
"immergasFix": "Fix per caldiaie Immergas",
|
||||
"alwaysSendIndoorTemp": "Invia la temp attuale interna alla caldaia"
|
||||
},
|
||||
|
||||
"nativeHeating": {
|
||||
"title": "Controllo del riscaldamento nativo (caldaia)",
|
||||
"note": "Lavora <u>SOLO</u> se la caldaia richiede la temperatura ambiente desiderata e regola autonomamente la temperatura del fluido. Non compatiblile con regolazioni PID e Equitherm del sistema."
|
||||
"nativeOTC": {
|
||||
"title": "Modalità nativa di calcolo della temperatura del vettore termico",
|
||||
"note": "Funziona <u>SOLO</u> se la caldaia è in modalità OTC: richiede e accetta la temperatura interna target e regola autonomamente la temperatura del vettore termico basata sulla modalità curve integrata. Incompatibile con PID e Equitherm."
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -327,7 +327,16 @@
|
||||
}
|
||||
},
|
||||
"heating": {
|
||||
"hyst": "Hysterese <small>(in graden)</small>",
|
||||
"hyst": {
|
||||
"title": "Hysterese",
|
||||
"desc": "Hysterese is nuttig voor het handhaven van een ingestelde binnentemperatuur (bij gebruik van «Equitherm» en/of «PID»). Forceert uitschakelen van verwarming wanneer <code>current indoor > target + value</code> en inschakelen van verwarming wanneer <code>current indoor < (target - value)</code>.",
|
||||
"value": "Waarde <small>(in graden)</small>",
|
||||
"action": {
|
||||
"title": "Actie",
|
||||
"disableHeating": "Verwarming uitschakelen",
|
||||
"set0target": "Stel null target in"
|
||||
}
|
||||
},
|
||||
"turboFactor": "Turbomodus coëff."
|
||||
},
|
||||
"emergency": {
|
||||
@@ -339,11 +348,26 @@
|
||||
"treshold": "Drempeltijd <small>(sec)</small>"
|
||||
},
|
||||
"equitherm": {
|
||||
"n": "N-factor",
|
||||
"k": "K-factor",
|
||||
"t": {
|
||||
"title": "T-factor",
|
||||
"note": "Niet gebruikt als PID is ingeschakeld"
|
||||
"slope": {
|
||||
"title": "Helling",
|
||||
"note": "Compensatie voor warmteverlies. Hoofdafstelparameter."
|
||||
},
|
||||
"exponent": {
|
||||
"title": "Exponent",
|
||||
"note": "Radiator efficiëntie. Typische waarden: <code>1.1</code> - Vloerverwarming, <code>1.2</code> - Gietijzer, <code>1.3</code> - Paneelradiatoren, <code>1.4</code> - Convectors."
|
||||
},
|
||||
"shift": {
|
||||
"title": "Verschuiving",
|
||||
"note": "Compenseert voor extra warmteverliezen (bijv. in leidingen) of extra warmtebronnen."
|
||||
},
|
||||
"targetDiffFactor": {
|
||||
"title": "T factor",
|
||||
"note": "Niet gebruikt als PID is ingeschakeld. Voegt aan de setpoint de verschil tussen de target en huidige binnentemperatuur toe: <code>setpoint = setpoint + ((target - indoor) * T)</code>."
|
||||
},
|
||||
"chart": {
|
||||
"targetTemp": "Doel binnentemperatuur",
|
||||
"setpointTemp": "Warmtedrager temperatuur",
|
||||
"outdoorTemp": "Buitentemperatuur"
|
||||
}
|
||||
},
|
||||
"pid": {
|
||||
@@ -398,11 +422,13 @@
|
||||
"autoFaultReset": "Automatische storingsreset <small>(niet aanbevolen!)</small>",
|
||||
"autoDiagReset": "Automatische diagnosereset <small>(niet aanbevolen!)</small>",
|
||||
"setDateAndTime": "Stel datum & tijd in op ketel",
|
||||
"immergasFix": "Fix voor Immergas-ketels"
|
||||
"immergasFix": "Fix voor Immergas-ketels",
|
||||
"alwaysSendIndoorTemp": "Stuur huidige binnentemp naar ketel"
|
||||
},
|
||||
"nativeHeating": {
|
||||
"title": "Natuurlijke verwarmingsregeling (ketel)",
|
||||
"note": "Werkt <u>ALLEEN</u> als de ketel de gewenste kamertemperatuur vereist en zelf de temperatuur van de warmtedrager regelt. Niet compatibel met PID- en Equitherm-regelaars in de firmware."
|
||||
|
||||
"nativeOTC": {
|
||||
"title": "Native warmtedrager temperatuur berekeningsmodus",
|
||||
"note": "Werkt <u>ALLEEN</u> als de ketel in OTC-modus is: vereist en accepteert de doel binnentemperatuur en regelt zelf de warmtedrager temperatuur op basis van de ingebouwde curves modus. Incompatibel met PID en Equitherm."
|
||||
}
|
||||
},
|
||||
"mqtt": {
|
||||
|
||||
@@ -356,7 +356,16 @@
|
||||
},
|
||||
|
||||
"heating": {
|
||||
"hyst": "Гистерезис <small>(в градусах)</small>",
|
||||
"hyst": {
|
||||
"title": "Гистерезис",
|
||||
"desc": "Гистерезис полезен для поддержания заданной внутр. темп. (при использовании «ПЗА» и/или «ПИД»). Принудительно откл. отопление, когда <code>current indoor > target + value</code>, и вкл. отопление, когда <code>current indoor < (target - value)</code>.",
|
||||
"value": "Значение <small>(в градусах)</small>",
|
||||
"action": {
|
||||
"title": "Действие",
|
||||
"disableHeating": "Отключить отопление",
|
||||
"set0target": "Установить 0 в качестве целевой темп."
|
||||
}
|
||||
},
|
||||
"turboFactor": "Коэфф. турбо режима"
|
||||
},
|
||||
|
||||
@@ -371,11 +380,26 @@
|
||||
},
|
||||
|
||||
"equitherm": {
|
||||
"n": "Коэффициент N",
|
||||
"k": "Коэффициент K",
|
||||
"t": {
|
||||
"slope": {
|
||||
"title": "Наклон",
|
||||
"note": "Компенсация теплопотерь. Основной параметр настройки."
|
||||
},
|
||||
"exponent": {
|
||||
"title": "Экспонента",
|
||||
"note": "Эффективность радиатора. Типичные значения: <code>1.1</code> - Тёплый пол, <code>1.2</code> - Чугунные радиаторы, <code>1.3</code> - Панельные радиаторы, <code>1.4</code> - Конвекторы."
|
||||
},
|
||||
"shift": {
|
||||
"title": "Смещение",
|
||||
"note": "Компенсирует дополнительные теплопотери (например, в трубах) или дополнительные источники тепла."
|
||||
},
|
||||
"targetDiffFactor": {
|
||||
"title": "Коэффициент T",
|
||||
"note": "Не используется, если ПИД включен"
|
||||
"note": "Не используется, если ПИД включен. Добавляет разницу между целевой и текущей температурой в помещении: <code>setpoint = setpoint + ((target - indoor) * T)</code>."
|
||||
},
|
||||
"chart": {
|
||||
"targetTemp": "Целевая внутренняя температура",
|
||||
"setpointTemp": "Температура теплоносителя",
|
||||
"outdoorTemp": "Наружная температура"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -433,12 +457,13 @@
|
||||
"autoFaultReset": "Автоматический сброс ошибок <small>(не рекомендуется!)</small>",
|
||||
"autoDiagReset": "Автоматический сброс диагностики <small>(не рекомендуется!)</small>",
|
||||
"setDateAndTime": "Устанавливать время и дату на котле",
|
||||
"immergasFix": "Фикс для котлов Immergas"
|
||||
"immergasFix": "Фикс для котлов Immergas",
|
||||
"alwaysSendIndoorTemp": "Передавать текущую темп. в помещении котлу"
|
||||
},
|
||||
|
||||
"nativeHeating": {
|
||||
"title": "Передать управление отоплением котлу",
|
||||
"note": "Работает <u>ТОЛЬКО</u> если котел требует и принимает целевую температуру в помещении и сам регулирует температуру теплоносителя на основе встроенного режима кривых. Несовместимо с ПИД и ПЗА."
|
||||
"nativeOTC": {
|
||||
"title": "Нативный режим OTC (расчёт температуры теплоносителя)",
|
||||
"note": "Работает <u>ТОЛЬКО</u> если котел в режиме OTC: требует и принимает целевую температуру в помещении и сам регулирует температуру теплоносителя на основе встроенного режима кривых. Несовместимо с ПИД и ПЗА."
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -241,7 +241,9 @@
|
||||
setCheckboxValue("[name='filtering']", data.filtering, sensorForm);
|
||||
setInputValue("[name='filteringFactor']", data.filteringFactor, {}, sensorForm);
|
||||
|
||||
sensorForm.querySelector("[name='type']").dispatchEvent(new Event("change"));
|
||||
setTimeout(() => {
|
||||
sensorForm.querySelector("[name='type']").dispatchEvent(new Event("change"));
|
||||
}, 10);
|
||||
|
||||
setBusy(".form-busy", "form", false, sensorNode);
|
||||
};
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
<option disabled selected data-i18n>settings.system.ntp.timezonePresets</option>
|
||||
</select>
|
||||
</div>
|
||||
</label>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
@@ -193,21 +193,48 @@
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<label>
|
||||
<span data-i18n>settings.heating.hyst</span>
|
||||
<input type="number" inputmode="decimal" name="heating[hysteresis]" min="0" max="5" step="0.05" required>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span data-i18n>settings.heating.turboFactor</span>
|
||||
<input type="number" inputmode="decimal" name="heating[turboFactor]" min="1.5" max="10" step="0.1" required>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span data-i18n>settings.maxModulation</span>
|
||||
<input type="number" inputmode="numeric" name="heating[maxModulation]" min="1" max="100" step="1" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label>
|
||||
<span data-i18n>settings.maxModulation</span>
|
||||
<input type="number" inputmode="numeric" name="heating[maxModulation]" min="1" max="100" step="1" required>
|
||||
</label>
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<summary><b data-i18n>settings.heating.hyst.title</b></summary>
|
||||
|
||||
<div>
|
||||
<fieldset>
|
||||
<label>
|
||||
<input type="checkbox" name="heating[hysteresis][enabled]" value="true">
|
||||
<span data-i18n>settings.enable</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<div class="grid">
|
||||
<label>
|
||||
<span data-i18n>settings.heating.hyst.value</span>
|
||||
<input type="number" inputmode="decimal" name="heating[hysteresis][value]" min="0" max="5" step="0.05" required>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span data-i18n>settings.heating.hyst.action.title</span>
|
||||
<select name="heating[hysteresis][action]">
|
||||
<option value="0" data-i18n>settings.heating.hyst.action.disableHeating</option>
|
||||
<option value="1" data-i18n>settings.heating.hyst.action.set0target</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<small data-i18n>settings.heating.hyst.desc</small>
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
@@ -354,21 +381,44 @@
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<canvas id="etChart"></canvas>
|
||||
</div>
|
||||
|
||||
<label>
|
||||
<div>
|
||||
<span data-i18n>settings.equitherm.chart.targetTemp</span>: <b class="etChartTargetTempValue"></b>°
|
||||
</div>
|
||||
<input class="etChartTargetTemp" type="range" value="0" min="0" max="0" step="0.5">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<label>
|
||||
<span data-i18n>settings.equitherm.n</span>
|
||||
<input type="number" inputmode="decimal" name="equitherm[n_factor]" min="0.001" max="10" step="0.001" required>
|
||||
<span data-i18n>settings.equitherm.slope.title</span>
|
||||
<input type="number" inputmode="decimal" name="equitherm[slope]" min="0.001" max="10" step="0.001" required>
|
||||
<small data-i18n>settings.equitherm.slope.note</small>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span data-i18n>settings.equitherm.k</span>
|
||||
<input type="number" inputmode="decimal" name="equitherm[k_factor]" min="0" max="10" step="0.01" required>
|
||||
<span data-i18n>settings.equitherm.exponent.title</span>
|
||||
<input type="number" inputmode="decimal" name="equitherm[exponent]" min="0.1" max="2" step="0.001" required>
|
||||
<small data-i18n>settings.equitherm.exponent.note</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<label>
|
||||
<span data-i18n>settings.equitherm.shift.title</span>
|
||||
<input type="number" inputmode="decimal" name="equitherm[shift]" min="-15" max="15" step="0.01" required>
|
||||
<small data-i18n>settings.equitherm.shift.note</small>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span data-i18n>settings.equitherm.t.title</span>
|
||||
<input type="number" inputmode="decimal" name="equitherm[t_factor]" min="0" max="10" step="0.01" required>
|
||||
<small data-i18n>settings.equitherm.t.note</small>
|
||||
<span data-i18n>settings.equitherm.targetDiffFactor.title</span>
|
||||
<input type="number" inputmode="decimal" name="equitherm[targetDiffFactor]" min="0" max="10" step="0.01" required>
|
||||
<small data-i18n>settings.equitherm.targetDiffFactor.note</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -642,12 +692,17 @@
|
||||
<span data-i18n>settings.ot.options.immergasFix</span>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="checkbox" name="opentherm[options][alwaysSendIndoorTemp]" value="true">
|
||||
<span data-i18n>settings.ot.options.alwaysSendIndoorTemp</span>
|
||||
</label>
|
||||
|
||||
<hr />
|
||||
|
||||
<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>
|
||||
<input type="checkbox" name="opentherm[options][nativeOTC]" value="true">
|
||||
<span data-i18n>settings.ot.nativeOTC.title</span><br />
|
||||
<small data-i18n>settings.ot.nativeOTC.note</small>
|
||||
</label>
|
||||
</fieldset>
|
||||
</div>
|
||||
@@ -866,11 +921,163 @@
|
||||
</footer>
|
||||
|
||||
<script src="/static/app.js?{BUILD_TIME}"></script>
|
||||
<script src="/static/chart.js?{BUILD_TIME}"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const lang = new Lang(document.getElementById('lang'));
|
||||
lang.build();
|
||||
|
||||
let etChart = null;
|
||||
let etChartConfig = {
|
||||
slope: null,
|
||||
exponent: null,
|
||||
shift: null,
|
||||
unitSystem: null,
|
||||
targetTemp: null,
|
||||
minTemp: null,
|
||||
maxTemp: null,
|
||||
decimated: false
|
||||
};
|
||||
|
||||
const hasNeedDecimationChart = () => {
|
||||
return window.innerWidth <= 800;
|
||||
}
|
||||
|
||||
const makeEquithermChart = () => {
|
||||
if (etChart == null) {
|
||||
const ctx = document.getElementById('etChart').getContext('2d');
|
||||
|
||||
try {
|
||||
etChart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
datasets: [{
|
||||
borderColor: (context) => {
|
||||
const chart = context.chart;
|
||||
const { ctx, chartArea } = chart;
|
||||
|
||||
if (!chartArea) {
|
||||
return;
|
||||
}
|
||||
|
||||
const gradient = ctx.createLinearGradient(0, chartArea.bottom, 0, chartArea.top);
|
||||
gradient.addColorStop(0, 'rgba(1, 114, 173, 1)');
|
||||
gradient.addColorStop(0.5, 'rgba(255, 99, 132, 1)');
|
||||
|
||||
return gradient;
|
||||
},
|
||||
borderWidth: 3,
|
||||
fill: false,
|
||||
tension: 0.1,
|
||||
pointRadius: 2,
|
||||
pointHoverRadius: 4,
|
||||
indexAxis: "x",
|
||||
data: []
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
resizeDelay: 500,
|
||||
parsing: false,
|
||||
interaction: {
|
||||
mode: 'nearest',
|
||||
intersect: false
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
position: 'nearest',
|
||||
displayColors: false,
|
||||
callbacks: {
|
||||
title: (items) => {
|
||||
return `${i18n("settings.equitherm.chart.outdoorTemp")}: ${items[0].label}`;
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
display: false
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
display: true,
|
||||
type: "linear",
|
||||
reverse: true,
|
||||
title: {
|
||||
display: true
|
||||
},
|
||||
ticks: {
|
||||
stepSize: 1,
|
||||
format: {
|
||||
style: "unit",
|
||||
unit: "degree",
|
||||
unitDisplay: "narrow"
|
||||
}
|
||||
}
|
||||
},
|
||||
y: {
|
||||
display: true,
|
||||
title: {
|
||||
display: true
|
||||
},
|
||||
ticks: {
|
||||
format: {
|
||||
style: "unit",
|
||||
unit: "degree",
|
||||
unitDisplay: "narrow"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
if (!etChart) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = [];
|
||||
etChartConfig.decimated = hasNeedDecimationChart();
|
||||
for (let value = 30; value >= -30; value -= etChartConfig.decimated ? 2 : 1) {
|
||||
const outdoorTemp = etChartConfig.unitSystem == 0 ? value : c2f(value);
|
||||
|
||||
data.push({
|
||||
x: parseFloat(outdoorTemp.toFixed(1)),
|
||||
y: parseFloat(calculateEquithermTemp(outdoorTemp).toFixed(1))
|
||||
});
|
||||
}
|
||||
|
||||
etChart.data.datasets[0].data = data;
|
||||
etChart.data.datasets[0].label = i18n("settings.equitherm.chart.setpointTemp");
|
||||
etChart.options.scales.x.title.text = i18n("settings.equitherm.chart.outdoorTemp");
|
||||
etChart.options.scales.y.title.text = i18n("settings.equitherm.chart.setpointTemp");
|
||||
etChart.update();
|
||||
}
|
||||
|
||||
const calculateEquithermTemp = (outdoorTemp) => {
|
||||
const tempDelta = etChartConfig.targetTemp - outdoorTemp;
|
||||
const maxPoint = etChartConfig.targetTemp - (
|
||||
etChartConfig.maxTemp - etChartConfig.targetTemp
|
||||
) / etChartConfig.slope;
|
||||
|
||||
const sf = (etChartConfig.maxTemp - etChartConfig.targetTemp) / Math.pow(
|
||||
etChartConfig.targetTemp - maxPoint,
|
||||
1 / etChartConfig.exponent
|
||||
);
|
||||
const result = etChartConfig.targetTemp + etChartConfig.shift + sf * (
|
||||
tempDelta >= 0
|
||||
? Math.pow(tempDelta, 1 / etChartConfig.exponent)
|
||||
: -(Math.pow(-(tempDelta), 1 / etChartConfig.exponent))
|
||||
);
|
||||
|
||||
return Math.max(Math.min(result, etChartConfig.maxTemp), etChartConfig.minTemp);
|
||||
}
|
||||
|
||||
const fillData = (data) => {
|
||||
// System
|
||||
setSelectValue("[name='system[logLevel]']", data.system.logLevel);
|
||||
@@ -915,8 +1122,9 @@
|
||||
setCheckboxValue("[name='opentherm[options][autoFaultReset]']", data.opentherm.options.autoFaultReset);
|
||||
setCheckboxValue("[name='opentherm[options][autoDiagReset]']", data.opentherm.options.autoDiagReset);
|
||||
setCheckboxValue("[name='opentherm[options][setDateAndTime]']", data.opentherm.options.setDateAndTime);
|
||||
setCheckboxValue("[name='opentherm[options][nativeHeatingControl]']", data.opentherm.options.nativeHeatingControl);
|
||||
setCheckboxValue("[name='opentherm[options][nativeOTC]']", data.opentherm.options.nativeOTC);
|
||||
setCheckboxValue("[name='opentherm[options][immergasFix]']", data.opentherm.options.immergasFix);
|
||||
setCheckboxValue("[name='opentherm[options][alwaysSendIndoorTemp]']", data.opentherm.options.alwaysSendIndoorTemp);
|
||||
setBusy('#ot-settings-busy', '#ot-settings', false);
|
||||
|
||||
// MQTT
|
||||
@@ -963,7 +1171,9 @@
|
||||
"min": data.system.unitSystem == 0 ? 1 : 33,
|
||||
"max": data.system.unitSystem == 0 ? 100 : 212
|
||||
});
|
||||
setInputValue("[name='heating[hysteresis]']", data.heating.hysteresis);
|
||||
setCheckboxValue("[name='heating[hysteresis][enabled]']", data.heating.hysteresis.enabled);
|
||||
setInputValue("[name='heating[hysteresis][value]']", data.heating.hysteresis.value);
|
||||
setSelectValue("[name='heating[hysteresis][action]']", data.heating.hysteresis.action);
|
||||
setInputValue("[name='heating[turboFactor]']", data.heating.turboFactor);
|
||||
setInputValue("[name='heating[maxModulation]']", data.heating.maxModulation);
|
||||
setInputValue("[name='heating[overheatProtection][highTemp]']", data.heating.overheatProtection.highTemp, {
|
||||
@@ -1002,7 +1212,7 @@
|
||||
setBusy('#dhw-settings-busy', '#dhw-settings', false);
|
||||
|
||||
// Emergency mode
|
||||
if (data.opentherm.options.nativeHeatingControl) {
|
||||
if (data.opentherm.options.nativeOTC) {
|
||||
setInputValue("[name='emergency[target]']", data.emergency.target, {
|
||||
"min": data.system.unitSystem == 0 ? 5 : 41,
|
||||
"max": data.system.unitSystem == 0 ? 40 : 104
|
||||
@@ -1019,9 +1229,10 @@
|
||||
|
||||
// Equitherm
|
||||
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);
|
||||
setInputValue("[name='equitherm[slope]']", data.equitherm.slope);
|
||||
setInputValue("[name='equitherm[exponent]']", data.equitherm.exponent);
|
||||
setInputValue("[name='equitherm[shift]']", data.equitherm.shift);
|
||||
setInputValue("[name='equitherm[targetDiffFactor]']", data.equitherm.targetDiffFactor);
|
||||
setBusy('#equitherm-settings-busy', '#equitherm-settings', false);
|
||||
|
||||
// PID
|
||||
@@ -1045,6 +1256,24 @@
|
||||
setInputValue("[name='pid[deadband][thresholdHigh]']", data.pid.deadband.thresholdHigh);
|
||||
setInputValue("[name='pid[deadband][thresholdLow]']", data.pid.deadband.thresholdLow);
|
||||
setBusy('#pid-settings-busy', '#pid-settings', false);
|
||||
|
||||
const etMinTemp = parseInt(data.system.unitSystem == 0 ? 5 : 41);
|
||||
const etMaxTemp = parseInt(data.system.unitSystem == 0 ? 30 : 86);
|
||||
const etTargetTemp = constrain(parseFloat(data.heating.target), etMinTemp, etMaxTemp);
|
||||
|
||||
setInputValue(".etChartTargetTemp", etTargetTemp.toFixed(1), {
|
||||
"min": etMinTemp,
|
||||
"max": etMaxTemp
|
||||
});
|
||||
|
||||
etChartConfig.slope = data.equitherm.slope;
|
||||
etChartConfig.exponent = data.equitherm.exponent;
|
||||
etChartConfig.shift = data.equitherm.shift;
|
||||
etChartConfig.unitSystem = data.system.unitSystem;
|
||||
etChartConfig.minTemp = data.heating.minTemp;
|
||||
etChartConfig.maxTemp = data.heating.maxTemp;
|
||||
|
||||
makeEquithermChart();
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -1097,6 +1326,57 @@
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
document.querySelector(".etChartTargetTemp").addEventListener("input", async (event) => {
|
||||
setValue('.etChartTargetTempValue', parseFloat(event.target.value).toFixed(1));
|
||||
});
|
||||
|
||||
document.querySelector(".etChartTargetTemp").addEventListener("change", async (event) => {
|
||||
if (!event.target.checkValidity()) {
|
||||
return;
|
||||
}
|
||||
|
||||
etChartConfig.targetTemp = parseFloat(event.target.value);
|
||||
setValue('.etChartTargetTempValue', etChartConfig.targetTemp.toFixed(1));
|
||||
makeEquithermChart();
|
||||
});
|
||||
|
||||
document.querySelector("[name='equitherm[slope]']").addEventListener("change", async (event) => {
|
||||
if (!event.target.checkValidity()) {
|
||||
return;
|
||||
}
|
||||
|
||||
etChartConfig.slope = parseFloat(event.target.value);
|
||||
makeEquithermChart();
|
||||
});
|
||||
|
||||
document.querySelector("[name='equitherm[exponent]']").addEventListener("change", async (event) => {
|
||||
if (!event.target.checkValidity()) {
|
||||
return;
|
||||
}
|
||||
|
||||
etChartConfig.exponent = parseFloat(event.target.value);
|
||||
makeEquithermChart();
|
||||
});
|
||||
|
||||
document.querySelector("[name='equitherm[shift]']").addEventListener("change", async (event) => {
|
||||
if (!event.target.checkValidity()) {
|
||||
return;
|
||||
}
|
||||
|
||||
etChartConfig.shift = parseFloat(event.target.value);
|
||||
makeEquithermChart();
|
||||
});
|
||||
|
||||
window.addEventListener('resize', async (event) => {
|
||||
if (etChart) {
|
||||
etChart.resize();
|
||||
|
||||
if (etChartConfig.decimated != hasNeedDecimationChart()) {
|
||||
makeEquithermChart();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
14
src_data/scripts/chart.js
Normal file
14
src_data/scripts/chart.js
Normal file
File diff suppressed because one or more lines are too long
@@ -5,8 +5,13 @@ const setupForm = (formSelector, onResultCallback = null, noCastItems = []) => {
|
||||
}
|
||||
|
||||
form.querySelectorAll('input').forEach(item => {
|
||||
item.addEventListener('change', (e) => {
|
||||
e.target.setAttribute('aria-invalid', !e.target.checkValidity());
|
||||
item.addEventListener('change', (event) => {
|
||||
if (!event.target.checkValidity()) {
|
||||
event.target.setAttribute('aria-invalid', true);
|
||||
|
||||
} else if (event.target.hasAttribute('aria-invalid')) {
|
||||
event.target.removeAttribute('aria-invalid');
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
@@ -630,6 +635,10 @@ const setCheckboxValue = (selector, value, parent = undefined) => {
|
||||
}
|
||||
|
||||
item.checked = value;
|
||||
|
||||
setTimeout(() => {
|
||||
item.dispatchEvent(new Event("change"));
|
||||
}, 10);
|
||||
}
|
||||
|
||||
const setRadioValue = (selector, value, parent = undefined) => {
|
||||
@@ -643,7 +652,14 @@ const setRadioValue = (selector, value, parent = undefined) => {
|
||||
}
|
||||
|
||||
for (let item of items) {
|
||||
item.checked = item.value == value;
|
||||
const checked = item.value == value;
|
||||
if (item.checked != checked) {
|
||||
item.checked = checked;
|
||||
|
||||
setTimeout(() => {
|
||||
item.dispatchEvent(new Event("change"));
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -658,13 +674,17 @@ const setInputValue = (selector, value, attrs = {}, parent = undefined) => {
|
||||
}
|
||||
|
||||
for (let item of items) {
|
||||
item.value = value;
|
||||
|
||||
if (attrs instanceof Object) {
|
||||
for (let attrKey of Object.keys(attrs)) {
|
||||
item.setAttribute(attrKey, attrs[attrKey]);
|
||||
}
|
||||
}
|
||||
|
||||
item.value = value;
|
||||
|
||||
setTimeout(() => {
|
||||
item.dispatchEvent(new Event("change"));
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -854,3 +874,11 @@ function dec2hex(i) {
|
||||
function constrain(amt, low, high) {
|
||||
return ((amt) < (low) ? (low) : ((amt) > (high) ? (high) : (amt)));
|
||||
}
|
||||
|
||||
function c2f(value) {
|
||||
return (9 / 5) * value + 32;
|
||||
}
|
||||
|
||||
function f2c(value) {
|
||||
return (value - 32) * (5 / 9);
|
||||
}
|
||||
Reference in New Issue
Block a user