Compare commits

...

2 Commits

Author SHA1 Message Date
Yurii dcc9b37010 refactor: locales updated 2026-05-16 01:03:10 +03:00
Yurii 554342e7f5 feat: added a choice of averaging type for indoor and outdoor temperatures 2026-05-16 00:44:54 +03:00
12 changed files with 199 additions and 24 deletions
+1 -1
View File
@@ -225,7 +225,7 @@ protected:
uint8_t availableSensors = 0; uint8_t availableSensors = 0;
if (Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::INDOOR_TEMP)) { 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) { if (value < lowTemp) {
lowTemp = value; lowTemp = value;
} }
+45 -1
View File
@@ -77,6 +77,12 @@ public:
RSSI = 3 RSSI = 3
}; };
enum class AverageType : uint8_t {
MEAN = 0,
MINIMUM = 1,
MAXIMUM = 2
};
typedef struct { typedef struct {
bool enabled = false; bool enabled = false;
char name[33]; char name[33];
@@ -330,7 +336,7 @@ public:
return updated; 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, bool onlyConnected = true) {
if (settings == nullptr || results == nullptr) { if (settings == nullptr || results == nullptr) {
return 0.0f; return 0.0f;
} }
@@ -340,6 +346,7 @@ public:
return 0.0f; return 0.0f;
} }
if (avgType == AverageType::MEAN) {
float value = 0.0f; float value = 0.0f;
uint8_t amount = 0; uint8_t amount = 0;
@@ -362,6 +369,43 @@ public:
} else { } else {
return value / amount; return value / amount;
} }
} else if (avgType == AverageType::MINIMUM) {
float value = NAN;
for (uint8_t id = 0; id <= getMaxSensorId(); id++) {
auto& sSensor = settings[id];
auto& rSensor = results[id];
if (sSensor.purpose == purpose && (!onlyConnected || rSensor.connected)) {
if (value == NAN || rSensor.values[valueId] < value) {
value = rSensor.values[valueId];
}
}
}
return value != NAN ? value : 0.0f;
} else if (avgType == AverageType::MAXIMUM) {
float value = NAN;
for (uint8_t id = 0; id <= getMaxSensorId(); id++) {
auto& sSensor = settings[id];
auto& rSensor = results[id];
if (sSensor.purpose == purpose && (!onlyConnected || rSensor.connected)) {
if (value == NAN || rSensor.values[valueId] > value) {
value = rSensor.values[valueId];
}
}
}
return value != NAN ? value : 0.0f;
} else {
// bad mean type
return 0.0f;
}
} }
static bool existsConnectedSensorsByPurpose(Purpose purpose) { static bool existsConnectedSensorsByPurpose(Purpose purpose) {
+2 -2
View File
@@ -423,8 +423,8 @@ protected:
} }
void updateMasterValues() { void updateMasterValues() {
vars.master.heating.outdoorTemp = Sensors::getMeanValueByPurpose(Sensors::Purpose::OUTDOOR_TEMP, Sensors::ValueType::PRIMARY); vars.master.heating.outdoorTemp = Sensors::getMeanValueByPurpose(Sensors::Purpose::OUTDOOR_TEMP, Sensors::ValueType::PRIMARY, settings.heating.outdoorTempAvgType);
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);
vars.master.heating.currentTemp = Sensors::getMeanValueByPurpose(Sensors::Purpose::HEATING_TEMP, Sensors::ValueType::PRIMARY); 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.returnTemp = Sensors::getMeanValueByPurpose(Sensors::Purpose::HEATING_RETURN_TEMP, Sensors::ValueType::PRIMARY);
+2
View File
@@ -107,6 +107,8 @@ struct Settings {
uint8_t minTemp = DEFAULT_HEATING_MIN_TEMP; uint8_t minTemp = DEFAULT_HEATING_MIN_TEMP;
uint8_t maxTemp = DEFAULT_HEATING_MAX_TEMP; uint8_t maxTemp = DEFAULT_HEATING_MAX_TEMP;
uint8_t maxModulation = 100; uint8_t maxModulation = 100;
Sensors::AverageType indoorTempAvgType = Sensors::AverageType::MEAN;
Sensors::AverageType outdoorTempAvgType = Sensors::AverageType::MEAN;
struct { struct {
bool enabled = true; bool enabled = true;
+2
View File
@@ -115,6 +115,7 @@ const char S_IGNORE_DIAG_STATE[] PROGMEM = "ignoreDiagState";
const char S_IMMERGAS_FIX[] PROGMEM = "immergasFix"; const char S_IMMERGAS_FIX[] PROGMEM = "immergasFix";
const char S_ALWAYS_SEND_INDOOR_TEMP[] PROGMEM = "alwaysSendIndoorTemp"; const char S_ALWAYS_SEND_INDOOR_TEMP[] PROGMEM = "alwaysSendIndoorTemp";
const char S_INDOOR_TEMP[] PROGMEM = "indoorTemp"; 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_INDOOR_TEMP_CONTROL[] PROGMEM = "indoorTempControl";
const char S_IN_GPIO[] PROGMEM = "inGpio"; const char S_IN_GPIO[] PROGMEM = "inGpio";
const char S_INPUT[] PROGMEM = "input"; 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_OPENTHERM[] PROGMEM = "opentherm";
const char S_OPTIONS[] PROGMEM = "options"; const char S_OPTIONS[] PROGMEM = "options";
const char S_OUTDOOR_TEMP[] PROGMEM = "outdoorTemp"; 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_OUT_GPIO[] PROGMEM = "outGpio";
const char S_OUTPUT[] PROGMEM = "output"; const char S_OUTPUT[] PROGMEM = "output";
const char S_OVERHEAT[] PROGMEM = "overheat"; const char S_OVERHEAT[] PROGMEM = "overheat";
+38
View File
@@ -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_MIN_TEMP)] = src.heating.minTemp;
heating[FPSTR(S_MAX_TEMP)] = src.heating.maxTemp; heating[FPSTR(S_MAX_TEMP)] = src.heating.maxTemp;
heating[FPSTR(S_MAX_MODULATION)] = src.heating.maxModulation; heating[FPSTR(S_MAX_MODULATION)] = src.heating.maxModulation;
heating[FPSTR(S_INDOOR_TEMP_AVG_TYPE)] = static_cast<uint8_t>(src.heating.indoorTempAvgType);
heating[FPSTR(S_OUTDOOR_TEMP_AVG_TYPE)] = static_cast<uint8_t>(src.heating.outdoorTempAvgType);
auto heatingOverheatProtection = heating[FPSTR(S_OVERHEAT_PROTECTION)].to<JsonObject>(); auto heatingOverheatProtection = heating[FPSTR(S_OVERHEAT_PROTECTION)].to<JsonObject>();
heatingOverheatProtection[FPSTR(S_HIGH_TEMP)] = src.heating.overheatProtection.highTemp; 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<uint8_t>();
switch (value) {
case static_cast<uint8_t>(Sensors::AverageType::MEAN):
case static_cast<uint8_t>(Sensors::AverageType::MINIMUM):
case static_cast<uint8_t>(Sensors::AverageType::MAXIMUM):
if (static_cast<uint8_t>(dst.heating.indoorTempAvgType) != value) {
dst.heating.indoorTempAvgType = static_cast<Sensors::AverageType>(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<uint8_t>();
switch (value) {
case static_cast<uint8_t>(Sensors::AverageType::MEAN):
case static_cast<uint8_t>(Sensors::AverageType::MINIMUM):
case static_cast<uint8_t>(Sensors::AverageType::MAXIMUM):
if (static_cast<uint8_t>(dst.heating.outdoorTempAvgType) != value) {
dst.heating.outdoorTempAvgType = static_cast<Sensors::AverageType>(value);
changed = true;
}
break;
default:
break;
}
}
if (!src[FPSTR(S_HEATING)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_HIGH_TEMP)].isNull()) { 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<unsigned char>(); unsigned char value = src[FPSTR(S_HEATING)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_HIGH_TEMP)].as<unsigned char>();
+14 -1
View File
@@ -315,6 +315,11 @@
"min": "最低温度", "min": "最低温度",
"max": "最高温度" "max": "最高温度"
}, },
"avgType": {
"mean": "平均温度",
"min": "最低温度",
"max": "最高温度"
},
"maxModulation": "最大调制范围", "maxModulation": "最大调制范围",
"ohProtection": { "ohProtection": {
"title": "超温保护", "title": "超温保护",
@@ -379,7 +384,15 @@
"set0target": "设置空目标" "set0target": "设置空目标"
} }
}, },
"turboFactor": "Turbo 模式系数" "turboFactor": "Turbo 模式系数",
"indoorTempAvgType": {
"title": "室内温度平均类型",
"desc": "使用两个或更多室内温度传感器时可能有用(使用 «Equitherm» 和/或 «PID» 时)。"
},
"outdoorTempAvgType": {
"title": "室外温度平均类型",
"desc": "使用两个或更多室外温度传感器时可能有用(使用 «Equitherm» 时)。"
}
}, },
"emergency": { "emergency": {
+14 -1
View File
@@ -315,6 +315,11 @@
"min": "Minimum temperature", "min": "Minimum temperature",
"max": "Maximum temperature" "max": "Maximum temperature"
}, },
"avgType": {
"mean": "Mean temperature",
"min": "Minimum temperature",
"max": "Maximum temperature"
},
"maxModulation": "Max modulation level", "maxModulation": "Max modulation level",
"ohProtection": { "ohProtection": {
"title": "Overheating protection", "title": "Overheating protection",
@@ -379,7 +384,15 @@
"set0target": "Set null target" "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": { "emergency": {
+14 -1
View File
@@ -315,6 +315,11 @@
"min": "Temperatura minima", "min": "Temperatura minima",
"max": "Temperatura massima" "max": "Temperatura massima"
}, },
"avgType": {
"mean": "Temperatura media",
"min": "Temperatura minima",
"max": "Temperatura massima"
},
"maxModulation": "Max livello modulazione", "maxModulation": "Max livello modulazione",
"ohProtection": { "ohProtection": {
"title": "Protezione contro il surriscaldamento", "title": "Protezione contro il surriscaldamento",
@@ -379,7 +384,15 @@
"set0target": "Imposta target nullo" "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": { "emergency": {
+14 -1
View File
@@ -290,6 +290,11 @@
"min": "Minimumtemperatuur", "min": "Minimumtemperatuur",
"max": "Maximumtemperatuur" "max": "Maximumtemperatuur"
}, },
"avgType": {
"mean": "Gemiddelde temperatuur",
"min": "Minimum temperatuur",
"max": "Maximum temperatuur"
},
"maxModulation": "Max. modulatieniveau", "maxModulation": "Max. modulatieniveau",
"ohProtection": { "ohProtection": {
"title": "Oververhittingsbeveiliging", "title": "Oververhittingsbeveiliging",
@@ -352,7 +357,15 @@
"set0target": "Stel null target in" "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": { "emergency": {
"desc": "Noodmodus wordt automatisch geactiveerd wanneer «PID» of «Equitherm» het instelpunt van de warmtedrager niet kan berekenen:<br />- als «Equitherm» is ingeschakeld en de buitentemperatuursensor is losgekoppeld;<br />- als «PID» of OT-optie <i>«Natuurlijke verwarmingsregeling»</i> is ingeschakeld en de binnentemperatuursensor is losgekoppeld.<br /><b>Let op:</b> Bij een netwerk- of MQTT-storing krijgen sensoren van het type <i>«Handmatig via MQTT/API»</i> de status ONVERBONDEN.", "desc": "Noodmodus wordt automatisch geactiveerd wanneer «PID» of «Equitherm» het instelpunt van de warmtedrager niet kan berekenen:<br />- als «Equitherm» is ingeschakeld en de buitentemperatuursensor is losgekoppeld;<br />- als «PID» of OT-optie <i>«Natuurlijke verwarmingsregeling»</i> is ingeschakeld en de binnentemperatuursensor is losgekoppeld.<br /><b>Let op:</b> Bij een netwerk- of MQTT-storing krijgen sensoren van het type <i>«Handmatig via MQTT/API»</i> de status ONVERBONDEN.",
+14 -1
View File
@@ -316,6 +316,11 @@
"min": "Мин. температура", "min": "Мин. температура",
"max": "Макс. температура" "max": "Макс. температура"
}, },
"avgType": {
"mean": "Средняя температура",
"min": "Минимальная температура",
"max": "Максимальная температура"
},
"maxModulation": "Макс. уровень модуляции", "maxModulation": "Макс. уровень модуляции",
"ohProtection": { "ohProtection": {
"title": "Защита от перегрева", "title": "Защита от перегрева",
@@ -380,7 +385,15 @@
"set0target": "Установить 0 в качестве целевой темп." "set0target": "Установить 0 в качестве целевой темп."
} }
}, },
"turboFactor": "Коэфф. турбо режима" "turboFactor": "Коэфф. турбо режима",
"indoorTempAvgType": {
"title": "Тип усреднения внутренней темп.",
"desc": "Полезно при использовании двух и более датчиков внутренней температуры (при использовании «Equitherm» и/или «PID»)."
},
"outdoorTempAvgType": {
"title": "Тип усреднения наружнной темп.",
"desc": "Полезно при использовании двух и более датчиков наружной температуры (при использовании «Equitherm»)."
}
}, },
"emergency": { "emergency": {
+24
View File
@@ -198,6 +198,28 @@
</label> </label>
</div> </div>
<div class="grid">
<label>
<span data-i18n>settings.heating.indoorTempAvgType.title</span>
<select name="heating[indoorTempAvgType]">
<option value="0" data-i18n>settings.avgType.mean</option>
<option value="1" data-i18n>settings.avgType.min</option>
<option value="2" data-i18n>settings.avgType.max</option>
</select>
<small data-i18n>settings.heating.indoorTempAvgType.desc</small>
</label>
<label>
<span data-i18n>settings.heating.outdoorTempAvgType.title</span>
<select name="heating[outdoorTempAvgType]">
<option value="0" data-i18n>settings.avgType.mean</option>
<option value="1" data-i18n>settings.avgType.min</option>
<option value="2" data-i18n>settings.avgType.max</option>
</select>
<small data-i18n>settings.heating.outdoorTempAvgType.desc</small>
</label>
</div>
<hr /> <hr />
<details> <details>
@@ -1171,6 +1193,8 @@
setSelectValue("[name='heating[hysteresis][action]']", data.heating.hysteresis.action); setSelectValue("[name='heating[hysteresis][action]']", data.heating.hysteresis.action);
setInputValue("[name='heating[turboFactor]']", data.heating.turboFactor); setInputValue("[name='heating[turboFactor]']", data.heating.turboFactor);
setInputValue("[name='heating[maxModulation]']", data.heating.maxModulation); 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, { setInputValue("[name='heating[overheatProtection][highTemp]']", data.heating.overheatProtection.highTemp, {
"min": 0, "min": 0,
"max": data.system.unitSystem == 0 ? 100 : 212 "max": data.system.unitSystem == 0 ? 100 : 212