OpenThermHelpers.cpp
8.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
#include "OpenThermHelpers.h"
#include <unordered_map>
static const char * TAG = "EasyOpenTherm OpenThermHelpers";
#if !defined(ESP32) && !defined(ESP_LOGE)
#define ESP_LOGE(...)
#define ESP_LOGI(...)
#define ESP_LOGV(...)
#endif
// If CH setpoint has changed write it to the boiler
// If writing CH setpoint to the boiler is successful, update the static previous CH setpoint for the next round
// Since OpenTherm::WRITE_DATA_ID::CONTROL_SETPOINT_CH is mandatory no need for tracking if the command is supported
// Returns false if writing to the boiler failed
// This function is robust: if writing fails it will write again in de next loop
bool CHSetpointToBoiler(OpenTherm & thermostat,
float CHSetpoint,
uint32_t & previousOTCommunicationMs) {
static float previousCHSetpoint = -1000.0f;
if(CHSetpoint == previousCHSetpoint) return true;
uint32_t timestampMs = millis();
if(thermostat.write(OpenTherm::WRITE_DATA_ID::CONTROL_SETPOINT_CH, CHSetpoint)) {
previousOTCommunicationMs = timestampMs;
Serial.printf("Central Heating (CH) temperature setpoint set to %.01f ºC\n", CHSetpoint);
previousCHSetpoint = CHSetpoint;
return true;
} else {
Serial.printf("Failed to set Central Heating (CH) temperature setpoint to %.01f ºC\n", CHSetpoint);
return false;
}
}
// If room temperature setpoint has changed write it to the boiler
// If writing room temperature setpoint to the boiler is successful, update the static previous room temperature setpoint for the next round
// Since OpenTherm::WRITE_DATA_ID::ROOM_SETPOINT is optional, so track if the command is supported
// Returns false if writing to the boiler failed
// This function is robust: if writing fails it will write again in de next loop
bool roomTemperatureSetpointToBoiler(OpenTherm & thermostat,
float roomTemperatureSetpoint,
uint32_t & previousOTCommunicationMs) {
static float previousRoomTemperatureSetpoint = -1000.0f;
static bool dataIDSupported = true; // until proven otherwise
roomTemperatureSetpoint = roundf(roomTemperatureSetpoint * 10.0f) / 10.0f; // Round to one decimal
if(roomTemperatureSetpoint == previousRoomTemperatureSetpoint) return true;
if(!dataIDSupported) return false;
uint32_t timestampMs = millis();
if(thermostat.write(OpenTherm::WRITE_DATA_ID::ROOM_SETPOINT, roomTemperatureSetpoint)) {
previousOTCommunicationMs = timestampMs;
if(thermostat.error() == OpenTherm::ERROR_CODES::UNKNOWN_DATA_ID) {
Serial.println("ROOM_SETPOINT not supported");
dataIDSupported = false; // Remember for the next round that this data ID is not supported
return false;
}
Serial.printf("Room temperature setpoint set to %.01f ºC\n", roomTemperatureSetpoint);
previousRoomTemperatureSetpoint = roomTemperatureSetpoint;
return true;
} else {
Serial.printf("Failed to set room temperature setpoint to %.01f ºC\n", roomTemperatureSetpoint);
return false;
}
}
// If room temperature has changed write it to the boiler
// If writing room temperature to the boiler is successful, update the static previous room temperature for the next round
// Since OpenTherm::WRITE_DATA_ID::ROOM_TEMPERATURE is optional, so track if the command is supported
// Returns false if writing to the boiler failed
// This function is robust: if writing fails it will write again in de next loop
bool roomTemperatureToBoiler(OpenTherm & thermostat,
float roomTemperature,
uint32_t & previousOTCommunicationMs) {
static float previousRoomTemperature = -1000.0f;
static bool dataIDSupported = true; // until proven otherwise
roomTemperature = roundf(roomTemperature * 10.0f) / 10.0f; // Round to one decimal
if(roomTemperature == previousRoomTemperature) return true;
if(!dataIDSupported) return false;
uint32_t timestampMs = millis();
if(thermostat.write(OpenTherm::WRITE_DATA_ID::ROOM_TEMPERATURE, roomTemperature)) {
previousOTCommunicationMs = timestampMs;
if(thermostat.error() == OpenTherm::ERROR_CODES::UNKNOWN_DATA_ID) {
Serial.println("ROOM_TEMPERATURE not supported");
dataIDSupported = false; // Remember for the next round that this data ID is not supported
return false;
}
Serial.printf("Room temperature set to %.01f ºC\n", roomTemperature);
previousRoomTemperature = roomTemperature;
return true;
} else {
Serial.printf("Failed to set room temperature to %.01f ºC\n", roomTemperature);
return false;
}
}
// Read the secondary configuration. Upon success return true
bool readSecondaryConfiguration(OpenTherm & thermostat,
OpenTherm::CONFIGURATION_FLAGS & configurationFlags,
uint32_t & previousOTCommunicationMs) {
uint8_t secondaryMemberIDCode;
uint8_t flags;
uint32_t timestampMs = millis();
if(thermostat.read(OpenTherm::READ_DATA_ID::SECONDARY_CONFIGURATION, flags, secondaryMemberIDCode)) { // It is mandatory for a boiler to suppport SECONDARY_CONFIGURATION
previousOTCommunicationMs = timestampMs;
if(thermostat.error() == OpenTherm::ERROR_CODES::UNKNOWN_DATA_ID) {
// Valid data is received but the for boilers mandatory DATA-ID OpenTherm::READ_DATA_ID::SECONDARY_CONFIGURATION is not recognised. This is not a boiler but another device!
ESP_LOGE(TAG, "Your remote device is not a boiler");
return true;
}
configurationFlags = OpenTherm::CONFIGURATION_FLAGS(flags);
return true;
} else {
// No data or invalid data received
ESP_LOGI(TAG, "Failed to get secondary configuration; is a boiler connected?");
return false;
}
}
// Read the secondary status flags. Upon success set the status flags and return true
bool readStatus(OpenTherm & thermostat,
uint8_t primaryFlags,
uint8_t & statusFlags,
uint32_t & previousOTCommunicationMs) {
static uint8_t previousStatusFlags = 0;
uint32_t timestampMs = millis();
if(thermostat.status(primaryFlags, statusFlags)) { // It is mandatory for the boiler to support it's status
previousOTCommunicationMs = timestampMs;
if(statusFlags != previousStatusFlags) {
// showSecondaryStatus(statusFlags); // UPDATE STATE JSON INSTEAD!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
previousStatusFlags = statusFlags;
} else {
ESP_LOGV(TAG, "Secondary status checked; unchanged");
}
return true;
} else {
ESP_LOGI(TAG, "Failed to get secondary status, error code is %d\n", thermostat.error());
return false;
}
}
// Read any sensor from the boiler over the OpenTherm interface that returns a float (f8.8) by using the DATA ID
// If the readSensor is called for the first time for a particular DATA ID, the sensor is read over the OpenTherm interface
// If the sensor can be read succesfully, this is stored as true into the unordered map
// If the instead an INVALID DATA ID response is read from the boiler, this is flagged as false into the unordered map, so
// the bext time this function is called it inmediately returns false without trying to read the sensor again.
bool readSensor(OpenTherm & thermostat,
OpenTherm::READ_DATA_ID dataID,
float & value,
uint32_t & previousOTCommunicationMs) {
static std::unordered_map<OpenTherm::READ_DATA_ID, bool> sensorPresent;
auto got = sensorPresent.find(dataID);
if(got != sensorPresent.end() && !got->second) {
return false;
}
uint32_t timestampMs = millis();
if(thermostat.read(dataID, value)) {
previousOTCommunicationMs = timestampMs;
if(thermostat.error() == OpenTherm::ERROR_CODES::UNKNOWN_DATA_ID) {
// Valid data is received for the dataID, however the boiler does not support it. Add an entry in the sensorPresent unordered_map to prevent another read out
ESP_LOGI(TAG, "Sensor with DATA ID %d not present", dataID);
sensorPresent[dataID] = false;
return false;
} else {
// Valid data is received for the dataID and the boiler supports it. Add an entry in the sensorPresent unordered_map, if not already present, to enable another read out
if(got == sensorPresent.end()) sensorPresent[dataID] = true;
return true;
}
} else {
// No data or invalid data received
return false;
}
}