Merge branch 'master' into new-equitherm

This commit is contained in:
Yurii
2025-05-19 22:49:09 +03:00
16 changed files with 665 additions and 133 deletions

View File

@@ -395,7 +395,7 @@ public:
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC);
doc[FPSTR(HA_DEVICE_CLASS)] = F("signal_strength");
//doc[FPSTR(HA_DEVICE_CLASS)] = F("signal_strength");
doc[FPSTR(HA_STATE_CLASS)] = FPSTR(HA_STATE_CLASS_MEASUREMENT);
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_PERCENT);
doc[FPSTR(HA_ICON)] = F("mdi:signal");

View File

@@ -196,6 +196,7 @@ protected:
// critical heap
if (!vars.states.restarting && (freeHeap < 2048 || maxFreeBlockHeap < 2048)) {
this->restartSignalReceivedTime = millis();
this->restartSignalReceived = true;
vars.states.restarting = true;
}

View File

@@ -93,8 +93,13 @@ protected:
this->instance->setAfterSendRequestCallback([this](unsigned long request, unsigned long response, OpenThermResponseStatus status, byte attempt) {
Log.sverboseln(
FPSTR(L_OT),
F("ID: %4d Request: %8lx Response: %8lx Attempt: %2d Status: %s"),
CustomOpenTherm::getDataID(request), request, response, attempt, CustomOpenTherm::statusToString(status)
F("ID: %4d Request: %8lx Response: %8lx Msg type: %s Attempt: %2d Status: %s"),
CustomOpenTherm::getDataID(request),
request,
response,
CustomOpenTherm::getResponseMessageTypeString(response),
attempt,
CustomOpenTherm::statusToString(status)
);
if (status == OpenThermResponseStatus::SUCCESS) {
@@ -138,7 +143,12 @@ protected:
return;
} else if (this->instance->status == OpenThermStatus::NOT_INITIALIZED) {
this->instance->begin();
if (!this->instance->begin()) {
Log.swarningln(FPSTR(L_OT), F("Failed begin"));
this->delay(5000);
return;
}
}
// RX LED GPIO setup
@@ -212,6 +222,26 @@ protected:
F("Failed receive boiler status: %s"),
CustomOpenTherm::statusToString(this->instance->getLastResponseStatus())
);
} else {
vars.slave.heating.active = CustomOpenTherm::isCentralHeatingActive(response);
vars.slave.dhw.active = settings.opentherm.options.dhwSupport ? CustomOpenTherm::isHotWaterActive(response) : false;
vars.slave.flame = CustomOpenTherm::isFlameOn(response);
vars.slave.cooling = CustomOpenTherm::isCoolingActive(response);
vars.slave.fault.active = CustomOpenTherm::isFault(response);
if (!settings.opentherm.options.ignoreDiagState) {
vars.slave.diag.active = CustomOpenTherm::isDiagnostic(response);
} else if (vars.slave.diag.active) {
vars.slave.diag.active = false;
}
Log.snoticeln(
FPSTR(L_OT), F("Received boiler status. Heating: %hhu; DHW: %hhu; flame: %hhu; cooling: %hhu; fault: %hhu; diag: %hhu"),
vars.slave.heating.active, vars.slave.dhw.active,
vars.slave.flame, vars.slave.cooling, vars.slave.fault.active, vars.slave.diag.active
);
}
// 5 request retries
@@ -257,6 +287,15 @@ protected:
Sensors::setConnectionStatusByType(Sensors::Type::OT_FAN_SPEED_SETPOINT, false);
Sensors::setConnectionStatusByType(Sensors::Type::OT_FAN_SPEED_CURRENT, false);
Sensors::setConnectionStatusByType(Sensors::Type::OT_BURNER_STARTS, false);
Sensors::setConnectionStatusByType(Sensors::Type::OT_DHW_BURNER_STARTS, false);
Sensors::setConnectionStatusByType(Sensors::Type::OT_HEATING_PUMP_STARTS, false);
Sensors::setConnectionStatusByType(Sensors::Type::OT_DHW_PUMP_STARTS, false);
Sensors::setConnectionStatusByType(Sensors::Type::OT_BURNER_HOURS, false);
Sensors::setConnectionStatusByType(Sensors::Type::OT_DHW_BURNER_HOURS, false);
Sensors::setConnectionStatusByType(Sensors::Type::OT_HEATING_PUMP_HOURS, false);
Sensors::setConnectionStatusByType(Sensors::Type::OT_DHW_PUMP_HOURS, false);
this->initialized = false;
this->disconnectedTime = millis();
vars.slave.connected = false;
@@ -310,34 +349,60 @@ protected:
Log.sinfoln(FPSTR(L_OT_DHW), vars.master.dhw.enabled ? F("Enabled") : F("Disabled"));
}
vars.slave.heating.active = CustomOpenTherm::isCentralHeatingActive(response);
vars.slave.dhw.active = settings.opentherm.options.dhwSupport ? CustomOpenTherm::isHotWaterActive(response) : false;
vars.slave.flame = CustomOpenTherm::isFlameOn(response);
vars.slave.cooling = CustomOpenTherm::isCoolingActive(response);
vars.slave.fault.active = CustomOpenTherm::isFault(response);
vars.slave.diag.active = CustomOpenTherm::isDiagnostic(response);
Log.snoticeln(
FPSTR(L_OT), F("Received boiler status. Heating: %hhu; DHW: %hhu; flame: %hhu; cooling: %hhu; fault: %hhu; diag: %hhu"),
vars.slave.heating.active, vars.slave.dhw.active,
vars.slave.flame, vars.slave.cooling, vars.slave.fault.active, vars.slave.diag.active
);
// These parameters will be updated every minute
if (millis() - this->prevUpdateNonEssentialVars > 60000) {
// Set date & time
if (settings.opentherm.options.setDateAndTime) {
struct tm ti;
if (getLocalTime(&ti)) {
if (this->setYear(&ti)) {
Log.sinfoln(FPSTR(L_OT), F("Year of date set successfully"));
} else {
Log.sinfoln(FPSTR(L_OT), F("Failed set year of date"));
}
if (this->setDayAndMonth(&ti)) {
Log.sinfoln(FPSTR(L_OT), F("Day and month of date set successfully"));
} else {
Log.sinfoln(FPSTR(L_OT), F("Failed set day and month of date"));
}
if (this->setTime(&ti)) {
Log.sinfoln(FPSTR(L_OT), F("Time set successfully"));
} else {
Log.sinfoln(FPSTR(L_OT), F("Failed set time"));
}
}
}
// Get min modulation level & max power
if (this->updateMinModulationLevel()) {
Log.snoticeln(
FPSTR(L_OT), F("Received min modulation: %hhu%%, max power: %.2f kW"),
vars.slave.modulation.min, vars.slave.power.max
);
if (settings.opentherm.maxModulation < vars.slave.modulation.min) {
settings.opentherm.maxModulation = vars.slave.modulation.min;
if (settings.heating.maxModulation < vars.slave.modulation.min) {
settings.heating.maxModulation = vars.slave.modulation.min;
fsSettings.update();
Log.swarningln(
FPSTR(L_SETTINGS_OT), F("Updated min modulation: %hhu%%"),
settings.opentherm.maxModulation
FPSTR(L_SETTINGS_HEATING), F("Updated min modulation: %hhu%%"),
settings.heating.maxModulation
);
}
if (settings.dhw.maxModulation < vars.slave.modulation.min) {
settings.dhw.maxModulation = vars.slave.modulation.min;
fsSettings.update();
Log.swarningln(
FPSTR(L_SETTINGS_DHW), F("Updated min modulation: %hhu%%"),
settings.dhw.maxModulation
);
}
@@ -356,29 +421,6 @@ protected:
Log.swarningln(FPSTR(L_OT), F("Failed receive min modulation and max power"));
}
if (!vars.master.heating.enabled && settings.opentherm.options.modulationSyncWithHeating) {
if (this->setMaxModulationLevel(0)) {
Log.snoticeln(FPSTR(L_OT), F("Set max modulation: 0% (response: %hhu%%)"), vars.slave.modulation.max);
} else {
Log.swarningln(FPSTR(L_OT), F("Failed set max modulation: 0% (response: %hhu%%)"), vars.slave.modulation.max);
}
} else {
if (this->setMaxModulationLevel(settings.opentherm.maxModulation)) {
Log.snoticeln(
FPSTR(L_OT), F("Set max modulation: %hhu%% (response: %hhu%%)"),
settings.opentherm.maxModulation, vars.slave.modulation.max
);
} else {
Log.swarningln(
FPSTR(L_OT), F("Failed set max modulation: %hhu%% (response: %hhu%%)"),
settings.opentherm.maxModulation, vars.slave.modulation.max
);
}
}
// Get DHW min/max temp (if necessary)
if (settings.opentherm.options.dhwSupport && settings.opentherm.options.getMinMaxTemp) {
@@ -503,9 +545,138 @@ protected:
vars.slave.diag.code = 0;
}
// Update burner starts
if (Sensors::getAmountByType(Sensors::Type::OT_BURNER_STARTS, true)) {
if (this->updateBurnerStarts()) {
Log.snoticeln(FPSTR(L_OT), F("Received burner starts: %hu"), vars.slave.stats.burnerStarts);
Sensors::setValueByType(
Sensors::Type::OT_BURNER_STARTS, vars.slave.stats.burnerStarts,
Sensors::ValueType::PRIMARY, true, true
);
}
}
// Update DHW burner starts
if (Sensors::getAmountByType(Sensors::Type::OT_DHW_BURNER_STARTS, true)) {
if (this->updateDhwBurnerStarts()) {
Log.snoticeln(FPSTR(L_OT), F("Received DHW burner starts: %hu"), vars.slave.stats.dhwBurnerStarts);
Sensors::setValueByType(
Sensors::Type::OT_DHW_BURNER_STARTS, vars.slave.stats.dhwBurnerStarts,
Sensors::ValueType::PRIMARY, true, true
);
}
}
// Update heating pump starts
if (Sensors::getAmountByType(Sensors::Type::OT_HEATING_PUMP_STARTS, true)) {
if (this->updateHeatingPumpStarts()) {
Log.snoticeln(FPSTR(L_OT), F("Received heating pump starts: %hu"), vars.slave.stats.heatingPumpStarts);
Sensors::setValueByType(
Sensors::Type::OT_HEATING_PUMP_STARTS, vars.slave.stats.heatingPumpStarts,
Sensors::ValueType::PRIMARY, true, true
);
}
}
// Update DHW pump starts
if (Sensors::getAmountByType(Sensors::Type::OT_DHW_PUMP_STARTS, true)) {
if (this->updateDhwPumpStarts()) {
Log.snoticeln(FPSTR(L_OT), F("Received DHW pump starts: %hu"), vars.slave.stats.dhwPumpStarts);
Sensors::setValueByType(
Sensors::Type::OT_DHW_PUMP_STARTS, vars.slave.stats.dhwPumpStarts,
Sensors::ValueType::PRIMARY, true, true
);
}
}
// Update burner hours
if (Sensors::getAmountByType(Sensors::Type::OT_BURNER_HOURS, true)) {
if (this->updateBurnerHours()) {
Log.snoticeln(FPSTR(L_OT), F("Received burner hours: %hu"), vars.slave.stats.burnerHours);
Sensors::setValueByType(
Sensors::Type::OT_BURNER_HOURS, vars.slave.stats.burnerHours,
Sensors::ValueType::PRIMARY, true, true
);
}
}
// Update DHW burner hours
if (Sensors::getAmountByType(Sensors::Type::OT_DHW_BURNER_HOURS, true)) {
if (this->updateDhwBurnerHours()) {
Log.snoticeln(FPSTR(L_OT), F("Received DHW burner hours: %hu"), vars.slave.stats.dhwBurnerHours);
Sensors::setValueByType(
Sensors::Type::OT_DHW_BURNER_HOURS, vars.slave.stats.dhwBurnerHours,
Sensors::ValueType::PRIMARY, true, true
);
}
}
// Update heating pump hours
if (Sensors::getAmountByType(Sensors::Type::OT_HEATING_PUMP_HOURS, true)) {
if (this->updateHeatingPumpHours()) {
Log.snoticeln(FPSTR(L_OT), F("Received heating pump hours: %hu"), vars.slave.stats.heatingPumpHours);
Sensors::setValueByType(
Sensors::Type::OT_HEATING_PUMP_HOURS, vars.slave.stats.heatingPumpHours,
Sensors::ValueType::PRIMARY, true, true
);
}
}
// Update DHW pump hours
if (Sensors::getAmountByType(Sensors::Type::OT_DHW_PUMP_HOURS, true)) {
if (this->updateDhwPumpHours()) {
Log.snoticeln(FPSTR(L_OT), F("Received DHW pump hours: %hu"), vars.slave.stats.dhwPumpHours);
Sensors::setValueByType(
Sensors::Type::OT_DHW_PUMP_HOURS, vars.slave.stats.dhwPumpHours,
Sensors::ValueType::PRIMARY, true, true
);
}
}
// Auto fault reset
if (settings.opentherm.options.autoFaultReset && vars.slave.fault.active && !vars.actions.resetFault) {
vars.actions.resetFault = true;
}
// Auto diag reset
if (settings.opentherm.options.autoDiagReset && vars.slave.diag.active && !vars.actions.resetDiagnostic) {
vars.actions.resetDiagnostic = true;
}
this->prevUpdateNonEssentialVars = millis();
}
// Set max modulation level
uint8_t targetMaxModulation = vars.slave.modulation.max;
if (vars.slave.heating.active) {
targetMaxModulation = settings.heating.maxModulation;
} else if (vars.slave.dhw.active) {
targetMaxModulation = settings.dhw.maxModulation;
}
if (vars.slave.modulation.max != targetMaxModulation) {
if (this->setMaxModulationLevel(targetMaxModulation)) {
Log.snoticeln(
FPSTR(L_OT), F("Set max modulation: %hhu%% (response: %hhu%%)"),
targetMaxModulation, vars.slave.modulation.max
);
} else {
Log.swarningln(
FPSTR(L_OT), F("Failed set max modulation: %hhu%% (response: %hhu%%)"),
targetMaxModulation, vars.slave.modulation.max
);
}
}
// Update modulation level
if (
@@ -1179,17 +1350,17 @@ protected:
bool needSetDhwTemp(const float target) {
return millis() - this->dhwSetTempTime > this->dhwSetTempInterval
|| fabsf(target - vars.slave.dhw.targetTemp) > 0.001f;
|| fabsf(target - vars.slave.dhw.targetTemp) > 0.05f;
}
bool needSetHeatingTemp(const float target) {
return millis() - this->heatingSetTempTime > this->heatingSetTempInterval
|| fabsf(target - vars.slave.heating.targetTemp) > 0.001f;
|| fabsf(target - vars.slave.heating.targetTemp) > 0.05f;
}
bool needSetCh2Temp(const float target) {
return millis() - this->ch2SetTempTime > this->ch2SetTempInterval
|| fabsf(target - vars.slave.ch2.targetTemp) > 0.001f;
|| fabsf(target - vars.slave.ch2.targetTemp) > 0.05f;
}
bool updateSlaveConfig() {
@@ -1228,6 +1399,65 @@ protected:
return true;
}
bool setYear(const struct tm *ptm) {
const unsigned int request = (ptm->tm_year + 1900) & 0xFFFF;
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::WRITE_DATA,
OpenThermMessageID::Year,
request
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::Year)) {
return false;
}
return CustomOpenTherm::getUInt(response) == request;
}
bool setDayAndMonth(const struct tm *ptm) {
const unsigned int request = ((ptm->tm_mon + 1) & 0xFF << 8)
| (ptm->tm_mday & 0xFF);
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::WRITE_DATA,
OpenThermMessageID::Date,
request
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::Date)) {
return false;
}
return CustomOpenTherm::getUInt(response) == request;
}
bool setTime(const struct tm *ptm) {
const uint8_t dayOfWeek = ptm->tm_wday == 0 ? 6 : ptm->tm_wday - 1;
const unsigned int request = ((dayOfWeek & 0x07) << 13)
| ((ptm->tm_hour & 0x1F) << 8)
| (ptm->tm_min & 0x3F);
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::WRITE_DATA,
OpenThermMessageID::DayTime,
request
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::DayTime)) {
return false;
}
return CustomOpenTherm::getUInt(response) == request;
}
bool setMaxModulationLevel(const uint8_t value) {
const unsigned int request = CustomOpenTherm::toFloat(value);
@@ -1350,7 +1580,7 @@ protected:
return CustomOpenTherm::getUInt(response) == request;
}
bool setMaxHeatingTemp(const uint8_t temperature) {
bool setMaxHeatingTemp(const float temperature) {
const unsigned int request = CustomOpenTherm::temperatureToData(temperature);
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermMessageType::WRITE_DATA,
@@ -1640,6 +1870,158 @@ protected:
return true;
}
bool updateBurnerStarts() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::SuccessfulBurnerStarts,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::SuccessfulBurnerStarts)) {
return false;
}
vars.slave.stats.burnerStarts = CustomOpenTherm::getUInt(response);
return true;
}
bool updateDhwBurnerStarts() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::DHWBurnerStarts,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::DHWBurnerStarts)) {
return false;
}
vars.slave.stats.dhwBurnerStarts = CustomOpenTherm::getUInt(response);
return true;
}
bool updateHeatingPumpStarts() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::CHPumpStarts,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::CHPumpStarts)) {
return false;
}
vars.slave.stats.heatingPumpStarts = CustomOpenTherm::getUInt(response);
return true;
}
bool updateDhwPumpStarts() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::DHWPumpValveStarts,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::DHWPumpValveStarts)) {
return false;
}
vars.slave.stats.dhwPumpStarts = CustomOpenTherm::getUInt(response);
return true;
}
bool updateBurnerHours() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::BurnerOperationHours,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::BurnerOperationHours)) {
return false;
}
vars.slave.stats.burnerHours = CustomOpenTherm::getUInt(response);
return true;
}
bool updateDhwBurnerHours() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::DHWBurnerOperationHours,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::DHWBurnerOperationHours)) {
return false;
}
vars.slave.stats.dhwBurnerHours = CustomOpenTherm::getUInt(response);
return true;
}
bool updateHeatingPumpHours() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::CHPumpOperationHours,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::CHPumpOperationHours)) {
return false;
}
vars.slave.stats.heatingPumpHours = CustomOpenTherm::getUInt(response);
return true;
}
bool updateDhwPumpHours() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::DHWPumpValveOperationHours,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::DHWPumpValveOperationHours)) {
return false;
}
vars.slave.stats.dhwPumpHours = CustomOpenTherm::getUInt(response);
return true;
}
bool updateModulationLevel() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,

View File

@@ -58,11 +58,11 @@ protected:
this->hysteresis();
vars.master.heating.targetTemp = settings.heating.target;
vars.master.heating.setpointTemp = constrain(
vars.master.heating.setpointTemp = roundf(constrain(
this->getHeatingSetpointTemp(),
this->getHeatingMinSetpointTemp(),
this->getHeatingMaxSetpointTemp()
);
), 0);
Sensors::setValueByType(
Sensors::Type::HEATING_SETPOINT_TEMP, vars.master.heating.setpointTemp,

View File

@@ -25,6 +25,15 @@ public:
OT_SOLAR_COLLECTOR_TEMP = 16,
OT_FAN_SPEED_SETPOINT = 17,
OT_FAN_SPEED_CURRENT = 18,
OT_BURNER_STARTS = 19,
OT_DHW_BURNER_STARTS = 20,
OT_HEATING_PUMP_STARTS = 21,
OT_DHW_PUMP_STARTS = 22,
OT_BURNER_HOURS = 23,
OT_DHW_BURNER_HOURS = 24,
OT_HEATING_PUMP_HOURS = 25,
OT_DHW_PUMP_HOURS = 26,
NTC_10K_TEMP = 50,
DALLAS_TEMP = 51,
@@ -46,6 +55,7 @@ public:
EXHAUST_TEMP = 7,
MODULATION_LEVEL = 8,
NUMBER = 247,
POWER_FACTOR = 248,
POWER = 249,
FAN_SPEED = 250,

View File

@@ -59,7 +59,6 @@ struct Settings {
byte rxLedGpio = DEFAULT_OT_RX_LED_GPIO;
uint8_t memberId = 0;
uint8_t flags = 0;
uint8_t maxModulation = 100;
float minPower = 0.0f;
float maxPower = 0.0f;
@@ -72,9 +71,12 @@ struct Settings {
bool heatingToCh2 = false;
bool dhwToCh2 = false;
bool dhwBlocking = false;
bool modulationSyncWithHeating = false;
bool maxTempSyncWithTargetTemp = true;
bool getMinMaxTemp = true;
bool ignoreDiagState = false;
bool autoFaultReset = false;
bool autoDiagReset = false;
bool setDateAndTime = false;
bool nativeHeatingControl = false;
bool immergasFix = false;
} options;
@@ -104,6 +106,7 @@ struct Settings {
float turboFactor = 7.5f;
byte minTemp = DEFAULT_HEATING_MIN_TEMP;
byte maxTemp = DEFAULT_HEATING_MAX_TEMP;
uint8_t maxModulation = 100;
} heating;
struct {
@@ -111,6 +114,7 @@ struct Settings {
float target = DEFAULT_DHW_TARGET_TEMP;
byte minTemp = DEFAULT_DHW_MIN_TEMP;
byte maxTemp = DEFAULT_DHW_MAX_TEMP;
uint8_t maxModulation = 100;
} dhw;
struct {
@@ -351,6 +355,17 @@ struct Variables {
uint16_t supply = 0;
} fanSpeed;
struct {
uint16_t burnerStarts = 0;
uint16_t dhwBurnerStarts = 0;
uint16_t heatingPumpStarts = 0;
uint16_t dhwPumpStarts = 0;
uint16_t burnerHours = 0;
uint16_t dhwBurnerHours = 0;
uint16_t heatingPumpHours = 0;
uint16_t dhwPumpHours = 0;
} stats;
struct {
bool active = false;
bool enabled = false;

View File

@@ -16,7 +16,7 @@
#define THERMOSTAT_INDOOR_DEFAULT_TEMP 20
#define THERMOSTAT_INDOOR_MIN_TEMP 5
#define THERMOSTAT_INDOOR_MAX_TEMP 30
#define THERMOSTAT_INDOOR_MAX_TEMP 40
#define DEFAULT_NTC_NOMINAL_RESISTANCE 10000.0f
#define DEFAULT_NTC_NOMINAL_TEMP 25.0f

View File

@@ -42,6 +42,8 @@ const char S_ANTI_STUCK_TIME[] PROGMEM = "antiStuckTime";
const char S_AP[] PROGMEM = "ap";
const char S_APP_VERSION[] PROGMEM = "appVersion";
const char S_AUTH[] PROGMEM = "auth";
const char S_AUTO_DIAG_RESET[] PROGMEM = "autoDiagReset";
const char S_AUTO_FAULT_RESET[] PROGMEM = "autoFaultReset";
const char S_BACKTRACE[] PROGMEM = "backtrace";
const char S_BATTERY[] PROGMEM = "battery";
const char S_BAUDRATE[] PROGMEM = "baudrate";
@@ -102,6 +104,7 @@ const char S_HOSTNAME[] PROGMEM = "hostname";
const char S_HUMIDITY[] PROGMEM = "humidity";
const char S_HYSTERESIS[] PROGMEM = "hysteresis";
const char S_ID[] PROGMEM = "id";
const char S_IGNORE_DIAG_STATE[] PROGMEM = "ignoreDiagState";
const char S_IMMERGAS_FIX[] PROGMEM = "immergasFix";
const char S_INDOOR_TEMP[] PROGMEM = "indoorTemp";
const char S_INDOOR_TEMP_CONTROL[] PROGMEM = "indoorTempControl";
@@ -131,7 +134,6 @@ const char S_MIN_POWER[] PROGMEM = "minPower";
const char S_MIN_TEMP[] PROGMEM = "minTemp";
const char S_MODEL[] PROGMEM = "model";
const char S_MODULATION[] PROGMEM = "modulation";
const char S_MODULATION_SYNC_WITH_HEATING[] PROGMEM = "modulationSyncWithHeating";
const char S_MQTT[] PROGMEM = "mqtt";
const char S_NAME[] PROGMEM = "name";
const char S_NATIVE_HEATING_CONTROL[] PROGMEM = "nativeHeatingControl";
@@ -172,6 +174,7 @@ const char S_SENSORS[] PROGMEM = "sensors";
const char S_SERIAL[] PROGMEM = "serial";
const char S_SERVER[] PROGMEM = "server";
const char S_SETTINGS[] PROGMEM = "settings";
const char S_SET_DATE_AND_TIME[] PROGMEM = "setDateAndTime";
const char S_SHIFT[] PROGMEM = "shift";
const char S_SIGNAL_QUALITY[] PROGMEM = "signalQuality";
const char S_SIZE[] PROGMEM = "size";

View File

@@ -449,7 +449,6 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
opentherm[FPSTR(S_RX_LED_GPIO)] = src.opentherm.rxLedGpio;
opentherm[FPSTR(S_MEMBER_ID)] = src.opentherm.memberId;
opentherm[FPSTR(S_FLAGS)] = src.opentherm.flags;
opentherm[FPSTR(S_MAX_MODULATION)] = src.opentherm.maxModulation;
opentherm[FPSTR(S_MIN_POWER)] = roundf(src.opentherm.minPower, 2);
opentherm[FPSTR(S_MAX_POWER)] = roundf(src.opentherm.maxPower, 2);
@@ -462,9 +461,12 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
otOptions[FPSTR(S_HEATING_TO_CH2)] = src.opentherm.options.heatingToCh2;
otOptions[FPSTR(S_DHW_TO_CH2)] = src.opentherm.options.dhwToCh2;
otOptions[FPSTR(S_DHW_BLOCKING)] = src.opentherm.options.dhwBlocking;
otOptions[FPSTR(S_MODULATION_SYNC_WITH_HEATING)] = src.opentherm.options.modulationSyncWithHeating;
otOptions[FPSTR(S_MAX_TEMP_SYNC_WITH_TARGET_TEMP)] = src.opentherm.options.maxTempSyncWithTargetTemp;
otOptions[FPSTR(S_GET_MIN_MAX_TEMP)] = src.opentherm.options.getMinMaxTemp;
otOptions[FPSTR(S_IGNORE_DIAG_STATE)] = src.opentherm.options.ignoreDiagState;
otOptions[FPSTR(S_AUTO_FAULT_RESET)] = src.opentherm.options.autoFaultReset;
otOptions[FPSTR(S_AUTO_DIAG_RESET)] = src.opentherm.options.autoDiagReset;
otOptions[FPSTR(S_SET_DATE_AND_TIME)] = src.opentherm.options.setDateAndTime;
otOptions[FPSTR(S_NATIVE_HEATING_CONTROL)] = src.opentherm.options.nativeHeatingControl;
otOptions[FPSTR(S_IMMERGAS_FIX)] = src.opentherm.options.immergasFix;
@@ -491,12 +493,14 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
heating[FPSTR(S_TURBO_FACTOR)] = roundf(src.heating.turboFactor, 3);
heating[FPSTR(S_MIN_TEMP)] = src.heating.minTemp;
heating[FPSTR(S_MAX_TEMP)] = src.heating.maxTemp;
heating[FPSTR(S_MAX_MODULATION)] = src.heating.maxModulation;
auto dhw = dst[FPSTR(S_DHW)].to<JsonObject>();
dhw[FPSTR(S_ENABLED)] = src.dhw.enabled;
dhw[FPSTR(S_TARGET)] = roundf(src.dhw.target, 1);
dhw[FPSTR(S_MIN_TEMP)] = src.dhw.minTemp;
dhw[FPSTR(S_MAX_TEMP)] = src.dhw.maxTemp;
dhw[FPSTR(S_MAX_MODULATION)] = src.dhw.maxModulation;
auto equitherm = dst[FPSTR(S_EQUITHERM)].to<JsonObject>();
equitherm[FPSTR(S_ENABLED)] = src.equitherm.enabled;
@@ -812,15 +816,6 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
}
}
if (!src[FPSTR(S_OPENTHERM)][FPSTR(S_MAX_MODULATION)].isNull()) {
unsigned char value = src[FPSTR(S_OPENTHERM)][FPSTR(S_MAX_MODULATION)].as<unsigned char>();
if (value > 0 && value <= 100 && value != dst.opentherm.maxModulation) {
dst.opentherm.maxModulation = value;
changed = true;
}
}
if (!src[FPSTR(S_OPENTHERM)][FPSTR(S_MIN_POWER)].isNull()) {
float value = src[FPSTR(S_OPENTHERM)][FPSTR(S_MIN_POWER)].as<float>();
@@ -929,15 +924,6 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_MODULATION_SYNC_WITH_HEATING)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_MODULATION_SYNC_WITH_HEATING)].as<bool>();
if (value != dst.opentherm.options.modulationSyncWithHeating) {
dst.opentherm.options.modulationSyncWithHeating = value;
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_MAX_TEMP_SYNC_WITH_TARGET_TEMP)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_MAX_TEMP_SYNC_WITH_TARGET_TEMP)].as<bool>();
@@ -956,6 +942,42 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_IGNORE_DIAG_STATE)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_IGNORE_DIAG_STATE)].as<bool>();
if (value != dst.opentherm.options.ignoreDiagState) {
dst.opentherm.options.ignoreDiagState = value;
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_AUTO_FAULT_RESET)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_AUTO_FAULT_RESET)].as<bool>();
if (value != dst.opentherm.options.autoFaultReset) {
dst.opentherm.options.autoFaultReset = value;
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_AUTO_DIAG_RESET)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_AUTO_DIAG_RESET)].as<bool>();
if (value != dst.opentherm.options.autoDiagReset) {
dst.opentherm.options.autoDiagReset = value;
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_SET_DATE_AND_TIME)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_SET_DATE_AND_TIME)].as<bool>();
if (value != dst.opentherm.options.setDateAndTime) {
dst.opentherm.options.setDateAndTime = value;
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_NATIVE_HEATING_CONTROL)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_NATIVE_HEATING_CONTROL)].as<bool>();
@@ -1311,6 +1333,16 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
}
if (!src[FPSTR(S_HEATING)][FPSTR(S_MAX_MODULATION)].isNull()) {
unsigned char value = src[FPSTR(S_HEATING)][FPSTR(S_MAX_MODULATION)].as<unsigned char>();
if (value > 0 && value <= 100 && value != dst.heating.maxModulation) {
dst.heating.maxModulation = value;
changed = true;
}
}
// dhw
if (src[FPSTR(S_DHW)][FPSTR(S_ENABLED)].is<bool>()) {
bool value = src[FPSTR(S_DHW)][FPSTR(S_ENABLED)].as<bool>();
@@ -1344,6 +1376,15 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
changed = true;
}
if (!src[FPSTR(S_DHW)][FPSTR(S_MAX_MODULATION)].isNull()) {
unsigned char value = src[FPSTR(S_DHW)][FPSTR(S_MAX_MODULATION)].as<unsigned char>();
if (value > 0 && value <= 100 && value != dst.dhw.maxModulation) {
dst.dhw.maxModulation = value;
changed = true;
}
}
if (!safe) {
// external pump
@@ -1702,6 +1743,7 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se
case static_cast<uint8_t>(Sensors::Purpose::EXHAUST_TEMP):
case static_cast<uint8_t>(Sensors::Purpose::MODULATION_LEVEL):
case static_cast<uint8_t>(Sensors::Purpose::NUMBER):
case static_cast<uint8_t>(Sensors::Purpose::POWER_FACTOR):
case static_cast<uint8_t>(Sensors::Purpose::POWER):
case static_cast<uint8_t>(Sensors::Purpose::FAN_SPEED):
@@ -1746,6 +1788,15 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se
case static_cast<uint8_t>(Sensors::Type::OT_FAN_SPEED_SETPOINT):
case static_cast<uint8_t>(Sensors::Type::OT_FAN_SPEED_CURRENT):
case static_cast<uint8_t>(Sensors::Type::OT_BURNER_STARTS):
case static_cast<uint8_t>(Sensors::Type::OT_DHW_BURNER_STARTS):
case static_cast<uint8_t>(Sensors::Type::OT_HEATING_PUMP_STARTS):
case static_cast<uint8_t>(Sensors::Type::OT_DHW_PUMP_STARTS):
case static_cast<uint8_t>(Sensors::Type::OT_BURNER_HOURS):
case static_cast<uint8_t>(Sensors::Type::OT_DHW_BURNER_HOURS):
case static_cast<uint8_t>(Sensors::Type::OT_HEATING_PUMP_HOURS):
case static_cast<uint8_t>(Sensors::Type::OT_DHW_PUMP_HOURS):
case static_cast<uint8_t>(Sensors::Type::NTC_10K_TEMP):
case static_cast<uint8_t>(Sensors::Type::DALLAS_TEMP):
case static_cast<uint8_t>(Sensors::Type::BLUETOOTH):