New features and refactoring

This commit is contained in:
Yurii
2022-11-17 03:24:39 +03:00
parent 0b887a8400
commit 5be63c4f85
10 changed files with 212 additions and 142 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
assets/connection.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

2
assets/logo.svg Normal file
View File

@@ -0,0 +1,2 @@
OTGATEWAY BOILER CONTROL GATEWAY

Binary file not shown.

View File

@@ -447,7 +447,7 @@ public:
doc["command_topic"] = _prefix + "/settings/set"; doc["command_topic"] = _prefix + "/settings/set";
doc["command_template"] = "{\"pid\": {\"p_factor\" : {{ value }}}}"; doc["command_template"] = "{\"pid\": {\"p_factor\" : {{ value }}}}";
doc["min"] = 0.001; doc["min"] = 0.001;
doc["max"] = 3; doc["max"] = 10;
doc["step"] = 0.001; doc["step"] = 0.001;
client.beginPublish((F("homeassistant/number/") + _prefix + "/pid_p_factor/config").c_str(), measureJson(doc), true); client.beginPublish((F("homeassistant/number/") + _prefix + "/pid_p_factor/config").c_str(), measureJson(doc), true);
@@ -478,7 +478,7 @@ public:
doc["command_topic"] = _prefix + "/settings/set"; doc["command_topic"] = _prefix + "/settings/set";
doc["command_template"] = "{\"pid\": {\"i_factor\" : {{ value }}}}"; doc["command_template"] = "{\"pid\": {\"i_factor\" : {{ value }}}}";
doc["min"] = 0; doc["min"] = 0;
doc["max"] = 3; doc["max"] = 10;
doc["step"] = 0.001; doc["step"] = 0.001;
client.beginPublish((F("homeassistant/number/") + _prefix + "/pid_i_factor/config").c_str(), measureJson(doc), true); client.beginPublish((F("homeassistant/number/") + _prefix + "/pid_i_factor/config").c_str(), measureJson(doc), true);
@@ -509,7 +509,7 @@ public:
doc["command_topic"] = _prefix + "/settings/set"; doc["command_topic"] = _prefix + "/settings/set";
doc["command_template"] = "{\"pid\": {\"d_factor\" : {{ value }}}}"; doc["command_template"] = "{\"pid\": {\"d_factor\" : {{ value }}}}";
doc["min"] = 0; doc["min"] = 0;
doc["max"] = 3; doc["max"] = 10;
doc["step"] = 0.001; doc["step"] = 0.001;
client.beginPublish((F("homeassistant/number/") + _prefix + "/pid_d_factor/config").c_str(), measureJson(doc), true); client.beginPublish((F("homeassistant/number/") + _prefix + "/pid_d_factor/config").c_str(), measureJson(doc), true);
@@ -617,6 +617,8 @@ public:
bool publishNumberEquithermFactorT(bool enabledByDefault = true) { bool publishNumberEquithermFactorT(bool enabledByDefault = true) {
StaticJsonDocument<1536> doc; StaticJsonDocument<1536> doc;
doc["availability"]["topic"] = _prefix + F("/settings");
doc["availability"]["value_template"] = F("{{ iif(value_json.pid.enable, 'offline', 'online') }}");
doc["device"]["identifiers"][0] = _prefix; doc["device"]["identifiers"][0] = _prefix;
doc["device"]["sw_version"] = _deviceVersion; doc["device"]["sw_version"] = _deviceVersion;
doc["device"]["manufacturer"] = _deviceManufacturer; doc["device"]["manufacturer"] = _deviceManufacturer;

View File

@@ -218,6 +218,10 @@ protected:
} }
if (!doc["restart"].isNull() && doc["restart"].is<bool>() && doc["restart"]) { if (!doc["restart"].isNull() && doc["restart"].is<bool>() && doc["restart"]) {
DEBUG("Received restart message...");
Scheduler.delay(10000);
DEBUG("Restart...");
eeSettings.updateNow(); eeSettings.updateNow();
ESP.restart(); ESP.restart();
} }

View File

@@ -13,6 +13,7 @@ public:
protected: protected:
bool tunerInit = false; bool tunerInit = false;
byte tunerState = 0; byte tunerState = 0;
byte tunerRegulator = 0;
float prevHeatingTarget = 0; float prevHeatingTarget = 0;
float prevEtResult = 0; float prevEtResult = 0;
float prevPidResult = 0; float prevPidResult = 0;
@@ -24,9 +25,20 @@ protected:
if (vars.states.emergency) { if (vars.states.emergency) {
newTemp = getEmergencyModeTemp(); newTemp = getEmergencyModeTemp();
} else { } else {
if ( vars.tuning.enable || tunerInit ) {
newTemp = getTuningModeTemp();
if ( newTemp == 0 ) {
vars.tuning.enable = false;
}
}
if ( !vars.tuning.enable ) {
newTemp = getNormalModeTemp(); newTemp = getNormalModeTemp();
} }
}
// Ограничиваем, если до этого не ограничило // Ограничиваем, если до этого не ограничило
if (newTemp < vars.parameters.heatingMinTemp || newTemp > vars.parameters.heatingMaxTemp) { if (newTemp < vars.parameters.heatingMinTemp || newTemp > vars.parameters.heatingMaxTemp) {
@@ -40,114 +52,126 @@ protected:
byte getEmergencyModeTemp() { byte getEmergencyModeTemp() {
byte newTemp = vars.parameters.heatingSetpoint; float newTemp = 0;
// if use equitherm // if use equitherm
if (settings.emergency.useEquitherm && settings.outdoorTempSource != 1) { if (settings.emergency.useEquitherm && settings.outdoorTempSource != 1) {
etRegulator.Kn = settings.equitherm.n_factor; float etResult = getEquithermTemp();
etRegulator.Kk = settings.equitherm.k_factor;
etRegulator.Kt = 0;
etRegulator.indoorTemp = 0;
etRegulator.outdoorTemp = vars.temperatures.outdoor;
etRegulator.setLimits(vars.parameters.heatingMinTemp, vars.parameters.heatingMaxTemp); if (fabs(prevEtResult - etResult) + 0.0001 >= 0.5) {
etRegulator.targetTemp = settings.emergency.target;
float etResult = etRegulator.getResult();
if (fabs(prevEtResult - etResult) + 0.0001 >= 1) {
prevEtResult = etResult; prevEtResult = etResult;
newTemp = round(etResult); newTemp += etResult;
INFO_F("New emergency equitherm result: %u (%f) \n", newTemp, etResult); INFO_F("[REGULATOR][EQUITHERM] New emergency result: %u (%f) \n", (byte) round(etResult), etResult);
} else {
newTemp += prevEtResult;
} }
} else { } else {
// default temp, manual mode // default temp, manual mode
newTemp = round(settings.emergency.target); newTemp = settings.emergency.target;
} }
return newTemp; return round(newTemp);
} }
byte getNormalModeTemp() { byte getNormalModeTemp() {
bool updateIntegral = false; float newTemp = 0;
byte newTemp = vars.parameters.heatingSetpoint;
if (fabs(prevHeatingTarget - settings.heating.target) > 0.0001) { if (fabs(prevHeatingTarget - settings.heating.target) > 0.0001) {
prevHeatingTarget = settings.heating.target; prevHeatingTarget = settings.heating.target;
updateIntegral = true;
INFO_F("New heating target: %f \n", settings.heating.target); INFO_F("[REGULATOR] New target: %f \n", settings.heating.target);
} }
// if use equitherm // if use equitherm
if (settings.equitherm.enable) { if (settings.equitherm.enable) {
if (vars.tuning.enable && vars.tuning.regulator == 0) { float etResult = getEquithermTemp();
if (settings.pid.enable) {
settings.pid.enable = false;
}
etRegulator.Kn = tuneEquithermN(etRegulator.Kn, vars.temperatures.indoor, settings.heating.target, 300, 1800, 0.01, 1); if (fabs(prevEtResult - etResult) + 0.0001 >= 0.5) {
} else {
etRegulator.Kn = settings.equitherm.n_factor;
}
if (settings.pid.enable) {
etRegulator.Kt = 0;
etRegulator.indoorTemp = round(vars.temperatures.indoor);
etRegulator.outdoorTemp = round(vars.temperatures.outdoor);
} else {
etRegulator.Kt = settings.equitherm.t_factor;
etRegulator.indoorTemp = vars.temperatures.indoor;
etRegulator.outdoorTemp = vars.temperatures.outdoor;
}
etRegulator.setLimits(vars.parameters.heatingMinTemp, vars.parameters.heatingMaxTemp);
etRegulator.Kk = settings.equitherm.k_factor;
etRegulator.targetTemp = settings.heating.target;
float etResult = etRegulator.getResult();
if (fabs(prevEtResult - etResult) + 0.0001 >= 1) {
prevEtResult = etResult; prevEtResult = etResult;
updateIntegral = true; newTemp += etResult;
newTemp = round(etResult);
INFO_F("New equitherm result: %u (%f) \n", newTemp, etResult); INFO_F("[REGULATOR][EQUITHERM] New result: %u (%f) \n", (byte) round(etResult), etResult);
} else { } else {
updateIntegral = false; newTemp += prevEtResult;
} }
} }
// if use pid // if use pid
if (settings.pid.enable && tunerInit && (!vars.tuning.enable || vars.tuning.regulator != 1)) { if (settings.pid.enable) {
pidTuner.reset(); float pidResult = getPidTemp();
tunerState = 0;
tunerInit = false; if (fabs(prevPidResult - pidResult) + 0.0001 >= 0.5) {
INFO(F("Tuning stopped")); prevPidResult = pidResult;
newTemp += pidResult;
INFO_F("[REGULATOR][PID] New result: %u (%f) \n", (byte) round(pidResult), pidResult);
} else {
newTemp += prevPidResult;
}
}
// default temp, manual mode
if (!settings.equitherm.enable && !settings.pid.enable) {
newTemp = settings.heating.target;
}
return round(newTemp);
}
byte getTuningModeTemp() {
if ( tunerInit && (!vars.tuning.enable || vars.tuning.regulator != tunerRegulator) ) {
if ( tunerRegulator == 0 ) {
pidTuner.reset();
}
tunerInit = false;
tunerRegulator = 0;
tunerState = 0;
INFO(F("[REGULATOR][TUNING] Stopped"));
}
if ( !vars.tuning.enable ) {
return 0;
}
if ( vars.tuning.regulator == 0 ) {
// @TODO дописать
INFO(F("[REGULATOR][TUNING][EQUITHERM] Not implemented"));
return 0;
} else if ( vars.tuning.regulator == 1 ) {
// PID tuner
float defaultTemp = settings.equitherm.enable ? getEquithermTemp() : settings.heating.target;
} else if (settings.pid.enable && vars.tuning.enable && vars.tuning.regulator == 1) {
if (tunerInit && pidTuner.getState() == 3) { if (tunerInit && pidTuner.getState() == 3) {
INFO(F("Tuning finished")); INFO(F("[REGULATOR][TUNING][PID] Finished"));
pidTuner.debugText(&INFO_STREAM); pidTuner.debugText(&INFO_STREAM);
pidTuner.reset();
tunerInit = false;
tunerRegulator = 0;
tunerState = 0;
if (pidTuner.getAccuracy() < 90) { if (pidTuner.getAccuracy() < 90) {
WARN(F("Tuning bad result, restart...")); WARN(F("[REGULATOR][TUNING][PID] Bad result, try again..."));
} else { } else {
settings.pid.p_factor = pidTuner.getPID_p(); settings.pid.p_factor = pidTuner.getPID_p();
settings.pid.i_factor = pidTuner.getPID_i(); settings.pid.i_factor = pidTuner.getPID_i();
settings.pid.d_factor = pidTuner.getPID_d(); settings.pid.d_factor = pidTuner.getPID_d();
vars.tuning.enable = false;
return 0;
}
} }
pidTuner.reset();
tunerState = 0;
tunerInit = false;
} else {
if (!tunerInit) { if (!tunerInit) {
INFO(F("Tuning start")); INFO(F("[REGULATOR][TUNING][PID] Start..."));
float step; float step;
if (vars.temperatures.indoor - vars.temperatures.outdoor > 10) { if (vars.temperatures.indoor - vars.temperatures.outdoor > 10) {
@@ -156,34 +180,56 @@ protected:
step = 5.0f; step = 5.0f;
} }
float startTemp = newTemp + step; float startTemp = step;
if (startTemp >= vars.parameters.heatingMaxTemp) { INFO_F("[REGULATOR][TUNING][PID] Started. Start value: %f, step: %f \n", startTemp, step);
startTemp = vars.parameters.heatingMaxTemp - 10;
}
INFO_F("Tuning started. Start temp: %f, step: %f \n", startTemp, step);
pidTuner.setParameters(NORMAL, startTemp, step, 20 * 60 * 1000, 0.15, 60 * 1000, 10000); pidTuner.setParameters(NORMAL, startTemp, step, 20 * 60 * 1000, 0.15, 60 * 1000, 10000);
tunerInit = true; tunerInit = true;
tunerRegulator = 1;
} }
pidTuner.setInput(vars.temperatures.indoor); pidTuner.setInput(vars.temperatures.indoor);
pidTuner.compute(); pidTuner.compute();
if (tunerState > 0 && pidTuner.getState() != tunerState) { if (tunerState > 0 && pidTuner.getState() != tunerState) {
INFO(F("Tuning log:")); INFO(F("[REGULATOR][TUNING][PID] Log:"));
pidTuner.debugText(&INFO_STREAM); pidTuner.debugText(&INFO_STREAM);
tunerState = pidTuner.getState(); tunerState = pidTuner.getState();
} }
newTemp = round(pidTuner.getOutput()); return round(defaultTemp + pidTuner.getOutput());
} else {
return 0;
} }
} }
if (settings.pid.enable && (!vars.tuning.enable || vars.tuning.enable && vars.tuning.regulator != 1)) { float getEquithermTemp() {
if (updateIntegral) { if ( vars.states.emergency ) {
pidRegulator.integral = settings.heating.target; etRegulator.Kt = 0;
etRegulator.indoorTemp = 0;
etRegulator.outdoorTemp = vars.temperatures.outdoor;
} else if (settings.pid.enable) {
etRegulator.Kt = 0;
etRegulator.indoorTemp = round(vars.temperatures.indoor);
etRegulator.outdoorTemp = round(vars.temperatures.outdoor);
} else {
etRegulator.Kt = settings.equitherm.t_factor;
etRegulator.indoorTemp = vars.temperatures.indoor;
etRegulator.outdoorTemp = vars.temperatures.outdoor;
} }
etRegulator.setLimits(vars.parameters.heatingMinTemp, vars.parameters.heatingMaxTemp);
etRegulator.Kn = settings.equitherm.n_factor;
// etRegulator.Kn = tuneEquithermN(etRegulator.Kn, vars.temperatures.indoor, settings.heating.target, 300, 1800, 0.01, 1);
etRegulator.Kk = settings.equitherm.k_factor;
etRegulator.targetTemp = vars.states.emergency ? settings.emergency.target : settings.heating.target;
return etRegulator.getResult();
}
float getPidTemp() {
pidRegulator.Kp = settings.pid.p_factor; pidRegulator.Kp = settings.pid.p_factor;
pidRegulator.Ki = settings.pid.i_factor; pidRegulator.Ki = settings.pid.i_factor;
pidRegulator.Kd = settings.pid.d_factor; pidRegulator.Kd = settings.pid.d_factor;
@@ -192,23 +238,8 @@ protected:
pidRegulator.input = vars.temperatures.indoor; pidRegulator.input = vars.temperatures.indoor;
pidRegulator.setpoint = settings.heating.target; pidRegulator.setpoint = settings.heating.target;
float pidResult = pidRegulator.getResultTimer(); return pidRegulator.getResultTimer();
if (abs(prevPidResult - pidResult) >= 0.5) {
prevPidResult = pidResult;
newTemp = round(pidResult);
INFO_F("New PID result: %u (%f) \n", newTemp, pidResult);
} }
}
// default temp, manual mode
if (!settings.equitherm.enable && !settings.pid.enable) {
newTemp = round(settings.heating.target);
}
return newTemp;
}
float tuneEquithermN(float ratio, float currentTemp, float setTemp, unsigned int dirtyInterval = 60, unsigned int accurateInterval = 1800, float accurateStep = 0.01, float accurateStepAfter = 1) { float tuneEquithermN(float ratio, float currentTemp, float setTemp, unsigned int dirtyInterval = 60, unsigned int accurateInterval = 1800, float accurateStep = 0.01, float accurateStepAfter = 1) {
static uint32_t _prevIteration = millis(); static uint32_t _prevIteration = millis();

View File

@@ -7,21 +7,39 @@ public:
SensorsTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {} SensorsTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {}
protected: protected:
float filteredOutdoorTemp = 0;
bool emptyOutdoorTemp = true;
void setup() {} void setup() {}
void loop() { void loop() {
// DS18B20 sensor // DS18B20 sensor
if (outdoorSensor.online()) { if (outdoorSensor.online()) {
if (outdoorSensor.readTemp()) { if (outdoorSensor.readTemp()) {
vars.temperatures.outdoor = outdoorSensor.getTemp(); float rawTemp = outdoorSensor.getTemp();
INFO_F("[SENSORS][DS18B20] Raw temp: %f \n", rawTemp);
if ( emptyOutdoorTemp ) {
filteredOutdoorTemp = rawTemp;
emptyOutdoorTemp = false;
} else { } else {
DEBUG("Invalid data from outdoor sensor (DS18B20)"); filteredOutdoorTemp += (rawTemp - filteredOutdoorTemp) * OUTDOOR_SENSOR_FILTER_K;
}
filteredOutdoorTemp = floor(filteredOutdoorTemp * 100) / 100;
if ( fabs(vars.temperatures.outdoor - filteredOutdoorTemp) > 0.099 ) {
vars.temperatures.outdoor = filteredOutdoorTemp;
INFO_F("[SENSORS][DS18B20] New temp: %f \n", filteredOutdoorTemp);
}
} else {
ERROR("[SENSORS][DS18B20] Invalid data from sensor");
} }
outdoorSensor.requestTemp(); outdoorSensor.requestTemp();
} else { } else {
WARN("Failed to connect to outdoor sensor (DS18B20)"); ERROR("[SENSORS][DS18B20] Failed to connect to sensor");
} }
} }
}; };

View File

@@ -19,7 +19,7 @@ public:
protected: protected:
void setup() { void setup() {
WiFi.mode(WIFI_STA); //WiFi.mode(WIFI_STA);
wm.setDebugOutput(settings.debug); wm.setDebugOutput(settings.debug);
wmHostname = new WiFiManagerParameter("hostname", "Hostname", settings.hostname, 80); wmHostname = new WiFiManagerParameter("hostname", "Hostname", settings.hostname, 80);
@@ -28,7 +28,6 @@ protected:
wmMqttServer = new WiFiManagerParameter("mqtt_server", "MQTT server", settings.mqtt.server, 80); wmMqttServer = new WiFiManagerParameter("mqtt_server", "MQTT server", settings.mqtt.server, 80);
wm.addParameter(wmMqttServer); wm.addParameter(wmMqttServer);
//char mqttPort[6];
sprintf(buffer, "%d", settings.mqtt.port); sprintf(buffer, "%d", settings.mqtt.port);
wmMqttPort = new WiFiManagerParameter("mqtt_port", "MQTT port", buffer, 6); wmMqttPort = new WiFiManagerParameter("mqtt_port", "MQTT port", buffer, 6);
wm.addParameter(wmMqttPort); wm.addParameter(wmMqttPort);
@@ -42,6 +41,9 @@ protected:
wmMqttPrefix = new WiFiManagerParameter("mqtt_prefix", "MQTT prefix", settings.mqtt.prefix, 32); wmMqttPrefix = new WiFiManagerParameter("mqtt_prefix", "MQTT prefix", settings.mqtt.prefix, 32);
wm.addParameter(wmMqttPrefix); wm.addParameter(wmMqttPrefix);
//wm.setCleanConnect(true);
wm.setRestorePersistent(false);
wm.setHostname(settings.hostname); wm.setHostname(settings.hostname);
wm.setWiFiAutoReconnect(true); wm.setWiFiAutoReconnect(true);
wm.setConfigPortalBlocking(false); wm.setConfigPortalBlocking(false);
@@ -49,16 +51,24 @@ protected:
wm.setConfigPortalTimeout(300); wm.setConfigPortalTimeout(300);
wm.setDisableConfigPortal(false); wm.setDisableConfigPortal(false);
if (wm.autoConnect(AP_SSID)) { wm.autoConnect(AP_SSID);
INFO_F("Wifi connected. IP: %s, RSSI: %d\n", WiFi.localIP().toString().c_str(), WiFi.RSSI());
wm.startWebPortal();
} else {
INFO(F("Failed to connect to WIFI, start the configuration portal..."));
}
} }
void loop() { void loop() {
if (connected && WiFi.status() != WL_CONNECTED) {
connected = false;
INFO("[wifi] Disconnected");
} else if (!connected && WiFi.status() == WL_CONNECTED) {
connected = true;
INFO_F("[wifi] Connected. IP address: %s, RSSI: %d\n", WiFi.localIP().toString().c_str(), WiFi.RSSI());
}
if (WiFi.status() == WL_CONNECTED && !wm.getWebPortalActive() && !wm.getConfigPortalActive()) {
wm.startWebPortal();
}
wm.process(); wm.process();
} }
@@ -74,4 +84,6 @@ protected:
eeSettings.updateNow(); eeSettings.updateNow();
INFO(F("Settings saved")); INFO(F("Settings saved"));
} }
bool connected = false;
}; };

View File

@@ -1,4 +1,4 @@
#define OT_GATEWAY_VERSION "1.0.5" #define OT_GATEWAY_VERSION "1.0.7"
#define AP_SSID "OpenTherm Gateway" #define AP_SSID "OpenTherm Gateway"
#define USE_TELNET #define USE_TELNET
@@ -11,7 +11,8 @@
#define OPENTHERM_OFFLINE_TRESHOLD 10 #define OPENTHERM_OFFLINE_TRESHOLD 10
#define DS18B20_PIN 2 #define DS18B20_PIN 2
#define DS18B20_INTERVAL 1000 #define DS18B20_INTERVAL 5000
#define OUTDOOR_SENSOR_FILTER_K 0.15
#define DS_CHECK_CRC true #define DS_CHECK_CRC true
#define DS_CRC_USE_TABLE true #define DS_CRC_USE_TABLE true