refactor: improved freeze protection & overheat protection; added desc for OT options

This commit is contained in:
Yurii
2025-06-30 02:41:44 +03:00
parent 792c6a6668
commit e63a0dc7eb
9 changed files with 299 additions and 193 deletions

View File

@@ -29,6 +29,7 @@ protected:
enum class PumpStartReason {NONE, HEATING, ANTISTUCK};
Blinker* blinker = nullptr;
unsigned long miscRunned = 0;
unsigned long lastHeapInfo = 0;
unsigned int minFreeHeap = 0;
unsigned int minMaxFreeBlockHeap = 0;
@@ -42,6 +43,8 @@ protected:
bool telnetStarted = false;
bool emergencyDetected = false;
unsigned long emergencyFlipTime = 0;
bool freezeDetected = false;
unsigned long freezeDetectedTime = 0;
#if defined(ARDUINO_ARCH_ESP32)
const char* getTaskName() override {
@@ -150,18 +153,16 @@ protected:
Sensors::setConnectionStatusByType(Sensors::Type::MANUAL, false, false);
}
this->yield();
this->heating();
this->emergency();
this->yield();
if (this->misc()) {
this->yield();
}
this->ledStatus();
this->cascadeControl();
this->externalPump();
this->yield();
// telnet
if (this->telnetStarted) {
this->yield();
telnetStream->loop();
this->yield();
}
@@ -180,14 +181,27 @@ protected:
// heap info
this->heap();
}
bool misc() {
if (millis() - this->miscRunned < 1000) {
return false;
}
// restart
// restart if required
if (this->restartSignalReceived && millis() - this->restartSignalReceivedTime > 15000) {
this->restartSignalReceived = false;
ESP.restart();
}
this->heating();
this->emergency();
this->cascadeControl();
this->externalPump();
this->miscRunned = millis();
return true;
}
void heap() {
@@ -230,15 +244,15 @@ protected:
}
void heating() {
// anti freeze protection
// freeze protection
if (!settings.heating.enabled) {
float minTemp = 255.0f;
float lowTemp = 255.0f;
uint8_t availableSensors = 0;
if (Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::INDOOR_TEMP)) {
auto value = Sensors::getMeanValueByPurpose(Sensors::Purpose::INDOOR_TEMP, Sensors::ValueType::PRIMARY);
if (value < minTemp) {
minTemp = value;
if (value < lowTemp) {
lowTemp = value;
}
availableSensors++;
@@ -246,8 +260,8 @@ protected:
if (Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::HEATING_TEMP)) {
auto value = Sensors::getMeanValueByPurpose(Sensors::Purpose::HEATING_TEMP, Sensors::ValueType::PRIMARY);
if (value < minTemp) {
minTemp = value;
if (value < lowTemp) {
lowTemp = value;
}
availableSensors++;
@@ -255,23 +269,36 @@ protected:
if (Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::HEATING_RETURN_TEMP)) {
auto value = Sensors::getMeanValueByPurpose(Sensors::Purpose::HEATING_RETURN_TEMP, Sensors::ValueType::PRIMARY);
if (value < minTemp) {
minTemp = value;
if (value < lowTemp) {
lowTemp = value;
}
availableSensors++;
}
if (availableSensors && minTemp <= settings.heating.antiFreezeTemp) {
settings.heating.enabled = true;
fsSettings.update();
if (availableSensors && lowTemp <= settings.heating.freezeProtection.lowTemp) {
if (!this->freezeDetected) {
this->freezeDetected = true;
this->freezeDetectedTime = millis();
Log.sinfoln(
FPSTR(L_MAIN),
F("Heating turned on by anti freeze protection, current min temp: %.2f, threshold: %hhu"),
minTemp, settings.heating.antiFreezeTemp
);
} else if (millis() - this->freezeDetectedTime > (settings.heating.freezeProtection.thresholdTime * 1000)) {
this->freezeDetected = false;
settings.heating.enabled = true;
fsSettings.update();
Log.sinfoln(
FPSTR(L_MAIN),
F("Heating turned on by freeze protection, current low temp: %.2f, threshold: %hhu"),
lowTemp, settings.heating.freezeProtection.lowTemp
);
}
} else if (this->freezeDetected) {
this->freezeDetected = false;
}
} else if (this->freezeDetected) {
this->freezeDetected = false;
}
}

View File

@@ -1320,7 +1320,7 @@ protected:
// Heating overheat control
if (settings.heating.overheatHighTemp > 0 && settings.heating.overheatLowTemp > 0) {
if (settings.heating.overheatProtection.highTemp > 0 && settings.heating.overheatProtection.lowTemp > 0) {
float highTemp = convertTemp(
max({
vars.slave.heating.currentTemp,
@@ -1332,22 +1332,22 @@ protected:
);
if (vars.master.heating.overheat) {
if ((float) settings.heating.overheatLowTemp - highTemp + 0.0001f >= 0.0f) {
if ((float) settings.heating.overheatProtection.lowTemp - highTemp + 0.0001f >= 0.0f) {
vars.master.heating.overheat = false;
Log.sinfoln(
FPSTR(L_OT_HEATING), F("Overheating not detected. Current high temp: %.2f, threshold (low): %hhu"),
highTemp, settings.heating.overheatLowTemp
highTemp, settings.heating.overheatProtection.lowTemp
);
}
} else if (vars.slave.heating.active) {
if (highTemp - (float) settings.heating.overheatHighTemp + 0.0001f >= 0.0f) {
if (highTemp - (float) settings.heating.overheatProtection.highTemp + 0.0001f >= 0.0f) {
vars.master.heating.overheat = true;
Log.swarningln(
FPSTR(L_OT_HEATING), F("Overheating detected! Current high temp: %.2f, threshold (high): %hhu"),
highTemp, settings.heating.overheatHighTemp
highTemp, settings.heating.overheatProtection.highTemp
);
}
}
@@ -1357,7 +1357,7 @@ protected:
}
// DHW overheat control
if (settings.dhw.overheatHighTemp > 0 && settings.dhw.overheatLowTemp > 0) {
if (settings.dhw.overheatProtection.highTemp > 0 && settings.dhw.overheatProtection.lowTemp > 0) {
float highTemp = convertTemp(
max({
vars.slave.heating.currentTemp,
@@ -1372,22 +1372,22 @@ protected:
);
if (vars.master.dhw.overheat) {
if ((float) settings.dhw.overheatLowTemp - highTemp + 0.0001f >= 0.0f) {
if ((float) settings.dhw.overheatProtection.lowTemp - highTemp + 0.0001f >= 0.0f) {
vars.master.dhw.overheat = false;
Log.sinfoln(
FPSTR(L_OT_DHW), F("Overheating not detected. Current high temp: %.2f, threshold (low): %hhu"),
highTemp, settings.dhw.overheatLowTemp
highTemp, settings.dhw.overheatProtection.lowTemp
);
}
} else if (vars.slave.dhw.active) {
if (highTemp - (float) settings.dhw.overheatHighTemp + 0.0001f >= 0.0f) {
if (highTemp - (float) settings.dhw.overheatProtection.highTemp + 0.0001f >= 0.0f) {
vars.master.dhw.overheat = true;
Log.swarningln(
FPSTR(L_OT_DHW), F("Overheating detected! Current high temp: %.2f, threshold (high): %hhu"),
highTemp, settings.dhw.overheatHighTemp
highTemp, settings.dhw.overheatProtection.highTemp
);
}
}

View File

@@ -108,9 +108,16 @@ struct Settings {
byte minTemp = DEFAULT_HEATING_MIN_TEMP;
byte maxTemp = DEFAULT_HEATING_MAX_TEMP;
uint8_t maxModulation = 100;
uint8_t overheatHighTemp = 95;
uint8_t overheatLowTemp = 90;
uint8_t antiFreezeTemp = 10;
struct {
uint8_t highTemp = 95;
uint8_t lowTemp = 90;
} overheatProtection;
struct {
uint8_t lowTemp = 10;
unsigned short thresholdTime = 600;
} freezeProtection;
} heating;
struct {
@@ -119,8 +126,11 @@ struct Settings {
byte minTemp = DEFAULT_DHW_MIN_TEMP;
byte maxTemp = DEFAULT_DHW_MAX_TEMP;
uint8_t maxModulation = 100;
uint8_t overheatHighTemp = 95;
uint8_t overheatLowTemp = 90;
struct {
uint8_t highTemp = 95;
uint8_t lowTemp = 90;
} overheatProtection;
} dhw;
struct {

View File

@@ -44,7 +44,6 @@ const char S_APP_VERSION[] PROGMEM = "appVersion";
const char S_AUTH[] PROGMEM = "auth";
const char S_AUTO_DIAG_RESET[] PROGMEM = "autoDiagReset";
const char S_AUTO_FAULT_RESET[] PROGMEM = "autoFaultReset";
const char S_ANTI_FREEZE_TEMP[] PROGMEM = "antiFreezeTemp";
const char S_BACKTRACE[] PROGMEM = "backtrace";
const char S_BATTERY[] PROGMEM = "battery";
const char S_BAUDRATE[] PROGMEM = "baudrate";
@@ -85,6 +84,7 @@ const char S_EQUITHERM[] PROGMEM = "equitherm";
const char S_EXTERNAL_PUMP[] PROGMEM = "externalPump";
const char S_FACTOR[] PROGMEM = "factor";
const char S_FAULT[] PROGMEM = "fault";
const char S_FREEZE_PROTECTION[] PROGMEM = "freezeProtection";
const char S_FILTERING[] PROGMEM = "filtering";
const char S_FILTERING_FACTOR[] PROGMEM = "filteringFactor";
const char S_FLAGS[] PROGMEM = "flags";
@@ -100,6 +100,7 @@ const char S_HEATING[] PROGMEM = "heating";
const char S_HEATING_TO_CH2[] PROGMEM = "heatingToCh2";
const char S_HEATING_STATE_TO_SUMMER_WINTER_MODE[] PROGMEM = "heatingStateToSummerWinterMode";
const char S_HIDDEN[] PROGMEM = "hidden";
const char S_HIGH_TEMP[] PROGMEM = "highTemp";
const char S_HOME_ASSISTANT_DISCOVERY[] PROGMEM = "homeAssistantDiscovery";
const char S_HOSTNAME[] PROGMEM = "hostname";
const char S_HUMIDITY[] PROGMEM = "humidity";
@@ -119,6 +120,7 @@ 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";
const char S_MAC[] PROGMEM = "mac";
const char S_MASTER[] PROGMEM = "master";
const char S_MAX[] PROGMEM = "max";
@@ -152,8 +154,7 @@ const char S_OUTDOOR_TEMP[] PROGMEM = "outdoorTemp";
const char S_OUT_GPIO[] PROGMEM = "outGpio";
const char S_OUTPUT[] PROGMEM = "output";
const char S_OVERHEAT[] PROGMEM = "overheat";
const char S_OVERHEAT_HIGH_TEMP[] PROGMEM = "overheatHighTemp";
const char S_OVERHEAT_LOW_TEMP[] PROGMEM = "overheatLowTemp";
const char S_OVERHEAT_PROTECTION[] PROGMEM = "overheatProtection";
const char S_PASSWORD[] PROGMEM = "password";
const char S_PID[] PROGMEM = "pid";
const char S_PORT[] PROGMEM = "port";

View File

@@ -495,9 +495,14 @@ 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_OVERHEAT_HIGH_TEMP)] = src.heating.overheatHighTemp;
heating[FPSTR(S_OVERHEAT_LOW_TEMP)] = src.heating.overheatLowTemp;
heating[FPSTR(S_ANTI_FREEZE_TEMP)] = src.heating.antiFreezeTemp;
auto heatingOverheatProtection = heating[FPSTR(S_OVERHEAT_PROTECTION)].to<JsonObject>();
heatingOverheatProtection[FPSTR(S_HIGH_TEMP)] = src.heating.overheatProtection.highTemp;
heatingOverheatProtection[FPSTR(S_LOW_TEMP)] = src.heating.overheatProtection.lowTemp;
auto freezeProtection = heating[FPSTR(S_FREEZE_PROTECTION)].to<JsonObject>();
freezeProtection[FPSTR(S_LOW_TEMP)] = src.heating.freezeProtection.lowTemp;
freezeProtection[FPSTR(S_THRESHOLD_TIME)] = src.heating.freezeProtection.thresholdTime;
auto dhw = dst[FPSTR(S_DHW)].to<JsonObject>();
dhw[FPSTR(S_ENABLED)] = src.dhw.enabled;
@@ -505,8 +510,10 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
dhw[FPSTR(S_MIN_TEMP)] = src.dhw.minTemp;
dhw[FPSTR(S_MAX_TEMP)] = src.dhw.maxTemp;
dhw[FPSTR(S_MAX_MODULATION)] = src.dhw.maxModulation;
dhw[FPSTR(S_OVERHEAT_HIGH_TEMP)] = src.dhw.overheatHighTemp;
dhw[FPSTR(S_OVERHEAT_LOW_TEMP)] = src.dhw.overheatLowTemp;
auto dhwOverheatProtection = dhw[FPSTR(S_OVERHEAT_PROTECTION)].to<JsonObject>();
dhwOverheatProtection[FPSTR(S_HIGH_TEMP)] = src.dhw.overheatProtection.highTemp;
dhwOverheatProtection[FPSTR(S_LOW_TEMP)] = src.dhw.overheatProtection.lowTemp;
auto equitherm = dst[FPSTR(S_EQUITHERM)].to<JsonObject>();
equitherm[FPSTR(S_ENABLED)] = src.equitherm.enabled;
@@ -1347,38 +1354,49 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
}
}
if (!src[FPSTR(S_HEATING)][FPSTR(S_OVERHEAT_HIGH_TEMP)].isNull()) {
unsigned char value = src[FPSTR(S_HEATING)][FPSTR(S_OVERHEAT_HIGH_TEMP)].as<unsigned char>();
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>();
if (isValidTemp(value, dst.system.unitSystem, 0.0f, 100.0f) && value != dst.heating.overheatHighTemp) {
dst.heating.overheatHighTemp = value;
if (isValidTemp(value, dst.system.unitSystem, 0.0f, 100.0f) && value != dst.heating.overheatProtection.highTemp) {
dst.heating.overheatProtection.highTemp = value;
changed = true;
}
}
if (!src[FPSTR(S_HEATING)][FPSTR(S_OVERHEAT_LOW_TEMP)].isNull()) {
unsigned char value = src[FPSTR(S_HEATING)][FPSTR(S_OVERHEAT_LOW_TEMP)].as<unsigned char>();
if (!src[FPSTR(S_HEATING)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_LOW_TEMP)].isNull()) {
unsigned char value = src[FPSTR(S_HEATING)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_LOW_TEMP)].as<unsigned char>();
if (isValidTemp(value, dst.system.unitSystem, 0.0f, 99.0f) && value != dst.heating.overheatLowTemp) {
dst.heating.overheatLowTemp = value;
if (isValidTemp(value, dst.system.unitSystem, 0.0f, 99.0f) && value != dst.heating.overheatProtection.lowTemp) {
dst.heating.overheatProtection.lowTemp = value;
changed = true;
}
}
if (dst.heating.overheatHighTemp < dst.heating.overheatLowTemp) {
dst.heating.overheatHighTemp = dst.heating.overheatLowTemp;
if (dst.heating.overheatProtection.highTemp < dst.heating.overheatProtection.lowTemp) {
dst.heating.overheatProtection.highTemp = dst.heating.overheatProtection.lowTemp;
changed = true;
}
if (!src[FPSTR(S_HEATING)][FPSTR(S_ANTI_FREEZE_TEMP)].isNull()) {
unsigned short value = src[FPSTR(S_HEATING)][FPSTR(S_ANTI_FREEZE_TEMP)].as<unsigned short>();
if (!src[FPSTR(S_HEATING)][FPSTR(S_FREEZE_PROTECTION)][FPSTR(S_LOW_TEMP)].isNull()) {
unsigned short value = src[FPSTR(S_HEATING)][FPSTR(S_FREEZE_PROTECTION)][FPSTR(S_LOW_TEMP)].as<uint8_t>();
if (isValidTemp(value, dst.system.unitSystem, 1, 30) && value != dst.heating.antiFreezeTemp) {
dst.heating.antiFreezeTemp = value;
if (isValidTemp(value, dst.system.unitSystem, 1, 30) && value != dst.heating.freezeProtection.lowTemp) {
dst.heating.freezeProtection.lowTemp = value;
changed = true;
}
}
if (!src[FPSTR(S_HEATING)][FPSTR(S_FREEZE_PROTECTION)][FPSTR(S_THRESHOLD_TIME)].isNull()) {
unsigned short value = src[FPSTR(S_HEATING)][FPSTR(S_FREEZE_PROTECTION)][FPSTR(S_THRESHOLD_TIME)].as<unsigned short>();
if (value >= 30 && value <= 1800) {
if (value != dst.heating.freezeProtection.thresholdTime) {
dst.heating.freezeProtection.thresholdTime = value;
changed = true;
}
}
}
// dhw
if (src[FPSTR(S_DHW)][FPSTR(S_ENABLED)].is<bool>()) {
@@ -1422,26 +1440,26 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
}
}
if (!src[FPSTR(S_DHW)][FPSTR(S_OVERHEAT_HIGH_TEMP)].isNull()) {
unsigned char value = src[FPSTR(S_DHW)][FPSTR(S_OVERHEAT_HIGH_TEMP)].as<unsigned char>();
if (!src[FPSTR(S_DHW)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_HIGH_TEMP)].isNull()) {
unsigned char value = src[FPSTR(S_DHW)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_HIGH_TEMP)].as<unsigned char>();
if (isValidTemp(value, dst.system.unitSystem, 0.0f, 100.0f) && value != dst.dhw.overheatHighTemp) {
dst.dhw.overheatHighTemp = value;
if (isValidTemp(value, dst.system.unitSystem, 0.0f, 100.0f) && value != dst.dhw.overheatProtection.highTemp) {
dst.dhw.overheatProtection.highTemp = value;
changed = true;
}
}
if (!src[FPSTR(S_DHW)][FPSTR(S_OVERHEAT_LOW_TEMP)].isNull()) {
unsigned char value = src[FPSTR(S_DHW)][FPSTR(S_OVERHEAT_LOW_TEMP)].as<unsigned char>();
if (!src[FPSTR(S_DHW)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_LOW_TEMP)].isNull()) {
unsigned char value = src[FPSTR(S_DHW)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_LOW_TEMP)].as<unsigned char>();
if (isValidTemp(value, dst.system.unitSystem, 0.0f, 99.0f) && value != dst.dhw.overheatLowTemp) {
dst.dhw.overheatLowTemp = value;
if (isValidTemp(value, dst.system.unitSystem, 0.0f, 99.0f) && value != dst.dhw.overheatProtection.lowTemp) {
dst.dhw.overheatProtection.lowTemp = value;
changed = true;
}
}
if (dst.dhw.overheatHighTemp < dst.dhw.overheatLowTemp) {
dst.dhw.overheatHighTemp = dst.dhw.overheatLowTemp;
if (dst.dhw.overheatProtection.highTemp < dst.dhw.overheatProtection.lowTemp) {
dst.dhw.overheatProtection.highTemp = dst.dhw.overheatProtection.lowTemp;
changed = true;
}