mirror of
https://github.com/Laxilef/OTGateway.git
synced 2025-12-10 18:24:27 +05:00
feat: added native heating control by boiler; refactoring; emergency settings removed from HA
This commit is contained in:
@@ -31,7 +31,6 @@
|
|||||||
- The current temperature of the heat carrier (usually the return heat carrier)
|
- The current temperature of the heat carrier (usually the return heat carrier)
|
||||||
- Set heat carrier temperature (depending on the selected mode)
|
- Set heat carrier temperature (depending on the selected mode)
|
||||||
- Current hot water temperature
|
- Current hot water temperature
|
||||||
- Auto tuning of PID and Equitherm parameters *(in development)*
|
|
||||||
- [Home Assistant](https://www.home-assistant.io/) integration via MQTT. The ability to create any automation for the boiler!
|
- [Home Assistant](https://www.home-assistant.io/) integration via MQTT. The ability to create any automation for the boiler!
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -126,6 +126,26 @@ public:
|
|||||||
return isValidResponse(response);
|
return isValidResponse(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool setRoomSetpoint(float temperature) {
|
||||||
|
unsigned long response = this->sendRequest(buildRequest(
|
||||||
|
OpenThermMessageType::WRITE_DATA,
|
||||||
|
OpenThermMessageID::TrSet,
|
||||||
|
temperatureToData(temperature)
|
||||||
|
));
|
||||||
|
|
||||||
|
return isValidResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool setRoomTemp(float temperature) {
|
||||||
|
unsigned long response = this->sendRequest(buildRequest(
|
||||||
|
OpenThermMessageType::WRITE_DATA,
|
||||||
|
OpenThermMessageID::Tr,
|
||||||
|
temperatureToData(temperature)
|
||||||
|
));
|
||||||
|
|
||||||
|
return isValidResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
bool sendBoilerReset() {
|
bool sendBoilerReset() {
|
||||||
unsigned int data = 1;
|
unsigned int data = 1;
|
||||||
data <<= 8;
|
data <<= 8;
|
||||||
|
|||||||
173
src/HaHelper.h
173
src/HaHelper.h
@@ -6,107 +6,6 @@ public:
|
|||||||
static const byte TEMP_SOURCE_HEATING = 0;
|
static const byte TEMP_SOURCE_HEATING = 0;
|
||||||
static const byte TEMP_SOURCE_INDOOR = 1;
|
static const byte TEMP_SOURCE_INDOOR = 1;
|
||||||
|
|
||||||
bool publishSwitchEmergency(bool enabledByDefault = true) {
|
|
||||||
JsonDocument doc;
|
|
||||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
|
||||||
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("emergency"));
|
|
||||||
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("emergency"));
|
|
||||||
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
|
|
||||||
doc[FPSTR(HA_NAME)] = F("Use emergency");
|
|
||||||
doc[FPSTR(HA_ICON)] = F("mdi:sun-snowflake-variant");
|
|
||||||
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
|
|
||||||
doc[FPSTR(HA_STATE_ON)] = true;
|
|
||||||
doc[FPSTR(HA_STATE_OFF)] = false;
|
|
||||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.emergency.enable }}");
|
|
||||||
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
|
|
||||||
doc[FPSTR(HA_PAYLOAD_ON)] = F("{\"emergency\": {\"enable\" : true}}");
|
|
||||||
doc[FPSTR(HA_PAYLOAD_OFF)] = F("{\"emergency\": {\"enable\" : false}}");
|
|
||||||
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
|
||||||
doc.shrinkToFit();
|
|
||||||
|
|
||||||
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("emergency")).c_str(), doc);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool publishNumberEmergencyTarget(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
|
|
||||||
JsonDocument doc;
|
|
||||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
|
||||||
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("emergency_target"));
|
|
||||||
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("emergency_target"));
|
|
||||||
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
|
|
||||||
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
|
|
||||||
|
|
||||||
if (unit == UnitSystem::METRIC) {
|
|
||||||
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C);
|
|
||||||
doc[FPSTR(HA_MIN)] = 5;
|
|
||||||
doc[FPSTR(HA_MAX)] = 50;
|
|
||||||
|
|
||||||
} else if (unit == UnitSystem::IMPERIAL) {
|
|
||||||
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F);
|
|
||||||
doc[FPSTR(HA_MIN)] = 41;
|
|
||||||
doc[FPSTR(HA_MAX)] = 122;
|
|
||||||
}
|
|
||||||
|
|
||||||
doc[FPSTR(HA_NAME)] = F("Emergency target temp");
|
|
||||||
doc[FPSTR(HA_ICON)] = F("mdi:thermometer-alert");
|
|
||||||
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
|
|
||||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.emergency.target|float(0)|round(1) }}");
|
|
||||||
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
|
|
||||||
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"emergency\": {\"target\" : {{ value }}}}");
|
|
||||||
doc[FPSTR(HA_STEP)] = 0.5;
|
|
||||||
doc[FPSTR(HA_MODE)] = "box";
|
|
||||||
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
|
||||||
doc.shrinkToFit();
|
|
||||||
|
|
||||||
return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("emergency_target")).c_str(), doc);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool publishSwitchEmergencyUseEquitherm(bool enabledByDefault = true) {
|
|
||||||
JsonDocument doc;
|
|
||||||
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("settings"));
|
|
||||||
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.sensors.outdoor.type != 1, 'online', 'offline') }}");
|
|
||||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
|
||||||
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("emergency_use_equitherm"));
|
|
||||||
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("emergency_use_equitherm"));
|
|
||||||
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
|
|
||||||
doc[FPSTR(HA_NAME)] = F("Use equitherm in emergency");
|
|
||||||
doc[FPSTR(HA_ICON)] = F("mdi:snowflake-alert");
|
|
||||||
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
|
|
||||||
doc[FPSTR(HA_STATE_ON)] = true;
|
|
||||||
doc[FPSTR(HA_STATE_OFF)] = false;
|
|
||||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.emergency.useEquitherm }}");
|
|
||||||
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
|
|
||||||
doc[FPSTR(HA_PAYLOAD_ON)] = F("{\"emergency\": {\"useEquitherm\" : true}}");
|
|
||||||
doc[FPSTR(HA_PAYLOAD_OFF)] = F("{\"emergency\": {\"useEquitherm\" : false}}");
|
|
||||||
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
|
||||||
doc.shrinkToFit();
|
|
||||||
|
|
||||||
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("emergency_use_equitherm")).c_str(), doc);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool publishSwitchEmergencyUsePid(bool enabledByDefault = true) {
|
|
||||||
JsonDocument doc;
|
|
||||||
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("settings"));
|
|
||||||
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.sensors.indoor.type != 1, 'online', 'offline') }}");
|
|
||||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
|
||||||
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("emergency_use_pid"));
|
|
||||||
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("emergency_use_pid"));
|
|
||||||
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
|
|
||||||
doc[FPSTR(HA_NAME)] = F("Use PID in emergency");
|
|
||||||
doc[FPSTR(HA_ICON)] = F("mdi:snowflake-alert");
|
|
||||||
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
|
|
||||||
doc[FPSTR(HA_STATE_ON)] = true;
|
|
||||||
doc[FPSTR(HA_STATE_OFF)] = false;
|
|
||||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.emergency.usePid }}");
|
|
||||||
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
|
|
||||||
doc[FPSTR(HA_PAYLOAD_ON)] = F("{\"emergency\": {\"usePid\" : true}}");
|
|
||||||
doc[FPSTR(HA_PAYLOAD_OFF)] = F("{\"emergency\": {\"usePid\" : false}}");
|
|
||||||
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
|
||||||
doc.shrinkToFit();
|
|
||||||
|
|
||||||
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("emergency_use_pid")).c_str(), doc);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool publishSwitchHeating(bool enabledByDefault = true) {
|
bool publishSwitchHeating(bool enabledByDefault = true) {
|
||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
|
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
|
||||||
@@ -175,7 +74,7 @@ public:
|
|||||||
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"heating\": {\"target\" : {{ value }}}}");
|
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"heating\": {\"target\" : {{ value }}}}");
|
||||||
doc[FPSTR(HA_MIN)] = minTemp;
|
doc[FPSTR(HA_MIN)] = minTemp;
|
||||||
doc[FPSTR(HA_MAX)] = maxTemp;
|
doc[FPSTR(HA_MAX)] = maxTemp;
|
||||||
doc[FPSTR(HA_STEP)] = 0.5;
|
doc[FPSTR(HA_STEP)] = 0.5f;
|
||||||
doc[FPSTR(HA_MODE)] = "box";
|
doc[FPSTR(HA_MODE)] = "box";
|
||||||
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
||||||
doc.shrinkToFit();
|
doc.shrinkToFit();
|
||||||
@@ -206,7 +105,7 @@ public:
|
|||||||
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"heating\": {\"hysteresis\" : {{ value }}}}");
|
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"heating\": {\"hysteresis\" : {{ value }}}}");
|
||||||
doc[FPSTR(HA_MIN)] = 0;
|
doc[FPSTR(HA_MIN)] = 0;
|
||||||
doc[FPSTR(HA_MAX)] = 5;
|
doc[FPSTR(HA_MAX)] = 5;
|
||||||
doc[FPSTR(HA_STEP)] = 0.1;
|
doc[FPSTR(HA_STEP)] = 0.1f;
|
||||||
doc[FPSTR(HA_MODE)] = "box";
|
doc[FPSTR(HA_MODE)] = "box";
|
||||||
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
||||||
doc.shrinkToFit();
|
doc.shrinkToFit();
|
||||||
@@ -234,7 +133,7 @@ public:
|
|||||||
doc[FPSTR(HA_NAME)] = F("Heating setpoint");
|
doc[FPSTR(HA_NAME)] = F("Heating setpoint");
|
||||||
doc[FPSTR(HA_ICON)] = F("mdi:coolant-temperature");
|
doc[FPSTR(HA_ICON)] = F("mdi:coolant-temperature");
|
||||||
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state"));
|
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state"));
|
||||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.parameters.heatingSetpoint|int(0) }}");
|
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.parameters.heatingSetpoint|float(0)|round(1) }}");
|
||||||
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
||||||
doc.shrinkToFit();
|
doc.shrinkToFit();
|
||||||
|
|
||||||
@@ -433,7 +332,7 @@ public:
|
|||||||
doc[FPSTR(HA_NAME)] = F("DHW target");
|
doc[FPSTR(HA_NAME)] = F("DHW target");
|
||||||
doc[FPSTR(HA_ICON)] = F("mdi:water-pump");
|
doc[FPSTR(HA_ICON)] = F("mdi:water-pump");
|
||||||
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
|
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
|
||||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.dhw.target|int(0) }}");
|
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.dhw.target|float(0)|round(1) }}");
|
||||||
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
|
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
|
||||||
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"dhw\": {\"target\" : {{ value|int(0) }}}}");
|
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"dhw\": {\"target\" : {{ value|int(0) }}}}");
|
||||||
doc[FPSTR(HA_MIN)] = minTemp;
|
doc[FPSTR(HA_MIN)] = minTemp;
|
||||||
@@ -605,9 +504,9 @@ public:
|
|||||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.pid.p_factor|float(0)|round(3) }}");
|
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.pid.p_factor|float(0)|round(3) }}");
|
||||||
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
|
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
|
||||||
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"pid\": {\"p_factor\" : {{ value }}}}");
|
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"pid\": {\"p_factor\" : {{ value }}}}");
|
||||||
doc[FPSTR(HA_MIN)] = 0.1;
|
doc[FPSTR(HA_MIN)] = 0.1f;
|
||||||
doc[FPSTR(HA_MAX)] = 1000;
|
doc[FPSTR(HA_MAX)] = 1000;
|
||||||
doc[FPSTR(HA_STEP)] = 0.1;
|
doc[FPSTR(HA_STEP)] = 0.1f;
|
||||||
doc[FPSTR(HA_MODE)] = "box";
|
doc[FPSTR(HA_MODE)] = "box";
|
||||||
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
||||||
doc.shrinkToFit();
|
doc.shrinkToFit();
|
||||||
@@ -628,7 +527,7 @@ public:
|
|||||||
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"pid\": {\"i_factor\" : {{ value }}}}");
|
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"pid\": {\"i_factor\" : {{ value }}}}");
|
||||||
doc[FPSTR(HA_MIN)] = 0;
|
doc[FPSTR(HA_MIN)] = 0;
|
||||||
doc[FPSTR(HA_MAX)] = 100;
|
doc[FPSTR(HA_MAX)] = 100;
|
||||||
doc[FPSTR(HA_STEP)] = 0.001;
|
doc[FPSTR(HA_STEP)] = 0.001f;
|
||||||
doc[FPSTR(HA_MODE)] = "box";
|
doc[FPSTR(HA_MODE)] = "box";
|
||||||
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
||||||
doc.shrinkToFit();
|
doc.shrinkToFit();
|
||||||
@@ -779,9 +678,9 @@ public:
|
|||||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.n_factor|float(0)|round(3) }}");
|
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.n_factor|float(0)|round(3) }}");
|
||||||
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
|
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
|
||||||
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"n_factor\" : {{ value }}}}");
|
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"n_factor\" : {{ value }}}}");
|
||||||
doc[FPSTR(HA_MIN)] = 0.001;
|
doc[FPSTR(HA_MIN)] = 0.001f;
|
||||||
doc[FPSTR(HA_MAX)] = 10;
|
doc[FPSTR(HA_MAX)] = 10;
|
||||||
doc[FPSTR(HA_STEP)] = 0.001;
|
doc[FPSTR(HA_STEP)] = 0.001f;
|
||||||
doc[FPSTR(HA_MODE)] = "box";
|
doc[FPSTR(HA_MODE)] = "box";
|
||||||
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
||||||
doc.shrinkToFit();
|
doc.shrinkToFit();
|
||||||
@@ -802,7 +701,7 @@ public:
|
|||||||
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"k_factor\" : {{ value }}}}");
|
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"k_factor\" : {{ value }}}}");
|
||||||
doc[FPSTR(HA_MIN)] = 0;
|
doc[FPSTR(HA_MIN)] = 0;
|
||||||
doc[FPSTR(HA_MAX)] = 10;
|
doc[FPSTR(HA_MAX)] = 10;
|
||||||
doc[FPSTR(HA_STEP)] = 0.01;
|
doc[FPSTR(HA_STEP)] = 0.01f;
|
||||||
doc[FPSTR(HA_MODE)] = "box";
|
doc[FPSTR(HA_MODE)] = "box";
|
||||||
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
||||||
doc.shrinkToFit();
|
doc.shrinkToFit();
|
||||||
@@ -825,7 +724,7 @@ public:
|
|||||||
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"t_factor\" : {{ value }}}}");
|
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"t_factor\" : {{ value }}}}");
|
||||||
doc[FPSTR(HA_MIN)] = 0;
|
doc[FPSTR(HA_MIN)] = 0;
|
||||||
doc[FPSTR(HA_MAX)] = 10;
|
doc[FPSTR(HA_MAX)] = 10;
|
||||||
doc[FPSTR(HA_STEP)] = 0.01;
|
doc[FPSTR(HA_STEP)] = 0.01f;
|
||||||
doc[FPSTR(HA_MODE)] = "box";
|
doc[FPSTR(HA_MODE)] = "box";
|
||||||
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
||||||
doc.shrinkToFit();
|
doc.shrinkToFit();
|
||||||
@@ -834,48 +733,6 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool publishSwitchTuning(bool enabledByDefault = true) {
|
|
||||||
JsonDocument doc;
|
|
||||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
|
||||||
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("tuning"));
|
|
||||||
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("tuning"));
|
|
||||||
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
|
|
||||||
doc[FPSTR(HA_NAME)] = F("Tuning");
|
|
||||||
doc[FPSTR(HA_ICON)] = F("mdi:tune-vertical");
|
|
||||||
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state"));
|
|
||||||
doc[FPSTR(HA_STATE_ON)] = true;
|
|
||||||
doc[FPSTR(HA_STATE_OFF)] = false;
|
|
||||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.tuning.enable }}");
|
|
||||||
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set"));
|
|
||||||
doc[FPSTR(HA_PAYLOAD_ON)] = F("{\"tuning\": {\"enable\" : true}}");
|
|
||||||
doc[FPSTR(HA_PAYLOAD_OFF)] = F("{\"tuning\": {\"enable\" : false}}");
|
|
||||||
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
|
||||||
doc.shrinkToFit();
|
|
||||||
|
|
||||||
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("tuning")).c_str(), doc);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool publishSelectTuningRegulator(bool enabledByDefault = true) {
|
|
||||||
JsonDocument doc;
|
|
||||||
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
|
|
||||||
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set"));
|
|
||||||
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"tuning\": {\"regulator\": {% if value == 'Equitherm' %}0{% elif value == 'PID' %}1{% endif %}}}");
|
|
||||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
|
||||||
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("tuning_regulator"));
|
|
||||||
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("tuning_regulator"));
|
|
||||||
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
|
|
||||||
doc[FPSTR(HA_NAME)] = F("Tuning regulator");
|
|
||||||
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state"));
|
|
||||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{% if value_json.tuning.regulator == 0 %}Equitherm{% elif value_json.tuning.regulator == 1 %}PID{% endif %}");
|
|
||||||
doc[FPSTR(HA_OPTIONS)][0] = F("Equitherm");
|
|
||||||
doc[FPSTR(HA_OPTIONS)][1] = F("PID");
|
|
||||||
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
|
||||||
doc.shrinkToFit();
|
|
||||||
|
|
||||||
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SELECT), F("tuning_regulator")).c_str(), doc);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool publishBinSensorStatus(bool enabledByDefault = true) {
|
bool publishBinSensorStatus(bool enabledByDefault = true) {
|
||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
||||||
@@ -1184,7 +1041,7 @@ public:
|
|||||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.temperatures.indoor|float(0)|round(1) }}");
|
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.temperatures.indoor|float(0)|round(1) }}");
|
||||||
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set"));
|
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set"));
|
||||||
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"temperatures\": {\"indoor\":{{ value }}}}");
|
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"temperatures\": {\"indoor\":{{ value }}}}");
|
||||||
doc[FPSTR(HA_STEP)] = 0.01;
|
doc[FPSTR(HA_STEP)] = 0.01f;
|
||||||
doc[FPSTR(HA_MODE)] = "box";
|
doc[FPSTR(HA_MODE)] = "box";
|
||||||
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
||||||
doc.shrinkToFit();
|
doc.shrinkToFit();
|
||||||
@@ -1243,7 +1100,7 @@ public:
|
|||||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.temperatures.outdoor|float(0)|round(1) }}");
|
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.temperatures.outdoor|float(0)|round(1) }}");
|
||||||
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set"));
|
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set"));
|
||||||
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"temperatures\": {\"outdoor\":{{ value }}}}");
|
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"temperatures\": {\"outdoor\":{{ value }}}}");
|
||||||
doc[FPSTR(HA_STEP)] = 0.01;
|
doc[FPSTR(HA_STEP)] = 0.01f;
|
||||||
doc[FPSTR(HA_MODE)] = "box";
|
doc[FPSTR(HA_MODE)] = "box";
|
||||||
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
||||||
doc.shrinkToFit();
|
doc.shrinkToFit();
|
||||||
@@ -1452,7 +1309,7 @@ public:
|
|||||||
|
|
||||||
doc[FPSTR(HA_MIN_TEMP)] = minTemp;
|
doc[FPSTR(HA_MIN_TEMP)] = minTemp;
|
||||||
doc[FPSTR(HA_MAX_TEMP)] = maxTemp;
|
doc[FPSTR(HA_MAX_TEMP)] = maxTemp;
|
||||||
doc[FPSTR(HA_TEMP_STEP)] = 0.5;
|
doc[FPSTR(HA_TEMP_STEP)] = 0.5f;
|
||||||
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
|
||||||
doc.shrinkToFit();
|
doc.shrinkToFit();
|
||||||
|
|
||||||
@@ -1475,7 +1332,7 @@ public:
|
|||||||
doc[FPSTR(HA_TEMPERATURE_COMMAND_TEMPLATE)] = F("{\"dhw\": {\"target\" : {{ value|int(0) }}}}");
|
doc[FPSTR(HA_TEMPERATURE_COMMAND_TEMPLATE)] = F("{\"dhw\": {\"target\" : {{ value|int(0) }}}}");
|
||||||
|
|
||||||
doc[FPSTR(HA_TEMPERATURE_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
|
doc[FPSTR(HA_TEMPERATURE_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
|
||||||
doc[FPSTR(HA_TEMPERATURE_STATE_TEMPLATE)] = F("{{ value_json.dhw.target|int(0) }}");
|
doc[FPSTR(HA_TEMPERATURE_STATE_TEMPLATE)] = F("{{ value_json.dhw.target|float(0)|round(1) }}");
|
||||||
|
|
||||||
if (unit == UnitSystem::METRIC) {
|
if (unit == UnitSystem::METRIC) {
|
||||||
doc[FPSTR(HA_TEMPERATURE_UNIT)] = "C";
|
doc[FPSTR(HA_TEMPERATURE_UNIT)] = "C";
|
||||||
|
|||||||
125
src/MqttTask.h
125
src/MqttTask.h
@@ -53,16 +53,25 @@ public:
|
|||||||
Log.sinfoln(FPSTR(L_MQTT), F("Enabled"));
|
Log.sinfoln(FPSTR(L_MQTT), F("Enabled"));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isConnected() {
|
inline bool isConnected() {
|
||||||
return this->connected;
|
return this->connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void resetPublishedSettingsTime() {
|
||||||
|
this->prevPubSettingsTime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void resetPublishedVarsTime() {
|
||||||
|
this->prevPubVarsTime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
MqttWiFiClient* wifiClient = nullptr;
|
MqttWiFiClient* wifiClient = nullptr;
|
||||||
MqttClient* client = nullptr;
|
MqttClient* client = nullptr;
|
||||||
HaHelper* haHelper = nullptr;
|
HaHelper* haHelper = nullptr;
|
||||||
MqttWriter* writer = nullptr;
|
MqttWriter* writer = nullptr;
|
||||||
UnitSystem currentUnitSystem = UnitSystem::METRIC;
|
UnitSystem currentUnitSystem = UnitSystem::METRIC;
|
||||||
|
bool currentHomeAssistantDiscovery = false;
|
||||||
unsigned short readyForSendTime = 15000;
|
unsigned short readyForSendTime = 15000;
|
||||||
unsigned long lastReconnectTime = 0;
|
unsigned long lastReconnectTime = 0;
|
||||||
unsigned long connectedTime = 0;
|
unsigned long connectedTime = 0;
|
||||||
@@ -84,7 +93,7 @@ protected:
|
|||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isReadyForSend() {
|
inline bool isReadyForSend() {
|
||||||
return millis() - this->connectedTime > this->readyForSendTime;
|
return millis() - this->connectedTime > this->readyForSendTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,15 +236,24 @@ protected:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// publish ha entities if not published
|
// publish ha entities if not published
|
||||||
if (this->newConnection || this->currentUnitSystem != settings.system.unitSystem) {
|
if (settings.mqtt.homeAssistantDiscovery) {
|
||||||
this->publishHaEntities();
|
if (this->newConnection || !this->currentHomeAssistantDiscovery || this->currentUnitSystem != settings.system.unitSystem) {
|
||||||
this->publishNonStaticHaEntities(true);
|
this->publishHaEntities();
|
||||||
this->newConnection = false;
|
this->publishNonStaticHaEntities(true);
|
||||||
this->currentUnitSystem = settings.system.unitSystem;
|
this->currentHomeAssistantDiscovery = true;
|
||||||
|
this->currentUnitSystem = settings.system.unitSystem;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// publish non static ha entities
|
// publish non static ha entities
|
||||||
this->publishNonStaticHaEntities();
|
this->publishNonStaticHaEntities();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (this->currentHomeAssistantDiscovery) {
|
||||||
|
this->currentHomeAssistantDiscovery = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->newConnection) {
|
||||||
|
this->newConnection = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,52 +309,26 @@ protected:
|
|||||||
Log.swarningln(FPSTR(L_MQTT_MSG), F("Not valid json"));
|
Log.swarningln(FPSTR(L_MQTT_MSG), F("Not valid json"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
doc.shrinkToFit();
|
||||||
|
|
||||||
if (this->haHelper->getDeviceTopic("state/set").equals(topic)) {
|
if (this->haHelper->getDeviceTopic("state/set").equals(topic)) {
|
||||||
this->writer->publish(this->haHelper->getDeviceTopic("state/set").c_str(), nullptr, 0, true);
|
this->writer->publish(this->haHelper->getDeviceTopic("state/set").c_str(), nullptr, 0, true);
|
||||||
this->updateVariables(doc);
|
|
||||||
|
if (jsonToVars(doc, vars)) {
|
||||||
|
this->resetPublishedVarsTime();
|
||||||
|
}
|
||||||
|
|
||||||
} else if (this->haHelper->getDeviceTopic("settings/set").equals(topic)) {
|
} else if (this->haHelper->getDeviceTopic("settings/set").equals(topic)) {
|
||||||
this->writer->publish(this->haHelper->getDeviceTopic("settings/set").c_str(), nullptr, 0, true);
|
this->writer->publish(this->haHelper->getDeviceTopic("settings/set").c_str(), nullptr, 0, true);
|
||||||
this->updateSettings(doc);
|
|
||||||
|
if (safeJsonToSettings(doc, settings)) {
|
||||||
|
this->resetPublishedSettingsTime();
|
||||||
|
fsSettings.update();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool updateSettings(JsonDocument& doc) {
|
|
||||||
bool changed = safeJsonToSettings(doc, settings);
|
|
||||||
doc.clear();
|
|
||||||
doc.shrinkToFit();
|
|
||||||
|
|
||||||
if (changed) {
|
|
||||||
this->prevPubSettingsTime = 0;
|
|
||||||
fsSettings.update();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool updateVariables(JsonDocument& doc) {
|
|
||||||
bool changed = jsonToVars(doc, vars);
|
|
||||||
doc.clear();
|
|
||||||
doc.shrinkToFit();
|
|
||||||
|
|
||||||
if (changed) {
|
|
||||||
this->prevPubVarsTime = 0;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void publishHaEntities() {
|
void publishHaEntities() {
|
||||||
// emergency
|
|
||||||
this->haHelper->publishSwitchEmergency();
|
|
||||||
this->haHelper->publishNumberEmergencyTarget(settings.system.unitSystem);
|
|
||||||
this->haHelper->publishSwitchEmergencyUseEquitherm();
|
|
||||||
this->haHelper->publishSwitchEmergencyUsePid();
|
|
||||||
|
|
||||||
// heating
|
// heating
|
||||||
this->haHelper->publishSwitchHeating(false);
|
this->haHelper->publishSwitchHeating(false);
|
||||||
this->haHelper->publishSwitchHeatingTurbo();
|
this->haHelper->publishSwitchHeatingTurbo();
|
||||||
@@ -363,10 +355,6 @@ protected:
|
|||||||
this->haHelper->publishNumberEquithermFactorK();
|
this->haHelper->publishNumberEquithermFactorK();
|
||||||
this->haHelper->publishNumberEquithermFactorT();
|
this->haHelper->publishNumberEquithermFactorT();
|
||||||
|
|
||||||
// tuning
|
|
||||||
this->haHelper->publishSwitchTuning();
|
|
||||||
this->haHelper->publishSelectTuningRegulator();
|
|
||||||
|
|
||||||
// states
|
// states
|
||||||
this->haHelper->publishBinSensorStatus();
|
this->haHelper->publishBinSensorStatus();
|
||||||
this->haHelper->publishBinSensorOtStatus();
|
this->haHelper->publishBinSensorOtStatus();
|
||||||
@@ -396,26 +384,22 @@ protected:
|
|||||||
|
|
||||||
bool publishNonStaticHaEntities(bool force = false) {
|
bool publishNonStaticHaEntities(bool force = false) {
|
||||||
static byte _heatingMinTemp, _heatingMaxTemp, _dhwMinTemp, _dhwMaxTemp = 0;
|
static byte _heatingMinTemp, _heatingMaxTemp, _dhwMinTemp, _dhwMaxTemp = 0;
|
||||||
static bool _isStupidMode, _editableOutdoorTemp, _editableIndoorTemp, _dhwPresent = false;
|
static bool _noRegulators, _editableOutdoorTemp, _editableIndoorTemp, _dhwPresent = false;
|
||||||
|
|
||||||
bool published = false;
|
bool published = false;
|
||||||
bool isStupidMode = !settings.pid.enable && !settings.equitherm.enable;
|
bool noRegulators = !settings.opentherm.nativeHeatingControl && !settings.pid.enable && !settings.equitherm.enable;
|
||||||
byte heatingMinTemp = 0;
|
byte heatingMinTemp = 0;
|
||||||
byte heatingMaxTemp = 0;
|
byte heatingMaxTemp = 0;
|
||||||
bool editableOutdoorTemp = settings.sensors.outdoor.type == SensorType::MANUAL;
|
bool editableOutdoorTemp = settings.sensors.outdoor.type == SensorType::MANUAL;
|
||||||
bool editableIndoorTemp = settings.sensors.indoor.type == SensorType::MANUAL;
|
bool editableIndoorTemp = settings.sensors.indoor.type == SensorType::MANUAL;
|
||||||
|
|
||||||
if (isStupidMode) {
|
if (noRegulators) {
|
||||||
heatingMinTemp = settings.heating.minTemp;
|
heatingMinTemp = settings.heating.minTemp;
|
||||||
heatingMaxTemp = settings.heating.maxTemp;
|
heatingMaxTemp = settings.heating.maxTemp;
|
||||||
|
|
||||||
} else if (settings.system.unitSystem == UnitSystem::METRIC) {
|
} else {
|
||||||
heatingMinTemp = 5;
|
heatingMinTemp = convertTemp(THERMOSTAT_INDOOR_MIN_TEMP, UnitSystem::METRIC, settings.system.unitSystem);
|
||||||
heatingMaxTemp = 30;
|
heatingMaxTemp = convertTemp(THERMOSTAT_INDOOR_MAX_TEMP, UnitSystem::METRIC, settings.system.unitSystem);
|
||||||
|
|
||||||
} else if (settings.system.unitSystem == UnitSystem::IMPERIAL) {
|
|
||||||
heatingMinTemp = 41;
|
|
||||||
heatingMaxTemp = 86;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (force || _dhwPresent != settings.opentherm.dhwPresent) {
|
if (force || _dhwPresent != settings.opentherm.dhwPresent) {
|
||||||
@@ -447,32 +431,17 @@ protected:
|
|||||||
published = true;
|
published = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (force || _heatingMinTemp != heatingMinTemp || _heatingMaxTemp != heatingMaxTemp) {
|
if (force || _noRegulators != noRegulators || _heatingMinTemp != heatingMinTemp || _heatingMaxTemp != heatingMaxTemp) {
|
||||||
if (settings.heating.target < heatingMinTemp || settings.heating.target > heatingMaxTemp) {
|
|
||||||
settings.heating.target = constrain(settings.heating.target, heatingMinTemp, heatingMaxTemp);
|
|
||||||
}
|
|
||||||
|
|
||||||
_heatingMinTemp = heatingMinTemp;
|
_heatingMinTemp = heatingMinTemp;
|
||||||
_heatingMaxTemp = heatingMaxTemp;
|
_heatingMaxTemp = heatingMaxTemp;
|
||||||
_isStupidMode = isStupidMode;
|
_noRegulators = noRegulators;
|
||||||
|
|
||||||
this->haHelper->publishNumberHeatingTarget(settings.system.unitSystem, heatingMinTemp, heatingMaxTemp, false);
|
this->haHelper->publishNumberHeatingTarget(settings.system.unitSystem, heatingMinTemp, heatingMaxTemp, false);
|
||||||
this->haHelper->publishClimateHeating(
|
this->haHelper->publishClimateHeating(
|
||||||
settings.system.unitSystem,
|
settings.system.unitSystem,
|
||||||
heatingMinTemp,
|
heatingMinTemp,
|
||||||
heatingMaxTemp,
|
heatingMaxTemp,
|
||||||
isStupidMode ? HaHelper::TEMP_SOURCE_HEATING : HaHelper::TEMP_SOURCE_INDOOR
|
noRegulators ? HaHelper::TEMP_SOURCE_HEATING : HaHelper::TEMP_SOURCE_INDOOR
|
||||||
);
|
|
||||||
|
|
||||||
published = true;
|
|
||||||
|
|
||||||
} else if (_isStupidMode != isStupidMode) {
|
|
||||||
_isStupidMode = isStupidMode;
|
|
||||||
this->haHelper->publishClimateHeating(
|
|
||||||
settings.system.unitSystem,
|
|
||||||
heatingMinTemp,
|
|
||||||
heatingMaxTemp,
|
|
||||||
isStupidMode ? HaHelper::TEMP_SOURCE_HEATING : HaHelper::TEMP_SOURCE_INDOOR
|
|
||||||
);
|
);
|
||||||
|
|
||||||
published = true;
|
published = true;
|
||||||
|
|||||||
@@ -101,7 +101,8 @@ protected:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
static byte currentHeatingTemp, currentDhwTemp = 0;
|
static float currentHeatingTemp = 0.0f;
|
||||||
|
static float currentDhwTemp = 0.0f;
|
||||||
|
|
||||||
if (this->instanceInGpio != settings.opentherm.inGpio || this->instanceOutGpio != settings.opentherm.outGpio) {
|
if (this->instanceInGpio != settings.opentherm.inGpio || this->instanceOutGpio != settings.opentherm.outGpio) {
|
||||||
this->setup();
|
this->setup();
|
||||||
@@ -351,18 +352,13 @@ protected:
|
|||||||
|
|
||||||
|
|
||||||
// Update DHW temp
|
// Update DHW temp
|
||||||
byte newDhwTemp = settings.dhw.target;
|
if (settings.opentherm.dhwPresent && settings.dhw.enable && (this->needSetDhwTemp() || fabs(settings.dhw.target - currentDhwTemp) > 0.0001f)) {
|
||||||
if (settings.opentherm.dhwPresent && settings.dhw.enable && (this->needSetDhwTemp() || newDhwTemp != currentDhwTemp)) {
|
float convertedTemp = convertTemp(settings.dhw.target, settings.system.unitSystem, settings.opentherm.unitSystem);
|
||||||
if (newDhwTemp < settings.dhw.minTemp || newDhwTemp > settings.dhw.maxTemp) {
|
Log.sinfoln(FPSTR(L_OT_DHW), F("Set temp: %.2f (converted: %.2f)"), settings.dhw.target, convertedTemp);
|
||||||
newDhwTemp = constrain(newDhwTemp, settings.dhw.minTemp, settings.dhw.maxTemp);
|
|
||||||
}
|
|
||||||
|
|
||||||
float convertedTemp = convertTemp(newDhwTemp, settings.system.unitSystem, settings.opentherm.unitSystem);
|
|
||||||
Log.sinfoln(FPSTR(L_OT_DHW), F("Set temp: %u (converted: %.2f)"), newDhwTemp, convertedTemp);
|
|
||||||
|
|
||||||
// Set DHW temp
|
// Set DHW temp
|
||||||
if (this->instance->setDhwTemp(convertedTemp)) {
|
if (this->instance->setDhwTemp(convertedTemp)) {
|
||||||
currentDhwTemp = newDhwTemp;
|
currentDhwTemp = settings.dhw.target;
|
||||||
this->dhwSetTempTime = millis();
|
this->dhwSetTempTime = millis();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -372,57 +368,94 @@ protected:
|
|||||||
// Set DHW temp to CH2
|
// Set DHW temp to CH2
|
||||||
if (settings.opentherm.dhwToCh2) {
|
if (settings.opentherm.dhwToCh2) {
|
||||||
if (!this->instance->setHeatingCh2Temp(convertedTemp)) {
|
if (!this->instance->setHeatingCh2Temp(convertedTemp)) {
|
||||||
Log.swarningln(FPSTR(L_OT_DHW), F("Failed set ch2 temp"));
|
Log.swarningln(FPSTR(L_OT_DHW), F("Failed set CH2 temp"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Update heating temp
|
// Native heating control
|
||||||
if (heatingEnabled && (this->needSetHeatingTemp() || fabs(vars.parameters.heatingSetpoint - currentHeatingTemp) > 0.0001)) {
|
if (settings.opentherm.nativeHeatingControl) {
|
||||||
float convertedTemp = convertTemp(vars.parameters.heatingSetpoint, settings.system.unitSystem, settings.opentherm.unitSystem);
|
// Set current indoor temp
|
||||||
Log.sinfoln(FPSTR(L_OT_HEATING), F("Set temp: %u (converted: %.2f)"), vars.parameters.heatingSetpoint, convertedTemp);
|
float indoorTemp = 0.0f;
|
||||||
|
float convertedTemp = 0.0f;
|
||||||
|
|
||||||
// Set max heating temp
|
if (!vars.states.emergency || settings.sensors.indoor.type != SensorType::MANUAL) {
|
||||||
if (this->setMaxHeatingTemp(convertedTemp)) {
|
indoorTemp = vars.temperatures.indoor;
|
||||||
currentHeatingTemp = vars.parameters.heatingSetpoint;
|
convertedTemp = convertTemp(indoorTemp, settings.system.unitSystem, settings.opentherm.unitSystem);
|
||||||
this->heatingSetTempTime = millis();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set max heating temp"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set heating temp
|
Log.sinfoln(FPSTR(L_OT_HEATING), F("Set current indoor temp: %.2f (converted: %.2f)"), indoorTemp, convertedTemp);
|
||||||
if (this->instance->setHeatingCh1Temp(convertedTemp)) {
|
if (!this->instance->setRoomTemp(convertedTemp)) {
|
||||||
currentHeatingTemp = vars.parameters.heatingSetpoint;
|
Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set current indoor temp"));
|
||||||
this->heatingSetTempTime = millis();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set CH1 temp"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set heating temp to CH2
|
// Set target indoor temp
|
||||||
if (settings.opentherm.heatingCh1ToCh2) {
|
if (this->needSetHeatingTemp() || fabs(vars.parameters.heatingSetpoint - currentHeatingTemp) > 0.0001f) {
|
||||||
if (!this->instance->setHeatingCh2Temp(convertedTemp)) {
|
convertedTemp = convertTemp(vars.parameters.heatingSetpoint, settings.system.unitSystem, settings.opentherm.unitSystem);
|
||||||
Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set CH2 temp"));
|
Log.sinfoln(FPSTR(L_OT_HEATING), F("Set target indoor temp: %.2f (converted: %.2f)"), vars.parameters.heatingSetpoint, convertedTemp);
|
||||||
|
|
||||||
|
if (this->instance->setRoomSetpoint(convertedTemp)) {
|
||||||
|
currentHeatingTemp = vars.parameters.heatingSetpoint;
|
||||||
|
this->heatingSetTempTime = millis();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set target indoor temp"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
// force enable pump
|
||||||
// Hysteresis
|
if (!this->pump) {
|
||||||
// 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.0001 >= halfHyst) {
|
|
||||||
this->pump = false;
|
|
||||||
|
|
||||||
} else if (!this->pump && vars.temperatures.indoor - settings.heating.target - 0.0001 <= -(halfHyst)) {
|
|
||||||
this->pump = true;
|
this->pump = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (!this->pump) {
|
} else {
|
||||||
this->pump = true;
|
// Update heating temp
|
||||||
|
if (heatingEnabled && (this->needSetHeatingTemp() || fabs(vars.parameters.heatingSetpoint - currentHeatingTemp) > 0.0001f)) {
|
||||||
|
float convertedTemp = convertTemp(vars.parameters.heatingSetpoint, settings.system.unitSystem, settings.opentherm.unitSystem);
|
||||||
|
Log.sinfoln(FPSTR(L_OT_HEATING), F("Set temp: %.2f (converted: %.2f)"), vars.parameters.heatingSetpoint, convertedTemp);
|
||||||
|
|
||||||
|
// Set max heating temp
|
||||||
|
if (this->setMaxHeatingTemp(convertedTemp)) {
|
||||||
|
currentHeatingTemp = vars.parameters.heatingSetpoint;
|
||||||
|
this->heatingSetTempTime = millis();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set max heating temp"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set heating temp
|
||||||
|
if (this->instance->setHeatingCh1Temp(convertedTemp)) {
|
||||||
|
currentHeatingTemp = vars.parameters.heatingSetpoint;
|
||||||
|
this->heatingSetTempTime = millis();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set CH1 temp"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set heating temp to CH2
|
||||||
|
if (settings.opentherm.heatingCh1ToCh2) {
|
||||||
|
if (!this->instance->setHeatingCh2Temp(convertedTemp)) {
|
||||||
|
Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set CH2 temp"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
} else if (!this->pump && vars.temperatures.indoor - settings.heating.target - 0.0001f <= -(halfHyst)) {
|
||||||
|
this->pump = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (!this->pump) {
|
||||||
|
this->pump = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -427,7 +427,9 @@ protected:
|
|||||||
if (changed) {
|
if (changed) {
|
||||||
doc.clear();
|
doc.clear();
|
||||||
doc.shrinkToFit();
|
doc.shrinkToFit();
|
||||||
|
|
||||||
fsSettings.update();
|
fsSettings.update();
|
||||||
|
tMqtt->resetPublishedSettingsTime();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -477,6 +479,13 @@ protected:
|
|||||||
doc.shrinkToFit();
|
doc.shrinkToFit();
|
||||||
|
|
||||||
this->bufferedWebServer->send(changed ? 201 : 200, "application/json", doc);
|
this->bufferedWebServer->send(changed ? 201 : 200, "application/json", doc);
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
doc.clear();
|
||||||
|
doc.shrinkToFit();
|
||||||
|
|
||||||
|
tMqtt->resetPublishedVarsTime();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this->webServer->on("/api/info", HTTP_GET, [this]() {
|
this->webServer->on("/api/info", HTTP_GET, [this]() {
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
#include <Equitherm.h>
|
#include <Equitherm.h>
|
||||||
#include <GyverPID.h>
|
#include <GyverPID.h>
|
||||||
#include <PIDtuner.h>
|
|
||||||
|
|
||||||
Equitherm etRegulator;
|
Equitherm etRegulator;
|
||||||
GyverPID pidRegulator(0, 0, 0);
|
GyverPID pidRegulator(0, 0, 0);
|
||||||
PIDtuner pidTuner;
|
|
||||||
|
|
||||||
|
|
||||||
class RegulatorTask : public LeanTask {
|
class RegulatorTask : public LeanTask {
|
||||||
@@ -12,9 +10,6 @@ public:
|
|||||||
RegulatorTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {}
|
RegulatorTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool tunerInit = false;
|
|
||||||
byte tunerState = 0;
|
|
||||||
byte tunerRegulator = 0;
|
|
||||||
float prevHeatingTarget = 0;
|
float prevHeatingTarget = 0;
|
||||||
float prevEtResult = 0;
|
float prevEtResult = 0;
|
||||||
float prevPidResult = 0;
|
float prevPidResult = 0;
|
||||||
@@ -32,7 +27,7 @@ protected:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
byte newTemp = vars.parameters.heatingSetpoint;
|
float newTemp = vars.parameters.heatingSetpoint;
|
||||||
|
|
||||||
if (vars.states.emergency) {
|
if (vars.states.emergency) {
|
||||||
if (settings.heating.turbo) {
|
if (settings.heating.turbo) {
|
||||||
@@ -41,74 +36,60 @@ protected:
|
|||||||
Log.sinfoln(FPSTR(L_REGULATOR), F("Turbo mode auto disabled"));
|
Log.sinfoln(FPSTR(L_REGULATOR), F("Turbo mode auto disabled"));
|
||||||
}
|
}
|
||||||
|
|
||||||
newTemp = getEmergencyModeTemp();
|
newTemp = this->getEmergencyModeTemp();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (vars.tuning.enable || tunerInit) {
|
if (settings.heating.turbo && (fabs(settings.heating.target - vars.temperatures.indoor) < 1 || !settings.heating.enable || (settings.equitherm.enable && settings.pid.enable))) {
|
||||||
if (settings.heating.turbo) {
|
settings.heating.turbo = false;
|
||||||
settings.heating.turbo = false;
|
|
||||||
|
|
||||||
Log.sinfoln(FPSTR(L_REGULATOR), F("Turbo mode auto disabled"));
|
Log.sinfoln(FPSTR(L_REGULATOR), F("Turbo mode auto disabled"));
|
||||||
}
|
|
||||||
|
|
||||||
newTemp = getTuningModeTemp();
|
|
||||||
|
|
||||||
if (newTemp == 0) {
|
|
||||||
vars.tuning.enable = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!vars.tuning.enable) {
|
newTemp = this->getNormalModeTemp();
|
||||||
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 = getNormalModeTemp();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Limits
|
// Limits
|
||||||
if (newTemp < settings.heating.minTemp || newTemp > settings.heating.maxTemp) {
|
newTemp = constrain(
|
||||||
newTemp = constrain(newTemp, settings.heating.minTemp, settings.heating.maxTemp);
|
newTemp,
|
||||||
}
|
!settings.opentherm.nativeHeatingControl ? settings.heating.minTemp : THERMOSTAT_INDOOR_MIN_TEMP,
|
||||||
|
!settings.opentherm.nativeHeatingControl ? settings.heating.maxTemp : THERMOSTAT_INDOOR_MAX_TEMP
|
||||||
|
);
|
||||||
|
|
||||||
if (abs(vars.parameters.heatingSetpoint - newTemp) + 0.0001 >= 1) {
|
if (fabs(vars.parameters.heatingSetpoint - newTemp) > 0.4999f) {
|
||||||
vars.parameters.heatingSetpoint = newTemp;
|
vars.parameters.heatingSetpoint = newTemp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
byte getEmergencyModeTemp() {
|
float getEmergencyModeTemp() {
|
||||||
float newTemp = 0;
|
float newTemp = 0;
|
||||||
|
|
||||||
// if use equitherm
|
// if use equitherm
|
||||||
if (settings.emergency.useEquitherm && settings.sensors.outdoor.type != SensorType::MANUAL) {
|
if (settings.emergency.useEquitherm) {
|
||||||
float etResult = getEquithermTemp(settings.heating.minTemp, settings.heating.maxTemp);
|
float etResult = getEquithermTemp(settings.heating.minTemp, settings.heating.maxTemp);
|
||||||
|
|
||||||
if (fabs(prevEtResult - etResult) + 0.0001 >= 0.5) {
|
if (fabs(prevEtResult - etResult) > 0.4999f) {
|
||||||
prevEtResult = etResult;
|
prevEtResult = etResult;
|
||||||
newTemp += etResult;
|
newTemp += etResult;
|
||||||
|
|
||||||
Log.sinfoln(FPSTR(L_REGULATOR_EQUITHERM), F("New emergency result: %hhu (%.2f)"), (uint8_t) round(etResult), etResult);
|
Log.sinfoln(FPSTR(L_REGULATOR_EQUITHERM), F("New emergency result: %.2f"), etResult);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
newTemp += prevEtResult;
|
newTemp += prevEtResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if(settings.emergency.usePid && settings.sensors.indoor.type != SensorType::MANUAL) {
|
} else if(settings.emergency.usePid) {
|
||||||
if (vars.parameters.heatingEnabled) {
|
if (vars.parameters.heatingEnabled) {
|
||||||
float pidResult = getPidTemp(
|
float pidResult = getPidTemp(
|
||||||
settings.heating.minTemp,
|
settings.heating.minTemp,
|
||||||
settings.heating.maxTemp
|
settings.heating.maxTemp
|
||||||
);
|
);
|
||||||
|
|
||||||
if (fabs(prevPidResult - pidResult) + 0.0001 >= 0.5) {
|
if (fabs(prevPidResult - pidResult) > 0.4999f) {
|
||||||
prevPidResult = pidResult;
|
prevPidResult = pidResult;
|
||||||
newTemp += pidResult;
|
newTemp += pidResult;
|
||||||
|
|
||||||
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("New emergency result: %hhu (%.2f)"), (uint8_t) round(pidResult), pidResult);
|
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("New emergency result: %.2f"), pidResult);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
newTemp += prevPidResult;
|
newTemp += prevPidResult;
|
||||||
@@ -123,13 +104,13 @@ protected:
|
|||||||
newTemp = settings.emergency.target;
|
newTemp = settings.emergency.target;
|
||||||
}
|
}
|
||||||
|
|
||||||
return round(newTemp);
|
return newTemp;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte getNormalModeTemp() {
|
float getNormalModeTemp() {
|
||||||
float newTemp = 0;
|
float newTemp = 0;
|
||||||
|
|
||||||
if (fabs(prevHeatingTarget - settings.heating.target) > 0.0001) {
|
if (fabs(prevHeatingTarget - settings.heating.target) > 0.0001f) {
|
||||||
prevHeatingTarget = settings.heating.target;
|
prevHeatingTarget = settings.heating.target;
|
||||||
Log.sinfoln(FPSTR(L_REGULATOR), F("New target: %.2f"), settings.heating.target);
|
Log.sinfoln(FPSTR(L_REGULATOR), F("New target: %.2f"), settings.heating.target);
|
||||||
|
|
||||||
@@ -143,11 +124,11 @@ protected:
|
|||||||
if (settings.equitherm.enable) {
|
if (settings.equitherm.enable) {
|
||||||
float etResult = getEquithermTemp(settings.heating.minTemp, settings.heating.maxTemp);
|
float etResult = getEquithermTemp(settings.heating.minTemp, settings.heating.maxTemp);
|
||||||
|
|
||||||
if (fabs(prevEtResult - etResult) + 0.0001 >= 0.5) {
|
if (fabs(prevEtResult - etResult) > 0.4999f) {
|
||||||
prevEtResult = etResult;
|
prevEtResult = etResult;
|
||||||
newTemp += etResult;
|
newTemp += etResult;
|
||||||
|
|
||||||
Log.sinfoln(FPSTR(L_REGULATOR_EQUITHERM), F("New result: %hhu (%.2f)"), (uint8_t) round(etResult), etResult);
|
Log.sinfoln(FPSTR(L_REGULATOR_EQUITHERM), F("New result: %.2f"), etResult);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
newTemp += prevEtResult;
|
newTemp += prevEtResult;
|
||||||
@@ -162,11 +143,11 @@ protected:
|
|||||||
settings.pid.maxTemp
|
settings.pid.maxTemp
|
||||||
);
|
);
|
||||||
|
|
||||||
if (fabs(prevPidResult - pidResult) + 0.0001 >= 0.5) {
|
if (fabs(prevPidResult - pidResult) > 0.4999f) {
|
||||||
prevPidResult = pidResult;
|
prevPidResult = pidResult;
|
||||||
newTemp += pidResult;
|
newTemp += pidResult;
|
||||||
|
|
||||||
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("New result: %hhd (%.2f)"), (int8_t) round(pidResult), pidResult);
|
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("New result: %.2f"), pidResult);
|
||||||
Log.straceln(FPSTR(L_REGULATOR_PID), F("Integral: %.2f"), pidRegulator.integral);
|
Log.straceln(FPSTR(L_REGULATOR_PID), F("Integral: %.2f"), pidRegulator.integral);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -176,7 +157,7 @@ protected:
|
|||||||
newTemp += prevPidResult;
|
newTemp += prevPidResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (pidRegulator.integral != 0) {
|
} else if (fabs(pidRegulator.integral) > 0.0001f) {
|
||||||
pidRegulator.integral = 0;
|
pidRegulator.integral = 0;
|
||||||
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
|
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
|
||||||
}
|
}
|
||||||
@@ -186,97 +167,9 @@ protected:
|
|||||||
newTemp = settings.heating.target;
|
newTemp = settings.heating.target;
|
||||||
}
|
}
|
||||||
|
|
||||||
newTemp = round(newTemp);
|
|
||||||
return newTemp;
|
return newTemp;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte getTuningModeTemp() {
|
|
||||||
if (tunerInit && (!vars.tuning.enable || vars.tuning.regulator != tunerRegulator)) {
|
|
||||||
if (tunerRegulator == 0) {
|
|
||||||
pidTuner.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
tunerInit = false;
|
|
||||||
tunerRegulator = 0;
|
|
||||||
tunerState = 0;
|
|
||||||
Log.sinfoln("REGULATOR.TUNING", F("Stopped"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!vars.tuning.enable) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (vars.tuning.regulator == 0) {
|
|
||||||
// @TODO дописать
|
|
||||||
Log.sinfoln("REGULATOR.TUNING.EQUITHERM", F("Not implemented"));
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
} else if (vars.tuning.regulator == 1) {
|
|
||||||
// PID tuner
|
|
||||||
float defaultTemp = settings.equitherm.enable
|
|
||||||
? getEquithermTemp(settings.heating.minTemp, settings.heating.maxTemp)
|
|
||||||
: settings.heating.target;
|
|
||||||
|
|
||||||
if (tunerInit && pidTuner.getState() == 3) {
|
|
||||||
Log.sinfoln("REGULATOR.TUNING.PID", F("Finished"));
|
|
||||||
for (Stream* stream : Log.getStreams()) {
|
|
||||||
pidTuner.debugText(stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
pidTuner.reset();
|
|
||||||
tunerInit = false;
|
|
||||||
tunerRegulator = 0;
|
|
||||||
tunerState = 0;
|
|
||||||
|
|
||||||
if (pidTuner.getAccuracy() < 90) {
|
|
||||||
Log.swarningln("REGULATOR.TUNING.PID", F("Bad result, try again..."));
|
|
||||||
|
|
||||||
} else {
|
|
||||||
settings.pid.p_factor = pidTuner.getPID_p();
|
|
||||||
settings.pid.i_factor = pidTuner.getPID_i();
|
|
||||||
settings.pid.d_factor = pidTuner.getPID_d();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tunerInit) {
|
|
||||||
Log.sinfoln("REGULATOR.TUNING.PID", F("Start..."));
|
|
||||||
|
|
||||||
float step;
|
|
||||||
if (vars.temperatures.indoor - vars.temperatures.outdoor > 10) {
|
|
||||||
step = ceil(vars.parameters.heatingSetpoint / vars.temperatures.indoor * 2);
|
|
||||||
} else {
|
|
||||||
step = 5.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
float startTemp = step;
|
|
||||||
Log.sinfoln("REGULATOR.TUNING.PID", F("Started. Start value: %f, step: %f"), startTemp, step);
|
|
||||||
pidTuner.setParameters(NORMAL, startTemp, step, 20 * 60 * 1000, 0.15, 60 * 1000, 10000);
|
|
||||||
tunerInit = true;
|
|
||||||
tunerRegulator = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pidTuner.setInput(vars.temperatures.indoor);
|
|
||||||
pidTuner.compute();
|
|
||||||
|
|
||||||
if (tunerState > 0 && pidTuner.getState() != tunerState) {
|
|
||||||
Log.sinfoln("REGULATOR.TUNING.PID", F("Log:"));
|
|
||||||
for (Stream* stream : Log.getStreams()) {
|
|
||||||
pidTuner.debugText(stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
tunerState = pidTuner.getState();
|
|
||||||
}
|
|
||||||
|
|
||||||
return round(defaultTemp + pidTuner.getOutput());
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the Equitherm Temp
|
* @brief Get the Equitherm Temp
|
||||||
* Calculations in degrees C, conversion occurs when using F
|
* Calculations in degrees C, conversion occurs when using F
|
||||||
@@ -357,32 +250,4 @@ protected:
|
|||||||
|
|
||||||
return pidRegulator.getResultTimer();
|
return pidRegulator.getResultTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
if (fabs(currentTemp - setTemp) < accurateStepAfter) {
|
|
||||||
if (millis() - _prevIteration < (accurateInterval * 1000)) {
|
|
||||||
return ratio;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentTemp - setTemp > 0.1f) {
|
|
||||||
ratio -= accurateStep;
|
|
||||||
|
|
||||||
} else if (currentTemp - setTemp < -0.1f) {
|
|
||||||
ratio += accurateStep;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if (millis() - _prevIteration < (dirtyInterval * 1000)) {
|
|
||||||
return ratio;
|
|
||||||
}
|
|
||||||
|
|
||||||
ratio = ratio * (setTemp / currentTemp);
|
|
||||||
}
|
|
||||||
|
|
||||||
_prevIteration = millis();
|
|
||||||
return ratio;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ protected:
|
|||||||
newTemp += c2f(this->filteredOutdoorTemp);
|
newTemp += c2f(this->filteredOutdoorTemp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fabs(vars.temperatures.outdoor - newTemp) > 0.099) {
|
if (fabs(vars.temperatures.outdoor - newTemp) > 0.099f) {
|
||||||
vars.temperatures.outdoor = newTemp;
|
vars.temperatures.outdoor = newTemp;
|
||||||
Log.sinfoln(FPSTR(L_SENSORS_OUTDOOR), F("New temp: %f"), vars.temperatures.outdoor);
|
Log.sinfoln(FPSTR(L_SENSORS_OUTDOOR), F("New temp: %f"), vars.temperatures.outdoor);
|
||||||
}
|
}
|
||||||
@@ -105,7 +105,7 @@ protected:
|
|||||||
newTemp += c2f(this->filteredIndoorTemp);
|
newTemp += c2f(this->filteredIndoorTemp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fabs(vars.temperatures.indoor - newTemp) > 0.099) {
|
if (fabs(vars.temperatures.indoor - newTemp) > 0.099f) {
|
||||||
vars.temperatures.indoor = newTemp;
|
vars.temperatures.indoor = newTemp;
|
||||||
Log.sinfoln(FPSTR(L_SENSORS_INDOOR), F("New temp: %f"), vars.temperatures.indoor);
|
Log.sinfoln(FPSTR(L_SENSORS_INDOOR), F("New temp: %f"), vars.temperatures.indoor);
|
||||||
}
|
}
|
||||||
@@ -163,7 +163,7 @@ protected:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
float rawTemp = ((pData[0] | (pData[1] << 8)) * 0.01);
|
float rawTemp = ((pData[0] | (pData[1] << 8)) * 0.01f);
|
||||||
Log.straceln(FPSTR(L_SENSORS_INDOOR), F("Raw temp: %f"), rawTemp);
|
Log.straceln(FPSTR(L_SENSORS_INDOOR), F("Raw temp: %f"), rawTemp);
|
||||||
|
|
||||||
if (this->emptyIndoorTemp) {
|
if (this->emptyIndoorTemp) {
|
||||||
@@ -235,7 +235,7 @@ protected:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.sinfoln(FPSTR(L_SENSORS_OUTDOOR), F("Starting on gpio %hhu..."), settings.sensors.outdoor.gpio);
|
Log.sinfoln(FPSTR(L_SENSORS_OUTDOOR), F("Starting on GPIO %hhu..."), settings.sensors.outdoor.gpio);
|
||||||
|
|
||||||
this->oneWireOutdoorSensor->begin(settings.sensors.outdoor.gpio);
|
this->oneWireOutdoorSensor->begin(settings.sensors.outdoor.gpio);
|
||||||
this->oneWireOutdoorSensor->reset();
|
this->oneWireOutdoorSensor->reset();
|
||||||
@@ -307,7 +307,7 @@ protected:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.sinfoln(FPSTR(L_SENSORS_INDOOR), F("Starting on gpio %hhu..."), settings.sensors.indoor.gpio);
|
Log.sinfoln(FPSTR(L_SENSORS_INDOOR), F("Starting on GPIO %hhu..."), settings.sensors.indoor.gpio);
|
||||||
|
|
||||||
this->oneWireIndoorSensor->begin(settings.sensors.indoor.gpio);
|
this->oneWireIndoorSensor->begin(settings.sensors.indoor.gpio);
|
||||||
this->oneWireIndoorSensor->reset();
|
this->oneWireIndoorSensor->reset();
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ struct Settings {
|
|||||||
bool dhwBlocking = false;
|
bool dhwBlocking = false;
|
||||||
bool modulationSyncWithHeating = false;
|
bool modulationSyncWithHeating = false;
|
||||||
bool getMinMaxTemp = true;
|
bool getMinMaxTemp = true;
|
||||||
|
bool nativeHeatingControl = false;
|
||||||
} opentherm;
|
} opentherm;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
@@ -70,11 +71,12 @@ struct Settings {
|
|||||||
char password[33] = DEFAULT_MQTT_PASSWORD;
|
char password[33] = DEFAULT_MQTT_PASSWORD;
|
||||||
char prefix[33] = DEFAULT_MQTT_PREFIX;
|
char prefix[33] = DEFAULT_MQTT_PREFIX;
|
||||||
unsigned short interval = 5;
|
unsigned short interval = 5;
|
||||||
|
bool homeAssistantDiscovery = true;
|
||||||
} mqtt;
|
} mqtt;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
bool enable = true;
|
bool enable = true;
|
||||||
float target = 40.0f;
|
float target = DEFAULT_HEATING_TARGET_TEMP;
|
||||||
unsigned short tresholdTime = 120;
|
unsigned short tresholdTime = 120;
|
||||||
bool useEquitherm = false;
|
bool useEquitherm = false;
|
||||||
bool usePid = false;
|
bool usePid = false;
|
||||||
@@ -85,7 +87,7 @@ struct Settings {
|
|||||||
struct {
|
struct {
|
||||||
bool enable = true;
|
bool enable = true;
|
||||||
bool turbo = false;
|
bool turbo = false;
|
||||||
float target = 40.0f;
|
float target = DEFAULT_HEATING_TARGET_TEMP;
|
||||||
float hysteresis = 0.5f;
|
float hysteresis = 0.5f;
|
||||||
byte minTemp = DEFAULT_HEATING_MIN_TEMP;
|
byte minTemp = DEFAULT_HEATING_MIN_TEMP;
|
||||||
byte maxTemp = DEFAULT_HEATING_MAX_TEMP;
|
byte maxTemp = DEFAULT_HEATING_MAX_TEMP;
|
||||||
@@ -94,7 +96,7 @@ struct Settings {
|
|||||||
|
|
||||||
struct {
|
struct {
|
||||||
bool enable = true;
|
bool enable = true;
|
||||||
byte target = 40;
|
float target = DEFAULT_DHW_TARGET_TEMP;
|
||||||
byte minTemp = DEFAULT_DHW_MIN_TEMP;
|
byte minTemp = DEFAULT_DHW_MIN_TEMP;
|
||||||
byte maxTemp = DEFAULT_DHW_MAX_TEMP;
|
byte maxTemp = DEFAULT_DHW_MAX_TEMP;
|
||||||
} dhw;
|
} dhw;
|
||||||
@@ -143,11 +145,6 @@ struct Settings {
|
|||||||
} settings;
|
} settings;
|
||||||
|
|
||||||
struct Variables {
|
struct Variables {
|
||||||
struct {
|
|
||||||
bool enable = false;
|
|
||||||
byte regulator = 0;
|
|
||||||
} tuning;
|
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
bool otStatus = false;
|
bool otStatus = false;
|
||||||
bool emergency = false;
|
bool emergency = false;
|
||||||
@@ -181,7 +178,7 @@ struct Variables {
|
|||||||
bool heatingEnabled = false;
|
bool heatingEnabled = false;
|
||||||
byte heatingMinTemp = DEFAULT_HEATING_MIN_TEMP;
|
byte heatingMinTemp = DEFAULT_HEATING_MIN_TEMP;
|
||||||
byte heatingMaxTemp = DEFAULT_HEATING_MAX_TEMP;
|
byte heatingMaxTemp = DEFAULT_HEATING_MAX_TEMP;
|
||||||
byte heatingSetpoint = 0;
|
float heatingSetpoint = 0;
|
||||||
unsigned long extPumpLastEnableTime = 0;
|
unsigned long extPumpLastEnableTime = 0;
|
||||||
byte dhwMinTemp = DEFAULT_DHW_MIN_TEMP;
|
byte dhwMinTemp = DEFAULT_DHW_MIN_TEMP;
|
||||||
byte dhwMaxTemp = DEFAULT_DHW_MAX_TEMP;
|
byte dhwMaxTemp = DEFAULT_DHW_MAX_TEMP;
|
||||||
|
|||||||
@@ -1,20 +1,27 @@
|
|||||||
#define PROJECT_NAME "OpenTherm Gateway"
|
#define PROJECT_NAME "OpenTherm Gateway"
|
||||||
#define PROJECT_VERSION "1.4.0-rc.23"
|
#define PROJECT_VERSION "1.4.0-rc.23"
|
||||||
#define PROJECT_REPO "https://github.com/Laxilef/OTGateway"
|
#define PROJECT_REPO "https://github.com/Laxilef/OTGateway"
|
||||||
|
|
||||||
#define MQTT_RECONNECT_INTERVAL 15000
|
#define MQTT_RECONNECT_INTERVAL 15000
|
||||||
|
|
||||||
#define EXT_SENSORS_INTERVAL 5000
|
#define EXT_SENSORS_INTERVAL 5000
|
||||||
#define EXT_SENSORS_FILTER_K 0.15
|
#define EXT_SENSORS_FILTER_K 0.15
|
||||||
|
|
||||||
#define CONFIG_URL "http://%s/"
|
#define CONFIG_URL "http://%s/"
|
||||||
#define SETTINGS_VALID_VALUE "stvalid" // only 8 chars!
|
#define SETTINGS_VALID_VALUE "stvalid" // only 8 chars!
|
||||||
|
#define GPIO_IS_NOT_CONFIGURED 0xff
|
||||||
|
|
||||||
#define GPIO_IS_NOT_CONFIGURED 0xff
|
#define DEFAULT_HEATING_TARGET_TEMP 40
|
||||||
#define DEFAULT_HEATING_MIN_TEMP 20
|
#define DEFAULT_HEATING_MIN_TEMP 20
|
||||||
#define DEFAULT_HEATING_MAX_TEMP 90
|
#define DEFAULT_HEATING_MAX_TEMP 90
|
||||||
#define DEFAULT_DHW_MIN_TEMP 30
|
|
||||||
#define DEFAULT_DHW_MAX_TEMP 60
|
#define DEFAULT_DHW_TARGET_TEMP 40
|
||||||
|
#define DEFAULT_DHW_MIN_TEMP 30
|
||||||
|
#define DEFAULT_DHW_MAX_TEMP 60
|
||||||
|
|
||||||
|
#define THERMOSTAT_INDOOR_DEFAULT_TEMP 20
|
||||||
|
#define THERMOSTAT_INDOOR_MIN_TEMP 5
|
||||||
|
#define THERMOSTAT_INDOOR_MAX_TEMP 30
|
||||||
|
|
||||||
#ifndef USE_SERIAL
|
#ifndef USE_SERIAL
|
||||||
#define USE_SERIAL true
|
#define USE_SERIAL true
|
||||||
|
|||||||
658
src/utils.h
658
src/utils.h
File diff suppressed because it is too large
Load Diff
@@ -250,7 +250,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
newSettings.dhw.target -= 1;
|
newSettings.dhw.target -= 1.0;
|
||||||
modifiedTime = Date.now();
|
modifiedTime = Date.now();
|
||||||
|
|
||||||
if (newSettings.dhw.target < prevSettings.dhw.minTemp) {
|
if (newSettings.dhw.target < prevSettings.dhw.minTemp) {
|
||||||
@@ -265,7 +265,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
newSettings.dhw.target += 1;
|
newSettings.dhw.target += 1.0;
|
||||||
modifiedTime = Date.now();
|
modifiedTime = Date.now();
|
||||||
|
|
||||||
if (newSettings.dhw.target > prevSettings.dhw.maxTemp) {
|
if (newSettings.dhw.target > prevSettings.dhw.maxTemp) {
|
||||||
@@ -326,7 +326,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
noRegulators = !result.equitherm.enable && !result.pid.enable;
|
noRegulators = !result.opentherm.nativeHeatingControl && !result.equitherm.enable && !result.pid.enable;
|
||||||
prevSettings = result;
|
prevSettings = result;
|
||||||
newSettings.heating.enable = result.heating.enable;
|
newSettings.heating.enable = result.heating.enable;
|
||||||
newSettings.heating.turbo = result.heating.turbo;
|
newSettings.heating.turbo = result.heating.turbo;
|
||||||
|
|||||||
@@ -435,6 +435,13 @@
|
|||||||
<input type="checkbox" id="opentherm-get-min-max-temp" name="opentherm[getMinMaxTemp]" value="true">
|
<input type="checkbox" id="opentherm-get-min-max-temp" name="opentherm[getMinMaxTemp]" value="true">
|
||||||
Get min/max temp from boiler
|
Get min/max temp from boiler
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
<label for="opentherm-native-heating-control">
|
||||||
|
<input type="checkbox" id="opentherm-native-heating-control" name="opentherm[nativeHeatingControl]" value="true">
|
||||||
|
Native heating control (boiler)<br />
|
||||||
|
<small>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.</small>
|
||||||
|
</label>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<button type="submit">Save</button>
|
<button type="submit">Save</button>
|
||||||
@@ -454,6 +461,11 @@
|
|||||||
<input type="checkbox" id="mqtt-enable" name="mqtt[enable]" value="true">
|
<input type="checkbox" id="mqtt-enable" name="mqtt[enable]" value="true">
|
||||||
Enable
|
Enable
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
<label for="mqtt-ha-discovery">
|
||||||
|
<input type="checkbox" id="mqtt-ha-discovery" name="mqtt[homeAssistantDiscovery]" value="true">
|
||||||
|
Home Assistant Discovery
|
||||||
|
</label>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
@@ -677,10 +689,12 @@
|
|||||||
setCheckboxValue('#opentherm-dhw-blocking', data.opentherm.dhwBlocking);
|
setCheckboxValue('#opentherm-dhw-blocking', data.opentherm.dhwBlocking);
|
||||||
setCheckboxValue('#opentherm-sync-modulation-with-heating', data.opentherm.modulationSyncWithHeating);
|
setCheckboxValue('#opentherm-sync-modulation-with-heating', data.opentherm.modulationSyncWithHeating);
|
||||||
setCheckboxValue('#opentherm-get-min-max-temp', data.opentherm.getMinMaxTemp);
|
setCheckboxValue('#opentherm-get-min-max-temp', data.opentherm.getMinMaxTemp);
|
||||||
|
setCheckboxValue('#opentherm-native-heating-control', data.opentherm.nativeHeatingControl);
|
||||||
setBusy('#opentherm-settings-busy', '#opentherm-settings', false);
|
setBusy('#opentherm-settings-busy', '#opentherm-settings', false);
|
||||||
|
|
||||||
// MQTT
|
// MQTT
|
||||||
setCheckboxValue('#mqtt-enable', data.mqtt.enable);
|
setCheckboxValue('#mqtt-enable', data.mqtt.enable);
|
||||||
|
setCheckboxValue('#mqtt-ha-discovery', data.mqtt.homeAssistantDiscovery);
|
||||||
setInputValue('#mqtt-server', data.mqtt.server);
|
setInputValue('#mqtt-server', data.mqtt.server);
|
||||||
setInputValue('#mqtt-port', data.mqtt.port);
|
setInputValue('#mqtt-port', data.mqtt.port);
|
||||||
setInputValue('#mqtt-user', data.mqtt.user);
|
setInputValue('#mqtt-user', data.mqtt.user);
|
||||||
|
|||||||
Reference in New Issue
Block a user