7 Commits

Author SHA1 Message Date
Yurii
a9220d9fa1 refactor: always set temperature to ID 1 2025-12-13 06:25:16 +03:00
Yurii
5a14857f52 refactor: minor changes 2025-12-13 06:21:21 +03:00
Yurii
e487c78921 feat: added sensor type "OT, Cooling hours" 2025-12-13 06:17:15 +03:00
Yurii
6c3b79bda1 fix: OT protocol version in JSON of slave fixed 2025-12-13 04:15:48 +03:00
Yurii
09c50d5df8 refactor: fixed localization after #204 2025-12-12 15:27:42 +03:00
Yurii
348fab39bb refactor: refactoring after #204 2025-12-12 15:21:02 +03:00
inediblePotato
f9cb421893 feat: added OT option alwaysSendIndoorTemp, rename nativeHeatingControl to nativeOTC (#204)
* added option to always send the current and target indoor temp, even if the current heating mode doesnt officially need it. This is a direct fix for WeHeat heatpumps which seem to require it.

* add option to AlwaysSendIndoorTemp and rename NativeHeatingControl to
NativeOTC.

Updated locals and settings page.

* change minor order
2025-12-12 15:08:44 +03:00
15 changed files with 297 additions and 155 deletions

View File

@@ -70,50 +70,89 @@ public:
}
}
bool sendBoilerReset() {
unsigned int data = 1;
data <<= 8;
unsigned long response = this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::RemoteRequest,
data
));
return isValidResponse(response) && isValidResponseId(response, OpenThermMessageID::RemoteRequest);
inline auto sendBoilerReset() {
return this->sendRequestCode(1);
}
bool sendServiceReset() {
unsigned int data = 10;
data <<= 8;
unsigned long response = this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::RemoteRequest,
data
));
return isValidResponse(response) && isValidResponseId(response, OpenThermMessageID::RemoteRequest);
inline auto sendServiceReset() {
return this->sendRequestCode(10);
}
bool sendWaterFilling() {
unsigned int data = 2;
data <<= 8;
inline auto sendWaterFilling() {
return this->sendRequestCode(2);
}
bool sendRequestCode(const uint8_t requestCode) {
unsigned long response = this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::RemoteRequest,
data
static_cast<unsigned int>(requestCode) << 8
));
return isValidResponse(response) && isValidResponseId(response, OpenThermMessageID::RemoteRequest);
if (!isValidResponse(response) || !isValidResponseId(response, OpenThermMessageID::RemoteRequest)) {
return false;
}
const uint8_t responseRequestCode = (response & 0xFFFF) >> 8;
const uint8_t responseCode = response & 0xFF;
if (responseRequestCode != requestCode || responseCode < 128) {
return false;
}
// reset
this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::RemoteRequest,
0u << 8
));
return true;
}
bool getStr(OpenThermMessageID id, char* buffer, uint16_t length = 50) {
if (buffer == nullptr || length == 0) {
return false;
}
unsigned long response;
uint8_t index = 0;
uint8_t maxIndex = 255;
while (index <= maxIndex && index < length) {
response = this->sendRequest(buildRequest(
OpenThermMessageType::READ_DATA,
id,
static_cast<unsigned int>(index) << 8
));
if (!isValidResponse(response) || !isValidResponseId(response, id)) {
break;
}
const uint8_t character = response & 0xFF;
if (character == 0) {
break;
}
if (index == 0) {
maxIndex = (response & 0xFFFF) >> 8;
}
buffer[index++] = static_cast<char>(character);
}
buffer[index] = '\0';
return index > 0;
}
static bool isCh2Active(unsigned long response) {
return response & 0x20;
return response & 0x20;
}
static bool isValidResponseId(unsigned long response, OpenThermMessageID id) {
uint8_t responseId = (response >> 16) & 0xFF;
const uint8_t responseId = (response >> 16) & 0xFF;
return (uint8_t)id == responseId;
return static_cast<uint8_t>(id) == responseId;
}
static uint8_t getResponseMessageTypeId(unsigned long response) {
@@ -124,10 +163,10 @@ public:
uint8_t msgType = getResponseMessageTypeId(response);
switch (msgType) {
case (uint8_t) OpenThermMessageType::READ_ACK:
case (uint8_t) OpenThermMessageType::WRITE_ACK:
case (uint8_t) OpenThermMessageType::DATA_INVALID:
case (uint8_t) OpenThermMessageType::UNKNOWN_DATA_ID:
case static_cast<uint8_t>(OpenThermMessageType::READ_ACK):
case static_cast<uint8_t>(OpenThermMessageType::WRITE_ACK):
case static_cast<uint8_t>(OpenThermMessageType::DATA_INVALID):
case static_cast<uint8_t>(OpenThermMessageType::UNKNOWN_DATA_ID):
return CustomOpenTherm::messageTypeToString(
static_cast<OpenThermMessageType>(msgType)
);

View File

@@ -319,7 +319,7 @@ protected:
emergencyFlags |= 0b00000010;
}
if (settings.opentherm.options.nativeHeatingControl) {
if (settings.opentherm.options.nativeOTC) {
emergencyFlags |= 0b00000100;
}
}

View File

@@ -171,7 +171,7 @@ protected:
vars.master.heating.enabled = this->isReady()
&& settings.heating.enabled
&& vars.cascadeControl.input
&& (!vars.master.heating.blocking || settings.heating.hysteresis.action != HysteresisAction::DISABLE_HEATING)
&& !vars.master.heating.blocking
&& !vars.master.heating.overheat;
// DHW settings
@@ -186,7 +186,9 @@ protected:
|| (settings.opentherm.options.dhwToCh2 && settings.opentherm.options.dhwSupport && settings.dhw.enabled);
if (settings.opentherm.options.heatingToCh2) {
vars.master.ch2.targetTemp = vars.master.heating.setpointTemp;
vars.master.ch2.targetTemp = !settings.opentherm.options.nativeOTC
? vars.master.heating.setpointTemp
: vars.master.heating.targetTemp;
} else if (settings.opentherm.options.dhwToCh2) {
vars.master.ch2.targetTemp = vars.master.dhw.targetTemp;
@@ -218,7 +220,7 @@ protected:
vars.master.heating.enabled,
vars.master.dhw.enabled,
settings.opentherm.options.coolingSupport,
settings.opentherm.options.nativeHeatingControl,
settings.opentherm.options.nativeOTC,
vars.master.ch2.enabled,
summerWinterMode,
dhwBlocking,
@@ -305,6 +307,7 @@ protected:
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);
Sensors::setConnectionStatusByType(Sensors::Type::OT_COOLING_HOURS, false);
this->initialized = false;
this->disconnectedTime = millis();
@@ -677,6 +680,21 @@ protected:
}
}
// Update cooling hours
if (Sensors::getAmountByType(Sensors::Type::OT_COOLING_HOURS, true)) {
if (this->updateCoolingHours()) {
Log.snoticeln(FPSTR(L_OT), F("Received cooling hours: %hu"), vars.slave.stats.coolingHours);
Sensors::setValueByType(
Sensors::Type::OT_COOLING_HOURS, vars.slave.stats.coolingHours,
Sensors::ValueType::PRIMARY, true, true
);
} else {
Log.swarningln(FPSTR(L_OT), F("Failed receive cooling hours"));
}
}
// Auto fault reset
if (settings.opentherm.options.autoFaultReset && vars.slave.fault.active && !vars.actions.resetFault) {
vars.actions.resetFault = true;
@@ -792,7 +810,7 @@ protected:
bool result = this->updateDhwTemp();
if (result) {
float convertedDhwTemp = convertTemp(
const float convertedDhwTemp = convertTemp(
vars.slave.dhw.currentTemp,
settings.opentherm.unitSystem,
settings.system.unitSystem
@@ -816,7 +834,7 @@ protected:
// Update DHW temp 2
if (settings.opentherm.options.dhwSupport && Sensors::getAmountByType(Sensors::Type::OT_DHW_TEMP2, true)) {
if (this->updateDhwTemp2()) {
float convertedDhwTemp2 = convertTemp(
const float convertedDhwTemp2 = convertTemp(
vars.slave.dhw.currentTemp2,
settings.opentherm.unitSystem,
settings.system.unitSystem
@@ -840,7 +858,7 @@ protected:
// Update DHW flow rate
if (settings.opentherm.options.dhwSupport && Sensors::getAmountByType(Sensors::Type::OT_DHW_FLOW_RATE, true)) {
if (this->updateDhwFlowRate()) {
float convertedDhwFlowRate = convertVolume(
const float convertedDhwFlowRate = convertVolume(
vars.slave.dhw.flowRate,
settings.opentherm.unitSystem,
settings.system.unitSystem
@@ -864,7 +882,7 @@ protected:
// Update heating temp
if (Sensors::getAmountByType(Sensors::Type::OT_HEATING_TEMP, true)) {
if (this->updateHeatingTemp()) {
float convertedHeatingTemp = convertTemp(
const float convertedHeatingTemp = convertTemp(
vars.slave.heating.currentTemp,
settings.opentherm.unitSystem,
settings.system.unitSystem
@@ -888,7 +906,7 @@ protected:
// Update heating return temp
if (Sensors::getAmountByType(Sensors::Type::OT_HEATING_RETURN_TEMP, true)) {
if (this->updateHeatingReturnTemp()) {
float convertedHeatingReturnTemp = convertTemp(
const float convertedHeatingReturnTemp = convertTemp(
vars.slave.heating.returnTemp,
settings.opentherm.unitSystem,
settings.system.unitSystem
@@ -911,9 +929,9 @@ protected:
// Update CH2 temp
if (Sensors::getAmountByType(Sensors::Type::OT_CH2_TEMP, true)) {
if (vars.master.ch2.enabled && !settings.opentherm.options.nativeHeatingControl) {
if (vars.master.ch2.enabled && !settings.opentherm.options.nativeOTC) {
if (this->updateCh2Temp()) {
float convertedCh2Temp = convertTemp(
const float convertedCh2Temp = convertTemp(
vars.slave.ch2.currentTemp,
settings.opentherm.unitSystem,
settings.system.unitSystem
@@ -938,7 +956,7 @@ protected:
// Update exhaust temp
if (Sensors::getAmountByType(Sensors::Type::OT_EXHAUST_TEMP, true)) {
if (this->updateExhaustTemp()) {
float convertedExhaustTemp = convertTemp(
const float convertedExhaustTemp = convertTemp(
vars.slave.exhaust.temp,
settings.opentherm.unitSystem,
settings.system.unitSystem
@@ -962,7 +980,7 @@ protected:
// Update heat exchanger temp
if (Sensors::getAmountByType(Sensors::Type::OT_HEAT_EXCHANGER_TEMP, true)) {
if (this->updateHeatExchangerTemp()) {
float convertedHeatExchTemp = convertTemp(
const float convertedHeatExchTemp = convertTemp(
vars.slave.heatExchangerTemp,
settings.opentherm.unitSystem,
settings.system.unitSystem
@@ -986,7 +1004,7 @@ protected:
// Update outdoor temp
if (Sensors::getAmountByType(Sensors::Type::OT_OUTDOOR_TEMP, true)) {
if (this->updateOutdoorTemp()) {
float convertedOutdoorTemp = convertTemp(
const float convertedOutdoorTemp = convertTemp(
vars.slave.heating.outdoorTemp,
settings.opentherm.unitSystem,
settings.system.unitSystem
@@ -1010,7 +1028,7 @@ protected:
// Update solar storage temp
if (Sensors::getAmountByType(Sensors::Type::OT_SOLAR_STORAGE_TEMP, true)) {
if (this->updateSolarStorageTemp()) {
float convertedSolarStorageTemp = convertTemp(
const float convertedSolarStorageTemp = convertTemp(
vars.slave.solar.storage,
settings.opentherm.unitSystem,
settings.system.unitSystem
@@ -1034,7 +1052,7 @@ protected:
// Update solar collector temp
if (Sensors::getAmountByType(Sensors::Type::OT_SOLAR_COLLECTOR_TEMP, true)) {
if (this->updateSolarCollectorTemp()) {
float convertedSolarCollectorTemp = convertTemp(
const float convertedSolarCollectorTemp = convertTemp(
vars.slave.solar.collector,
settings.opentherm.unitSystem,
settings.system.unitSystem
@@ -1080,7 +1098,7 @@ protected:
// Update pressure
if (Sensors::getAmountByType(Sensors::Type::OT_PRESSURE, true)) {
if (this->updatePressure()) {
float convertedPressure = convertPressure(
const float convertedPressure = convertPressure(
vars.slave.pressure,
settings.opentherm.unitSystem,
settings.system.unitSystem
@@ -1186,9 +1204,12 @@ protected:
// Update DHW temp
if (vars.master.dhw.enabled) {
// Target dhw temp
const float& targetTemp = vars.master.dhw.targetTemp;
// Converted target dhw temp
float convertedTemp = convertTemp(
vars.master.dhw.targetTemp,
const float convertedTemp = convertTemp(
targetTemp,
settings.system.unitSystem,
settings.opentherm.unitSystem
);
@@ -1200,7 +1221,7 @@ protected:
Log.sinfoln(
FPSTR(L_OT_DHW), F("Set temp: %.2f (converted: %.2f, response: %.2f)"),
vars.master.dhw.targetTemp, convertedTemp, vars.slave.dhw.targetTemp
targetTemp, convertedTemp, vars.slave.dhw.targetTemp
);
} else {
@@ -1209,16 +1230,19 @@ protected:
}
}
// Native heating control
if (settings.opentherm.options.nativeHeatingControl) {
// Send indoor temp if AlwaysSendIndoorTemp option is enabled.
if (settings.opentherm.options.nativeOTC || settings.opentherm.options.alwaysSendIndoorTemp) {
// Current indoor temp
const float& indoorTemp = vars.master.heating.indoorTemp;
// Converted current indoor temp
float convertedTemp = convertTemp(vars.master.heating.indoorTemp, settings.system.unitSystem, settings.opentherm.unitSystem);
const float convertedTemp = convertTemp(indoorTemp, settings.system.unitSystem, settings.opentherm.unitSystem);
// Set current indoor temp
if (this->setRoomTemp(convertedTemp)) {
Log.sinfoln(
FPSTR(L_OT_HEATING), F("Set current indoor temp: %.2f (converted: %.2f, response: %.2f)"),
vars.master.heating.indoorTemp, convertedTemp, vars.slave.heating.indoorTemp
indoorTemp, convertedTemp, vars.slave.heating.indoorTemp
);
} else {
@@ -1230,17 +1254,26 @@ protected:
if (this->setRoomTempCh2(convertedTemp)) {
Log.sinfoln(
FPSTR(L_OT_HEATING), F("Set current CH2 indoor temp: %.2f (converted: %.2f, response: %.2f)"),
vars.master.heating.indoorTemp, convertedTemp, vars.slave.ch2.indoorTemp
indoorTemp, convertedTemp, vars.slave.ch2.indoorTemp
);
} else {
Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set current CH2 indoor temp"));
}
}
}
// NativeOTC
if (settings.opentherm.options.nativeOTC) {
// Target indoor temp
const float& targetTemp = vars.master.heating.targetTemp;
// Converted target indoor temp
convertedTemp = convertTemp(vars.master.heating.targetTemp, settings.system.unitSystem, settings.opentherm.unitSystem);
const float convertedTemp = convertTemp(
targetTemp,
settings.system.unitSystem,
settings.opentherm.unitSystem
);
// Set target indoor temp
if (this->needSetHeatingTemp(convertedTemp)) {
@@ -1249,7 +1282,7 @@ protected:
Log.sinfoln(
FPSTR(L_OT_HEATING), F("Set target indoor temp: %.2f (converted: %.2f, response: %.2f)"),
vars.master.heating.targetTemp, convertedTemp, vars.slave.heating.targetTemp
targetTemp, convertedTemp, vars.slave.heating.targetTemp
);
} else {
@@ -1264,7 +1297,7 @@ protected:
Log.sinfoln(
FPSTR(L_OT_HEATING), F("Set target CH2 indoor temp: %.2f (converted: %.2f, response: %.2f)"),
vars.master.heating.targetTemp, convertedTemp, vars.slave.ch2.targetTemp
targetTemp, convertedTemp, vars.slave.ch2.targetTemp
);
} else {
@@ -1273,10 +1306,22 @@ protected:
}
}
// Normal heating control
if (!settings.opentherm.options.nativeHeatingControl && vars.master.heating.enabled) {
// Set heating temp
{
// Target heating temp
float targetTemp = 0.0f;
if (vars.master.heating.enabled) {
targetTemp = !settings.opentherm.options.nativeOTC
? vars.master.heating.setpointTemp
: vars.master.heating.targetTemp;
}
// Converted target heating temp
float convertedTemp = convertTemp(vars.master.heating.setpointTemp, settings.system.unitSystem, settings.opentherm.unitSystem);
const float convertedTemp = convertTemp(
targetTemp,
settings.system.unitSystem,
settings.opentherm.unitSystem
);
if (this->needSetHeatingTemp(convertedTemp)) {
// Set max heating temp
@@ -1284,13 +1329,13 @@ protected:
if (this->setMaxHeatingTemp(convertedTemp)) {
Log.sinfoln(
FPSTR(L_OT_HEATING), F("Set max heating temp: %.2f (converted: %.2f)"),
vars.master.heating.setpointTemp, convertedTemp
targetTemp, convertedTemp
);
} else {
Log.swarningln(
FPSTR(L_OT_HEATING), F("Failed set max heating temp: %.2f (converted: %.2f)"),
vars.master.heating.setpointTemp, convertedTemp
targetTemp, convertedTemp
);
}
}
@@ -1301,7 +1346,7 @@ protected:
Log.sinfoln(
FPSTR(L_OT_HEATING), F("Set target temp: %.2f (converted: %.2f, response: %.2f)"),
vars.master.heating.setpointTemp, convertedTemp, vars.slave.heating.targetTemp
targetTemp, convertedTemp, vars.slave.heating.targetTemp
);
} else {
@@ -1311,27 +1356,30 @@ protected:
}
// Set CH2 temp
if (!settings.opentherm.options.nativeHeatingControl && vars.master.ch2.enabled) {
if (settings.opentherm.options.heatingToCh2 || settings.opentherm.options.dhwToCh2) {
// Converted target CH2 temp
float convertedTemp = convertTemp(
vars.master.ch2.targetTemp,
settings.system.unitSystem,
settings.opentherm.unitSystem
);
if (settings.opentherm.options.heatingToCh2 || settings.opentherm.options.dhwToCh2) {
// Target CH2 heating temp
const float targetTemp = vars.master.ch2.enabled
? vars.master.ch2.targetTemp
: 0.0f;
if (this->needSetCh2Temp(convertedTemp)) {
if (this->setCh2Temp(convertedTemp)) {
this->ch2SetTempTime = millis();
// Converted target CH2 temp
const float convertedTemp = convertTemp(
targetTemp,
settings.system.unitSystem,
settings.opentherm.unitSystem
);
Log.sinfoln(
FPSTR(L_OT_CH2), F("Set temp: %.2f (converted: %.2f, response: %.2f)"),
vars.master.ch2.targetTemp, convertedTemp, vars.slave.ch2.targetTemp
);
if (this->needSetCh2Temp(convertedTemp)) {
if (this->setCh2Temp(convertedTemp)) {
this->ch2SetTempTime = millis();
} else {
Log.swarningln(FPSTR(L_OT_CH2), F("Failed set temp"));
}
Log.sinfoln(
FPSTR(L_OT_CH2), F("Set temp: %.2f (converted: %.2f, response: %.2f)"),
targetTemp, convertedTemp, vars.slave.ch2.targetTemp
);
} else {
Log.swarningln(FPSTR(L_OT_CH2), F("Failed set temp"));
}
}
}
@@ -1339,7 +1387,7 @@ protected:
// Heating overheat control
if (settings.heating.overheatProtection.highTemp > 0 && settings.heating.overheatProtection.lowTemp > 0) {
float highTemp = convertTemp(
const float highTemp = convertTemp(
max({
vars.slave.heating.currentTemp,
vars.slave.heating.returnTemp,
@@ -1376,7 +1424,7 @@ protected:
// DHW overheat control
if (settings.dhw.overheatProtection.highTemp > 0 && settings.dhw.overheatProtection.lowTemp > 0) {
float highTemp = convertTemp(
const float highTemp = convertTemp(
max({
vars.slave.heating.currentTemp,
vars.slave.heating.returnTemp,
@@ -1470,6 +1518,19 @@ protected:
} else {
Log.swarningln(FPSTR(L_OT), F("Failed set master config"));
}
/*char buf[100];
if (this->instance->getStr(OpenThermMessageID::Brand, buf, sizeof(buf) - 1)) {
Log.snoticeln(FPSTR(L_OT), F("Slave brand: %s"), buf);
}
if (this->instance->getStr(OpenThermMessageID::BrandVersion, buf, sizeof(buf) - 1)) {
Log.snoticeln(FPSTR(L_OT), F("Slave brand version: %s"), buf);
}
if (this->instance->getStr(OpenThermMessageID::BrandSerialNumber, buf, sizeof(buf) - 1)) {
Log.snoticeln(FPSTR(L_OT), F("Slave brand s/n: %s"), buf);
}*/
}
bool isReady() {
@@ -1647,7 +1708,7 @@ protected:
}
bool setRoomTemp(float temperature) {
bool setRoomTemp(const float temperature) {
const unsigned int request = CustomOpenTherm::temperatureToData(temperature);
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermMessageType::WRITE_DATA,
@@ -1667,7 +1728,7 @@ protected:
return CustomOpenTherm::getUInt(response) == request;
}
bool setRoomTempCh2(float temperature) {
bool setRoomTempCh2(const float temperature) {
const unsigned int request = CustomOpenTherm::temperatureToData(temperature);
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermMessageType::WRITE_DATA,
@@ -2169,6 +2230,25 @@ protected:
return true;
}
bool updateCoolingHours() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::CoolingOperationHours,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
} else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::CoolingOperationHours)) {
return false;
}
vars.slave.stats.coolingHours = CustomOpenTherm::getUInt(response);
return true;
}
bool updateModulationLevel() {
const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
@@ -2183,7 +2263,7 @@ protected:
return false;
}
float value = CustomOpenTherm::getFloat(response);
const float value = CustomOpenTherm::getFloat(response);
if (value < 0) {
return false;
}
@@ -2207,7 +2287,7 @@ protected:
return false;
}
float value = CustomOpenTherm::getFloat(response);
const float value = CustomOpenTherm::getFloat(response);
if (value <= 0) {
return false;
}
@@ -2231,7 +2311,7 @@ protected:
return false;
}
float value = CustomOpenTherm::getFloat(response);
const float value = CustomOpenTherm::getFloat(response);
if (value <= 0) {
return false;
}
@@ -2285,7 +2365,7 @@ protected:
return false;
}
float value = CustomOpenTherm::getFloat(response);
const float value = CustomOpenTherm::getFloat(response);
if (value <= 0) {
return false;
}
@@ -2349,7 +2429,7 @@ protected:
return false;
}
float value = (float) CustomOpenTherm::getInt(response);
const float value = (float) CustomOpenTherm::getInt(response);
if (!isValidTemp(value, settings.opentherm.unitSystem, -40, 500)) {
return false;
}
@@ -2373,7 +2453,7 @@ protected:
return false;
}
float value = (float) CustomOpenTherm::getInt(response);
const float value = (float) CustomOpenTherm::getInt(response);
if (value <= 0) {
return false;
}
@@ -2474,7 +2554,7 @@ protected:
return false;
}
float value = CustomOpenTherm::getFloat(response);
const float value = CustomOpenTherm::getFloat(response);
if (value < 0) {
return false;
}

View File

@@ -37,7 +37,7 @@ protected:
this->indoorSensorsConnected = Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::INDOOR_TEMP);
//this->outdoorSensorsConnected = Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::OUTDOOR_TEMP);
if (settings.equitherm.enabled || settings.pid.enabled || settings.opentherm.options.nativeHeatingControl) {
if (settings.equitherm.enabled || settings.pid.enabled || settings.opentherm.options.nativeOTC) {
vars.master.heating.indoorTempControl = true;
vars.master.heating.minTemp = THERMOSTAT_INDOOR_MIN_TEMP;
vars.master.heating.maxTemp = THERMOSTAT_INDOOR_MAX_TEMP;
@@ -57,23 +57,12 @@ protected:
this->turbo();
this->hysteresis();
if (vars.master.heating.blocking && settings.heating.hysteresis.action == HysteresisAction::SET_ZERO_TARGET) {
vars.master.heating.targetTemp = 0.0f;
vars.master.heating.setpointTemp = 0.0f;
// tick if PID enabled
if (settings.pid.enabled) {
this->getHeatingSetpointTemp();
}
} else {
vars.master.heating.targetTemp = settings.heating.target;
vars.master.heating.setpointTemp = roundf(constrain(
this->getHeatingSetpointTemp(),
this->getHeatingMinSetpointTemp(),
this->getHeatingMaxSetpointTemp()
), 0);
}
vars.master.heating.targetTemp = settings.heating.target;
vars.master.heating.setpointTemp = roundf(constrain(
this->getHeatingSetpointTemp(),
this->getHeatingMinSetpointTemp(),
this->getHeatingMaxSetpointTemp()
), 0);
Sensors::setValueByType(
Sensors::Type::HEATING_SETPOINT_TEMP, vars.master.heating.setpointTemp,
@@ -102,7 +91,7 @@ protected:
void hysteresis() {
bool useHyst = false;
if (settings.heating.hysteresis.enabled && this->indoorSensorsConnected) {
useHyst = settings.equitherm.enabled || settings.pid.enabled || settings.opentherm.options.nativeHeatingControl;
useHyst = settings.equitherm.enabled || settings.pid.enabled || settings.opentherm.options.nativeOTC;
}
if (useHyst) {
@@ -119,13 +108,13 @@ protected:
}
inline float getHeatingMinSetpointTemp() {
return settings.opentherm.options.nativeHeatingControl
return settings.opentherm.options.nativeOTC
? vars.master.heating.minTemp
: settings.heating.minTemp;
}
inline float getHeatingMaxSetpointTemp() {
return settings.opentherm.options.nativeHeatingControl
return settings.opentherm.options.nativeOTC
? vars.master.heating.maxTemp
: settings.heating.maxTemp;
}
@@ -146,7 +135,7 @@ protected:
if (vars.emergency.state) {
return settings.emergency.target;
} else if (settings.opentherm.options.nativeHeatingControl) {
} else if (settings.opentherm.options.nativeOTC) {
return settings.heating.target;
} else if (!settings.equitherm.enabled && !settings.pid.enabled) {

View File

@@ -34,6 +34,7 @@ public:
OT_DHW_BURNER_HOURS = 24,
OT_HEATING_PUMP_HOURS = 25,
OT_DHW_PUMP_HOURS = 26,
OT_COOLING_HOURS = 27,
NTC_10K_TEMP = 50,
DALLAS_TEMP = 51,

View File

@@ -78,7 +78,8 @@ struct Settings {
bool autoFaultReset = false;
bool autoDiagReset = false;
bool setDateAndTime = false;
bool nativeHeatingControl = false;
bool alwaysSendIndoorTemp = true;
bool nativeOTC = false;
bool immergasFix = false;
} options;
} opentherm;
@@ -388,6 +389,7 @@ struct Variables {
uint16_t dhwBurnerStarts = 0;
uint16_t heatingPumpStarts = 0;
uint16_t dhwPumpStarts = 0;
uint16_t coolingHours = 0;
uint16_t burnerHours = 0;
uint16_t dhwBurnerHours = 0;
uint16_t heatingPumpHours = 0;

View File

@@ -110,6 +110,7 @@ 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_ALWAYS_SEND_INDOOR_TEMP[] PROGMEM = "alwaysSendIndoorTemp";
const char S_INDOOR_TEMP[] PROGMEM = "indoorTemp";
const char S_INDOOR_TEMP_CONTROL[] PROGMEM = "indoorTempControl";
const char S_IN_GPIO[] PROGMEM = "inGpio";
@@ -141,7 +142,7 @@ const char S_MODEL[] PROGMEM = "model";
const char S_MODULATION[] PROGMEM = "modulation";
const char S_MQTT[] PROGMEM = "mqtt";
const char S_NAME[] PROGMEM = "name";
const char S_NATIVE_HEATING_CONTROL[] PROGMEM = "nativeHeatingControl";
const char S_NATIVE_OTC[] PROGMEM = "nativeOTC";
const char S_NETWORK[] PROGMEM = "network";
const char S_NTP[] PROGMEM = "ntp";
const char S_OFFSET[] PROGMEM = "offset";

View File

@@ -468,8 +468,10 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
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_ALWAYS_SEND_INDOOR_TEMP)] = src.opentherm.options.alwaysSendIndoorTemp;
otOptions[FPSTR(S_NATIVE_OTC)] = src.opentherm.options.nativeOTC;
otOptions[FPSTR(S_IMMERGAS_FIX)] = src.opentherm.options.immergasFix;
auto mqtt = dst[FPSTR(S_MQTT)].to<JsonObject>();
mqtt[FPSTR(S_ENABLED)] = src.mqtt.enabled;
@@ -1003,11 +1005,20 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
}
}
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>();
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_ALWAYS_SEND_INDOOR_TEMP)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_ALWAYS_SEND_INDOOR_TEMP)].as<bool>();
if (value != dst.opentherm.options.nativeHeatingControl) {
dst.opentherm.options.nativeHeatingControl = value;
if (value != dst.opentherm.options.alwaysSendIndoorTemp) {
dst.opentherm.options.alwaysSendIndoorTemp = value;
changed = true;
}
}
if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_NATIVE_OTC)].is<bool>()) {
bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_NATIVE_OTC)].as<bool>();
if (value != dst.opentherm.options.nativeOTC) {
dst.opentherm.options.nativeOTC = value;
if (value) {
dst.equitherm.enabled = false;
@@ -1027,7 +1038,6 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
}
}
// mqtt
if (src[FPSTR(S_MQTT)][FPSTR(S_ENABLED)].is<bool>()) {
bool value = src[FPSTR(S_MQTT)][FPSTR(S_ENABLED)].as<bool>();
@@ -1118,7 +1128,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
if (src[FPSTR(S_EQUITHERM)][FPSTR(S_ENABLED)].is<bool>()) {
bool value = src[FPSTR(S_EQUITHERM)][FPSTR(S_ENABLED)].as<bool>();
if (!dst.opentherm.options.nativeHeatingControl) {
if (!dst.opentherm.options.nativeOTC) {
if (value != dst.equitherm.enabled) {
dst.equitherm.enabled = value;
changed = true;
@@ -1171,7 +1181,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
if (src[FPSTR(S_PID)][FPSTR(S_ENABLED)].is<bool>()) {
bool value = src[FPSTR(S_PID)][FPSTR(S_ENABLED)].as<bool>();
if (!dst.opentherm.options.nativeHeatingControl) {
if (!dst.opentherm.options.nativeOTC) {
if (value != dst.pid.enabled) {
dst.pid.enabled = value;
changed = true;
@@ -1704,7 +1714,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
// force check emergency target
{
float value = !src[FPSTR(S_EMERGENCY)][FPSTR(S_TARGET)].isNull() ? src[FPSTR(S_EMERGENCY)][FPSTR(S_TARGET)].as<float>() : dst.emergency.target;
bool noRegulators = !dst.opentherm.options.nativeHeatingControl;
bool noRegulators = !dst.opentherm.options.nativeOTC;
bool valid = isValidTemp(
value,
dst.system.unitSystem,
@@ -1729,7 +1739,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
// force check heating target
{
bool indoorTempControl = dst.equitherm.enabled || dst.pid.enabled || dst.opentherm.options.nativeHeatingControl;
bool indoorTempControl = dst.equitherm.enabled || dst.pid.enabled || dst.opentherm.options.nativeOTC;
float minTemp = indoorTempControl ? THERMOSTAT_INDOOR_MIN_TEMP : dst.heating.minTemp;
float maxTemp = indoorTempControl ? THERMOSTAT_INDOOR_MAX_TEMP : dst.heating.maxTemp;
@@ -1922,6 +1932,7 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se
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::OT_COOLING_HOURS):
case static_cast<uint8_t>(Sensors::Type::NTC_10K_TEMP):
case static_cast<uint8_t>(Sensors::Type::DALLAS_TEMP):
@@ -2121,7 +2132,7 @@ void varsToJson(const Variables& src, JsonVariant dst) {
slave[FPSTR(S_FLAGS)] = src.slave.flags;
slave[FPSTR(S_TYPE)] = src.slave.type;
slave[FPSTR(S_APP_VERSION)] = src.slave.appVersion;
slave[FPSTR(S_PROTOCOL_VERSION)] = src.slave.appVersion;
slave[FPSTR(S_PROTOCOL_VERSION)] = src.slave.protocolVersion;
slave[FPSTR(S_CONNECTED)] = src.slave.connected;
slave[FPSTR(S_FLAME)] = src.slave.flame;

View File

@@ -243,6 +243,7 @@
"otDhwBurnerHours": "OpenTherm, number of burner operating hours (DHW)",
"otHeatingPumpHours": "OpenTherm, number of pump operating hours (heating)",
"otDhwPumpHours": "OpenTherm, number of pump operating hours (DHW)",
"otCoolingHours": "OpenTherm, number of cooling hours",
"ntcTemp": "NTC 传感器",
"dallasTemp": "DALLAS 传感器",
@@ -457,12 +458,13 @@
"autoFaultReset": "自动报警复位 <small>(不推荐!)</small>",
"autoDiagReset": "自动诊断复位 <small>(不推荐!)</small>",
"setDateAndTime": "同步设置锅炉日期与时间",
"immergasFix": "针对Immergas锅炉的兼容性修复"
"immergasFix": "针对Immergas锅炉的兼容性修复",
"alwaysSendIndoorTemp": "向锅炉发送当前室内温度"
},
"nativeHeating": {
"title": "原生锅炉供暖控制",
"note": "<u>注意:</u> 仅适用于锅炉需接收目标室温并自主调节载热介质温度的场景与固件中的PID及Equithermq气候补偿功能不兼容。"
"nativeOTC": {
"title": "原生热载体温度计算模式",
"note": "仅在锅炉处于 OTC 模式时<u>才</u>工作:需要并接受目标室内温度,并基于内置曲线模式自行调节热载体温度。与 PID 和 Equitherm 不兼容。"
}
},

View File

@@ -243,6 +243,7 @@
"otDhwBurnerHours": "OpenTherm, number of burner operating hours (DHW)",
"otHeatingPumpHours": "OpenTherm, number of pump operating hours (heating)",
"otDhwPumpHours": "OpenTherm, number of pump operating hours (DHW)",
"otCoolingHours": "OpenTherm, number of cooling hours",
"ntcTemp": "NTC sensor",
"dallasTemp": "DALLAS sensor",
@@ -457,12 +458,13 @@
"autoFaultReset": "Auto fault reset <small>(not recommended!)</small>",
"autoDiagReset": "Auto diag reset <small>(not recommended!)</small>",
"setDateAndTime": "Set date & time on boiler",
"immergasFix": "Fix for Immergas boilers"
"immergasFix": "Fix for Immergas boilers",
"alwaysSendIndoorTemp": "Send current indoor temp to boiler"
},
"nativeHeating": {
"title": "Native heating control (boiler)",
"note": "Works <u>ONLY</u> if the boiler requires the desired room temperature and regulates the temperature of the coolant itself. Not compatible with PID and Equitherm regulators in firmware."
"nativeOTC": {
"title": "Native OTC mode",
"note": "Works <u>ONLY</u> if the boiler is in OTC mode: requires and accepts the target indoor temperature and self-regulates the heat carrier temperature based on the built-in curves mode. Incompatible with PID and Equitherm."
}
},

View File

@@ -243,6 +243,7 @@
"otDhwBurnerHours": "OpenTherm, numero di ore di funzionamento del bruciatore (ACS)",
"otHeatingPumpHours": "OpenTherm, numero di ore di funzionamento della pompa (riscaldamento)",
"otDhwPumpHours": "OpenTherm, numero di ore di funzionamento della pompa (ACS)",
"otCoolingHours": "OpenTherm, numero di ore di funzionamento della cooling",
"ntcTemp": "Sensore NTC",
"dallasTemp": "Sensore DALLAS",
@@ -457,12 +458,13 @@
"autoFaultReset": "Ripristino automatico degli errori <small>(sconsigliato!)</small>",
"autoDiagReset": "Ripristino diagnostico automatica <small>(sconsigliato!)</small>",
"setDateAndTime": "Imposta data e ora sulla caldaia",
"immergasFix": "Fix per caldiaie Immergas"
"immergasFix": "Fix per caldiaie Immergas",
"alwaysSendIndoorTemp": "Invia la temp attuale interna alla caldaia"
},
"nativeHeating": {
"title": "Controllo del riscaldamento nativo (caldaia)",
"note": "Lavora <u>SOLO</u> se la caldaia richiede la temperatura ambiente desiderata e regola autonomamente la temperatura del fluido. Non compatiblile con regolazioni PID e Equitherm del sistema."
"nativeOTC": {
"title": "Modalità nativa di calcolo della temperatura del vettore termico",
"note": "Funziona <u>SOLO</u> se la caldaia è in modalità OTC: richiede e accetta la temperatura interna target e regola autonomamente la temperatura del vettore termico basata sulla modalità curve integrata. Incompatibile con PID e Equitherm."
}
},

View File

@@ -222,6 +222,8 @@
"otDhwBurnerHours": "OpenTherm, aantal branderuren (warm water)",
"otHeatingPumpHours": "OpenTherm, aantal pompuren (verwarming)",
"otDhwPumpHours": "OpenTherm, aantal pompuren (warm water)",
"otCoolingHours": "OpenTherm, aantal cooling",
"ntcTemp": "NTC-sensor",
"dallasTemp": "DALLAS-sensor",
"bluetooth": "BLE-sensor",
@@ -422,11 +424,13 @@
"autoFaultReset": "Automatische storingsreset <small>(niet aanbevolen!)</small>",
"autoDiagReset": "Automatische diagnosereset <small>(niet aanbevolen!)</small>",
"setDateAndTime": "Stel datum & tijd in op ketel",
"immergasFix": "Fix voor Immergas-ketels"
"immergasFix": "Fix voor Immergas-ketels",
"alwaysSendIndoorTemp": "Stuur huidige binnentemp naar ketel"
},
"nativeHeating": {
"title": "Natuurlijke verwarmingsregeling (ketel)",
"note": "Werkt <u>ALLEEN</u> als de ketel de gewenste kamertemperatuur vereist en zelf de temperatuur van de warmtedrager regelt. Niet compatibel met PID- en Equitherm-regelaars in de firmware."
"nativeOTC": {
"title": "Native warmtedrager temperatuur berekeningsmodus",
"note": "Werkt <u>ALLEEN</u> als de ketel in OTC-modus is: vereist en accepteert de doel binnentemperatuur en regelt zelf de warmtedrager temperatuur op basis van de ingebouwde curves modus. Incompatibel met PID en Equitherm."
}
},
"mqtt": {

View File

@@ -243,6 +243,7 @@
"otDhwBurnerHours": "OpenTherm, кол-во часов работы горелки (ГВС)",
"otHeatingPumpHours": "OpenTherm, кол-во часов работы насоса (отопление)",
"otDhwPumpHours": "OpenTherm, кол-во часов работы насоса (ГВС)",
"otCoolingHours": "OpenTherm, кол-во часов работы охлаждения",
"ntcTemp": "NTC датчик",
"dallasTemp": "DALLAS датчик",
@@ -457,12 +458,13 @@
"autoFaultReset": "Автоматический сброс ошибок <small>(не рекомендуется!)</small>",
"autoDiagReset": "Автоматический сброс диагностики <small>(не рекомендуется!)</small>",
"setDateAndTime": "Устанавливать время и дату на котле",
"immergasFix": "Фикс для котлов Immergas"
"immergasFix": "Фикс для котлов Immergas",
"alwaysSendIndoorTemp": "Передавать текущую темп. в помещении котлу"
},
"nativeHeating": {
"title": "Передать управление отоплением котлу",
"note": "Работает <u>ТОЛЬКО</u> если котел требует и принимает целевую температуру в помещении и сам регулирует температуру теплоносителя на основе встроенного режима кривых. Несовместимо с ПИД и ПЗА."
"nativeOTC": {
"title": "Нативный режим OTC (расчёт температуры теплоносителя)",
"note": "Работает <u>ТОЛЬКО</u> если котел в режиме OTC: требует и принимает целевую температуру в помещении и сам регулирует температуру теплоносителя на основе встроенного режима кривых. Несовместимо с ПИД и ПЗА."
}
},

View File

@@ -113,6 +113,7 @@
<option value="24" data-i18n>sensors.types.otDhwBurnerHours</option>
<option value="25" data-i18n>sensors.types.otHeatingPumpHours</option>
<option value="26" data-i18n>sensors.types.otDhwPumpHours</option>
<option value="27" data-i18n>sensors.types.otCoolingHours</option>
<option value="50" data-i18n>sensors.types.ntcTemp</option>
<option value="51" data-i18n>sensors.types.dallasTemp</option>

View File

@@ -692,12 +692,17 @@
<span data-i18n>settings.ot.options.immergasFix</span>
</label>
<label>
<input type="checkbox" name="opentherm[options][alwaysSendIndoorTemp]" value="true">
<span data-i18n>settings.ot.options.alwaysSendIndoorTemp</span>
</label>
<hr />
<label>
<input type="checkbox" name="opentherm[options][nativeHeatingControl]" value="true">
<span data-i18n>settings.ot.nativeHeating.title</span><br />
<small data-i18n>settings.ot.nativeHeating.note</small>
<input type="checkbox" name="opentherm[options][nativeOTC]" value="true">
<span data-i18n>settings.ot.nativeOTC.title</span><br />
<small data-i18n>settings.ot.nativeOTC.note</small>
</label>
</fieldset>
</div>
@@ -1117,8 +1122,9 @@
setCheckboxValue("[name='opentherm[options][autoFaultReset]']", data.opentherm.options.autoFaultReset);
setCheckboxValue("[name='opentherm[options][autoDiagReset]']", data.opentherm.options.autoDiagReset);
setCheckboxValue("[name='opentherm[options][setDateAndTime]']", data.opentherm.options.setDateAndTime);
setCheckboxValue("[name='opentherm[options][nativeHeatingControl]']", data.opentherm.options.nativeHeatingControl);
setCheckboxValue("[name='opentherm[options][nativeOTC]']", data.opentherm.options.nativeOTC);
setCheckboxValue("[name='opentherm[options][immergasFix]']", data.opentherm.options.immergasFix);
setCheckboxValue("[name='opentherm[options][alwaysSendIndoorTemp]']", data.opentherm.options.alwaysSendIndoorTemp);
setBusy('#ot-settings-busy', '#ot-settings', false);
// MQTT
@@ -1206,7 +1212,7 @@
setBusy('#dhw-settings-busy', '#dhw-settings', false);
// Emergency mode
if (data.opentherm.options.nativeHeatingControl) {
if (data.opentherm.options.nativeOTC) {
setInputValue("[name='emergency[target]']", data.emergency.target, {
"min": data.system.unitSystem == 0 ? 5 : 41,
"max": data.system.unitSystem == 0 ? 40 : 104