Files
OTGateway/src/OpenThermTask.h

1124 lines
36 KiB
C++

#include <CustomOpenTherm.h>
extern FileData fsSettings;
class OpenThermTask : public Task {
public:
OpenThermTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {}
~OpenThermTask() {
delete this->instance;
}
protected:
const unsigned short readyTime = 60000;
const unsigned short dhwSetTempInterval = 60000;
const unsigned short heatingSetTempInterval = 60000;
const unsigned int initializingInterval = 3600000;
CustomOpenTherm* instance = nullptr;
unsigned long instanceCreatedTime = 0;
byte instanceInGpio = 0;
byte instanceOutGpio = 0;
bool isInitialized = false;
unsigned long initializedTime = 0;
unsigned int initializedMemberIdCode = 0;
bool pump = true;
unsigned long lastSuccessResponse = 0;
unsigned long prevUpdateNonEssentialVars = 0;
unsigned long dhwSetTempTime = 0;
unsigned long heatingSetTempTime = 0;
byte configuredRxLedGpio = GPIO_IS_NOT_CONFIGURED;
byte configuredFaultStateGpio = GPIO_IS_NOT_CONFIGURED;
bool faultState = false;
#if defined(ARDUINO_ARCH_ESP32)
const char* getTaskName() override {
return "OpenTherm";
}
BaseType_t getTaskCore() override {
return 1;
}
int getTaskPriority() override {
return 5;
}
#endif
void setup() {
if (settings.system.unitSystem != UnitSystem::METRIC) {
vars.parameters.heatingMinTemp = convertTemp(vars.parameters.heatingMinTemp, UnitSystem::METRIC, settings.system.unitSystem);
vars.parameters.heatingMaxTemp = convertTemp(vars.parameters.heatingMaxTemp, UnitSystem::METRIC, settings.system.unitSystem);
vars.parameters.dhwMinTemp = convertTemp(vars.parameters.dhwMinTemp, UnitSystem::METRIC, settings.system.unitSystem);
vars.parameters.dhwMaxTemp = convertTemp(vars.parameters.dhwMaxTemp, UnitSystem::METRIC, settings.system.unitSystem);
}
// delete instance
if (this->instance != nullptr) {
delete this->instance;
this->instance = nullptr;
Log.sinfoln(FPSTR(L_OT), F("Stopped"));
}
if (!GPIO_IS_VALID(settings.opentherm.inGpio) || !GPIO_IS_VALID(settings.opentherm.outGpio)) {
Log.swarningln(FPSTR(L_OT), F("Not started. GPIO IN: %hhu or GPIO OUT: %hhu is not valid"), settings.opentherm.inGpio, settings.opentherm.outGpio);
return;
}
// create instance
this->instance = new CustomOpenTherm(settings.opentherm.inGpio, settings.opentherm.outGpio);
// flags
this->instanceCreatedTime = millis();
this->instanceInGpio = settings.opentherm.inGpio;
this->instanceOutGpio = settings.opentherm.outGpio;
this->isInitialized = false;
Log.sinfoln(FPSTR(L_OT), F("Started. GPIO IN: %hhu, GPIO OUT: %hhu"), settings.opentherm.inGpio, settings.opentherm.outGpio);
this->instance->setAfterSendRequestCallback([this](unsigned long request, unsigned long response, OpenThermResponseStatus status, byte attempt) {
Log.straceln(
FPSTR(L_OT),
F("ID: %4d Request: %8lx Response: %8lx Attempt: %2d Status: %s"),
CustomOpenTherm::getDataID(request), request, response, attempt, CustomOpenTherm::statusToString(status)
);
if (status == OpenThermResponseStatus::SUCCESS) {
this->lastSuccessResponse = millis();
if (this->configuredRxLedGpio != GPIO_IS_NOT_CONFIGURED) {
digitalWrite(this->configuredRxLedGpio, HIGH);
delayMicroseconds(2000);
digitalWrite(this->configuredRxLedGpio, LOW);
}
}
});
this->instance->setYieldCallback([this]() {
this->delay(25);
});
this->instance->begin();
}
void loop() {
static float currentHeatingTemp = 0.0f;
static float currentDhwTemp = 0.0f;
if (this->instanceInGpio != settings.opentherm.inGpio || this->instanceOutGpio != settings.opentherm.outGpio) {
this->setup();
} else if (this->initializedMemberIdCode != settings.opentherm.memberIdCode || millis() - this->initializedTime > this->initializingInterval) {
this->isInitialized = false;
}
if (this->instance == nullptr) {
this->delay(5000);
return;
}
// RX LED GPIO setup
if (settings.opentherm.rxLedGpio != this->configuredRxLedGpio) {
if (this->configuredRxLedGpio != GPIO_IS_NOT_CONFIGURED) {
digitalWrite(this->configuredRxLedGpio, LOW);
}
if (GPIO_IS_VALID(settings.opentherm.rxLedGpio)) {
this->configuredRxLedGpio = settings.opentherm.rxLedGpio;
pinMode(this->configuredRxLedGpio, OUTPUT);
digitalWrite(this->configuredRxLedGpio, LOW);
} else if (this->configuredRxLedGpio != GPIO_IS_NOT_CONFIGURED) {
this->configuredRxLedGpio = GPIO_IS_NOT_CONFIGURED;
}
}
// Fault state setup
if (settings.opentherm.faultStateGpio != this->configuredFaultStateGpio) {
if (this->configuredFaultStateGpio != GPIO_IS_NOT_CONFIGURED) {
digitalWrite(this->configuredFaultStateGpio, LOW);
}
if (GPIO_IS_VALID(settings.opentherm.faultStateGpio)) {
this->configuredFaultStateGpio = settings.opentherm.faultStateGpio;
this->faultState = false ^ settings.opentherm.invertFaultState ? HIGH : LOW;
pinMode(this->configuredFaultStateGpio, OUTPUT);
digitalWrite(
this->configuredFaultStateGpio,
this->faultState
);
} else if (this->configuredFaultStateGpio != GPIO_IS_NOT_CONFIGURED) {
this->configuredFaultStateGpio = GPIO_IS_NOT_CONFIGURED;
}
}
bool heatingEnabled = (vars.states.emergency || settings.heating.enable) && this->pump && this->isReady();
bool heatingCh2Enabled = settings.opentherm.heatingCh2Enabled;
if (settings.opentherm.heatingCh1ToCh2) {
heatingCh2Enabled = heatingEnabled;
} else if (settings.opentherm.dhwToCh2) {
heatingCh2Enabled = settings.opentherm.dhwPresent && settings.dhw.enable;
}
// Set boiler status LB
// Some boilers require this, although this is against protocol
uint8_t statusLb = 0;
// Immergas fix
// https://arduino.ru/forum/programmirovanie/termostat-opentherm-na-esp8266?page=15#comment-649392
if (settings.opentherm.immergasFix) {
statusLb = 0xCA;
}
unsigned long response = this->instance->setBoilerStatus(
heatingEnabled,
settings.opentherm.dhwPresent && settings.dhw.enable,
false,
settings.opentherm.nativeHeatingControl,
heatingCh2Enabled,
settings.opentherm.summerWinterMode,
settings.opentherm.dhwBlocking,
statusLb
);
if (!CustomOpenTherm::isValidResponse(response)) {
Log.swarningln(FPSTR(L_OT), F("Invalid response after setBoilerStatus: %s"), CustomOpenTherm::statusToString(this->instance->getLastResponseStatus()));
}
if (!vars.states.otStatus && millis() - this->lastSuccessResponse < 1150) {
Log.sinfoln(FPSTR(L_OT), F("Connected"));
vars.states.otStatus = true;
} else if (vars.states.otStatus && millis() - this->lastSuccessResponse > 1150) {
Log.swarningln(FPSTR(L_OT), F("Disconnected"));
vars.states.otStatus = false;
this->isInitialized = false;
}
// If boiler is disconnected, no need try setting other OT stuff
if (!vars.states.otStatus) {
vars.states.heating = false;
vars.states.dhw = false;
vars.states.flame = false;
vars.states.fault = false;
vars.states.diagnostic = false;
// Force fault state = on
if (this->configuredFaultStateGpio != GPIO_IS_NOT_CONFIGURED) {
bool fState = true ^ settings.opentherm.invertFaultState ? HIGH : LOW;
if (fState != this->faultState) {
this->faultState = fState;
digitalWrite(this->configuredFaultStateGpio, this->faultState);
}
}
return;
}
if (!this->isInitialized) {
Log.sinfoln(FPSTR(L_OT), F("Initializing..."));
this->isInitialized = true;
this->initializedTime = millis();
this->initializedMemberIdCode = settings.opentherm.memberIdCode;
this->initialize();
}
if (vars.parameters.heatingEnabled != heatingEnabled) {
this->prevUpdateNonEssentialVars = 0;
vars.parameters.heatingEnabled = heatingEnabled;
Log.sinfoln(FPSTR(L_OT_HEATING), "%s", heatingEnabled ? F("Enabled") : F("Disabled"));
}
vars.states.heating = CustomOpenTherm::isCentralHeatingActive(response);
vars.states.dhw = settings.opentherm.dhwPresent ? CustomOpenTherm::isHotWaterActive(response) : false;
vars.states.flame = CustomOpenTherm::isFlameOn(response);
vars.states.fault = CustomOpenTherm::isFault(response);
vars.states.diagnostic = CustomOpenTherm::isDiagnostic(response);
// Fault state
if (this->configuredFaultStateGpio != GPIO_IS_NOT_CONFIGURED) {
bool fState = vars.states.fault ^ settings.opentherm.invertFaultState ? HIGH : LOW;
if (fState != this->faultState) {
this->faultState = fState;
digitalWrite(this->configuredFaultStateGpio, this->faultState);
}
}
// These parameters will be updated every minute
if (millis() - this->prevUpdateNonEssentialVars > 60000) {
if (this->updateMinModulationLevel()) {
Log.straceln(FPSTR(L_OT), F("Min modulation: %u%%, boiler max power: %u kW"), vars.parameters.minModulation, vars.sensors.maxPower);
if (vars.parameters.minModulation > settings.heating.maxModulation) {
settings.heating.maxModulation = vars.parameters.minModulation;
fsSettings.update();
Log.snoticeln(FPSTR(L_OT_DHW), F("Updated min modulation: %hhu"), settings.heating.maxModulation);
}
} else {
Log.swarningln(FPSTR(L_OT), F("Failed get min modulation & max power"));
}
if (!heatingEnabled && settings.opentherm.modulationSyncWithHeating) {
if (this->setMaxModulationLevel(0)) {
Log.snoticeln(FPSTR(L_OT_HEATING), F("Set max modulation 0% (off)"));
} else {
Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set max modulation 0% (off)"));
}
} else {
if (this->setMaxModulationLevel(settings.heating.maxModulation)) {
Log.snoticeln(FPSTR(L_OT_HEATING), F("Set max modulation %hhu%%"), settings.heating.maxModulation);
} else {
Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set max modulation %hhu%%"), settings.heating.maxModulation);
}
}
// Get DHW min/max temp (if necessary)
if (settings.opentherm.dhwPresent && settings.opentherm.getMinMaxTemp) {
if (this->updateMinMaxDhwTemp()) {
if (settings.dhw.minTemp < vars.parameters.dhwMinTemp) {
settings.dhw.minTemp = vars.parameters.dhwMinTemp;
fsSettings.update();
Log.snoticeln(FPSTR(L_OT_DHW), F("Updated min temp: %hhu"), settings.dhw.minTemp);
}
if (settings.dhw.maxTemp > vars.parameters.dhwMaxTemp) {
settings.dhw.maxTemp = vars.parameters.dhwMaxTemp;
fsSettings.update();
Log.snoticeln(FPSTR(L_OT_DHW), F("Updated max temp: %hhu"), settings.dhw.maxTemp);
}
} else {
vars.parameters.dhwMinTemp = convertTemp(DEFAULT_DHW_MIN_TEMP, UnitSystem::METRIC, settings.system.unitSystem);
vars.parameters.dhwMaxTemp = convertTemp(DEFAULT_DHW_MAX_TEMP, UnitSystem::METRIC, settings.system.unitSystem);
Log.swarningln(FPSTR(L_OT_DHW), F("Failed get min/max temp"));
}
if (settings.dhw.minTemp >= settings.dhw.maxTemp) {
settings.dhw.minTemp = vars.parameters.dhwMinTemp;
settings.dhw.maxTemp = vars.parameters.dhwMaxTemp;
fsSettings.update();
}
}
// Get heating min/max temp
if (settings.opentherm.getMinMaxTemp) {
if (this->updateMinMaxHeatingTemp()) {
if (settings.heating.minTemp < vars.parameters.heatingMinTemp) {
settings.heating.minTemp = vars.parameters.heatingMinTemp;
fsSettings.update();
Log.snoticeln(FPSTR(L_OT_HEATING), F("Updated min temp: %hhu"), settings.heating.minTemp);
}
if (settings.heating.maxTemp > vars.parameters.heatingMaxTemp) {
settings.heating.maxTemp = vars.parameters.heatingMaxTemp;
fsSettings.update();
Log.snoticeln(FPSTR(L_OT_HEATING), F("Updated max temp: %hhu"), settings.heating.maxTemp);
}
} else {
vars.parameters.heatingMinTemp = convertTemp(DEFAULT_HEATING_MIN_TEMP, UnitSystem::METRIC, settings.system.unitSystem);
vars.parameters.heatingMaxTemp = convertTemp(DEFAULT_HEATING_MAX_TEMP, UnitSystem::METRIC, settings.system.unitSystem);
Log.swarningln(FPSTR(L_OT_HEATING), F("Failed get min/max temp"));
}
}
if (settings.heating.minTemp >= settings.heating.maxTemp) {
settings.heating.minTemp = vars.parameters.heatingMinTemp;
settings.heating.maxTemp = vars.parameters.heatingMaxTemp;
fsSettings.update();
}
// Get fault code (if necessary)
if (vars.states.fault) {
this->updateFaultCode();
} else if (vars.sensors.faultCode != 0) {
vars.sensors.faultCode = 0;
}
// Get diagnostic code (if necessary)
if (vars.states.fault || vars.states.diagnostic) {
this->updateDiagCode();
} else if (vars.sensors.diagnosticCode != 0) {
vars.sensors.diagnosticCode = 0;
}
// If filtering is disabled, then it is enough to
// update these parameters once a minute
if (!settings.opentherm.filterNumValues.enable) {
// Get outdoor temp (if necessary)
if (settings.sensors.outdoor.type == SensorType::BOILER) {
this->updateOutdoorTemp();
}
// Get pressure
this->updatePressure();
}
this->prevUpdateNonEssentialVars = millis();
}
// Get current modulation level (if necessary)
if (vars.states.flame && this->updateModulationLevel()) {
vars.sensors.currentPower = vars.sensors.maxPower > 0
? vars.sensors.maxPower * (vars.sensors.modulation / 100)
: 0;
Log.sverboseln(
FPSTR(L_OT_DHW),
F("Current modulation level: %.2f%%, power: %.2f of %hhu kW"),
vars.sensors.modulation,
vars.sensors.currentPower,
vars.sensors.maxPower
);
} else {
vars.sensors.modulation = 0;
vars.sensors.currentPower = 0;
}
// Update DHW sensors (if necessary)
if (settings.opentherm.dhwPresent) {
this->updateDhwTemp();
this->updateDhwFlowRate();
} else {
vars.temperatures.dhw = 0.0f;
vars.sensors.dhwFlowRate = 0.0f;
}
// Get current heating temp
this->updateHeatingTemp();
// Get heating return temp
this->updateHeatingReturnTemp();
// Get exhaust temp
this->updateExhaustTemp();
// If filtering is enabled, these parameters
// must be updated every time.
if (settings.opentherm.filterNumValues.enable) {
// Get outdoor temp (if necessary)
if (settings.sensors.outdoor.type == SensorType::BOILER) {
this->updateOutdoorTemp();
}
// Get pressure
this->updatePressure();
}
// Fault reset action
if (vars.actions.resetFault) {
if (vars.states.fault) {
if (this->instance->sendBoilerReset()) {
Log.sinfoln(FPSTR(L_OT), F("Boiler fault reset successfully"));
} else {
Log.serrorln(FPSTR(L_OT), F("Boiler fault reset failed"));
}
}
vars.actions.resetFault = false;
}
// Diag reset action
if (vars.actions.resetDiagnostic) {
if (vars.states.diagnostic) {
if (this->instance->sendServiceReset()) {
Log.sinfoln(FPSTR(L_OT), F("Boiler diagnostic reset successfully"));
} else {
Log.serrorln(FPSTR(L_OT), F("Boiler diagnostic reset failed"));
}
}
vars.actions.resetDiagnostic = false;
}
// Update DHW temp
if (settings.opentherm.dhwPresent && settings.dhw.enable && (this->needSetDhwTemp() || fabs(settings.dhw.target - currentDhwTemp) > 0.0001f)) {
float convertedTemp = convertTemp(settings.dhw.target, settings.system.unitSystem, settings.opentherm.unitSystem);
Log.sinfoln(FPSTR(L_OT_DHW), F("Set temp: %.2f (converted: %.2f)"), settings.dhw.target, convertedTemp);
// Set DHW temp
if (this->instance->setDhwTemp(convertedTemp)) {
currentDhwTemp = settings.dhw.target;
this->dhwSetTempTime = millis();
} else {
Log.swarningln(FPSTR(L_OT_DHW), F("Failed set temp"));
}
// Set DHW temp to CH2
if (settings.opentherm.dhwToCh2) {
if (!this->instance->setHeatingCh2Temp(convertedTemp)) {
Log.swarningln(FPSTR(L_OT_DHW), F("Failed set CH2 temp"));
}
}
}
// Native heating control
if (settings.opentherm.nativeHeatingControl) {
// Set current indoor temp
float indoorTemp = 0.0f;
float convertedTemp = 0.0f;
if (!vars.states.emergency || settings.sensors.indoor.type != SensorType::MANUAL) {
indoorTemp = vars.temperatures.indoor;
convertedTemp = convertTemp(indoorTemp, settings.system.unitSystem, settings.opentherm.unitSystem);
}
Log.sinfoln(FPSTR(L_OT_HEATING), F("Set current indoor temp: %.2f (converted: %.2f)"), indoorTemp, convertedTemp);
if (!this->instance->setRoomTemp(convertedTemp)) {
Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set current indoor temp"));
}
// Set target indoor temp
if (this->needSetHeatingTemp() || fabs(vars.parameters.heatingSetpoint - currentHeatingTemp) > 0.0001f) {
convertedTemp = convertTemp(vars.parameters.heatingSetpoint, settings.system.unitSystem, settings.opentherm.unitSystem);
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"));
}
// Set target temp to CH2
if (settings.opentherm.heatingCh1ToCh2) {
if (!this->instance->setRoomSetpointCh2(convertedTemp)) {
Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set target indoor temp to CH2"));
}
}
}
// force enable pump
if (!this->pump) {
this->pump = true;
}
} else {
// 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;
}
}
}
void initialize() {
// Not all boilers support these, only try once when the boiler becomes connected
if (this->updateSlaveVersion()) {
Log.straceln(FPSTR(L_OT), F("Slave version: %u, type: %u"), vars.parameters.slaveVersion, vars.parameters.slaveType);
} else {
Log.swarningln(FPSTR(L_OT), F("Get slave version failed"));
}
// 0x013F
if (this->setMasterVersion(0x3F, 0x01)) {
Log.straceln(FPSTR(L_OT), F("Master version: %u, type: %u"), vars.parameters.masterVersion, vars.parameters.masterType);
} else {
Log.swarningln(FPSTR(L_OT), F("Set master version failed"));
}
if (this->updateSlaveOtVersion()) {
Log.straceln(FPSTR(L_OT), F("Slave OT version: %f"), vars.parameters.slaveOtVersion);
} else {
Log.swarningln(FPSTR(L_OT), F("Get slave OT version failed"));
}
if (this->setMasterOtVersion(2.2f)) {
Log.straceln(FPSTR(L_OT), F("Master OT version: %f"), vars.parameters.masterOtVersion);
} else {
Log.swarningln(FPSTR(L_OT), F("Set master OT version failed"));
}
if (this->updateSlaveConfig()) {
Log.straceln(FPSTR(L_OT), F("Slave member id: %u, flags: %u"), vars.parameters.slaveMemberId, vars.parameters.slaveFlags);
} else {
Log.swarningln(FPSTR(L_OT), F("Get slave config failed"));
}
if (this->setMasterConfig(settings.opentherm.memberIdCode & 0xFF, (settings.opentherm.memberIdCode & 0xFFFF) >> 8)) {
Log.straceln(FPSTR(L_OT), F("Master member id: %u, flags: %u"), vars.parameters.masterMemberId, vars.parameters.masterFlags);
} else {
Log.swarningln(FPSTR(L_OT), F("Set master config failed"));
}
}
bool isReady() {
return millis() - this->instanceCreatedTime > this->readyTime;
}
bool needSetDhwTemp() {
return millis() - this->dhwSetTempTime > this->dhwSetTempInterval;
}
bool needSetHeatingTemp() {
return millis() - this->heatingSetTempTime > this->heatingSetTempInterval;
}
bool updateSlaveConfig() {
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::SConfigSMemberIDcode,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
}
vars.parameters.slaveMemberId = response & 0xFF;
vars.parameters.slaveFlags = (response & 0xFFFF) >> 8;
/*uint8_t flags = (response & 0xFFFF) >> 8;
Log.straceln(
"OT",
F("MasterMemberIdCode:\r\n DHW present: %u\r\n Control type: %u\r\n Cooling configuration: %u\r\n DHW configuration: %u\r\n Pump control: %u\r\n CH2 present: %u\r\n Remote water filling function: %u\r\n Heat/cool mode control: %u\r\n Slave MemberID Code: %u\r\n Raw: %u"),
(bool) (flags & 0x01),
(bool) (flags & 0x02),
(bool) (flags & 0x04),
(bool) (flags & 0x08),
(bool) (flags & 0x10),
(bool) (flags & 0x20),
(bool) (flags & 0x40),
(bool) (flags & 0x80),
response & 0xFF,
response
);*/
return true;
}
/**
* @brief Set the Master Config
* From slave member id code:
* id: slave.memberIdCode & 0xFF,
* flags: (slave.memberIdCode & 0xFFFF) >> 8
* @param id
* @param flags
* @param force
* @return true
* @return false
*/
bool setMasterConfig(uint8_t id, uint8_t flags, bool force = false) {
//uint8_t configId = settings.opentherm.memberIdCode & 0xFF;
//uint8_t configFlags = (settings.opentherm.memberIdCode & 0xFFFF) >> 8;
vars.parameters.masterMemberId = (force || id || settings.opentherm.memberIdCode > 65535)
? id
: vars.parameters.slaveMemberId;
vars.parameters.masterFlags = (force || flags || settings.opentherm.memberIdCode > 65535)
? flags
: vars.parameters.slaveFlags;
unsigned int request = (unsigned int) vars.parameters.masterMemberId | (unsigned int) vars.parameters.masterFlags << 8;
// if empty request
if (!request) {
return true;
}
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::WRITE_DATA,
OpenThermMessageID::MConfigMMemberIDcode,
request
));
return CustomOpenTherm::isValidResponse(response);
}
bool setMaxModulationLevel(byte value) {
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::WRITE_DATA,
OpenThermMessageID::MaxRelModLevelSetting,
CustomOpenTherm::toFloat(value)
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
}
vars.parameters.maxModulation = CustomOpenTherm::getFloat(response);
return true;
}
bool updateSlaveOtVersion() {
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::OpenThermVersionSlave,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
}
vars.parameters.slaveOtVersion = CustomOpenTherm::getFloat(response);
return true;
}
bool setMasterOtVersion(float version) {
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::WRITE_DATA,
OpenThermMessageID::OpenThermVersionMaster,
CustomOpenTherm::toFloat(version)
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
}
vars.parameters.masterOtVersion = CustomOpenTherm::getFloat(response);
return true;
}
bool updateSlaveVersion() {
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::SlaveVersion,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
}
vars.parameters.slaveVersion = response & 0xFF;
vars.parameters.slaveType = (response & 0xFFFF) >> 8;
return true;
}
bool setMasterVersion(uint8_t version, uint8_t type) {
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::WRITE_DATA,
OpenThermMessageID::MasterVersion,
(unsigned int) version | (unsigned int) type << 8
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
}
vars.parameters.masterVersion = response & 0xFF;
vars.parameters.masterType = (response & 0xFFFF) >> 8;
return true;
}
bool updateMinMaxDhwTemp() {
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::TdhwSetUBTdhwSetLB,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
}
byte minTemp = response & 0xFF;
byte maxTemp = (response & 0xFFFF) >> 8;
if (minTemp >= 0 && maxTemp > 0 && maxTemp > minTemp) {
vars.parameters.dhwMinTemp = convertTemp(minTemp, settings.opentherm.unitSystem, settings.system.unitSystem);
vars.parameters.dhwMaxTemp = convertTemp(maxTemp, settings.opentherm.unitSystem, settings.system.unitSystem);
return true;
}
return false;
}
bool updateMinMaxHeatingTemp() {
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::MaxTSetUBMaxTSetLB,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
}
byte minTemp = response & 0xFF;
byte maxTemp = (response & 0xFFFF) >> 8;
if (minTemp >= 0 && maxTemp > 0 && maxTemp > minTemp) {
vars.parameters.heatingMinTemp = convertTemp(minTemp, settings.opentherm.unitSystem, settings.system.unitSystem);
vars.parameters.heatingMaxTemp = convertTemp(maxTemp, settings.opentherm.unitSystem, settings.system.unitSystem);
return true;
}
return false;
}
bool setMaxHeatingTemp(byte value) {
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::MaxTSet,
CustomOpenTherm::temperatureToData(value)
));
return CustomOpenTherm::isValidResponse(response);
}
bool updateOutdoorTemp() {
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::Toutside,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
}
float value = settings.sensors.outdoor.offset + convertTemp(
CustomOpenTherm::getFloat(response),
settings.opentherm.unitSystem,
settings.system.unitSystem
);
if (settings.opentherm.filterNumValues.enable && fabs(vars.temperatures.outdoor) >= 0.1f) {
vars.temperatures.outdoor += (value - vars.temperatures.outdoor) * settings.opentherm.filterNumValues.factor;
} else {
vars.temperatures.outdoor = value;
}
return true;
}
bool updateExhaustTemp() {
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::Texhaust,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
}
float value = (float) CustomOpenTherm::getInt(response);
if (!isValidTemp(value, settings.opentherm.unitSystem, -40, 500)) {
return false;
}
value = convertTemp(
value,
settings.opentherm.unitSystem,
settings.system.unitSystem
);
if (settings.opentherm.filterNumValues.enable && fabs(vars.temperatures.exhaust) >= 0.1f) {
vars.temperatures.exhaust += (value - vars.temperatures.exhaust) * settings.opentherm.filterNumValues.factor;
} else {
vars.temperatures.exhaust = value;
}
return true;
}
bool updateHeatingTemp() {
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermMessageType::READ_DATA,
OpenThermMessageID::Tboiler,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
}
float value = CustomOpenTherm::getFloat(response);
if (value <= 0) {
return false;
}
value = convertTemp(
value,
settings.opentherm.unitSystem,
settings.system.unitSystem
);
if (settings.opentherm.filterNumValues.enable && fabs(vars.temperatures.heating) >= 0.1f) {
vars.temperatures.heating += (value - vars.temperatures.heating) * settings.opentherm.filterNumValues.factor;
} else {
vars.temperatures.heating = value;
}
return true;
}
bool updateHeatingReturnTemp() {
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermMessageType::READ_DATA,
OpenThermMessageID::Tret,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
}
float value = convertTemp(
CustomOpenTherm::getFloat(response),
settings.opentherm.unitSystem,
settings.system.unitSystem
);
if (settings.opentherm.filterNumValues.enable && fabs(vars.temperatures.heatingReturn) >= 0.1f) {
vars.temperatures.heatingReturn += (value - vars.temperatures.heatingReturn) * settings.opentherm.filterNumValues.factor;
} else {
vars.temperatures.heatingReturn = value;
}
return true;
}
bool updateDhwTemp() {
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermMessageType::READ_DATA,
OpenThermMessageID::Tdhw,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
}
float value = CustomOpenTherm::getFloat(response);
if (value <= 0) {
return false;
}
value = convertTemp(
value,
settings.opentherm.unitSystem,
settings.system.unitSystem
);
if (settings.opentherm.filterNumValues.enable && fabs(vars.temperatures.dhw) >= 0.1f) {
vars.temperatures.dhw += (value - vars.temperatures.dhw) * settings.opentherm.filterNumValues.factor;
} else {
vars.temperatures.dhw = value;
}
return true;
}
bool updateDhwFlowRate() {
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermMessageType::READ_DATA,
OpenThermMessageID::DHWFlowRate,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
}
float value = CustomOpenTherm::getFloat(response);
if (value < 0 || value > convertVolume(16, UnitSystem::METRIC, settings.opentherm.unitSystem)) {
return false;
}
value = convertVolume(
value * settings.opentherm.dhwFlowRateFactor,
settings.opentherm.unitSystem,
settings.system.unitSystem
);
vars.sensors.dhwFlowRate = value > 0.09f ? value : 0.0f;
return true;
}
bool updateFaultCode() {
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::ASFflags,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
vars.sensors.faultCode = 0;
return false;
}
vars.sensors.faultCode = response & 0xFF;
return true;
}
bool updateDiagCode() {
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::OEMDiagnosticCode,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
vars.sensors.diagnosticCode = 0;
return false;
}
vars.sensors.diagnosticCode = CustomOpenTherm::getUInt(response);
return true;
}
bool updateModulationLevel() {
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::RelModLevel,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
}
float value = CustomOpenTherm::getFloat(response);
if (settings.opentherm.filterNumValues.enable && fabs(vars.sensors.modulation) >= 0.1f) {
vars.sensors.modulation += (value - vars.sensors.modulation) * settings.opentherm.filterNumValues.factor;
} else {
vars.sensors.modulation = value;
}
return true;
}
bool updateMinModulationLevel() {
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::MaxCapacityMinModLevel,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
}
byte minModulation = response & 0xFF;
byte maxPower = (response & 0xFFFF) >> 8;
vars.parameters.minModulation = minModulation;
vars.sensors.maxPower = maxPower;
return true;
}
bool updatePressure() {
unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest(
OpenThermRequestType::READ_DATA,
OpenThermMessageID::CHPressure,
0
));
if (!CustomOpenTherm::isValidResponse(response)) {
return false;
}
float value = CustomOpenTherm::getFloat(response);
if (value < 0 || value > convertPressure(5, UnitSystem::METRIC, settings.opentherm.unitSystem)) {
return false;
}
value = convertPressure(
value * settings.opentherm.pressureFactor,
settings.opentherm.unitSystem,
settings.system.unitSystem
);
if (settings.opentherm.filterNumValues.enable && fabs(vars.sensors.pressure) >= 0.1f) {
vars.sensors.pressure += (value - vars.sensors.pressure) * settings.opentherm.filterNumValues.factor;
} else {
vars.sensors.pressure = value;
}
return true;
}
};