2020-05-07 09:40:03 +01:00
|
|
|
/*
|
2020-05-07 15:03:27 +01:00
|
|
|
xsns_69_opentherm_protocol.ino - OpenTherm protocol support for Tasmota
|
2020-05-07 09:40:03 +01:00
|
|
|
|
2021-01-01 12:44:04 +00:00
|
|
|
Copyright (C) 2021 Yuriy Sannikov
|
2020-05-07 09:40:03 +01:00
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef USE_OPENTHERM
|
|
|
|
|
2020-05-07 15:03:27 +01:00
|
|
|
#include "OpenTherm.h"
|
|
|
|
|
2020-05-07 09:40:03 +01:00
|
|
|
// Temperature tolerance. If temperature setpoint difference is less than the value,
|
|
|
|
// OT (1)(Control setpoint) command will be skipped
|
|
|
|
#define OPENTHERM_BOILER_SETPOINT_TOLERANCE 1.0
|
|
|
|
|
|
|
|
typedef union {
|
|
|
|
uint8_t m_flags;
|
|
|
|
struct
|
|
|
|
{
|
|
|
|
uint8_t notSupported : 1; // If set, boiler does not support this command
|
|
|
|
uint8_t supported : 1; // Set if at least one response were successfull
|
|
|
|
uint8_t retryCount : 2; // Retry counter before notSupported flag being set
|
|
|
|
};
|
|
|
|
} OpenThermParamFlags;
|
|
|
|
|
|
|
|
typedef union {
|
|
|
|
float m_float;
|
|
|
|
uint8_t m_u8;
|
|
|
|
uint16_t m_u16;
|
|
|
|
unsigned long m_ul;
|
|
|
|
bool m_bool;
|
|
|
|
} ResponseStorage;
|
|
|
|
|
|
|
|
typedef struct OpenThermCommandT
|
|
|
|
{
|
|
|
|
const char *m_command_name;
|
|
|
|
uint8_t m_command_code;
|
|
|
|
OpenThermParamFlags m_flags;
|
|
|
|
ResponseStorage m_results[2];
|
|
|
|
unsigned long (*m_ot_make_request)(OpenThermCommandT *self, OT_BOILER_STATUS_T *boilerStatus);
|
|
|
|
void (*m_ot_parse_response)(OpenThermCommandT *self, OT_BOILER_STATUS_T *boilerStatus, unsigned long response);
|
|
|
|
void (*m_ot_appent_telemetry)(OpenThermCommandT *self);
|
|
|
|
} OpenThermCommand;
|
|
|
|
|
|
|
|
OpenThermCommand sns_opentherm_commands[] = {
|
|
|
|
{// Get/Set Slave Status Flags
|
|
|
|
.m_command_name = "SLAVE",
|
|
|
|
.m_command_code = 0,
|
|
|
|
// OpenTherm ID(0) should never go into the notSupported state due to some connectivity issues
|
|
|
|
// otherwice it may lose boiler control
|
|
|
|
.m_flags = {.supported = 1},
|
|
|
|
.m_results = {{.m_u8 = 0}, {.m_u8 = 0}},
|
|
|
|
.m_ot_make_request = sns_opentherm_set_slave_flags,
|
|
|
|
.m_ot_parse_response = sns_opentherm_parse_slave_flags,
|
|
|
|
.m_ot_appent_telemetry = sns_opentherm_tele_slave_flags},
|
|
|
|
{// Set boiler temperature
|
|
|
|
.m_command_name = "BTMP",
|
|
|
|
.m_command_code = 0,
|
|
|
|
// OpenTherm ID(1) also should never go into the notSupported state due to some connectivity issues
|
|
|
|
.m_flags = {.supported = 1},
|
|
|
|
.m_results = {{.m_u8 = 0}, {.m_u8 = 0}},
|
|
|
|
.m_ot_make_request = sns_opentherm_set_boiler_temperature,
|
|
|
|
.m_ot_parse_response = sns_opentherm_parse_set_boiler_temperature,
|
|
|
|
.m_ot_appent_telemetry = sns_opentherm_tele_boiler_temperature},
|
|
|
|
{// Set Hot Water temperature
|
|
|
|
.m_command_name = "HWTMP",
|
|
|
|
.m_command_code = 0,
|
|
|
|
// OpenTherm ID(56) may not be supported
|
|
|
|
.m_flags = 0,
|
|
|
|
.m_results = {{.m_u8 = 0}, {.m_u8 = 0}},
|
|
|
|
.m_ot_make_request = sns_opentherm_set_boiler_dhw_temperature,
|
|
|
|
.m_ot_parse_response = sns_opentherm_parse_boiler_dhw_temperature,
|
|
|
|
.m_ot_appent_telemetry = sns_opentherm_tele_boiler_dhw_temperature},
|
|
|
|
{// Read Application-specific fault flags and OEM fault code
|
|
|
|
.m_command_name = "ASFF",
|
|
|
|
.m_command_code = 0,
|
|
|
|
.m_flags = 0,
|
|
|
|
.m_results = {{.m_u8 = 0}, {.m_u8 = 0}},
|
|
|
|
.m_ot_make_request = sns_opentherm_get_flags,
|
|
|
|
.m_ot_parse_response = sns_opentherm_parse_flags,
|
|
|
|
.m_ot_appent_telemetry = sns_opentherm_tele_flags},
|
|
|
|
{// Read An OEM-specific diagnostic/service code
|
|
|
|
.m_command_name = "OEMD",
|
|
|
|
.m_command_code = 0,
|
|
|
|
.m_flags = 0,
|
|
|
|
.m_results = {{.m_u8 = 0}, {.m_u8 = 0}},
|
|
|
|
.m_ot_make_request = sns_opentherm_get_oem_diag,
|
|
|
|
.m_ot_parse_response = sns_opentherm_parse_oem_diag,
|
|
|
|
.m_ot_appent_telemetry = sns_opentherm_tele_oem_diag},
|
|
|
|
{// Read Flame modulation
|
|
|
|
.m_command_name = "FLM",
|
|
|
|
.m_command_code = (uint8_t)OpenThermMessageID::RelModLevel,
|
|
|
|
.m_flags = 0,
|
|
|
|
.m_results = {{.m_u8 = 0}, {.m_u8 = 0}},
|
|
|
|
.m_ot_make_request = sns_opentherm_get_generic_float,
|
|
|
|
.m_ot_parse_response = sns_opentherm_parse_flame_modulation,
|
|
|
|
.m_ot_appent_telemetry = sns_opentherm_tele_generic_float},
|
|
|
|
{// Read Boiler Temperature
|
|
|
|
.m_command_name = "TB",
|
|
|
|
.m_command_code = (uint8_t)OpenThermMessageID::Tboiler,
|
|
|
|
.m_flags = 0,
|
|
|
|
.m_results = {{.m_u8 = 0}, {.m_u8 = 0}},
|
|
|
|
.m_ot_make_request = sns_opentherm_get_generic_float,
|
|
|
|
.m_ot_parse_response = sns_opentherm_parse_boiler_temperature,
|
|
|
|
.m_ot_appent_telemetry = sns_opentherm_tele_generic_float},
|
|
|
|
{// Read DHW temperature
|
|
|
|
.m_command_name = "TDHW",
|
|
|
|
.m_command_code = (uint8_t)OpenThermMessageID::Tdhw,
|
|
|
|
.m_flags = 0,
|
|
|
|
.m_results = {{.m_u8 = 0}, {.m_u8 = 0}},
|
|
|
|
.m_ot_make_request = sns_opentherm_get_generic_float,
|
|
|
|
.m_ot_parse_response = sns_opentherm_parse_generic_float,
|
|
|
|
.m_ot_appent_telemetry = sns_opentherm_tele_generic_float},
|
|
|
|
{// Read Outside temperature
|
|
|
|
.m_command_name = "TOUT",
|
|
|
|
.m_command_code = (uint8_t)OpenThermMessageID::Toutside,
|
|
|
|
.m_flags = 0,
|
|
|
|
.m_results = {{.m_u8 = 0}, {.m_u8 = 0}},
|
|
|
|
.m_ot_make_request = sns_opentherm_get_generic_float,
|
|
|
|
.m_ot_parse_response = sns_opentherm_parse_generic_float,
|
|
|
|
.m_ot_appent_telemetry = sns_opentherm_tele_generic_float},
|
|
|
|
{// Read Return water temperature
|
|
|
|
.m_command_name = "TRET",
|
|
|
|
.m_command_code = (uint8_t)OpenThermMessageID::Tret,
|
|
|
|
.m_flags = 0,
|
|
|
|
.m_results = {{.m_u8 = 0}, {.m_u8 = 0}},
|
|
|
|
.m_ot_make_request = sns_opentherm_get_generic_float,
|
|
|
|
.m_ot_parse_response = sns_opentherm_parse_generic_float,
|
|
|
|
.m_ot_appent_telemetry = sns_opentherm_tele_generic_float},
|
|
|
|
{// Read DHW setpoint
|
|
|
|
.m_command_name = "DHWS",
|
|
|
|
.m_command_code = (uint8_t)OpenThermMessageID::TdhwSet,
|
|
|
|
.m_flags = 0,
|
|
|
|
.m_results = {{.m_u8 = 0}, {.m_u8 = 0}},
|
|
|
|
.m_ot_make_request = sns_opentherm_get_generic_float,
|
|
|
|
.m_ot_parse_response = sns_opentherm_parse_dhw_setpoint,
|
|
|
|
.m_ot_appent_telemetry = sns_opentherm_tele_generic_float},
|
|
|
|
{// Read max CH water setpoint
|
|
|
|
.m_command_name = "TMAX",
|
|
|
|
.m_command_code = (uint8_t)OpenThermMessageID::MaxTSet,
|
|
|
|
.m_flags = 0,
|
|
|
|
.m_results = {{.m_u8 = 0}, {.m_u8 = 0}},
|
|
|
|
.m_ot_make_request = sns_opentherm_get_generic_float,
|
|
|
|
.m_ot_parse_response = sns_opentherm_parse_generic_float,
|
|
|
|
.m_ot_appent_telemetry = sns_opentherm_tele_generic_float},
|
2021-06-05 19:11:49 +01:00
|
|
|
{// Read Number of Transparent-Slave-Parameters supported by slave
|
|
|
|
.m_command_name = "OT10",
|
|
|
|
.m_command_code = (uint8_t)OpenThermMessageID::TSP,
|
|
|
|
.m_flags = 0,
|
|
|
|
.m_results = {{.m_u8 = 0}, {.m_u8 = 0}},
|
|
|
|
.m_ot_make_request = sns_opentherm_get_generic_u16,
|
|
|
|
.m_ot_parse_response = sns_opentherm_parse_generic_u16,
|
|
|
|
.m_ot_appent_telemetry = sns_opentherm_tele_u8_u8},
|
|
|
|
{// Read Size of Fault-History-Buffer supported by slave
|
|
|
|
.m_command_name = "OT12",
|
|
|
|
.m_command_code = (uint8_t)OpenThermMessageID::FHBsize,
|
|
|
|
.m_flags = 0,
|
|
|
|
.m_results = {{.m_u8 = 0}, {.m_u8 = 0}},
|
|
|
|
.m_ot_make_request = sns_opentherm_get_generic_u16,
|
|
|
|
.m_ot_parse_response = sns_opentherm_parse_generic_u16,
|
|
|
|
.m_ot_appent_telemetry = sns_opentherm_tele_u8_u8},
|
|
|
|
{// Read Maximum relative modulation level setting (%)
|
|
|
|
.m_command_name = "OT14",
|
|
|
|
.m_command_code = (uint8_t)OpenThermMessageID::MaxRelModLevelSetting,
|
|
|
|
.m_flags = 0,
|
|
|
|
.m_results = {{.m_u8 = 0}, {.m_u8 = 0}},
|
|
|
|
.m_ot_make_request = sns_opentherm_get_generic_float,
|
|
|
|
.m_ot_parse_response = sns_opentherm_parse_generic_float,
|
|
|
|
.m_ot_appent_telemetry = sns_opentherm_tele_generic_float},
|
|
|
|
{// Read Maximum boiler capacity (kW) / Minimum boiler modulation level(%)
|
|
|
|
.m_command_name = "OT15",
|
|
|
|
.m_command_code = (uint8_t)OpenThermMessageID::MaxCapacityMinModLevel,
|
|
|
|
.m_flags = 0,
|
|
|
|
.m_results = {{.m_u8 = 0}, {.m_u8 = 0}},
|
|
|
|
.m_ot_make_request = sns_opentherm_get_generic_u16,
|
|
|
|
.m_ot_parse_response = sns_opentherm_parse_generic_u16,
|
|
|
|
.m_ot_appent_telemetry = sns_opentherm_tele_u8_u8},
|
|
|
|
{// Read Water pressure in CH circuit
|
|
|
|
.m_command_name = "OT18",
|
|
|
|
.m_command_code = (uint8_t)OpenThermMessageID::CHPressure,
|
|
|
|
.m_flags = 0,
|
|
|
|
.m_results = {{.m_u8 = 0}, {.m_u8 = 0}},
|
|
|
|
.m_ot_make_request = sns_opentherm_get_generic_float,
|
|
|
|
.m_ot_parse_response = sns_opentherm_parse_generic_float,
|
|
|
|
.m_ot_appent_telemetry = sns_opentherm_tele_generic_float},
|
|
|
|
{// Read Water flow rate in DHW circuit. (litres/minute)
|
|
|
|
.m_command_name = "OT19",
|
|
|
|
.m_command_code = (uint8_t)OpenThermMessageID::DHWFlowRate,
|
|
|
|
.m_flags = 0,
|
|
|
|
.m_results = {{.m_u8 = 0}, {.m_u8 = 0}},
|
|
|
|
.m_ot_make_request = sns_opentherm_get_generic_float,
|
|
|
|
.m_ot_parse_response = sns_opentherm_parse_generic_float,
|
|
|
|
.m_ot_appent_telemetry = sns_opentherm_tele_generic_float},
|
|
|
|
{// Boiler exhaust temperature (°C)
|
|
|
|
.m_command_name = "OT33",
|
|
|
|
.m_command_code = (uint8_t)OpenThermMessageID::Texhaust,
|
|
|
|
.m_flags = 0,
|
|
|
|
.m_results = {{.m_u8 = 0}, {.m_u8 = 0}},
|
|
|
|
.m_ot_make_request = sns_opentherm_get_generic_u16,
|
|
|
|
.m_ot_parse_response = sns_opentherm_parse_generic_u16,
|
|
|
|
.m_ot_appent_telemetry = sns_opentherm_tele_generic_u16},
|
|
|
|
{// Number of starts burner
|
|
|
|
.m_command_name = "OT116",
|
|
|
|
.m_command_code = (uint8_t)OpenThermMessageID::BurnerStarts,
|
|
|
|
.m_flags = 0,
|
|
|
|
.m_results = {{.m_u8 = 0}, {.m_u8 = 0}},
|
|
|
|
.m_ot_make_request = sns_opentherm_get_generic_u16,
|
|
|
|
.m_ot_parse_response = sns_opentherm_parse_generic_u16,
|
|
|
|
.m_ot_appent_telemetry = sns_opentherm_tele_generic_u16},
|
|
|
|
{// Number of starts CH pump
|
|
|
|
.m_command_name = "OT117",
|
|
|
|
.m_command_code = (uint8_t)OpenThermMessageID::CHPumpStarts,
|
|
|
|
.m_flags = 0,
|
|
|
|
.m_results = {{.m_u8 = 0}, {.m_u8 = 0}},
|
|
|
|
.m_ot_make_request = sns_opentherm_get_generic_u16,
|
|
|
|
.m_ot_parse_response = sns_opentherm_parse_generic_u16,
|
|
|
|
.m_ot_appent_telemetry = sns_opentherm_tele_generic_u16},
|
|
|
|
{// Number of hours that burner is in operation (i.e. flame on)
|
|
|
|
.m_command_name = "OT120",
|
|
|
|
.m_command_code = (uint8_t)OpenThermMessageID::BurnerOperationHours,
|
|
|
|
.m_flags = 0,
|
|
|
|
.m_results = {{.m_u8 = 0}, {.m_u8 = 0}},
|
|
|
|
.m_ot_make_request = sns_opentherm_get_generic_u16,
|
|
|
|
.m_ot_parse_response = sns_opentherm_parse_generic_u16,
|
|
|
|
.m_ot_appent_telemetry = sns_opentherm_tele_generic_u16},
|
|
|
|
{// Number of hours that CH pump has been running
|
|
|
|
.m_command_name = "OT121",
|
|
|
|
.m_command_code = (uint8_t)OpenThermMessageID::CHPumpOperationHours,
|
|
|
|
.m_flags = 0,
|
|
|
|
.m_results = {{.m_u8 = 0}, {.m_u8 = 0}},
|
|
|
|
.m_ot_make_request = sns_opentherm_get_generic_u16,
|
|
|
|
.m_ot_parse_response = sns_opentherm_parse_generic_u16,
|
|
|
|
.m_ot_appent_telemetry = sns_opentherm_tele_generic_u16},
|
2020-05-07 09:40:03 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
/////////////////////////////////// Process Slave Status Flags & Control //////////////////////////////////////////////////
|
|
|
|
unsigned long sns_opentherm_set_slave_flags(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *status)
|
|
|
|
{
|
|
|
|
bool centralHeatingIsOn = status->m_enableCentralHeating;
|
|
|
|
|
|
|
|
if (status->m_useDiagnosticIndicationAsHeatRequest) {
|
|
|
|
centralHeatingIsOn |= OpenTherm::isDiagnostic(status->m_slave_raw_status);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (self->m_results[1].m_bool != centralHeatingIsOn) {
|
2021-01-23 16:10:06 +00:00
|
|
|
AddLog(LOG_LEVEL_INFO,
|
2020-05-07 09:40:03 +01:00
|
|
|
PSTR("[OTH]: Central Heating transitioning from %s to %s"),
|
|
|
|
self->m_results[1].m_bool ? "on" : "off",
|
2021-06-07 09:31:49 +01:00
|
|
|
centralHeatingIsOn ? "on" : "off");
|
|
|
|
|
|
|
|
if (centralHeatingIsOn) {
|
|
|
|
status->m_forceSetpointSet = true;
|
|
|
|
}
|
2020-05-07 09:40:03 +01:00
|
|
|
}
|
|
|
|
self->m_results[1].m_bool = centralHeatingIsOn;
|
|
|
|
|
|
|
|
unsigned int data = centralHeatingIsOn |
|
|
|
|
(status->m_enableHotWater << 1) |
|
|
|
|
(status->m_enableCooling << 2) |
|
|
|
|
(status->m_enableOutsideTemperatureCompensation << 3) |
|
|
|
|
(status->m_enableCentralHeating2 << 4);
|
|
|
|
|
|
|
|
data <<= 8;
|
|
|
|
|
|
|
|
return OpenTherm::buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Status, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
void sns_opentherm_parse_slave_flags(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *boilerStatus, unsigned long response)
|
|
|
|
{
|
|
|
|
boilerStatus->m_slave_raw_status = response;
|
|
|
|
self->m_results[0].m_ul = response;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define OT_FLAG_TO_ON_OFF(status, flag) ((((status) & (flag)) != 0) ? 1 : 0)
|
|
|
|
void sns_opentherm_tele_slave_flags(struct OpenThermCommandT *self)
|
|
|
|
{
|
|
|
|
unsigned long st = self->m_results[0].m_ul;
|
|
|
|
ResponseAppend_P(PSTR("{\"FAULT\":%d,\"CH\":%d,\"DHW\":%d,\"FL\":%d,\"COOL\":%d,\"CH2\":%d,\"DIAG\":%d,\"RAW\":%lu}"),
|
|
|
|
OT_FLAG_TO_ON_OFF(st, 0x01),
|
|
|
|
OT_FLAG_TO_ON_OFF(st, 0x02),
|
|
|
|
OT_FLAG_TO_ON_OFF(st, 0x04),
|
|
|
|
OT_FLAG_TO_ON_OFF(st, 0x08),
|
|
|
|
OT_FLAG_TO_ON_OFF(st, 0x10),
|
|
|
|
OT_FLAG_TO_ON_OFF(st, 0x20),
|
|
|
|
OT_FLAG_TO_ON_OFF(st, 0x40),
|
|
|
|
st);
|
|
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////// Set Boiler Temperature //////////////////////////////////////////////////
|
|
|
|
unsigned long sns_opentherm_set_boiler_temperature(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *status)
|
|
|
|
{
|
|
|
|
// Assuming some boilers might write setpoint temperature into the Flash memory
|
|
|
|
// Having PID controlled appliance may produce a lot of small fluctuations in the setpoint value
|
|
|
|
// wearing out Boiler flash memory.
|
|
|
|
float diff = abs(status->m_boilerSetpoint - self->m_results[0].m_float);
|
|
|
|
// Ignore small changes in the boiler setpoint temperature
|
2021-06-07 09:31:49 +01:00
|
|
|
if (diff < OPENTHERM_BOILER_SETPOINT_TOLERANCE && !status->m_forceSetpointSet)
|
2020-05-07 09:40:03 +01:00
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
2021-01-23 16:10:06 +00:00
|
|
|
AddLog(LOG_LEVEL_INFO,
|
2021-06-07 09:31:49 +01:00
|
|
|
PSTR("[OTH]: Setting Boiler Temp. Old: %d, New: %d, Force: %s"),
|
2020-05-07 09:40:03 +01:00
|
|
|
(int)self->m_results[0].m_float,
|
2021-06-07 09:31:49 +01:00
|
|
|
(int)status->m_boilerSetpoint,
|
|
|
|
status->m_forceSetpointSet ? "Y" : "N");
|
|
|
|
|
|
|
|
status->m_forceSetpointSet = false;
|
2020-05-07 09:40:03 +01:00
|
|
|
self->m_results[0].m_float = status->m_boilerSetpoint;
|
|
|
|
|
|
|
|
unsigned int data = OpenTherm::temperatureToData(status->m_boilerSetpoint);
|
|
|
|
return OpenTherm::buildRequest(OpenThermMessageType::WRITE_DATA, OpenThermMessageID::TSet, data);
|
|
|
|
}
|
|
|
|
void sns_opentherm_parse_set_boiler_temperature(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *boilerStatus, unsigned long response)
|
|
|
|
{
|
|
|
|
self->m_results[1].m_float = OpenTherm::getFloat(response);
|
|
|
|
}
|
|
|
|
void sns_opentherm_tele_boiler_temperature(struct OpenThermCommandT *self)
|
|
|
|
{
|
|
|
|
char requested[FLOATSZ];
|
2021-06-11 17:14:12 +01:00
|
|
|
dtostrfd(self->m_results[0].m_float, Settings->flag2.temperature_resolution, requested);
|
2020-05-07 09:40:03 +01:00
|
|
|
char actual[FLOATSZ];
|
2021-06-11 17:14:12 +01:00
|
|
|
dtostrfd(self->m_results[1].m_float, Settings->flag2.temperature_resolution, actual);
|
2020-05-07 09:40:03 +01:00
|
|
|
|
|
|
|
// indicate fault if tepmerature demand and actual setpoint are greater then tolerance
|
|
|
|
bool isFault = abs(self->m_results[1].m_float - self->m_results[0].m_float) > OPENTHERM_BOILER_SETPOINT_TOLERANCE;
|
|
|
|
|
|
|
|
ResponseAppend_P(PSTR("{\"FAULT\":%d,\"REQ\":%s,\"ACT\": %s}"),
|
|
|
|
(int)isFault,
|
|
|
|
requested,
|
|
|
|
actual);
|
|
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////// Set Domestic Hot Water Temperature //////////////////////////////////////////////////
|
|
|
|
unsigned long sns_opentherm_set_boiler_dhw_temperature(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *status)
|
|
|
|
{
|
|
|
|
// The same consideration as for the boiler temperature
|
|
|
|
float diff = abs(status->m_hotWaterSetpoint - self->m_results[0].m_float);
|
|
|
|
// Ignore small changes in the boiler setpoint temperature
|
|
|
|
if (diff < OPENTHERM_BOILER_SETPOINT_TOLERANCE)
|
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
2021-01-23 16:10:06 +00:00
|
|
|
AddLog(LOG_LEVEL_INFO,
|
2020-05-07 09:40:03 +01:00
|
|
|
PSTR("[OTH]: Setting Hot Water Temp. Old: %d, New: %d"),
|
|
|
|
(int)self->m_results[0].m_float,
|
|
|
|
(int)status->m_hotWaterSetpoint);
|
|
|
|
|
|
|
|
self->m_results[0].m_float = status->m_hotWaterSetpoint;
|
|
|
|
|
|
|
|
unsigned int data = OpenTherm::temperatureToData(status->m_hotWaterSetpoint);
|
|
|
|
return OpenTherm::buildRequest(OpenThermMessageType::WRITE_DATA, OpenThermMessageID::TdhwSet, data);
|
|
|
|
}
|
|
|
|
void sns_opentherm_parse_boiler_dhw_temperature(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *boilerStatus, unsigned long response)
|
|
|
|
{
|
|
|
|
self->m_results[1].m_float = OpenTherm::getFloat(response);
|
|
|
|
}
|
|
|
|
void sns_opentherm_tele_boiler_dhw_temperature(struct OpenThermCommandT *self)
|
|
|
|
{
|
|
|
|
char requested[FLOATSZ];
|
2021-06-11 17:14:12 +01:00
|
|
|
dtostrfd(self->m_results[0].m_float, Settings->flag2.temperature_resolution, requested);
|
2020-05-07 09:40:03 +01:00
|
|
|
char actual[FLOATSZ];
|
2021-06-11 17:14:12 +01:00
|
|
|
dtostrfd(self->m_results[1].m_float, Settings->flag2.temperature_resolution, actual);
|
2020-05-07 09:40:03 +01:00
|
|
|
|
|
|
|
ResponseAppend_P(PSTR("{\"REQ\":%s,\"ACT\": %s}"),
|
|
|
|
requested,
|
|
|
|
actual);
|
|
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////// App Specific Fault Flags //////////////////////////////////////////////////
|
|
|
|
unsigned long sns_opentherm_get_flags(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *)
|
|
|
|
{
|
|
|
|
return OpenTherm::buildRequest(OpenThermRequestType::READ, OpenThermMessageID::ASFflags, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void sns_opentherm_parse_flags(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *boilerStatus, unsigned long response)
|
|
|
|
{
|
|
|
|
uint8_t fault_code = (response >> 8) & 0xFF;
|
|
|
|
uint8_t oem_fault_code = response & 0xFF;
|
|
|
|
boilerStatus->m_fault_code = fault_code;
|
|
|
|
boilerStatus->m_oem_fault_code = fault_code;
|
|
|
|
self->m_results[0].m_u8 = fault_code;
|
|
|
|
self->m_results[1].m_u8 = oem_fault_code;
|
|
|
|
}
|
|
|
|
|
|
|
|
void sns_opentherm_tele_flags(struct OpenThermCommandT *self)
|
|
|
|
{
|
|
|
|
ResponseAppend_P(PSTR("{\"FC\":%d,\"OFC\":%d}"),
|
|
|
|
(int)self->m_results[0].m_u8,
|
|
|
|
(int)self->m_results[1].m_u8);
|
|
|
|
}
|
|
|
|
|
2021-06-05 19:11:49 +01:00
|
|
|
void sns_opentherm_tele_u8_u8(struct OpenThermCommandT *self)
|
|
|
|
{
|
|
|
|
ResponseAppend_P(PSTR("{\"lo\":%d,\"hi\":%d}"),
|
|
|
|
(int)self->m_results[0].m_u8,
|
|
|
|
(int)self->m_results[1].m_u8);
|
|
|
|
}
|
|
|
|
|
|
|
|
void sns_opentherm_tele_u16(struct OpenThermCommandT *self)
|
|
|
|
{
|
|
|
|
ResponseAppend_P(PSTR("%d"), (int)self->m_results[0].m_u16);
|
|
|
|
}
|
|
|
|
|
2020-05-07 09:40:03 +01:00
|
|
|
/////////////////////////////////// OEM Diag Code //////////////////////////////////////////////////
|
|
|
|
unsigned long sns_opentherm_get_oem_diag(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *)
|
|
|
|
{
|
|
|
|
return OpenTherm::buildRequest(OpenThermRequestType::READ, OpenThermMessageID::OEMDiagnosticCode, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void sns_opentherm_parse_oem_diag(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *boilerStatus, unsigned long response)
|
|
|
|
{
|
|
|
|
uint16_t diag_code = (uint16_t)response & 0xFFFF;
|
|
|
|
boilerStatus->m_oem_diag_code = diag_code;
|
|
|
|
self->m_results[0].m_u16 = diag_code;
|
|
|
|
}
|
|
|
|
|
|
|
|
void sns_opentherm_tele_oem_diag(struct OpenThermCommandT *self)
|
|
|
|
{
|
|
|
|
ResponseAppend_P(PSTR("%d"), (int)self->m_results[0].m_u16);
|
|
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////// Generic Single Float /////////////////////////////////////////////////
|
|
|
|
unsigned long sns_opentherm_get_generic_float(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *)
|
|
|
|
{
|
|
|
|
return OpenTherm::buildRequest(OpenThermRequestType::READ, (OpenThermMessageID)self->m_command_code, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void sns_opentherm_parse_generic_float(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *boilerStatus, unsigned long response)
|
|
|
|
{
|
|
|
|
self->m_results[0].m_float = OpenTherm::getFloat(response);
|
|
|
|
}
|
|
|
|
|
|
|
|
void sns_opentherm_tele_generic_float(struct OpenThermCommandT *self)
|
|
|
|
{
|
|
|
|
char str[FLOATSZ];
|
2021-06-11 17:14:12 +01:00
|
|
|
dtostrfd(self->m_results[0].m_float, Settings->flag2.temperature_resolution, str);
|
2020-05-07 09:40:03 +01:00
|
|
|
ResponseAppend_P(PSTR("%s"), str);
|
|
|
|
}
|
|
|
|
|
2021-06-05 19:11:49 +01:00
|
|
|
/////////////////////////////////// Generic U16 /////////////////////////////////////////////////
|
|
|
|
unsigned long sns_opentherm_get_generic_u16(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *)
|
|
|
|
{
|
|
|
|
return OpenTherm::buildRequest(OpenThermRequestType::READ, (OpenThermMessageID)self->m_command_code, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void sns_opentherm_parse_generic_u16(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *boilerStatus, unsigned long response)
|
|
|
|
{
|
|
|
|
self->m_results[0].m_u16 = response & 0xFFFF;
|
|
|
|
}
|
|
|
|
|
|
|
|
void sns_opentherm_tele_generic_u16(struct OpenThermCommandT *self)
|
|
|
|
{
|
|
|
|
ResponseAppend_P(PSTR("%d"), self->m_results[0].m_u16);
|
|
|
|
}
|
|
|
|
|
2020-05-07 09:40:03 +01:00
|
|
|
/////////////////////////////////// Specific Floats Rerports to the /////////////////////////////////////////////////
|
|
|
|
void sns_opentherm_parse_dhw_setpoint(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *boilerStatus, unsigned long response)
|
|
|
|
{
|
|
|
|
self->m_results[0].m_float = OpenTherm::getFloat(response);
|
|
|
|
boilerStatus->m_hotWaterSetpoint_read = self->m_results[0].m_float;
|
|
|
|
}
|
|
|
|
|
|
|
|
void sns_opentherm_parse_flame_modulation(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *boilerStatus, unsigned long response)
|
|
|
|
{
|
|
|
|
self->m_results[0].m_float = OpenTherm::getFloat(response);
|
|
|
|
boilerStatus->m_flame_modulation_read = self->m_results[0].m_float;
|
|
|
|
}
|
|
|
|
|
|
|
|
void sns_opentherm_parse_boiler_temperature(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *boilerStatus, unsigned long response)
|
|
|
|
{
|
|
|
|
self->m_results[0].m_float = OpenTherm::getFloat(response);
|
|
|
|
boilerStatus->m_boiler_temperature_read = self->m_results[0].m_float;
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
#define SNS_OT_COMMANDS_COUNT (sizeof(sns_opentherm_commands) / sizeof(OpenThermCommand))
|
|
|
|
int sns_opentherm_current_command = SNS_OT_COMMANDS_COUNT;
|
|
|
|
|
|
|
|
unsigned long sns_opentherm_get_next_request(struct OT_BOILER_STATUS_T *boilerStatus)
|
|
|
|
{
|
|
|
|
// get next and loop the command
|
|
|
|
if (++sns_opentherm_current_command >= SNS_OT_COMMANDS_COUNT)
|
|
|
|
{
|
|
|
|
sns_opentherm_current_command = 0;
|
|
|
|
}
|
|
|
|
|
2021-06-05 19:11:49 +01:00
|
|
|
// Select next supported command
|
|
|
|
while (sns_opentherm_current_command < SNS_OT_COMMANDS_COUNT)
|
2020-05-07 09:40:03 +01:00
|
|
|
{
|
2021-06-05 19:11:49 +01:00
|
|
|
struct OpenThermCommandT *cmd = &sns_opentherm_commands[sns_opentherm_current_command];
|
|
|
|
// Return error if command known as not supported
|
|
|
|
if (!cmd->m_flags.notSupported)
|
|
|
|
{
|
|
|
|
// Retrurn OT compatible request
|
|
|
|
return cmd->m_ot_make_request(cmd, boilerStatus);
|
|
|
|
}
|
|
|
|
++sns_opentherm_current_command;
|
2020-05-07 09:40:03 +01:00
|
|
|
}
|
2021-06-05 19:11:49 +01:00
|
|
|
return -1;
|
2020-05-07 09:40:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void sns_opentherm_check_retry_request()
|
|
|
|
{
|
|
|
|
if (sns_opentherm_current_command >= SNS_OT_COMMANDS_COUNT)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
struct OpenThermCommandT *cmd = &sns_opentherm_commands[sns_opentherm_current_command];
|
|
|
|
|
|
|
|
bool canRetry = ++cmd->m_flags.retryCount < 3;
|
|
|
|
// In case of last retry and if this command never respond successfully, set notSupported flag
|
|
|
|
if (!canRetry && !cmd->m_flags.supported)
|
|
|
|
{
|
|
|
|
cmd->m_flags.notSupported = true;
|
2021-01-23 16:10:06 +00:00
|
|
|
AddLog(LOG_LEVEL_ERROR,
|
2020-05-07 09:40:03 +01:00
|
|
|
PSTR("[OTH]: command %s is not supported by the boiler. Last status: %s"),
|
|
|
|
cmd->m_command_name,
|
|
|
|
sns_ot_master->statusToString(sns_ot_master->getLastResponseStatus()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void sns_opentherm_process_success_response(struct OT_BOILER_STATUS_T *boilerStatus, unsigned long response)
|
|
|
|
{
|
|
|
|
if (sns_opentherm_current_command >= SNS_OT_COMMANDS_COUNT)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
struct OpenThermCommandT *cmd = &sns_opentherm_commands[sns_opentherm_current_command];
|
|
|
|
// mark command as supported
|
|
|
|
cmd->m_flags.supported = true;
|
|
|
|
|
|
|
|
cmd->m_ot_parse_response(cmd, boilerStatus, response);
|
|
|
|
}
|
|
|
|
|
|
|
|
void sns_opentherm_dump_telemetry()
|
|
|
|
{
|
|
|
|
bool add_coma = false;
|
|
|
|
for (int i = 0; i < SNS_OT_COMMANDS_COUNT; ++i)
|
|
|
|
{
|
|
|
|
struct OpenThermCommandT *cmd = &sns_opentherm_commands[i];
|
|
|
|
if (!cmd->m_flags.supported)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
ResponseAppend_P(PSTR("%s\"%s\":"), add_coma ? "," : "", cmd->m_command_name);
|
|
|
|
|
|
|
|
cmd->m_ot_appent_telemetry(cmd);
|
|
|
|
|
|
|
|
add_coma = true;
|
|
|
|
}
|
|
|
|
}
|
2021-05-26 10:27:47 +01:00
|
|
|
|
|
|
|
void sns_opentherm_protocol_reset()
|
|
|
|
{
|
2021-05-26 10:47:34 +01:00
|
|
|
sns_opentherm_current_command = SNS_OT_COMMANDS_COUNT;
|
2021-05-26 10:27:47 +01:00
|
|
|
for (int i = 0; i < SNS_OT_COMMANDS_COUNT; ++i)
|
|
|
|
{
|
|
|
|
struct OpenThermCommandT *cmd = &sns_opentherm_commands[i];
|
|
|
|
cmd->m_flags.m_flags = 0;
|
|
|
|
memset(cmd->m_results, 0, sizeof(OpenThermCommandT::m_results));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-07 09:40:03 +01:00
|
|
|
#endif
|