diff --git a/src/MainTask.h b/src/MainTask.h index f97329b..f087921 100644 --- a/src/MainTask.h +++ b/src/MainTask.h @@ -225,7 +225,7 @@ protected: uint8_t availableSensors = 0; if (Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::INDOOR_TEMP)) { - auto value = Sensors::getMeanValueByPurpose(Sensors::Purpose::INDOOR_TEMP, Sensors::ValueType::PRIMARY); + auto value = Sensors::getMeanValueByPurpose(Sensors::Purpose::INDOOR_TEMP, Sensors::ValueType::PRIMARY, settings.heating.indoorTempAvgType); if (value < lowTemp) { lowTemp = value; } diff --git a/src/Sensors.h b/src/Sensors.h index bd9b524..8095414 100644 --- a/src/Sensors.h +++ b/src/Sensors.h @@ -77,6 +77,12 @@ public: RSSI = 3 }; + enum class AverageType : uint8_t { + MEAN = 0, + MINIMUM = 1, + MAXIMUM = 2 + }; + typedef struct { bool enabled = false; char name[33]; @@ -330,38 +336,61 @@ public: return updated; } - static float getMeanValueByPurpose(Purpose purpose, const ValueType valueType, bool onlyConnected = true) { + static float getMeanValueByPurpose(Purpose purpose, const ValueType valueType, const AverageType avgType = AverageType::MEAN, const bool onlyConnected = true, const float defaultValue = NAN) { if (settings == nullptr || results == nullptr) { - return 0.0f; + return defaultValue; } uint8_t valueId = (uint8_t) valueType; if (!isValidValueId(valueId)) { - return 0.0f; + return defaultValue; } float value = 0.0f; uint8_t amount = 0; - for (uint8_t id = 0; id <= getMaxSensorId(); id++) { - auto& sSensor = settings[id]; - auto& rSensor = results[id]; + if (avgType == AverageType::MEAN) { + float sum = 0.0f; + for (uint8_t id = 0; id <= getMaxSensorId(); id++) { + auto& sSensor = settings[id]; + auto& rSensor = results[id]; - if (sSensor.purpose == purpose && (!onlyConnected || rSensor.connected)) { - value += rSensor.values[valueId]; - amount++; + if (sSensor.purpose == purpose && (!onlyConnected || rSensor.connected)) { + sum += rSensor.values[valueId]; + amount++; + } + } + + value = amount == 1 ? sum : (sum / amount); + + } else if (avgType == AverageType::MINIMUM) { + for (uint8_t id = 0; id <= getMaxSensorId(); id++) { + auto& sSensor = settings[id]; + auto& rSensor = results[id]; + + if (sSensor.purpose == purpose && (!onlyConnected || rSensor.connected)) { + if (amount == 0 || rSensor.values[valueId] < value) { + value = rSensor.values[valueId]; + amount++; + } + } + } + + } else if (avgType == AverageType::MAXIMUM) { + for (uint8_t id = 0; id <= getMaxSensorId(); id++) { + auto& sSensor = settings[id]; + auto& rSensor = results[id]; + + if (sSensor.purpose == purpose && (!onlyConnected || rSensor.connected)) { + if (amount == 0 || rSensor.values[valueId] > value) { + value = rSensor.values[valueId]; + amount++; + } + } } } - if (!amount) { - return 0.0f; - - } else if (amount == 1) { - return value; - - } else { - return value / amount; - } + return amount > 0 ? value : defaultValue; } static bool existsConnectedSensorsByPurpose(Purpose purpose) { diff --git a/src/SensorsTask.h b/src/SensorsTask.h index 7c5fa6e..905bd88 100644 --- a/src/SensorsTask.h +++ b/src/SensorsTask.h @@ -423,14 +423,50 @@ protected: } void updateMasterValues() { - vars.master.heating.outdoorTemp = Sensors::getMeanValueByPurpose(Sensors::Purpose::OUTDOOR_TEMP, Sensors::ValueType::PRIMARY); - vars.master.heating.indoorTemp = Sensors::getMeanValueByPurpose(Sensors::Purpose::INDOOR_TEMP, Sensors::ValueType::PRIMARY); + vars.master.heating.indoorTemp = Sensors::getMeanValueByPurpose( + Sensors::Purpose::INDOOR_TEMP, + Sensors::ValueType::PRIMARY, + settings.heating.indoorTempAvgType, + true, + 0.0f + ); + vars.master.heating.outdoorTemp = Sensors::getMeanValueByPurpose( + Sensors::Purpose::OUTDOOR_TEMP, + Sensors::ValueType::PRIMARY, + settings.heating.outdoorTempAvgType, + true, + 0.0f + ); - vars.master.heating.currentTemp = Sensors::getMeanValueByPurpose(Sensors::Purpose::HEATING_TEMP, Sensors::ValueType::PRIMARY); - vars.master.heating.returnTemp = Sensors::getMeanValueByPurpose(Sensors::Purpose::HEATING_RETURN_TEMP, Sensors::ValueType::PRIMARY); + vars.master.heating.currentTemp = Sensors::getMeanValueByPurpose( + Sensors::Purpose::HEATING_TEMP, + Sensors::ValueType::PRIMARY, + Sensors::AverageType::MEAN, + true, + 0.0f + ); + vars.master.heating.returnTemp = Sensors::getMeanValueByPurpose( + Sensors::Purpose::HEATING_RETURN_TEMP, + Sensors::ValueType::PRIMARY, + Sensors::AverageType::MEAN, + true, + 0.0f + ); - vars.master.dhw.currentTemp = Sensors::getMeanValueByPurpose(Sensors::Purpose::DHW_TEMP, Sensors::ValueType::PRIMARY); - vars.master.dhw.returnTemp = Sensors::getMeanValueByPurpose(Sensors::Purpose::DHW_RETURN_TEMP, Sensors::ValueType::PRIMARY); + vars.master.dhw.currentTemp = Sensors::getMeanValueByPurpose( + Sensors::Purpose::DHW_TEMP, + Sensors::ValueType::PRIMARY, + Sensors::AverageType::MEAN, + true, + 0.0f + ); + vars.master.dhw.returnTemp = Sensors::getMeanValueByPurpose( + Sensors::Purpose::DHW_RETURN_TEMP, + Sensors::ValueType::PRIMARY, + Sensors::AverageType::MEAN, + true, + 0.0f + ); } void makeDallasInstances() { diff --git a/src/Settings.h b/src/Settings.h index 62e6289..2f3e23a 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -107,6 +107,8 @@ struct Settings { uint8_t minTemp = DEFAULT_HEATING_MIN_TEMP; uint8_t maxTemp = DEFAULT_HEATING_MAX_TEMP; uint8_t maxModulation = 100; + Sensors::AverageType indoorTempAvgType = Sensors::AverageType::MEAN; + Sensors::AverageType outdoorTempAvgType = Sensors::AverageType::MEAN; struct { bool enabled = true; diff --git a/src/strings.h b/src/strings.h index c7a17af..d4b8c62 100644 --- a/src/strings.h +++ b/src/strings.h @@ -115,6 +115,7 @@ 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_AVG_TYPE[] PROGMEM = "indoorTempAvgType"; const char S_INDOOR_TEMP_CONTROL[] PROGMEM = "indoorTempControl"; const char S_IN_GPIO[] PROGMEM = "inGpio"; const char S_INPUT[] PROGMEM = "input"; @@ -155,6 +156,7 @@ 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_OUTDOOR_TEMP_AVG_TYPE[] PROGMEM = "outdoorTempAvgType"; const char S_OUT_GPIO[] PROGMEM = "outGpio"; const char S_OUTPUT[] PROGMEM = "output"; const char S_OVERHEAT[] PROGMEM = "overheat"; diff --git a/src/utils.h b/src/utils.h index ea4f5c8..52c5d13 100644 --- a/src/utils.h +++ b/src/utils.h @@ -498,6 +498,8 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) { heating[FPSTR(S_MIN_TEMP)] = src.heating.minTemp; heating[FPSTR(S_MAX_TEMP)] = src.heating.maxTemp; heating[FPSTR(S_MAX_MODULATION)] = src.heating.maxModulation; + heating[FPSTR(S_INDOOR_TEMP_AVG_TYPE)] = static_cast(src.heating.indoorTempAvgType); + heating[FPSTR(S_OUTDOOR_TEMP_AVG_TYPE)] = static_cast(src.heating.outdoorTempAvgType); auto heatingOverheatProtection = heating[FPSTR(S_OVERHEAT_PROTECTION)].to(); heatingOverheatProtection[FPSTR(S_HIGH_TEMP)] = src.heating.overheatProtection.highTemp; @@ -1393,6 +1395,42 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } + if (!src[FPSTR(S_HEATING)][FPSTR(S_INDOOR_TEMP_AVG_TYPE)].isNull()) { + uint8_t value = src[FPSTR(S_HEATING)][FPSTR(S_INDOOR_TEMP_AVG_TYPE)].as(); + + switch (value) { + case static_cast(Sensors::AverageType::MEAN): + case static_cast(Sensors::AverageType::MINIMUM): + case static_cast(Sensors::AverageType::MAXIMUM): + if (static_cast(dst.heating.indoorTempAvgType) != value) { + dst.heating.indoorTempAvgType = static_cast(value); + changed = true; + } + break; + + default: + break; + } + } + + if (!src[FPSTR(S_HEATING)][FPSTR(S_OUTDOOR_TEMP_AVG_TYPE)].isNull()) { + uint8_t value = src[FPSTR(S_HEATING)][FPSTR(S_OUTDOOR_TEMP_AVG_TYPE)].as(); + + switch (value) { + case static_cast(Sensors::AverageType::MEAN): + case static_cast(Sensors::AverageType::MINIMUM): + case static_cast(Sensors::AverageType::MAXIMUM): + if (static_cast(dst.heating.outdoorTempAvgType) != value) { + dst.heating.outdoorTempAvgType = static_cast(value); + changed = true; + } + break; + + default: + break; + } + } + if (!src[FPSTR(S_HEATING)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_HIGH_TEMP)].isNull()) { unsigned char value = src[FPSTR(S_HEATING)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_HIGH_TEMP)].as(); diff --git a/src_data/locales/cn.json b/src_data/locales/cn.json index bd2314b..2d97a04 100644 --- a/src_data/locales/cn.json +++ b/src_data/locales/cn.json @@ -291,6 +291,11 @@ "min": "最低温度", "max": "最高温度" }, + "avgType": { + "mean": "平均温度", + "min": "最低温度", + "max": "最高温度" + }, "maxModulation": "最大调制范围", "ohProtection": { "title": "超温保护", @@ -352,7 +357,15 @@ "set0target": "设置空目标" } }, - "turboFactor": "Turbo 模式系数" + "turboFactor": "Turbo 模式系数", + "indoorTempAvgType": { + "title": "室内温度平均类型", + "desc": "使用两个或更多室内温度传感器时可能有用(使用 «Equitherm» 和/或 «PID» 时)。" + }, + "outdoorTempAvgType": { + "title": "室外温度平均类型", + "desc": "使用两个或更多室外温度传感器时可能有用(使用 «Equitherm» 时)。" + } }, "emergency": { "desc": "紧急模式会在以下情况自动激活(当PID或气候补偿无法计算热媒设定值时):
启用气候补偿但室外温度传感器断开连接;
启用PID或 OpenTherm 选项中启用原生供暖控制但室内温度传感器断开连接。
注意: 网络故障或MQTT 服务器连接故障时,类型为通过MQTT/API手动控制的传感器将显示为断开连接状态。", diff --git a/src_data/locales/en.json b/src_data/locales/en.json index c78a461..a093f6c 100644 --- a/src_data/locales/en.json +++ b/src_data/locales/en.json @@ -291,6 +291,11 @@ "min": "Minimum temperature", "max": "Maximum temperature" }, + "avgType": { + "mean": "Mean temperature", + "min": "Minimum temperature", + "max": "Maximum temperature" + }, "maxModulation": "Max modulation level", "ohProtection": { "title": "Overheating protection", @@ -352,7 +357,15 @@ "set0target": "Set null target" } }, - "turboFactor": "Turbo mode coeff." + "turboFactor": "Turbo mode coeff.", + "indoorTempAvgType": { + "title": "Indoor temp. averaging type", + "desc": "May be useful when using two or more indoor temp. sensors (when using «Equitherm» and/or «PID»)." + }, + "outdoorTempAvgType": { + "title": "Outdoor temp. averaging type", + "desc": "May be useful when using two or more outdoor temp. sensors (when using «Equitherm»)." + } }, "emergency": { "desc": "Emergency mode is activated automatically when «PID» or «Equitherm» cannot calculate the heat carrier setpoint:
- if «Equitherm» is enabled and the outdoor temperature sensor is disconnected;
- if «PID» or OT option «Native heating control» is enabled and the indoor temperature sensor is disconnected.
Note: On network fault or MQTT fault, sensors with «Manual via MQTT/API» type will be in DISCONNECTED state.", diff --git a/src_data/locales/it.json b/src_data/locales/it.json index 54555e6..0764ffb 100644 --- a/src_data/locales/it.json +++ b/src_data/locales/it.json @@ -291,6 +291,11 @@ "min": "Temperatura minima", "max": "Temperatura massima" }, + "avgType": { + "mean": "Temperatura media", + "min": "Temperatura minima", + "max": "Temperatura massima" + }, "maxModulation": "Max livello modulazione", "ohProtection": { "title": "Protezione contro il surriscaldamento", @@ -352,7 +357,15 @@ "set0target": "Imposta target nullo" } }, - "turboFactor": "Turbo mode coeff." + "turboFactor": "Turbo mode coeff.", + "indoorTempAvgType": { + "title": "Tipo di media temperatura interna", + "desc": "Utile con due o più sensori di temperatura interna (quando si usa «Equitherm» e/o «PID»)." + }, + "outdoorTempAvgType": { + "title": "Tipo di media temperatura esterna", + "desc": "Utile con due o più sensori di temperatura esterna (quando si usa «Equitherm»)." + } }, "emergency": { "desc": "Il modo emergenza è attivato automaticamente quando «PID» o «Equitherm» non possono calcolare il setpoint:
- se «Equitherm» è attivato e il sensore della temperatura esternare è disconnesso;
- se «PID» o l'opzione OT «Impostazioni riscaldamento native» è attiva e il sensore di temperatura interno è disconnesso.
Nota: In mancanza di rete o MQTT, sensore di tipo «Manuale via MQTT/API» è in stato Disconnesso.", diff --git a/src_data/locales/nl.json b/src_data/locales/nl.json index fcce416..0d74311 100644 --- a/src_data/locales/nl.json +++ b/src_data/locales/nl.json @@ -291,6 +291,11 @@ "min": "Minimumtemperatuur", "max": "Maximumtemperatuur" }, + "avgType": { + "mean": "Gemiddelde temperatuur", + "min": "Minimum temperatuur", + "max": "Maximum temperatuur" + }, "maxModulation": "Max. modulatieniveau", "ohProtection": { "title": "Oververhittingsbeveiliging", @@ -352,7 +357,15 @@ "set0target": "Stel null target in" } }, - "turboFactor": "Turbomodus coëff." + "turboFactor": "Turbomodus coëff.", + "indoorTempAvgType": { + "title": "Binnentemperatuur gemiddelde type", + "desc": "Nuttig bij twee of meer binnentemperatuursensoren (bij gebruik van «Equitherm» en/of «PID»)." + }, + "outdoorTempAvgType": { + "title": "Buitentemperatuur gemiddelde type", + "desc": "Nuttig bij twee of meer buitensensoren (bij gebruik van «Equitherm»)." + } }, "emergency": { "desc": "Noodmodus wordt automatisch geactiveerd wanneer «PID» of «Equitherm» het instelpunt van de warmtedrager niet kan berekenen:
- als «Equitherm» is ingeschakeld en de buitentemperatuursensor is losgekoppeld;
- als «PID» of OT-optie «Natuurlijke verwarmingsregeling» is ingeschakeld en de binnentemperatuursensor is losgekoppeld.
Let op: Bij een netwerk- of MQTT-storing krijgen sensoren van het type «Handmatig via MQTT/API» de status ONVERBONDEN.", diff --git a/src_data/locales/ru.json b/src_data/locales/ru.json index 726f0b1..1194fb0 100644 --- a/src_data/locales/ru.json +++ b/src_data/locales/ru.json @@ -291,6 +291,11 @@ "min": "Мин. температура", "max": "Макс. температура" }, + "avgType": { + "mean": "Средняя температура", + "min": "Минимальная температура", + "max": "Максимальная температура" + }, "maxModulation": "Макс. уровень модуляции", "ohProtection": { "title": "Защита от перегрева", @@ -352,7 +357,15 @@ "set0target": "Установить 0 в качестве целевой темп." } }, - "turboFactor": "Коэфф. турбо режима" + "turboFactor": "Коэфф. турбо режима", + "indoorTempAvgType": { + "title": "Тип усреднения внутренней темп.", + "desc": "Полезно при использовании двух и более датчиков внутренней температуры (при использовании «Equitherm» и/или «PID»)." + }, + "outdoorTempAvgType": { + "title": "Тип усреднения наружнной темп.", + "desc": "Полезно при использовании двух и более датчиков наружной температуры (при использовании «Equitherm»)." + } }, "emergency": { "desc": "Аварийный режим активируется автоматически, если «ПИД» или «ПЗА» не могут рассчитать уставку теплоносителя:
- если «ПЗА» включен и датчик наружной температуры отключен;
- если включен «ПИД» или OT опция «Передать управление отоплением котлу» и датчик внутренней температуры отключен.
Примечание: При сбое сети или MQTT датчики с типом «Вручную через MQTT/API» будут находиться в состоянии ОТКЛЮЧЕН.", diff --git a/src_data/pages/settings.html b/src_data/pages/settings.html index 68276fa..63b765c 100644 --- a/src_data/pages/settings.html +++ b/src_data/pages/settings.html @@ -198,6 +198,28 @@ +
+ + + +
+
@@ -1171,6 +1193,8 @@ setSelectValue("[name='heating[hysteresis][action]']", data.heating.hysteresis.action); setInputValue("[name='heating[turboFactor]']", data.heating.turboFactor); setInputValue("[name='heating[maxModulation]']", data.heating.maxModulation); + setSelectValue("[name='heating[indoorTempAvgType]']", data.heating.indoorTempAvgType); + setSelectValue("[name='heating[outdoorTempAvgType]']", data.heating.outdoorTempAvgType); setInputValue("[name='heating[overheatProtection][highTemp]']", data.heating.overheatProtection.highTemp, { "min": 0, "max": data.system.unitSystem == 0 ? 100 : 212