From e5f4281d4c5e3e539e32308e5a7298a8f56d3bc9 Mon Sep 17 00:00:00 2001 From: P43YM Date: Fri, 28 Feb 2025 23:21:55 +0300 Subject: [PATCH] feat: new equitherm algorithm and chart for it (#144) --- lib/Equitherm/Equitherm.h | 31 +++--- src/HaHelper.h | 23 ++++ src/RegulatorTask.h | 1 + src/Settings.h | 1 + src/strings.h | 1 + src/utils.h | 9 ++ src_data/locales/en.json | 5 + src_data/locales/it.json | 5 + src_data/locales/ru.json | 5 + src_data/pages/settings.html | 201 ++++++++++++++++++++++++++++++++++- 10 files changed, 263 insertions(+), 19 deletions(-) diff --git a/lib/Equitherm/Equitherm.h b/lib/Equitherm/Equitherm.h index 287b4f2..cdc8bfc 100644 --- a/lib/Equitherm/Equitherm.h +++ b/lib/Equitherm/Equitherm.h @@ -16,14 +16,16 @@ public: float Kn = 0.0; float Kk = 0.0; float Kt = 0.0; + float Ke = 1.3; Equitherm() = default; - // kn, kk, kt - Equitherm(float new_kn, float new_kk, float new_kt) { + // kn, kk, kt, Ke + Equitherm(float new_kn, float new_kk, float new_kt, float new_ke) { Kn = new_kn; Kk = new_kk; Kt = new_kt; + Ke = new_ke; } // лимит выходной величины @@ -34,7 +36,7 @@ public: // возвращает новое значение при вызове datatype getResult() { - datatype output = getResultN() + getResultK() + getResultT(); + datatype output = getResultN() + Kk + getResultT(); output = constrain(output, _minOut, _maxOut); // ограничиваем выход return output; } @@ -42,22 +44,17 @@ public: 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 getResultN() + { + float tempDelta = targetTemp - outdoorTemp, + maxPoint = targetTemp - (_maxOut - targetTemp) / Kn, + sf = (_maxOut - targetTemp) / pow(targetTemp - maxPoint, 1.0 / Ke), + T_rad = targetTemp + sf * (tempDelta >= 0 ? pow(tempDelta, 1.0 / Ke) : -pow(-tempDelta, 1.0 / Ke)); + return T_rad > _maxOut ? _maxOut : T_rad; } - // поправка на желаемую комнатную температуру - datatype getResultK() { - return (targetTemp - 20) * Kk; - } - - // Расчет поправки (ошибки) термостата + // Реакция на разницу с целевой температурой datatype getResultT() { return constrain((targetTemp - indoorTemp), -3, 3) * Kt; } -}; \ No newline at end of file +}; diff --git a/src/HaHelper.h b/src/HaHelper.h index 3864783..ea5198e 100644 --- a/src/HaHelper.h +++ b/src/HaHelper.h @@ -886,6 +886,29 @@ public: return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_k_factor")).c_str(), doc); } + bool publishInputEquithermFactorE(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->getObjectIdWithPrefix(F("equitherm_e")); + doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); + doc[FPSTR(HA_NAME)] = F("Equitherm Exponent E"); + doc[FPSTR(HA_ICON)] = F("mdi:alpha-e-circle-outline"); + doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str(); + doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.e_factor|float(0)|round(2) }}"); + doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str(); + doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"e_factor\" : {{ value }}}}"); + doc[FPSTR(HA_MIN)] = 1; + doc[FPSTR(HA_MAX)] = 2; + 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_e_factor")).c_str(), doc); + } + bool publishInputEquithermFactorT(bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); diff --git a/src/RegulatorTask.h b/src/RegulatorTask.h index cc67d39..109f289 100644 --- a/src/RegulatorTask.h +++ b/src/RegulatorTask.h @@ -172,6 +172,7 @@ protected: etRegulator.setLimits(minTemp, maxTemp); etRegulator.Kn = settings.equitherm.n_factor; etRegulator.Kk = settings.equitherm.k_factor; + etRegulator.Ke = settings.equitherm.e_factor; etRegulator.targetTemp = targetTemp; etRegulator.outdoorTemp = outdoorTemp; float etResult = etRegulator.getResult(); diff --git a/src/Settings.h b/src/Settings.h index 5a493e2..2deb5ca 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -137,6 +137,7 @@ struct Settings { float n_factor = 0.7f; float k_factor = 3.0f; float t_factor = 2.0f; + float e_factor = 1.3f; } equitherm; struct { diff --git a/src/strings.h b/src/strings.h index 32ad73a..6da8e30 100644 --- a/src/strings.h +++ b/src/strings.h @@ -73,6 +73,7 @@ const char S_DNS[] PROGMEM = "dns"; const char S_DT[] PROGMEM = "dt"; const char S_D_FACTOR[] PROGMEM = "d_factor"; const char S_D_MULTIPLIER[] PROGMEM = "d_multiplier"; +const char S_E_FACTOR[] PROGMEM = "e_factor"; const char S_EMERGENCY[] PROGMEM = "emergency"; const char S_ENABLED[] PROGMEM = "enabled"; const char S_ENV[] PROGMEM = "env"; diff --git a/src/utils.h b/src/utils.h index 261d2d2..3a8cb1d 100644 --- a/src/utils.h +++ b/src/utils.h @@ -502,6 +502,7 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) { 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_E_FACTOR)] = roundf(src.equitherm.e_factor, 3); equitherm[FPSTR(S_T_FACTOR)] = roundf(src.equitherm.t_factor, 3); auto pid = dst[FPSTR(S_PID)].to(); @@ -1100,6 +1101,14 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } + if (!src[FPSTR(S_EQUITHERM)][FPSTR(S_E_FACTOR)].isNull()) { + float value = src[FPSTR(S_EQUITHERM)][FPSTR(S_E_FACTOR)].as(); + + if (value >= 1 && value <= 2 && fabsf(value - dst.equitherm.e_factor) > 0.0001f) { + dst.equitherm.e_factor = 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(); diff --git a/src_data/locales/en.json b/src_data/locales/en.json index 8dcbf79..3eb3469 100644 --- a/src_data/locales/en.json +++ b/src_data/locales/en.json @@ -342,9 +342,14 @@ "equitherm": { "n": "N factor", "k": "K factor", + "e": "Exponent E", "t": { "title": "T factor", "note": "Not used if PID is enabled" + }, + "chart": { + "radiatorTemp": "Radiator Temperature (°C)", + "outdoorTemp": "Outdoor Temperature (°C)" } }, diff --git a/src_data/locales/it.json b/src_data/locales/it.json index a0b8e05..643a6df 100644 --- a/src_data/locales/it.json +++ b/src_data/locales/it.json @@ -342,9 +342,14 @@ "equitherm": { "n": "Fattore N", "k": "Fattore K", + "e": "Esponente E", "t": { "title": "Fattore T", "note": "Non usato se PID è attivato" + }, + "chart": { + "radiatorTemp": "Temperatura Del Radiatore (°C)", + "outdoorTemp": "Outdoor Temperature (°C)" } }, diff --git a/src_data/locales/ru.json b/src_data/locales/ru.json index fa1daa8..6f98b81 100644 --- a/src_data/locales/ru.json +++ b/src_data/locales/ru.json @@ -342,9 +342,14 @@ "equitherm": { "n": "Коэффициент N", "k": "Коэффициент K", + "e": "Экспонента E", "t": { "title": "Коэффициент T", "note": "Не используется, если ПИД включен" + }, + "chart": { + "radiatorTemp": "Температура радиатора (°C)", + "outdoorTemp": "Наружная температура (°C)" } }, diff --git a/src_data/pages/settings.html b/src_data/pages/settings.html index 2b9d7d7..69b6bbc 100644 --- a/src_data/pages/settings.html +++ b/src_data/pages/settings.html @@ -261,9 +261,10 @@
- +
settings.section.equitherm +