ESPHome  2024.10.2
opentherm.h
Go to the documentation of this file.
1 /*
2  * OpenTherm protocol implementation. Originally taken from https://github.com/jpraus/arduino-opentherm, but
3  * heavily modified to comply with ESPHome coding standards and provide better logging.
4  * Original code is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
5  * Public License, which is compatible with GPLv3 license, which covers C++ part of ESPHome project.
6  */
7 
8 #pragma once
9 
10 #include <string>
11 #include <sstream>
12 #include <iomanip>
13 #include "esphome/core/hal.h"
14 #include "esphome/core/log.h"
15 
16 #if defined(ESP32) || defined(USE_ESP_IDF)
17 #include "driver/timer.h"
18 #endif
19 
20 namespace esphome {
21 namespace opentherm {
22 
23 // TODO: Account for immutable semantics change in hub.cpp when doing later installments of OpenTherm PR
24 template<class T> constexpr T read_bit(T value, uint8_t bit) { return (value >> bit) & 0x01; }
25 
26 template<class T> constexpr T set_bit(T value, uint8_t bit) { return value |= (1UL << bit); }
27 
28 template<class T> constexpr T clear_bit(T value, uint8_t bit) { return value &= ~(1UL << bit); }
29 
30 template<class T> constexpr T write_bit(T value, uint8_t bit, uint8_t bit_value) {
31  return bit_value ? setBit(value, bit) : clearBit(value, bit);
32 }
33 
35  IDLE = 0, // no operation
36 
37  LISTEN = 1, // waiting for transmission to start
38  READ = 2, // reading 32-bit data frame
39  RECEIVED = 3, // data frame received with valid start and stop bit
40 
41  WRITE = 4, // writing data with timer_
42  SENT = 5, // all data written to output
43 
44  ERROR_PROTOCOL = 8, // manchester protocol data transfer error
45  ERROR_TIMEOUT = 9 // read timeout
46 };
47 
49  NO_ERROR = 0, // No error
50  NO_TRANSITION = 1, // No transition in the middle of the bit
51  INVALID_STOP_BIT = 2, // Stop bit wasn't present when expected
52  PARITY_ERROR = 3, // Parity check didn't pass
53  NO_CHANGE_TOO_LONG = 4, // No level change for too much timer ticks
54 };
55 
57  READ_DATA = 0,
58  READ_ACK = 4,
60  WRITE_ACK = 5,
64 };
65 
66 enum MessageId {
67  STATUS = 0,
73  REMOTE = 6,
77  TSP_COUNT = 10,
79  FHB_SIZE = 12,
82  MAX_BOILER_CAPACITY = 15, // u8_hb - u8_lb gives min modulation level
87  DAY_TIME = 20,
88  DATE = 21,
89  YEAR = 22,
91  ROOM_TEMP = 24,
92  FEED_TEMP = 25,
93  DHW_TEMP = 26,
99  DHW2_TEMP = 32,
101  FAN_SPEED = 35,
104  CH_BOUNDS = 49,
109 
110  // HVAC Specific Message IDs
121 
136 };
137 
138 enum BitPositions { STOP_BIT = 33 };
139 
145  uint8_t type;
146  uint8_t id;
147  uint8_t valueHB;
148  uint8_t valueLB;
149 
150  OpenthermData() : type(0), id(0), valueHB(0), valueLB(0) {}
151 
155  float f88();
156 
160  void f88(float value);
161 
165  uint16_t u16();
166 
170  void u16(uint16_t value);
171 
175  int16_t s16();
176 
180  void s16(int16_t value);
181 };
182 
185  uint32_t capture;
186  uint8_t clock;
187  uint32_t data;
188  uint8_t bit_pos;
189 };
190 
194 class OpenTherm {
195  public:
196  OpenTherm(InternalGPIOPin *in_pin, InternalGPIOPin *out_pin, int32_t device_timeout = 800);
197 
201  bool initialize();
202 
210  void listen();
211 
217  bool has_message() { return mode_ == OperationMode::RECEIVED; }
218 
226  bool get_message(OpenthermData &data);
227 
235  void send(OpenthermData &data);
236 
241  void stop();
242 
248  bool get_protocol_error(OpenThermError &error);
249 
255  bool is_sent() { return mode_ == OperationMode::SENT; }
256 
263  bool is_idle() { return mode_ == OperationMode::IDLE; }
264 
272 
277  bool is_timeout() { return mode_ == OperationMode::ERROR_TIMEOUT; }
278 
284 
285  bool is_active() { return mode_ == LISTEN || mode_ == READ || mode_ == WRITE; }
286 
287  OperationMode get_mode() { return mode_; }
288 
289  std::string debug_data(OpenthermData &data);
290  std::string debug_error(OpenThermError &error);
291 
292  const char *protocol_error_to_to_str(ProtocolErrorType error_type);
293  const char *message_type_to_str(MessageType message_type);
294  const char *operation_mode_to_str(OperationMode mode);
295  const char *message_id_to_str(MessageId id);
296 
297  static bool timer_isr(OpenTherm *arg);
298 
299 #ifdef ESP8266
300  static void esp8266_timer_isr();
301 #endif
302 
303  private:
304  InternalGPIOPin *in_pin_;
305  InternalGPIOPin *out_pin_;
306  ISRInternalGPIOPin isr_in_pin_;
307  ISRInternalGPIOPin isr_out_pin_;
308 
309 #if defined(ESP32) || defined(USE_ESP_IDF)
310  timer_group_t timer_group_;
311  timer_idx_t timer_idx_;
312 #endif
313 
314  OperationMode mode_;
315  ProtocolErrorType error_type_;
316  uint32_t capture_;
317  uint8_t clock_;
318  uint32_t data_;
319  uint8_t bit_pos_;
320  int32_t timeout_counter_; // <0 no timeout
321 
322  int32_t device_timeout_;
323 
324 #if defined(ESP32) || defined(USE_ESP_IDF)
325  bool init_esp32_timer_();
326  void start_esp32_timer_(uint64_t alarm_value);
327 #endif
328 
329  void stop_timer_();
330 
331  void read_(); // data detected start reading
332  void start_read_timer_(); // reading timer_ to sample at 1/5 of manchester code bit length (at 5kHz)
333  void start_write_timer_(); // writing timer_ to send manchester code (at 2kHz)
334  bool check_parity_(uint32_t val);
335 
336  void bit_read_(uint8_t value);
337  ProtocolErrorType verify_stop_bit_(uint8_t value);
338  void write_bit_(uint8_t high, uint8_t clock);
339 
340 #ifdef ESP8266
341  // ESP8266 timer can accept callback with no parameters, so we have this hack to save a static instance of OpenTherm
342  static OpenTherm *instance_;
343 #endif
344 };
345 
346 } // namespace opentherm
347 } // namespace esphome
Copy of GPIOPin that is safe to use from ISRs (with no virtual functions)
Definition: gpio.h:66
constexpr T read_bit(T value, uint8_t bit)
Definition: opentherm.h:24
bool is_protocol_error()
Indicates whether last listen() or send() operation ends up with a protocol error.
Definition: opentherm.h:283
bool is_idle()
Indicates whether listinig or sending is not in progress.
Definition: opentherm.h:263
mopeka_std_values val[4]
constexpr T write_bit(T value, uint8_t bit, uint8_t bit_value)
Definition: opentherm.h:30
bool has_message()
Use this function to check whether listen() function already captured a valid data packet...
Definition: opentherm.h:217
constexpr T clear_bit(T value, uint8_t bit)
Definition: opentherm.h:28
Opentherm static class that supports either listening or sending Opentherm data packets in the same t...
Definition: opentherm.h:194
BedjetMode mode
BedJet operating mode.
Definition: bedjet_codec.h:183
constexpr T set_bit(T value, uint8_t bit)
Definition: opentherm.h:26
bool is_sent()
Use this function to check whether send() function already finished sending data packed to line...
Definition: opentherm.h:255
bool is_error()
Indicates whether last listen() or send() operation ends up with an error.
Definition: opentherm.h:271
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
Structure to hold Opentherm data packet content.
Definition: opentherm.h:144
OperationMode get_mode()
Definition: opentherm.h:287
bool is_timeout()
Indicates whether last listen() or send() operation ends up with a timeout error. ...
Definition: opentherm.h:277