ESPHome  2024.12.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 "esphome/core/hal.h"
12 #include "esphome/core/log.h"
13 #include "esphome/core/helpers.h"
14 
15 #if defined(ESP32) || defined(USE_ESP_IDF)
16 #include "driver/timer.h"
17 #endif
18 
19 namespace esphome {
20 namespace opentherm {
21 
22 template<class T> constexpr T read_bit(T value, uint8_t bit) { return (value >> bit) & 0x01; }
23 
24 template<class T> constexpr T set_bit(T value, uint8_t bit) { return value |= (1UL << bit); }
25 
26 template<class T> constexpr T clear_bit(T value, uint8_t bit) { return value &= ~(1UL << bit); }
27 
28 template<class T> constexpr T write_bit(T value, uint8_t bit, uint8_t bit_value) {
29  return bit_value ? set_bit(value, bit) : clear_bit(value, bit);
30 }
31 
33  IDLE = 0, // no operation
34 
35  LISTEN = 1, // waiting for transmission to start
36  READ = 2, // reading 32-bit data frame
37  RECEIVED = 3, // data frame received with valid start and stop bit
38 
39  WRITE = 4, // writing data with timer_
40  SENT = 5, // all data written to output
41 
42  ERROR_PROTOCOL = 8, // manchester protocol data transfer error
43  ERROR_TIMEOUT = 9 // read timeout
44 };
45 
47  NO_ERROR = 0, // No error
48  NO_TRANSITION = 1, // No transition in the middle of the bit
49  INVALID_STOP_BIT = 2, // Stop bit wasn't present when expected
50  PARITY_ERROR = 3, // Parity check didn't pass
51  NO_CHANGE_TOO_LONG = 4, // No level change for too much timer ticks
52 };
53 
55  READ_DATA = 0,
56  READ_ACK = 4,
58  WRITE_ACK = 5,
62 };
63 
64 enum MessageId {
65  STATUS = 0,
71  REMOTE = 6,
75  TSP_COUNT = 10,
77  FHB_SIZE = 12,
80  MAX_BOILER_CAPACITY = 15, // u8_hb - u8_lb gives min modulation level
85  DAY_TIME = 20,
86  DATE = 21,
87  YEAR = 22,
89  ROOM_TEMP = 24,
90  FEED_TEMP = 25,
91  DHW_TEMP = 26,
97  DHW2_TEMP = 32,
99  FAN_SPEED = 35,
104  CH_BOUNDS = 49,
109 
110  // HVAC Specific Message IDs
130 
131  RF_SIGNAL = 98,
132  DHW_MODE = 99,
134 
135  // Solar Specific Message IDs
136  SOLAR_MODE_FLAGS = 101, // hb0-2 Controller storage mode
137  // lb0 Device fault
138  // lb1-3 Device mode status
139  // lb4-5 Device status
140  SOLAR_ASF = 102,
148  SOLAR_HOURS = 110,
151 
167 };
168 
169 enum BitPositions { STOP_BIT = 33 };
170 
176  uint8_t type;
177  uint8_t id;
178  uint8_t valueHB;
179  uint8_t valueLB;
180 
181  OpenthermData() : type(0), id(0), valueHB(0), valueLB(0) {}
182 
186  float f88();
187 
191  void f88(float value);
192 
196  uint16_t u16();
197 
201  void u16(uint16_t value);
202 
206  int16_t s16();
207 
211  void s16(int16_t value);
212 };
213 
216  uint32_t capture;
217  uint8_t clock;
218  uint32_t data;
219  uint8_t bit_pos;
220 };
221 
225 class OpenTherm {
226  public:
227  OpenTherm(InternalGPIOPin *in_pin, InternalGPIOPin *out_pin, int32_t device_timeout = 800);
228 
232  bool initialize();
233 
241  void listen();
242 
248  bool has_message() { return mode_ == OperationMode::RECEIVED; }
249 
257  bool get_message(OpenthermData &data);
258 
266  void send(OpenthermData &data);
267 
272  void stop();
273 
279  bool get_protocol_error(OpenThermError &error);
280 
286  bool is_sent() { return mode_ == OperationMode::SENT; }
287 
294  bool is_idle() { return mode_ == OperationMode::IDLE; }
295 
303 
308  bool is_timeout() { return mode_ == OperationMode::ERROR_TIMEOUT; }
309 
315 
316  bool is_active() { return mode_ == LISTEN || mode_ == READ || mode_ == WRITE; }
317 
318  OperationMode get_mode() { return mode_; }
319 
320  void debug_data(OpenthermData &data);
321  void debug_error(OpenThermError &error) const;
322 
323  const char *protocol_error_to_to_str(ProtocolErrorType error_type);
324  const char *message_type_to_str(MessageType message_type);
325  const char *operation_mode_to_str(OperationMode mode);
326  const char *message_id_to_str(MessageId id);
327 
328  static bool timer_isr(OpenTherm *arg);
329 
330 #ifdef ESP8266
331  static void esp8266_timer_isr();
332 #endif
333 
334  private:
335  InternalGPIOPin *in_pin_;
336  InternalGPIOPin *out_pin_;
337  ISRInternalGPIOPin isr_in_pin_;
338  ISRInternalGPIOPin isr_out_pin_;
339 
340 #if defined(ESP32) || defined(USE_ESP_IDF)
341  timer_group_t timer_group_;
342  timer_idx_t timer_idx_;
343 #endif
344 
345  OperationMode mode_;
346  ProtocolErrorType error_type_;
347  uint32_t capture_;
348  uint8_t clock_;
349  uint32_t data_;
350  uint8_t bit_pos_;
351  int32_t timeout_counter_; // <0 no timeout
352 
353  int32_t device_timeout_;
354 
355 #if defined(ESP32) || defined(USE_ESP_IDF)
356  bool init_esp32_timer_();
357  void start_esp32_timer_(uint64_t alarm_value);
358 #endif
359 
360  void stop_timer_();
361 
362  void read_(); // data detected start reading
363  void start_read_timer_(); // reading timer_ to sample at 1/5 of manchester code bit length (at 5kHz)
364  void start_write_timer_(); // writing timer_ to send manchester code (at 2kHz)
365  bool check_parity_(uint32_t val);
366 
367  void bit_read_(uint8_t value);
368  ProtocolErrorType verify_stop_bit_(uint8_t value);
369  void write_bit_(uint8_t high, uint8_t clock);
370 
371 #ifdef ESP8266
372  // ESP8266 timer can accept callback with no parameters, so we have this hack to save a static instance of OpenTherm
373  static OpenTherm *instance; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
374 #endif
375 };
376 
377 } // namespace opentherm
378 } // 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:22
bool is_protocol_error()
Indicates whether last listen() or send() operation ends up with a protocol error.
Definition: opentherm.h:314
bool is_idle()
Indicates whether listinig or sending is not in progress.
Definition: opentherm.h:294
mopeka_std_values val[4]
constexpr T write_bit(T value, uint8_t bit, uint8_t bit_value)
Definition: opentherm.h:28
bool has_message()
Use this function to check whether listen() function already captured a valid data packet...
Definition: opentherm.h:248
constexpr T clear_bit(T value, uint8_t bit)
Definition: opentherm.h:26
Opentherm static class that supports either listening or sending Opentherm data packets in the same t...
Definition: opentherm.h:225
BedjetMode mode
BedJet operating mode.
Definition: bedjet_codec.h:183
constexpr T set_bit(T value, uint8_t bit)
Definition: opentherm.h:24
bool is_sent()
Use this function to check whether send() function already finished sending data packed to line...
Definition: opentherm.h:286
bool is_error()
Indicates whether last listen() or send() operation ends up with an error.
Definition: opentherm.h:302
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
Structure to hold Opentherm data packet content.
Definition: opentherm.h:175
OperationMode get_mode()
Definition: opentherm.h:318
bool is_timeout()
Indicates whether last listen() or send() operation ends up with a timeout error. ...
Definition: opentherm.h:308