ESPHome  2025.2.0
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 to output
40  SENT = 5, // all data written to output
41 
42  ERROR_PROTOCOL = 8, // protocol error, can happed only during READ
43  ERROR_TIMEOUT = 9, // timeout while waiting for response from device, only during LISTEN
44  ERROR_TIMER = 10 // error operating the ESP32 timer
45 };
46 
48  NO_ERROR = 0, // No error
49  NO_TRANSITION = 1, // No transition in the middle of the bit
50  INVALID_STOP_BIT = 2, // Stop bit wasn't present when expected
51  PARITY_ERROR = 3, // Parity check didn't pass
52  NO_CHANGE_TOO_LONG = 4, // No level change for too much timer ticks
53 };
54 
56  NO_TIMER_ERROR = 0, // No error
57  SET_ALARM_VALUE_ERROR = 1, // No transition in the middle of the bit
58  TIMER_START_ERROR = 2, // Stop bit wasn't present when expected
59  TIMER_PAUSE_ERROR = 3, // Parity check didn't pass
60  SET_COUNTER_VALUE_ERROR = 4, // No level change for too much timer ticks
61 };
62 
64  READ_DATA = 0,
65  READ_ACK = 4,
67  WRITE_ACK = 5,
71 };
72 
73 enum MessageId {
74  STATUS = 0,
80  REMOTE = 6,
84  TSP_COUNT = 10,
86  FHB_SIZE = 12,
89  MAX_BOILER_CAPACITY = 15, // u8_hb - u8_lb gives min modulation level
94  DAY_TIME = 20,
95  DATE = 21,
96  YEAR = 22,
98  ROOM_TEMP = 24,
99  FEED_TEMP = 25,
100  DHW_TEMP = 26,
106  DHW2_TEMP = 32,
108  FAN_SPEED = 35,
113  CH_BOUNDS = 49,
118 
119  // HVAC Specific Message IDs
139 
140  RF_SIGNAL = 98,
141  DHW_MODE = 99,
143 
144  // Solar Specific Message IDs
145  SOLAR_MODE_FLAGS = 101, // hb0-2 Controller storage mode
146  // lb0 Device fault
147  // lb1-3 Device mode status
148  // lb4-5 Device status
149  SOLAR_ASF = 102,
157  SOLAR_HOURS = 110,
160 
176 };
177 
178 enum BitPositions { STOP_BIT = 33 };
179 
185  uint8_t type;
186  uint8_t id;
187  uint8_t valueHB;
188  uint8_t valueLB;
189 
190  OpenthermData() : type(0), id(0), valueHB(0), valueLB(0) {}
191 
195  float f88();
196 
200  void f88(float value);
201 
205  uint16_t u16();
206 
210  void u16(uint16_t value);
211 
215  int16_t s16();
216 
220  void s16(int16_t value);
221 };
222 
225  uint32_t capture;
226  uint8_t clock;
227  uint32_t data;
228  uint8_t bit_pos;
229 };
230 
234 class OpenTherm {
235  public:
236  OpenTherm(InternalGPIOPin *in_pin, InternalGPIOPin *out_pin, int32_t device_timeout = 800);
237 
241  bool initialize();
242 
250  void listen();
251 
257  bool has_message() { return mode_ == OperationMode::RECEIVED; }
258 
266  bool get_message(OpenthermData &data);
267 
275  void send(OpenthermData &data);
276 
281  void stop();
282 
288  bool get_protocol_error(OpenThermError &error);
289 
295  bool is_sent() { return mode_ == OperationMode::SENT; }
296 
303  bool is_idle() { return mode_ == OperationMode::IDLE; }
304 
311  bool is_error() {
312  return mode_ == OperationMode::ERROR_TIMEOUT || mode_ == OperationMode::ERROR_PROTOCOL || mode_ == ERROR_TIMER;
313  }
314 
319  bool is_timeout() { return mode_ == OperationMode::ERROR_TIMEOUT; }
320 
326 
331  bool is_timer_error() { return mode_ == OperationMode::ERROR_TIMER; }
332 
333  bool is_active() { return mode_ == LISTEN || mode_ == READ || mode_ == WRITE; }
334 
335  OperationMode get_mode() { return mode_; }
336 
337  void debug_data(OpenthermData &data);
338  void debug_error(OpenThermError &error) const;
339  void report_and_reset_timer_error();
340 
341  const char *protocol_error_to_str(ProtocolErrorType error_type);
342  const char *timer_error_to_str(TimerErrorType error_type);
343  const char *message_type_to_str(MessageType message_type);
344  const char *operation_mode_to_str(OperationMode mode);
345  const char *message_id_to_str(MessageId id);
346 
347  static bool timer_isr(OpenTherm *arg);
348 
349 #ifdef ESP8266
350  static void esp8266_timer_isr();
351 #endif
352 
353  private:
354  InternalGPIOPin *in_pin_;
355  InternalGPIOPin *out_pin_;
356  ISRInternalGPIOPin isr_in_pin_;
357  ISRInternalGPIOPin isr_out_pin_;
358 
359 #if defined(ESP32) || defined(USE_ESP_IDF)
360  timer_group_t timer_group_;
361  timer_idx_t timer_idx_;
362 #endif
363 
364  OperationMode mode_;
365  ProtocolErrorType error_type_;
366  uint32_t capture_;
367  uint8_t clock_;
368  uint32_t data_;
369  uint8_t bit_pos_;
370  int32_t timeout_counter_; // <0 no timeout
371  int32_t device_timeout_;
372 
373 #if defined(ESP32) || defined(USE_ESP_IDF)
374  esp_err_t timer_error_ = ESP_OK;
376 
377  bool init_esp32_timer_();
378  void start_esp32_timer_(uint64_t alarm_value);
379 #endif
380 
381  void stop_timer_();
382 
383  void read_(); // data detected start reading
384  void start_read_timer_(); // reading timer_ to sample at 1/5 of manchester code bit length (at 5kHz)
385  void start_write_timer_(); // writing timer_ to send manchester code (at 2kHz)
386  bool check_parity_(uint32_t val);
387 
388  void bit_read_(uint8_t value);
389  ProtocolErrorType verify_stop_bit_(uint8_t value);
390  void write_bit_(uint8_t high, uint8_t clock);
391 
392 #ifdef ESP8266
393  // ESP8266 timer can accept callback with no parameters, so we have this hack to save a static instance of OpenTherm
394  static OpenTherm *instance; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
395 #endif
396 };
397 
398 } // namespace opentherm
399 } // namespace esphome
Copy of GPIOPin that is safe to use from ISRs (with no virtual functions)
Definition: gpio.h:73
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:325
bool is_idle()
Indicates whether listinig or sending is not in progress.
Definition: opentherm.h:303
bool is_timer_error()
Indicates whether start_esp32_timer_() or stop_timer_() had an error.
Definition: opentherm.h:331
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:257
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:234
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:295
bool is_error()
Indicates whether last listen() or send() operation ends up with an error.
Definition: opentherm.h:311
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
Structure to hold Opentherm data packet content.
Definition: opentherm.h:184
OperationMode get_mode()
Definition: opentherm.h:335
bool is_timeout()
Indicates whether last listen() or send() operation ends up with a timeout error. ...
Definition: opentherm.h:319