feat: fault state gpio setting replaced with cascade control

This commit is contained in:
Yurii
2024-10-18 06:14:09 +03:00
parent 57cf98ca19
commit 7f701a74e7
9 changed files with 506 additions and 117 deletions

View File

@@ -119,6 +119,7 @@ protected:
this->emergency();
this->ledStatus();
this->cascadeControl();
this->externalPump();
this->yield();
@@ -336,6 +337,170 @@ protected:
this->blinker->tick();
}
void cascadeControl() {
static uint8_t configuredInputGpio = GPIO_IS_NOT_CONFIGURED;
static uint8_t configuredOutputGpio = GPIO_IS_NOT_CONFIGURED;
static bool inputTempValue = false;
static unsigned long inputChangedTs = 0;
static bool outputTempValue = false;
static unsigned long outputChangedTs = 0;
// input
if (settings.cascadeControl.input.enable) {
if (settings.cascadeControl.input.gpio != configuredInputGpio) {
if (configuredInputGpio != GPIO_IS_NOT_CONFIGURED) {
pinMode(configuredInputGpio, OUTPUT);
digitalWrite(configuredInputGpio, LOW);
Log.sinfoln(FPSTR(L_CASCADE_INPUT), F("Deinitialized on GPIO %hhu"), configuredInputGpio);
}
if (GPIO_IS_VALID(settings.cascadeControl.input.gpio)) {
configuredInputGpio = settings.cascadeControl.input.gpio;
pinMode(configuredInputGpio, INPUT);
Log.sinfoln(FPSTR(L_CASCADE_INPUT), F("Initialized on GPIO %hhu"), configuredInputGpio);
} else if (configuredInputGpio != GPIO_IS_NOT_CONFIGURED) {
configuredInputGpio = GPIO_IS_NOT_CONFIGURED;
Log.swarningln(FPSTR(L_CASCADE_INPUT), F("Failed initialize: GPIO %hhu is not valid!"), configuredInputGpio);
}
}
if (configuredInputGpio != GPIO_IS_NOT_CONFIGURED) {
bool value;
if (digitalRead(configuredInputGpio) == HIGH) {
value = true ^ settings.cascadeControl.input.invertState;
} else {
value = false ^ settings.cascadeControl.input.invertState;
}
if (value != vars.cascadeControl.input) {
if (value != inputTempValue) {
inputTempValue = value;
inputChangedTs = millis();
} else if (millis() - inputChangedTs >= settings.cascadeControl.input.thresholdTime * 1000u) {
vars.cascadeControl.input = value;
Log.sinfoln(
FPSTR(L_CASCADE_INPUT),
F("State changed to %s"),
value ? F("TRUE") : F("FALSE")
);
}
} else if (value != inputTempValue) {
inputTempValue = value;
}
}
}
if (!settings.cascadeControl.input.enable || configuredInputGpio == GPIO_IS_NOT_CONFIGURED) {
if (!vars.cascadeControl.input) {
vars.cascadeControl.input = true;
Log.sinfoln(
FPSTR(L_CASCADE_INPUT),
F("Disabled, state changed to %s"),
vars.cascadeControl.input ? F("TRUE") : F("FALSE")
);
}
}
// output
if (settings.cascadeControl.output.enable) {
if (settings.cascadeControl.output.gpio != configuredOutputGpio) {
if (configuredOutputGpio != GPIO_IS_NOT_CONFIGURED) {
pinMode(configuredOutputGpio, OUTPUT);
digitalWrite(configuredOutputGpio, LOW);
Log.sinfoln(FPSTR(L_CASCADE_OUTPUT), F("Deinitialized on GPIO %hhu"), configuredOutputGpio);
}
if (GPIO_IS_VALID(settings.cascadeControl.output.gpio)) {
configuredOutputGpio = settings.cascadeControl.output.gpio;
pinMode(configuredOutputGpio, OUTPUT);
digitalWrite(
configuredOutputGpio,
settings.cascadeControl.output.invertState
? HIGH
: LOW
);
Log.sinfoln(FPSTR(L_CASCADE_OUTPUT), F("Initialized on GPIO %hhu"), configuredOutputGpio);
} else if (configuredOutputGpio != GPIO_IS_NOT_CONFIGURED) {
configuredOutputGpio = GPIO_IS_NOT_CONFIGURED;
Log.swarningln(FPSTR(L_CASCADE_OUTPUT), F("Failed initialize: GPIO %hhu is not valid!"), configuredOutputGpio);
}
}
if (configuredOutputGpio != GPIO_IS_NOT_CONFIGURED) {
bool value = false;
if (settings.cascadeControl.output.onFault && vars.states.fault) {
value = true;
} else if (settings.cascadeControl.output.onLossConnection && !vars.states.otStatus) {
value = true;
} else if (settings.cascadeControl.output.onEnabledHeating && settings.heating.enable && vars.cascadeControl.input) {
value = true;
}
if (value != vars.cascadeControl.output) {
if (value != outputTempValue) {
outputTempValue = value;
outputChangedTs = millis();
} else if (millis() - outputChangedTs >= settings.cascadeControl.output.thresholdTime * 1000u) {
vars.cascadeControl.output = value;
digitalWrite(
configuredOutputGpio,
vars.cascadeControl.output ^ settings.cascadeControl.output.invertState
? HIGH
: LOW
);
Log.sinfoln(
FPSTR(L_CASCADE_OUTPUT),
F("State changed to %s"),
value ? F("TRUE") : F("FALSE")
);
}
} else if (value != outputTempValue) {
outputTempValue = value;
}
}
}
if (!settings.cascadeControl.output.enable || configuredOutputGpio == GPIO_IS_NOT_CONFIGURED) {
if (vars.cascadeControl.output) {
vars.cascadeControl.output = false;
if (configuredOutputGpio != GPIO_IS_NOT_CONFIGURED) {
digitalWrite(
configuredOutputGpio,
vars.cascadeControl.output ^ settings.cascadeControl.output.invertState
? HIGH
: LOW
);
}
Log.sinfoln(
FPSTR(L_CASCADE_OUTPUT),
F("Disabled, state changed to %s"),
vars.cascadeControl.output ? F("TRUE") : F("FALSE")
);
}
}
}
void externalPump() {
static uint8_t configuredGpio = GPIO_IS_NOT_CONFIGURED;

View File

@@ -28,8 +28,6 @@ protected:
unsigned long dhwSetTempTime = 0;
unsigned long heatingSetTempTime = 0;
byte configuredRxLedGpio = GPIO_IS_NOT_CONFIGURED;
byte configuredFaultStateGpio = GPIO_IS_NOT_CONFIGURED;
bool faultState = false;
#if defined(ARDUINO_ARCH_ESP32)
const char* getTaskName() override {
@@ -133,28 +131,7 @@ protected:
}
}
// Fault state setup
if (settings.opentherm.faultStateGpio != this->configuredFaultStateGpio) {
if (this->configuredFaultStateGpio != GPIO_IS_NOT_CONFIGURED) {
digitalWrite(this->configuredFaultStateGpio, LOW);
}
if (GPIO_IS_VALID(settings.opentherm.faultStateGpio)) {
this->configuredFaultStateGpio = settings.opentherm.faultStateGpio;
this->faultState = false ^ settings.opentherm.invertFaultState ? HIGH : LOW;
pinMode(this->configuredFaultStateGpio, OUTPUT);
digitalWrite(
this->configuredFaultStateGpio,
this->faultState
);
} else if (this->configuredFaultStateGpio != GPIO_IS_NOT_CONFIGURED) {
this->configuredFaultStateGpio = GPIO_IS_NOT_CONFIGURED;
}
}
bool heatingEnabled = (vars.states.emergency || settings.heating.enable) && this->pump && this->isReady();
bool heatingEnabled = (vars.states.emergency || settings.heating.enable) && this->pump && vars.cascadeControl.input && this->isReady();
bool heatingCh2Enabled = settings.opentherm.heatingCh2Enabled;
if (settings.opentherm.heatingCh1ToCh2) {
heatingCh2Enabled = heatingEnabled;
@@ -212,16 +189,6 @@ protected:
vars.states.fault = false;
vars.states.diagnostic = false;
// Force fault state = on
if (this->configuredFaultStateGpio != GPIO_IS_NOT_CONFIGURED) {
bool fState = true ^ settings.opentherm.invertFaultState ? HIGH : LOW;
if (fState != this->faultState) {
this->faultState = fState;
digitalWrite(this->configuredFaultStateGpio, this->faultState);
}
}
return;
}
@@ -251,16 +218,6 @@ protected:
vars.states.heating, vars.states.dhw, vars.states.flame, vars.states.fault, vars.states.diagnostic
);
// Fault state
if (this->configuredFaultStateGpio != GPIO_IS_NOT_CONFIGURED) {
bool fState = vars.states.fault ^ settings.opentherm.invertFaultState ? HIGH : LOW;
if (fState != this->faultState) {
this->faultState = fState;
digitalWrite(this->configuredFaultStateGpio, this->faultState);
}
}
// These parameters will be updated every minute
if (millis() - this->prevUpdateNonEssentialVars > 60000) {
if (this->updateMinModulationLevel()) {

View File

@@ -51,8 +51,6 @@ struct Settings {
byte inGpio = DEFAULT_OT_IN_GPIO;
byte outGpio = DEFAULT_OT_OUT_GPIO;
byte rxLedGpio = DEFAULT_OT_RX_LED_GPIO;
byte faultStateGpio = DEFAULT_OT_FAULT_STATE_GPIO;
byte invertFaultState = false;
unsigned int memberIdCode = 0;
uint8_t maxModulation = 100;
float pressureFactor = 1.0f;
@@ -156,6 +154,25 @@ struct Settings {
unsigned short antiStuckTime = 300;
} externalPump;
struct {
struct {
bool enable = false;
byte gpio = GPIO_IS_NOT_CONFIGURED;
byte invertState = false;
unsigned short thresholdTime = 60;
} input;
struct {
bool enable = false;
byte gpio = GPIO_IS_NOT_CONFIGURED;
byte invertState = false;
unsigned short thresholdTime = 60;
bool onFault = true;
bool onLossConnection = true;
bool onEnabledHeating = false;
} output;
} cascadeControl;
char validationValue[8] = SETTINGS_VALID_VALUE;
} settings;
@@ -205,6 +222,11 @@ struct Variables {
float exhaust = 0.0f;
} temperatures;
struct {
bool input = false;
bool output = false;
} cascadeControl;
struct {
bool heatingEnabled = false;
byte heatingMinTemp = DEFAULT_HEATING_MIN_TEMP;

View File

@@ -24,4 +24,6 @@ const char L_SENSORS_INDOOR[] PROGMEM = "SENSORS.INDOOR";
const char L_SENSORS_BLE[] PROGMEM = "SENSORS.BLE";
const char L_REGULATOR[] PROGMEM = "REGULATOR";
const char L_REGULATOR_PID[] PROGMEM = "REGULATOR.PID";
const char L_REGULATOR_EQUITHERM[] PROGMEM = "REGULATOR.EQUITHERM";
const char L_REGULATOR_EQUITHERM[] PROGMEM = "REGULATOR.EQUITHERM";
const char L_CASCADE_INPUT[] PROGMEM = "CASCADE.INPUT";
const char L_CASCADE_OUTPUT[] PROGMEM = "CASCADE.OUTPUT";

View File

@@ -342,8 +342,6 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
dst["opentherm"]["inGpio"] = src.opentherm.inGpio;
dst["opentherm"]["outGpio"] = src.opentherm.outGpio;
dst["opentherm"]["rxLedGpio"] = src.opentherm.rxLedGpio;
dst["opentherm"]["faultStateGpio"] = src.opentherm.faultStateGpio;
dst["opentherm"]["invertFaultState"] = src.opentherm.invertFaultState;
dst["opentherm"]["memberIdCode"] = src.opentherm.memberIdCode;
dst["opentherm"]["maxModulation"] = src.opentherm.maxModulation;
dst["opentherm"]["pressureFactor"] = roundd(src.opentherm.pressureFactor, 2);
@@ -447,6 +445,19 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
dst["externalPump"]["postCirculationTime"] = roundd(src.externalPump.postCirculationTime / 60, 0);
dst["externalPump"]["antiStuckInterval"] = roundd(src.externalPump.antiStuckInterval / 86400, 0);
dst["externalPump"]["antiStuckTime"] = roundd(src.externalPump.antiStuckTime / 60, 0);
dst["cascadeControl"]["input"]["enable"] = src.cascadeControl.input.enable;
dst["cascadeControl"]["input"]["gpio"] = src.cascadeControl.input.gpio;
dst["cascadeControl"]["input"]["invertState"] = src.cascadeControl.input.invertState;
dst["cascadeControl"]["input"]["thresholdTime"] = src.cascadeControl.input.thresholdTime;
dst["cascadeControl"]["output"]["enable"] = src.cascadeControl.output.enable;
dst["cascadeControl"]["output"]["gpio"] = src.cascadeControl.output.gpio;
dst["cascadeControl"]["output"]["invertState"] = src.cascadeControl.output.invertState;
dst["cascadeControl"]["output"]["thresholdTime"] = src.cascadeControl.output.thresholdTime;
dst["cascadeControl"]["output"]["onFault"] = src.cascadeControl.output.onFault;
dst["cascadeControl"]["output"]["onLossConnection"] = src.cascadeControl.output.onLossConnection;
dst["cascadeControl"]["output"]["onEnabledHeating"] = src.cascadeControl.output.onEnabledHeating;
}
}
@@ -665,32 +676,6 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
}
}
if (!src["opentherm"]["faultStateGpio"].isNull()) {
if (src["opentherm"]["faultStateGpio"].is<JsonString>() && src["opentherm"]["faultStateGpio"].as<JsonString>().size() == 0) {
if (dst.opentherm.faultStateGpio != GPIO_IS_NOT_CONFIGURED) {
dst.opentherm.faultStateGpio = GPIO_IS_NOT_CONFIGURED;
changed = true;
}
} else {
unsigned char value = src["opentherm"]["faultStateGpio"].as<unsigned char>();
if (GPIO_IS_VALID(value) && value != dst.opentherm.faultStateGpio) {
dst.opentherm.faultStateGpio = value;
changed = true;
}
}
}
if (src["opentherm"]["invertFaultState"].is<bool>()) {
bool value = src["opentherm"]["invertFaultState"].as<bool>();
if (value != dst.opentherm.invertFaultState) {
dst.opentherm.invertFaultState = value;
changed = true;
}
}
if (!src["opentherm"]["memberIdCode"].isNull()) {
unsigned int value = src["opentherm"]["memberIdCode"].as<unsigned int>();
@@ -1470,6 +1455,127 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
}
}
}
// cascade control
if (src["cascadeControl"]["input"]["enable"].is<bool>()) {
bool value = src["cascadeControl"]["input"]["enable"].as<bool>();
if (value != dst.cascadeControl.input.enable) {
dst.cascadeControl.input.enable = value;
changed = true;
}
}
if (!src["cascadeControl"]["input"]["gpio"].isNull()) {
if (src["cascadeControl"]["input"]["gpio"].is<JsonString>() && src["cascadeControl"]["input"]["gpio"].as<JsonString>().size() == 0) {
if (dst.cascadeControl.input.gpio != GPIO_IS_NOT_CONFIGURED) {
dst.cascadeControl.input.gpio = GPIO_IS_NOT_CONFIGURED;
changed = true;
}
} else {
unsigned char value = src["cascadeControl"]["input"]["gpio"].as<unsigned char>();
if (GPIO_IS_VALID(value) && value != dst.cascadeControl.input.gpio) {
dst.cascadeControl.input.gpio = value;
changed = true;
}
}
}
if (src["cascadeControl"]["input"]["invertState"].is<bool>()) {
bool value = src["cascadeControl"]["input"]["invertState"].as<bool>();
if (value != dst.cascadeControl.input.invertState) {
dst.cascadeControl.input.invertState = value;
changed = true;
}
}
if (!src["cascadeControl"]["input"]["thresholdTime"].isNull()) {
unsigned short value = src["cascadeControl"]["input"]["thresholdTime"].as<unsigned short>();
if (value >= 5 && value <= 600) {
if (value != dst.cascadeControl.input.thresholdTime) {
dst.cascadeControl.input.thresholdTime = value;
changed = true;
}
}
}
if (src["cascadeControl"]["output"]["enable"].is<bool>()) {
bool value = src["cascadeControl"]["output"]["enable"].as<bool>();
if (value != dst.cascadeControl.output.enable) {
dst.cascadeControl.output.enable = value;
changed = true;
}
}
if (!src["cascadeControl"]["output"]["gpio"].isNull()) {
if (src["cascadeControl"]["output"]["gpio"].is<JsonString>() && src["cascadeControl"]["output"]["gpio"].as<JsonString>().size() == 0) {
if (dst.cascadeControl.output.gpio != GPIO_IS_NOT_CONFIGURED) {
dst.cascadeControl.output.gpio = GPIO_IS_NOT_CONFIGURED;
changed = true;
}
} else {
unsigned char value = src["cascadeControl"]["output"]["gpio"].as<unsigned char>();
if (GPIO_IS_VALID(value) && value != dst.cascadeControl.output.gpio) {
dst.cascadeControl.output.gpio = value;
changed = true;
}
}
}
if (src["cascadeControl"]["output"]["invertState"].is<bool>()) {
bool value = src["cascadeControl"]["output"]["invertState"].as<bool>();
if (value != dst.cascadeControl.output.invertState) {
dst.cascadeControl.output.invertState = value;
changed = true;
}
}
if (!src["cascadeControl"]["output"]["thresholdTime"].isNull()) {
unsigned short value = src["cascadeControl"]["output"]["thresholdTime"].as<unsigned short>();
if (value >= 5 && value <= 600) {
if (value != dst.cascadeControl.output.thresholdTime) {
dst.cascadeControl.output.thresholdTime = value;
changed = true;
}
}
}
if (src["cascadeControl"]["output"]["onFault"].is<bool>()) {
bool value = src["cascadeControl"]["output"]["onFault"].as<bool>();
if (value != dst.cascadeControl.output.onFault) {
dst.cascadeControl.output.onFault = value;
changed = true;
}
}
if (src["cascadeControl"]["output"]["onLossConnection"].is<bool>()) {
bool value = src["cascadeControl"]["output"]["onLossConnection"].as<bool>();
if (value != dst.cascadeControl.output.onLossConnection) {
dst.cascadeControl.output.onLossConnection = value;
changed = true;
}
}
if (src["cascadeControl"]["output"]["onEnabledHeating"].is<bool>()) {
bool value = src["cascadeControl"]["output"]["onEnabledHeating"].as<bool>();
if (value != dst.cascadeControl.output.onEnabledHeating) {
dst.cascadeControl.output.onEnabledHeating = value;
changed = true;
}
}
}
// force check emergency target
@@ -1587,6 +1693,9 @@ void varsToJson(const Variables& src, JsonVariant dst) {
dst["temperatures"]["dhw"] = roundd(src.temperatures.dhw, 2);
dst["temperatures"]["exhaust"] = roundd(src.temperatures.exhaust, 2);
dst["cascadeControl"]["input"] = src.cascadeControl.input;
dst["cascadeControl"]["output"] = src.cascadeControl.output;
dst["parameters"]["heatingEnabled"] = src.parameters.heatingEnabled;
dst["parameters"]["heatingMinTemp"] = src.parameters.heatingMinTemp;
dst["parameters"]["heatingMaxTemp"] = src.parameters.heatingMaxTemp;