refactor: reworked emergency mode; reworked hysteresis algorithm; improved detection of connection state for MANUAL & BOILER type sensors

This commit is contained in:
Yurii
2024-10-31 01:36:21 +03:00
parent 11b1277d79
commit a6e8953807
10 changed files with 245 additions and 451 deletions

View File

@@ -27,7 +27,7 @@ public:
}
// лимит выходной величины
void setLimits(int min_output, int max_output) {
void setLimits(unsigned short min_output, unsigned short max_output) {
_minOut = min_output;
_maxOut = max_output;
}
@@ -40,7 +40,7 @@ public:
}
private:
int _minOut = 20, _maxOut = 90;
unsigned short _minOut = 20, _maxOut = 90;
// температура контура отопления в зависимости от наружной температуры
datatype getResultN() {

View File

@@ -565,12 +565,12 @@ public:
if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C);
doc[FPSTR(HA_MIN)] = 0;
doc[FPSTR(HA_MIN)] = -99;
doc[FPSTR(HA_MAX)] = 99;
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F);
doc[FPSTR(HA_MIN)] = 0;
doc[FPSTR(HA_MIN)] = -146;
doc[FPSTR(HA_MAX)] = 211;
}

View File

@@ -105,6 +105,14 @@ protected:
tMqtt->disable();
}
if (settings.sensors.indoor.type == SensorType::MANUAL) {
vars.sensors.indoor.connected = !settings.mqtt.enable || vars.states.mqtt;
}
if (settings.sensors.outdoor.type == SensorType::MANUAL) {
vars.sensors.outdoor.connected = !settings.mqtt.enable || vars.states.mqtt;
}
} else {
if (this->telnetStarted) {
telnetStream->stop();
@@ -114,6 +122,14 @@ protected:
if (tMqtt->isEnabled()) {
tMqtt->disable();
}
if (settings.sensors.indoor.type == SensorType::MANUAL) {
vars.sensors.indoor.connected = false;
}
if (settings.sensors.outdoor.type == SensorType::MANUAL) {
vars.sensors.outdoor.connected = false;
}
}
this->yield();
@@ -189,42 +205,22 @@ protected:
}
void emergency() {
if (!settings.emergency.enable && vars.states.emergency) {
this->emergencyDetected = false;
vars.states.emergency = false;
Log.sinfoln(FPSTR(L_MAIN), F("Emergency mode disabled"));
}
if (!settings.emergency.enable) {
return;
}
// flags
uint8_t emergencyFlags = 0b00000000;
// set network flag
if (settings.emergency.onNetworkFault && !network->isConnected()) {
// set outdoor sensor flag
if (settings.equitherm.enable && !vars.sensors.outdoor.connected) {
emergencyFlags |= 0b00000001;
}
// set mqtt flag
if (settings.emergency.onMqttFault && (!tMqtt->isEnabled() || !tMqtt->isConnected())) {
// set indoor sensor flag
if (!settings.equitherm.enable && settings.pid.enable && !vars.sensors.indoor.connected) {
emergencyFlags |= 0b00000010;
}
// set outdoor sensor flag
if (settings.sensors.outdoor.type == SensorType::DS18B20 || settings.sensors.outdoor.type == SensorType::BLUETOOTH) {
if (settings.emergency.onOutdoorSensorDisconnect && !vars.sensors.outdoor.connected) {
emergencyFlags |= 0b00000100;
}
}
// set indoor sensor flag
if (settings.sensors.indoor.type == SensorType::DS18B20 || settings.sensors.indoor.type == SensorType::BLUETOOTH) {
if (settings.emergency.onIndoorSensorDisconnect && !vars.sensors.indoor.connected) {
emergencyFlags |= 0b00001000;
}
// set indoor sensor flag for OT native heating control
if (settings.opentherm.nativeHeatingControl && !vars.sensors.indoor.connected) {
emergencyFlags |= 0b00000100;
}
// if any flags is true

View File

@@ -22,11 +22,11 @@ protected:
bool isInitialized = false;
unsigned long initializedTime = 0;
unsigned int initializedMemberIdCode = 0;
bool pump = true;
unsigned long lastSuccessResponse = 0;
unsigned long prevUpdateNonEssentialVars = 0;
unsigned long dhwSetTempTime = 0;
unsigned long heatingSetTempTime = 0;
bool heatingBlocking = false;
byte configuredRxLedGpio = GPIO_IS_NOT_CONFIGURED;
#if defined(ARDUINO_ARCH_ESP32)
@@ -136,7 +136,10 @@ protected:
}
}
bool heatingEnabled = (vars.states.emergency || settings.heating.enable) && this->pump && vars.cascadeControl.input && this->isReady();
bool heatingEnabled = (vars.states.emergency || settings.heating.enable)
&& vars.cascadeControl.input
&& this->isReady()
&& !this->heatingBlocking;
bool heatingCh2Enabled = settings.opentherm.heatingCh2Enabled;
if (settings.opentherm.heatingCh1ToCh2) {
heatingCh2Enabled = heatingEnabled;
@@ -182,6 +185,10 @@ protected:
} else if (vars.states.otStatus && millis() - this->lastSuccessResponse > 1150) {
Log.swarningln(FPSTR(L_OT), F("Disconnected"));
if (settings.sensors.outdoor.type == SensorType::BOILER) {
vars.sensors.outdoor.connected = false;
}
vars.states.otStatus = false;
this->isInitialized = false;
}
@@ -386,9 +393,17 @@ protected:
// Get outdoor temp (if necessary)
if (settings.sensors.outdoor.type == SensorType::BOILER) {
if (this->updateOutdoorTemp()) {
if (!vars.sensors.outdoor.connected) {
vars.sensors.outdoor.connected = true;
}
Log.snoticeln(FPSTR(L_OT), F("Received outdoor temp: %.2f"), vars.temperatures.outdoor);
} else {
if (vars.sensors.outdoor.connected) {
vars.sensors.outdoor.connected = false;
}
Log.swarningln(FPSTR(L_OT), F("Failed receive outdoor temp"));
}
}
@@ -489,9 +504,17 @@ protected:
// Get outdoor temp (if necessary)
if (settings.sensors.outdoor.type == SensorType::BOILER) {
if (this->updateOutdoorTemp()) {
if (!vars.sensors.outdoor.connected) {
vars.sensors.outdoor.connected = true;
}
Log.snoticeln(FPSTR(L_OT), F("Received outdoor temp: %.2f"), vars.temperatures.outdoor);
} else {
if (vars.sensors.outdoor.connected) {
vars.sensors.outdoor.connected = false;
}
Log.swarningln(FPSTR(L_OT), F("Failed receive outdoor temp"));
}
}
@@ -564,7 +587,7 @@ protected:
float indoorTemp = 0.0f;
float convertedTemp = 0.0f;
if (!vars.states.emergency || settings.sensors.indoor.type != SensorType::MANUAL) {
if (!vars.sensors.indoor.connected) {
indoorTemp = vars.temperatures.indoor;
convertedTemp = convertTemp(indoorTemp, settings.system.unitSystem, settings.opentherm.unitSystem);
}
@@ -595,11 +618,6 @@ protected:
}
}
// force enable pump
if (!this->pump) {
this->pump = true;
}
} else {
// Update heating temp
if (heatingEnabled && (this->needSetHeatingTemp() || fabs(vars.parameters.heatingSetpoint - currentHeatingTemp) > 0.0001f)) {
@@ -634,18 +652,22 @@ protected:
// Hysteresis
// Only if enabled PID or/and Equitherm
if (settings.heating.hysteresis > 0 && (!vars.states.emergency || settings.emergency.usePid) && (settings.equitherm.enable || settings.pid.enable)) {
float halfHyst = settings.heating.hysteresis / 2;
if (this->pump && vars.temperatures.indoor - settings.heating.target + 0.0001f >= halfHyst) {
this->pump = false;
// Only if enabled PID or/and Equitherm or Native heating control via OT
bool useHyst = false;
if (settings.heating.hysteresis > 0.01f && vars.sensors.indoor.connected) {
useHyst = settings.equitherm.enable || settings.pid.enable || settings.opentherm.nativeHeatingControl;
}
} else if (!this->pump && vars.temperatures.indoor - settings.heating.target - 0.0001f <= -(halfHyst)) {
this->pump = true;
if (useHyst) {
if (!this->heatingBlocking && vars.temperatures.indoor - settings.heating.target + 0.0001f >= settings.heating.hysteresis) {
this->heatingBlocking = true;
} else if (this->heatingBlocking && vars.temperatures.indoor - settings.heating.target - 0.0001f <= -(settings.heating.hysteresis)) {
this->heatingBlocking = false;
}
} else if (!this->pump) {
this->pump = true;
} else if (this->heatingBlocking) {
this->heatingBlocking = false;
}
}
}

View File

@@ -29,27 +29,55 @@ protected:
#endif
void loop() {
float newTemp = vars.parameters.heatingSetpoint;
if (vars.states.emergency) {
if (settings.heating.turbo) {
settings.heating.turbo = false;
Log.sinfoln(FPSTR(L_REGULATOR), F("Turbo mode auto disabled"));
}
newTemp = this->getEmergencyModeTemp();
} else {
if (settings.heating.turbo && (fabs(settings.heating.target - vars.temperatures.indoor) < 1 || !settings.heating.enable || (settings.equitherm.enable && settings.pid.enable))) {
settings.heating.turbo = false;
Log.sinfoln(FPSTR(L_REGULATOR), F("Turbo mode auto disabled"));
}
newTemp = this->getNormalModeTemp();
bool pidIntergralResetted = false;
if (fabs(pidRegulator.Kp - settings.pid.p_factor) >= 0.0001f) {
pidRegulator.Kp = settings.pid.p_factor;
pidRegulator.integral = 0;
pidIntergralResetted = true;
}
if (fabs(pidRegulator.Ki - settings.pid.i_factor) >= 0.0001f) {
pidRegulator.Ki = settings.pid.i_factor;
pidRegulator.integral = 0;
pidIntergralResetted = true;
}
if (fabs(pidRegulator.Kd - settings.pid.d_factor) >= 0.0001f) {
pidRegulator.Kd = settings.pid.d_factor;
pidRegulator.integral = 0;
pidIntergralResetted = true;
}
if (!settings.pid.enable && fabs(pidRegulator.integral) > 0.01f) {
pidRegulator.integral = 0.0f;
pidIntergralResetted = true;
}
if (pidIntergralResetted) {
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
}
if (settings.heating.turbo) {
if (!settings.heating.enable || vars.states.emergency) {
settings.heating.turbo = false;
} else if (settings.pid.enable) {
settings.heating.turbo = false; // not implemented
} else if (fabs(settings.heating.target - vars.temperatures.indoor) < 1.0f) {
settings.heating.turbo = false;
}
if (!settings.heating.turbo) {
Log.sinfoln(FPSTR(L_REGULATOR), F("Turbo mode auto disabled"));
}
}
float newTemp = vars.states.emergency
? settings.emergency.target
: this->getNormalModeTemp();
// Limits
newTemp = constrain(
newTemp,
@@ -57,58 +85,12 @@ protected:
!settings.opentherm.nativeHeatingControl ? settings.heating.maxTemp : THERMOSTAT_INDOOR_MAX_TEMP
);
if (fabs(vars.parameters.heatingSetpoint - newTemp) > 0.4999f) {
if (fabs(vars.parameters.heatingSetpoint - newTemp) > 0.09f) {
vars.parameters.heatingSetpoint = newTemp;
}
}
float getEmergencyModeTemp() {
float newTemp = 0;
// if use equitherm
if (settings.emergency.useEquitherm) {
float etResult = getEquithermTemp(settings.heating.minTemp, settings.heating.maxTemp);
if (fabs(prevEtResult - etResult) > 0.4999f) {
prevEtResult = etResult;
newTemp += etResult;
Log.sinfoln(FPSTR(L_REGULATOR_EQUITHERM), F("New emergency result: %.2f"), etResult);
} else {
newTemp += prevEtResult;
}
} else if(settings.emergency.usePid) {
if (vars.parameters.heatingEnabled) {
float pidResult = getPidTemp(
settings.heating.minTemp,
settings.heating.maxTemp
);
if (fabs(prevPidResult - pidResult) > 0.4999f) {
prevPidResult = pidResult;
newTemp += pidResult;
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("New emergency result: %.2f"), pidResult);
} else {
newTemp += prevPidResult;
}
} else if (!vars.parameters.heatingEnabled && prevPidResult != 0) {
newTemp += prevPidResult;
}
} else {
// default temp, manual mode
newTemp = settings.emergency.target;
}
return newTemp;
}
float getNormalModeTemp() {
float newTemp = 0;
@@ -116,17 +98,49 @@ protected:
prevHeatingTarget = settings.heating.target;
Log.sinfoln(FPSTR(L_REGULATOR), F("New target: %.2f"), settings.heating.target);
if (/*settings.equitherm.enable && */settings.pid.enable) {
/*if (settings.pid.enable) {
pidRegulator.integral = 0;
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
}
}*/
}
// if use equitherm
if (settings.equitherm.enable) {
float etResult = getEquithermTemp(settings.heating.minTemp, settings.heating.maxTemp);
unsigned short minTemp = settings.heating.minTemp;
unsigned short maxTemp = settings.heating.maxTemp;
float targetTemp = settings.heating.target;
float indoorTemp = vars.temperatures.indoor;
float outdoorTemp = vars.temperatures.outdoor;
if (fabs(prevEtResult - etResult) > 0.4999f) {
if (settings.system.unitSystem == UnitSystem::IMPERIAL) {
minTemp = f2c(minTemp);
maxTemp = f2c(maxTemp);
targetTemp = f2c(targetTemp);
indoorTemp = f2c(indoorTemp);
outdoorTemp = f2c(outdoorTemp);
}
if (!vars.sensors.indoor.connected || settings.pid.enable) {
etRegulator.Kt = 0;
etRegulator.indoorTemp = 0;
} else {
etRegulator.Kt = settings.heating.turbo ? 10.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);
}
if (fabs(prevEtResult - etResult) > 0.09f) {
prevEtResult = etResult;
newTemp += etResult;
@@ -140,13 +154,14 @@ protected:
// if use pid
if (settings.pid.enable) {
//if (vars.parameters.heatingEnabled) {
if (settings.heating.enable) {
float pidResult = getPidTemp(
settings.equitherm.enable ? (settings.pid.maxTemp * -1) : settings.pid.minTemp,
settings.pid.maxTemp
);
if (settings.heating.enable && vars.sensors.indoor.connected) {
pidRegulator.setLimits(settings.pid.minTemp, settings.pid.maxTemp);
pidRegulator.setDt(settings.pid.dt * 1000u);
pidRegulator.input = vars.temperatures.indoor;
pidRegulator.setpoint = settings.heating.target;
if (fabs(prevPidResult - pidResult) > 0.4999f) {
float pidResult = pidRegulator.getResultTimer();
if (fabs(prevPidResult - pidResult) > 0.09f) {
prevPidResult = pidResult;
newTemp += pidResult;
@@ -156,13 +171,10 @@ protected:
} else {
newTemp += prevPidResult;
}
} else {
newTemp += prevPidResult;
}
} else if (fabs(pidRegulator.integral) > 0.0001f) {
pidRegulator.integral = 0;
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
}
// default temp, manual mode
@@ -172,96 +184,4 @@ protected:
return newTemp;
}
/**
* @brief Get the Equitherm Temp
* Calculations in degrees C, conversion occurs when using F
*
* @param minTemp
* @param maxTemp
* @return float
*/
float getEquithermTemp(int minTemp, int maxTemp) {
float targetTemp = vars.states.emergency ? settings.emergency.target : settings.heating.target;
float indoorTemp = vars.temperatures.indoor;
float outdoorTemp = vars.temperatures.outdoor;
if (settings.system.unitSystem == UnitSystem::IMPERIAL) {
minTemp = f2c(minTemp);
maxTemp = f2c(maxTemp);
targetTemp = f2c(targetTemp);
indoorTemp = f2c(indoorTemp);
outdoorTemp = f2c(outdoorTemp);
}
if (vars.states.emergency) {
if (settings.sensors.indoor.type == SensorType::MANUAL) {
etRegulator.Kt = 0;
etRegulator.indoorTemp = 0;
} else if ((settings.sensors.indoor.type == SensorType::DS18B20 || settings.sensors.indoor.type == SensorType::BLUETOOTH) && !vars.sensors.indoor.connected) {
etRegulator.Kt = 0;
etRegulator.indoorTemp = 0;
} else {
etRegulator.Kt = settings.equitherm.t_factor;
etRegulator.indoorTemp = indoorTemp;
}
etRegulator.outdoorTemp = outdoorTemp;
} else if (settings.pid.enable) {
etRegulator.Kt = 0;
etRegulator.indoorTemp = round(indoorTemp);
etRegulator.outdoorTemp = round(outdoorTemp);
} else {
if (settings.heating.turbo) {
etRegulator.Kt = 10;
} else {
etRegulator.Kt = settings.equitherm.t_factor;
}
etRegulator.indoorTemp = indoorTemp;
etRegulator.outdoorTemp = outdoorTemp;
}
etRegulator.setLimits(minTemp, maxTemp);
etRegulator.Kn = settings.equitherm.n_factor;
etRegulator.Kk = settings.equitherm.k_factor;
etRegulator.targetTemp = targetTemp;
float result = etRegulator.getResult();
if (settings.system.unitSystem == UnitSystem::IMPERIAL) {
result = c2f(result);
}
return result;
}
float getPidTemp(int minTemp, int maxTemp) {
if (fabs(pidRegulator.Kp - settings.pid.p_factor) >= 0.0001f) {
pidRegulator.Kp = settings.pid.p_factor;
pidRegulator.integral = 0;
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
}
if (fabs(pidRegulator.Ki - settings.pid.i_factor) >= 0.0001f) {
pidRegulator.Ki = settings.pid.i_factor;
pidRegulator.integral = 0;
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
}
if (fabs(pidRegulator.Kd - settings.pid.d_factor) >= 0.0001f) {
pidRegulator.Kd = settings.pid.d_factor;
pidRegulator.integral = 0;
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
}
pidRegulator.setLimits(minTemp, maxTemp);
pidRegulator.setDt(settings.pid.dt * 1000u);
pidRegulator.input = vars.temperatures.indoor;
pidRegulator.setpoint = vars.states.emergency ? settings.emergency.target : settings.heating.target;
return pidRegulator.getResultTimer();
}
};

View File

@@ -86,15 +86,8 @@ struct Settings {
} mqtt;
struct {
bool enable = false;
float target = DEFAULT_HEATING_TARGET_TEMP;
unsigned short tresholdTime = 120;
bool useEquitherm = false;
bool usePid = false;
bool onNetworkFault = true;
bool onMqttFault = true;
bool onIndoorSensorDisconnect = false;
bool onOutdoorSensorDisconnect = false;
} emergency;
struct {
@@ -119,8 +112,8 @@ struct Settings {
float i_factor = 0.0055f;
float d_factor = 0;
unsigned short dt = 180;
byte minTemp = 0;
byte maxTemp = DEFAULT_HEATING_MAX_TEMP;
short minTemp = 0;
short maxTemp = DEFAULT_HEATING_MAX_TEMP;
} pid;
struct {

View File

@@ -386,15 +386,8 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
dst["mqtt"]["interval"] = src.mqtt.interval;
dst["mqtt"]["homeAssistantDiscovery"] = src.mqtt.homeAssistantDiscovery;
dst["emergency"]["enable"] = src.emergency.enable;
dst["emergency"]["target"] = roundd(src.emergency.target, 2);
dst["emergency"]["tresholdTime"] = src.emergency.tresholdTime;
dst["emergency"]["useEquitherm"] = src.emergency.useEquitherm;
dst["emergency"]["usePid"] = src.emergency.usePid;
dst["emergency"]["onNetworkFault"] = src.emergency.onNetworkFault;
dst["emergency"]["onMqttFault"] = src.emergency.onMqttFault;
dst["emergency"]["onIndoorSensorDisconnect"] = src.emergency.onIndoorSensorDisconnect;
dst["emergency"]["onOutdoorSensorDisconnect"] = src.emergency.onOutdoorSensorDisconnect;
}
dst["heating"]["enable"] = src.heating.enable;
@@ -409,6 +402,11 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
dst["dhw"]["minTemp"] = src.dhw.minTemp;
dst["dhw"]["maxTemp"] = src.dhw.maxTemp;
dst["equitherm"]["enable"] = src.equitherm.enable;
dst["equitherm"]["n_factor"] = roundd(src.equitherm.n_factor, 3);
dst["equitherm"]["k_factor"] = roundd(src.equitherm.k_factor, 3);
dst["equitherm"]["t_factor"] = roundd(src.equitherm.t_factor, 3);
dst["pid"]["enable"] = src.pid.enable;
dst["pid"]["p_factor"] = roundd(src.pid.p_factor, 3);
dst["pid"]["i_factor"] = roundd(src.pid.i_factor, 4);
@@ -417,11 +415,6 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
dst["pid"]["minTemp"] = src.pid.minTemp;
dst["pid"]["maxTemp"] = src.pid.maxTemp;
dst["equitherm"]["enable"] = src.equitherm.enable;
dst["equitherm"]["n_factor"] = roundd(src.equitherm.n_factor, 3);
dst["equitherm"]["k_factor"] = roundd(src.equitherm.k_factor, 3);
dst["equitherm"]["t_factor"] = roundd(src.equitherm.t_factor, 3);
dst["sensors"]["outdoor"]["type"] = static_cast<byte>(src.sensors.outdoor.type);
dst["sensors"]["outdoor"]["gpio"] = src.sensors.outdoor.gpio;
@@ -861,8 +854,6 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
dst.opentherm.nativeHeatingControl = value;
if (value) {
dst.emergency.useEquitherm = false;
dst.emergency.usePid = false;
dst.equitherm.enable = false;
dst.pid.enable = false;
}
@@ -956,15 +947,6 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
// emergency
if (src["emergency"]["enable"].is<bool>()) {
bool value = src["emergency"]["enable"].as<bool>();
if (value != dst.emergency.enable) {
dst.emergency.enable = value;
changed = true;
}
}
if (!src["emergency"]["tresholdTime"].isNull()) {
unsigned short value = src["emergency"]["tresholdTime"].as<unsigned short>();
@@ -973,83 +955,49 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
changed = true;
}
}
}
if (src["emergency"]["useEquitherm"].is<bool>()) {
bool value = src["emergency"]["useEquitherm"].as<bool>();
if (!dst.opentherm.nativeHeatingControl && dst.sensors.outdoor.type != SensorType::MANUAL && dst.sensors.outdoor.type != SensorType::BLUETOOTH) {
if (value != dst.emergency.useEquitherm) {
dst.emergency.useEquitherm = value;
changed = true;
}
// equitherm
if (src["equitherm"]["enable"].is<bool>()) {
bool value = src["equitherm"]["enable"].as<bool>();
} else if (dst.emergency.useEquitherm) {
dst.emergency.useEquitherm = false;
if (!dst.opentherm.nativeHeatingControl) {
if (value != dst.equitherm.enable) {
dst.equitherm.enable = value;
changed = true;
}
if (dst.emergency.useEquitherm && dst.emergency.usePid) {
dst.emergency.usePid = false;
changed = true;
}
} else if (dst.equitherm.enable) {
dst.equitherm.enable = false;
changed = true;
}
}
if (src["emergency"]["usePid"].is<bool>()) {
bool value = src["emergency"]["usePid"].as<bool>();
if (!src["equitherm"]["n_factor"].isNull()) {
float value = src["equitherm"]["n_factor"].as<float>();
if (!dst.opentherm.nativeHeatingControl && dst.sensors.indoor.type != SensorType::MANUAL && dst.sensors.indoor.type != SensorType::BLUETOOTH) {
if (value != dst.emergency.usePid) {
dst.emergency.usePid = value;
changed = true;
}
} else if (dst.emergency.usePid) {
dst.emergency.usePid = false;
changed = true;
}
if (dst.emergency.usePid && dst.emergency.useEquitherm) {
dst.emergency.useEquitherm = false;
changed = true;
}
if (value > 0 && value <= 10 && fabs(value - dst.equitherm.n_factor) > 0.0001f) {
dst.equitherm.n_factor = roundd(value, 3);
changed = true;
}
}
if (src["emergency"]["onNetworkFault"].is<bool>()) {
bool value = src["emergency"]["onNetworkFault"].as<bool>();
if (!src["equitherm"]["k_factor"].isNull()) {
float value = src["equitherm"]["k_factor"].as<float>();
if (value != dst.emergency.onNetworkFault) {
dst.emergency.onNetworkFault = value;
changed = true;
}
if (value >= 0 && value <= 10 && fabs(value - dst.equitherm.k_factor) > 0.0001f) {
dst.equitherm.k_factor = roundd(value, 3);
changed = true;
}
}
if (src["emergency"]["onMqttFault"].is<bool>()) {
bool value = src["emergency"]["onMqttFault"].as<bool>();
if (!src["equitherm"]["t_factor"].isNull()) {
float value = src["equitherm"]["t_factor"].as<float>();
if (value != dst.emergency.onMqttFault) {
dst.emergency.onMqttFault = value;
changed = true;
}
}
if (src["emergency"]["onIndoorSensorDisconnect"].is<bool>()) {
bool value = src["emergency"]["onIndoorSensorDisconnect"].as<bool>();
if (value != dst.emergency.onIndoorSensorDisconnect) {
dst.emergency.onIndoorSensorDisconnect = value;
dst.emergency.usePid = false;
changed = true;
}
}
if (src["emergency"]["onOutdoorSensorDisconnect"].is<bool>()) {
bool value = src["emergency"]["onOutdoorSensorDisconnect"].as<bool>();
if (value != dst.emergency.onOutdoorSensorDisconnect) {
dst.emergency.onOutdoorSensorDisconnect = value;
dst.emergency.useEquitherm = false;
changed = true;
}
if (value >= 0 && value <= 10 && fabs(value - dst.equitherm.t_factor) > 0.0001f) {
dst.equitherm.t_factor = roundd(value, 3);
changed = true;
}
}
@@ -1106,66 +1054,27 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
}
}
if (!src["pid"]["maxTemp"].isNull()) {
unsigned char value = src["pid"]["maxTemp"].as<unsigned char>();
if (isValidTemp(value, dst.system.unitSystem) && value > dst.pid.minTemp && value != dst.pid.maxTemp) {
dst.pid.maxTemp = value;
changed = true;
}
}
if (!src["pid"]["minTemp"].isNull()) {
unsigned char value = src["pid"]["minTemp"].as<unsigned char>();
short value = src["pid"]["minTemp"].as<short>();
if (isValidTemp(value, dst.system.unitSystem) && value < dst.pid.maxTemp && value != dst.pid.minTemp) {
if (isValidTemp(value, dst.system.unitSystem, dst.equitherm.enable ? -99.9f : 0.0f) && value != dst.pid.minTemp) {
dst.pid.minTemp = value;
changed = true;
}
}
if (!src["pid"]["maxTemp"].isNull()) {
short value = src["pid"]["maxTemp"].as<short>();
// equitherm
if (src["equitherm"]["enable"].is<bool>()) {
bool value = src["equitherm"]["enable"].as<bool>();
if (!dst.opentherm.nativeHeatingControl) {
if (value != dst.equitherm.enable) {
dst.equitherm.enable = value;
changed = true;
}
} else if (dst.equitherm.enable) {
dst.equitherm.enable = false;
if (isValidTemp(value, dst.system.unitSystem) && value != dst.pid.maxTemp) {
dst.pid.maxTemp = value;
changed = true;
}
}
if (!src["equitherm"]["n_factor"].isNull()) {
float value = src["equitherm"]["n_factor"].as<float>();
if (value > 0 && value <= 10 && fabs(value - dst.equitherm.n_factor) > 0.0001f) {
dst.equitherm.n_factor = roundd(value, 3);
changed = true;
}
}
if (!src["equitherm"]["k_factor"].isNull()) {
float value = src["equitherm"]["k_factor"].as<float>();
if (value >= 0 && value <= 10 && fabs(value - dst.equitherm.k_factor) > 0.0001f) {
dst.equitherm.k_factor = roundd(value, 3);
changed = true;
}
}
if (!src["equitherm"]["t_factor"].isNull()) {
float value = src["equitherm"]["t_factor"].as<float>();
if (value >= 0 && value <= 10 && fabs(value - dst.equitherm.t_factor) > 0.0001f) {
dst.equitherm.t_factor = roundd(value, 3);
changed = true;
}
if (dst.pid.maxTemp < dst.pid.minTemp) {
dst.pid.maxTemp = dst.pid.minTemp;
changed = true;
}
@@ -1215,6 +1124,11 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
}
}
if (dst.heating.maxTemp < dst.heating.minTemp) {
dst.heating.maxTemp = dst.heating.minTemp;
changed = true;
}
// dhw
if (src["dhw"]["enable"].is<bool>()) {
@@ -1229,7 +1143,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
if (!src["dhw"]["minTemp"].isNull()) {
unsigned char value = src["dhw"]["minTemp"].as<unsigned char>();
if (value >= vars.parameters.dhwMinTemp && value < vars.parameters.dhwMaxTemp && value != dst.dhw.minTemp) {
if (value >= vars.parameters.dhwMinTemp && value != dst.dhw.minTemp) {
dst.dhw.minTemp = value;
changed = true;
}
@@ -1238,12 +1152,16 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
if (!src["dhw"]["maxTemp"].isNull()) {
unsigned char value = src["dhw"]["maxTemp"].as<unsigned char>();
if (value > vars.parameters.dhwMinTemp && value <= vars.parameters.dhwMaxTemp && value != dst.dhw.maxTemp) {
if (value > vars.parameters.dhwMinTemp && value != dst.dhw.maxTemp) {
dst.dhw.maxTemp = value;
changed = true;
}
}
if (dst.dhw.maxTemp < dst.dhw.minTemp) {
dst.dhw.maxTemp = dst.dhw.minTemp;
changed = true;
}
// sensors
if (!src["sensors"]["outdoor"]["type"].isNull()) {
@@ -1260,7 +1178,6 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
case static_cast<byte>(SensorType::MANUAL):
if (dst.sensors.outdoor.type != SensorType::MANUAL) {
dst.sensors.outdoor.type = SensorType::MANUAL;
dst.emergency.useEquitherm = false;
changed = true;
}
break;
@@ -1276,7 +1193,6 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
case static_cast<byte>(SensorType::BLUETOOTH):
if (dst.sensors.outdoor.type != SensorType::BLUETOOTH) {
dst.sensors.outdoor.type = SensorType::BLUETOOTH;
dst.emergency.useEquitherm = false;
changed = true;
}
break;
@@ -1335,8 +1251,6 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
case static_cast<byte>(SensorType::MANUAL):
if (dst.sensors.indoor.type != SensorType::MANUAL) {
dst.sensors.indoor.type = SensorType::MANUAL;
dst.emergency.usePid = false;
dst.opentherm.nativeHeatingControl = false;
changed = true;
}
break;
@@ -1352,7 +1266,6 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
case static_cast<byte>(SensorType::BLUETOOTH):
if (dst.sensors.indoor.type != SensorType::BLUETOOTH) {
dst.sensors.indoor.type = SensorType::BLUETOOTH;
dst.emergency.usePid = false;
changed = true;
}
break;
@@ -1597,7 +1510,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
// force check emergency target
{
float value = !src["emergency"]["target"].isNull() ? src["emergency"]["target"].as<float>() : dst.emergency.target;
bool noRegulators = !dst.opentherm.nativeHeatingControl && !dst.emergency.useEquitherm && !dst.emergency.usePid;
bool noRegulators = !dst.opentherm.nativeHeatingControl;
bool valid = isValidTemp(
value,
dst.system.unitSystem,

View File

@@ -224,27 +224,13 @@
},
"emergency": {
"desc": "<b>!</b> Emergency mode can be useful <u>only</u> when using Equitherm and/or PID (when normal work) and when reporting indoor/outdoor temperature via MQTT/API/BLE. In this mode, sensor values that are reported via MQTT/API/BLE are not used.",
"desc": "Emergency mode is activated automatically when «PID» or «Equitherm» cannot calculate the heat carrier setpoint:<br />- if «Equitherm» is enabled and the outdoor temperature sensor is disconnected;<br />- if «PID» or OT option <i>«Native heating control»</i> is enabled and the indoor temperature sensor is disconnected.<br /><b>Note:</b> On network fault or MQTT fault, sensors with <i>«Manual via MQTT/API»</i> type will be in DISCONNECTED state.",
"target": {
"title": "Target temperature",
"note": "<u>Indoor temperature</u> if Equitherm or PID is <b>enabled</b><br /><u>Heat carrier temperature</u> if Equitherm and PID <b>is disabled</b>"
"note": "<b>Important:</b> <u>Target indoor temperature</u> if OT option <i>«Native heating control»</i> is enabled.<br />In all other cases, the <u>target heat carrier temperature</u>."
},
"treshold": "Treshold time <small>(sec)</small>",
"events": {
"desc": "Events",
"network": "On network fault",
"mqtt": "On MQTT fault",
"indoorSensorDisconnect": "On loss connection with indoor sensor",
"outdoorSensorDisconnect": "On loss connection with outdoor sensor"
},
"regulators": {
"desc": "Using regulators",
"equitherm": "Equitherm <small>(requires at least an external (DS18B20) or boiler <u>outdoor</u> sensor)</small>",
"pid": "PID <small>(requires at least an external (DS18B20) <u>indoor</u> sensor)</small>"
}
"treshold": "Treshold time <small>(sec)</small>"
},
"equitherm": {
@@ -260,7 +246,8 @@
"p": "P factor",
"i": "I factor",
"d": "D factor",
"dt": "DT <small>in seconds</small>"
"dt": "DT <small>in seconds</small>",
"noteMinMaxTemp": "<b>Important:</b> When using «Equitherm» and «PID» at the same time, the min and max temperatures limit the influence on the «Equitherm» result temperature.<br />Thus, if the min temperature is set to -15 and the max temperature is set to 15, then the final heat carrier setpoint will be from <code>equitherm_result - 15</code> to <code>equitherm_result + 15</code>."
},
"ot": {
@@ -313,7 +300,7 @@
"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 and hysteresis in firmware."
"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."
}
},

View File

@@ -224,11 +224,11 @@
},
"emergency": {
"desc": "<b>!</b> Аварийный режим может быть полезен <u>только</u> при использовании ПЗА и/или ПИД и при передачи наружной/внутренней температуры через MQTT/API/BLE. В этом режиме значения датчиков, передаваемые через MQTT/API/BLE, не используются.",
"desc": "Аварийный режим активируется автоматически, если «PID» или «Equitherm» не могут рассчитать уставку теплоносителя:<br />- если «Equitherm» включен и датчик наружной температуры отключен;<br />- если включен «PID» или OT опция <i>«Передать управление отоплением котлу»</i> и датчик температуры в помещении отключен.<br /><b>Примечание:</b> При сбое сети или MQTT датчики с типом <i>«Вручную через MQTT/API»</i> будут находиться в состоянии ОТКЛЮЧЕН.",
"target": {
"title": "Целевая температура",
"note": "Целевая <u>внутренняя температура</u> если ПЗА и/или ПИД <b>включены</b><br />Целевая <u>температура теплоносителя</u> если ПЗА и ПИД <b>выключены</b>"
"note": "<b>Важно:</b> <u>Целевая температура в помещении</u>, если включена ОТ опция <i>«Передать управление отоплением котлу»</i>.<br />Во всех остальных случаях <u>целевая температура теплоносителя</u>."
},
"treshold": "Пороговое время включения <small>(сек)</small>",
@@ -260,7 +260,8 @@
"p": "Коэффициент P",
"i": "Коэффициент I",
"d": "Коэффициент D",
"dt": "DT <small>(сек)</small>"
"dt": "DT <small>(сек)</small>",
"noteMinMaxTemp": "<b>Важно:</b> При использовании «Equitherm» и «PID» одновременно, мин. и макс. температура ограничивает влияние на расчётную температуру «Equitherm».<br />Таким образом, если мин. температура задана как -15, а макс. как 15, то конечная температура теплоносителя будет от <code>equitherm_result - 15</code> до <code>equitherm_result + 15</code>."
},
"ot": {
@@ -313,7 +314,7 @@
"nativeHeating": {
"title": "Передать управление отоплением котлу",
"note": "Работает <u>ТОЛЬКО</u> если котел требует и принимает целевую температуру в помещении и сам регулирует температуру теплоносителя на основе встроенного режима кривых. Несовместимо с ПИД, ПЗА и гистерезисом."
"note": "Работает <u>ТОЛЬКО</u> если котел требует и принимает целевую температуру в помещении и сам регулирует температуру теплоносителя на основе встроенного режима кривых. Несовместимо с ПИД и ПЗА."
}
},

View File

@@ -208,11 +208,6 @@
<div id="emergency-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="emergency-settings" class="hidden">
<fieldset>
<label for="emergency-enable">
<input type="checkbox" id="emergency-enable" name="emergency[enable]" value="true">
<span data-i18n>settings.enable</span>
</label>
<small data-i18n>settings.emergency.desc</small>
</fieldset>
@@ -229,43 +224,6 @@
</label>
</div>
<fieldset>
<legend data-i18n>settings.emergency.events.desc</legend>
<label for="emergency-on-network-fault">
<input type="checkbox" id="emergency-on-network-fault" name="emergency[onNetworkFault]" value="true">
<span data-i18n>settings.emergency.events.network</span>
</label>
<label for="emergency-on-mqtt-fault">
<input type="checkbox" id="emergency-on-mqtt-fault" name="emergency[onMqttFault]" value="true">
<span data-i18n>settings.emergency.events.mqtt</span>
</label>
<label for="emergency-on-indoor-sensor-disconnect">
<input type="checkbox" id="emergency-on-indoor-sensor-disconnect" name="emergency[onIndoorSensorDisconnect]" value="true">
<span data-i18n>settings.emergency.events.indoorSensorDisconnect</span>
</label>
<label for="emergency-on-outdoor-sensor-disconnect">
<input type="checkbox" id="emergency-on-outdoor-sensor-disconnect" name="emergency[onOutdoorSensorDisconnect]" value="true">
<span data-i18n>settings.emergency.events.outdoorSensorDisconnect</span>
</label>
</fieldset>
<fieldset>
<legend data-i18n>settings.emergency.regulators.desc</legend>
<label for="emergency-use-equitherm">
<input type="checkbox" id="emergency-use-equitherm" name="emergency[useEquitherm]" value="true">
<span data-i18n>settings.emergency.regulators.equitherm</span>
</label>
<label for="emergency-use-pid">
<input type="checkbox" id="emergency-use-pid" name="emergency[usePid]" value="true">
<span data-i18n>settings.emergency.regulators.pid</span>
</label>
</fieldset>
<button type="submit" data-i18n>button.save</button>
</form>
</div>
@@ -358,6 +316,8 @@
</label>
</div>
<small data-i18n>settings.pid.noteMinMaxTemp</small>
<button type="submit" data-i18n>button.save</button>
</form>
</div>
@@ -958,18 +918,20 @@
setBusy('#dhw-settings-busy', '#dhw-settings', false);
// Emergency mode
setCheckboxValue('#emergency-enable', data.emergency.enable);
setInputValue('#emergency-treshold-time', data.emergency.tresholdTime);
setCheckboxValue('#emergency-use-equitherm', data.emergency.useEquitherm);
setCheckboxValue('#emergency-use-pid', data.emergency.usePid);
setCheckboxValue('#emergency-on-network-fault', data.emergency.onNetworkFault);
setCheckboxValue('#emergency-on-mqtt-fault', data.emergency.onMqttFault);
setCheckboxValue('#emergency-on-indoor-sensor-disconnect', data.emergency.onIndoorSensorDisconnect);
setCheckboxValue('#emergency-on-outdoor-sensor-disconnect', data.emergency.onOutdoorSensorDisconnect);
setInputValue('#emergency-target', data.emergency.target, {
"min": (!data.emergency.useEquitherm && !data.emergency.usePid) ? data.heating.minTemp : 10,
"max": (!data.emergency.useEquitherm && !data.emergency.usePid) ? data.heating.maxTemp : 30,
});
if (data.opentherm.nativeHeatingControl) {
setInputValue('#emergency-target', data.emergency.target, {
"min": data.system.unitSystem == 0 ? 5 : 41,
"max": data.system.unitSystem == 0 ? 30 : 86
});
} else {
setInputValue('#emergency-target', data.emergency.target, {
"min": data.heating.minTemp,
"max": data.heating.maxTemp,
});
}
setBusy('#emergency-settings-busy', '#emergency-settings', false);
// Equitherm
@@ -986,12 +948,12 @@
setInputValue('#pid-d-factor', data.pid.d_factor);
setInputValue('#pid-dt', data.pid.dt);
setInputValue('#pid-min-temp', data.pid.minTemp, {
"min": 0,
"max": data.system.unitSystem == 0 ? 99 : 211
"min": data.equitherm.enable ? (data.system.unitSystem == 0 ? -100 : -146) : (data.system.unitSystem == 0 ? 0 : 32),
"max": (data.system.unitSystem == 0 ? 99 : 211)
});
setInputValue('#pid-max-temp', data.pid.maxTemp, {
"min": 1,
"max": data.system.unitSystem == 0 ? 100 : 212
"min": (data.system.unitSystem == 0 ? 0 : 33),
"max": (data.system.unitSystem == 0 ? 100 : 212)
});
setBusy('#pid-settings-busy', '#pid-settings', false);
};