ESPHome  2024.11.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 <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 template<class T> constexpr T read_bit(T value, uint8_t bit) { return (value >> bit) & 0x01; }
24 
25 template<class T> constexpr T set_bit(T value, uint8_t bit) { return value |= (1UL << bit); }
26 
27 template<class T> constexpr T clear_bit(T value, uint8_t bit) { return value &= ~(1UL << bit); }
28 
29 template<class T> constexpr T write_bit(T value, uint8_t bit, uint8_t bit_value) {
30  return bit_value ? set_bit(value, bit) : clear_bit(value, bit);
31 }
32 
34  IDLE = 0, // no operation
35 
36  LISTEN = 1, // waiting for transmission to start
37  READ = 2, // reading 32-bit data frame
38  RECEIVED = 3, // data frame received with valid start and stop bit
39 
40  WRITE = 4, // writing data with timer_
41  SENT = 5, // all data written to output
42 
43  ERROR_PROTOCOL = 8, // manchester protocol data transfer error
44  ERROR_TIMEOUT = 9 // read timeout
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  READ_DATA = 0,
57  READ_ACK = 4,
59  WRITE_ACK = 5,
63 };
64 
65 enum MessageId {
66  STATUS = 0,
72  REMOTE = 6,
76  TSP_COUNT = 10,
78  FHB_SIZE = 12,
81  MAX_BOILER_CAPACITY = 15, // u8_hb - u8_lb gives min modulation level
86  DAY_TIME = 20,
87  DATE = 21,
88  YEAR = 22,
90  ROOM_TEMP = 24,
91  FEED_TEMP = 25,
92  DHW_TEMP = 26,
98  DHW2_TEMP = 32,
100  FAN_SPEED = 35,
105  CH_BOUNDS = 49,
110 
111  // HVAC Specific Message IDs
131 
132  RF_SIGNAL = 98,
133  DHW_MODE = 99,
135 
136  // Solar Specific Message IDs
137  SOLAR_MODE_FLAGS = 101, // hb0-2 Controller storage mode
138  // lb0 Device fault
139  // lb1-3 Device mode status
140  // lb4-5 Device status
141  SOLAR_ASF = 102,
149  SOLAR_HOURS = 110,
152 
168 };
169 
170 enum BitPositions { STOP_BIT = 33 };
171 
177  uint8_t type;
178  uint8_t id;
179  uint8_t valueHB;
180  uint8_t valueLB;
181 
182  OpenthermData() : type(0), id(0), valueHB(0), valueLB(0) {}
183 
187  float f88();
188 
192  void f88(float value);
193 
197  uint16_t u16();
198 
202  void u16(uint16_t value);
203 
207  int16_t s16();
208 
212  void s16(int16_t value);
213 };
214 
217  uint32_t capture;
218  uint8_t clock;
219  uint32_t data;
220  uint8_t bit_pos;
221 };
222 
226 class OpenTherm {
227  public:
228  OpenTherm(InternalGPIOPin *in_pin, InternalGPIOPin *out_pin, int32_t device_timeout = 800);
229 
233  bool initialize();
234 
242  void listen();
243 
249  bool has_message() { return mode_ == OperationMode::RECEIVED; }
250 
258  bool get_message(OpenthermData &data);
259 
267  void send(OpenthermData &data);
268 
273  void stop();
274 
280  bool get_protocol_error(OpenThermError &error);
281 
287  bool is_sent() { return mode_ == OperationMode::SENT; }
288 
295  bool is_idle() { return mode_ == OperationMode::IDLE; }
296 
304 
309  bool is_timeout() { return mode_ == OperationMode::ERROR_TIMEOUT; }
310 
316 
317  bool is_active() { return mode_ == LISTEN || mode_ == READ || mode_ == WRITE; }
318 
319  OperationMode get_mode() { return mode_; }
320 
321  std::string debug_data(OpenthermData &data);
322  std::string debug_error(OpenThermError &error);
323 
324  const char *protocol_error_to_to_str(ProtocolErrorType error_type);
325  const char *message_type_to_str(MessageType message_type);
326  const char *operation_mode_to_str(OperationMode mode);
327  const char *message_id_to_str(MessageId id);
328 
329  static bool timer_isr(OpenTherm *arg);
330 
331 #ifdef ESP8266
332  static void esp8266_timer_isr();
333 #endif
334 
335  private:
336  InternalGPIOPin *in_pin_;
337  InternalGPIOPin *out_pin_;
338  ISRInternalGPIOPin isr_in_pin_;
339  ISRInternalGPIOPin isr_out_pin_;
340 
341 #if defined(ESP32) || defined(USE_ESP_IDF)
342  timer_group_t timer_group_;
343  timer_idx_t timer_idx_;
344 #endif
345 
346  OperationMode mode_;
347  ProtocolErrorType error_type_;
348  uint32_t capture_;
349  uint8_t clock_;
350  uint32_t data_;
351  uint8_t bit_pos_;
352  int32_t timeout_counter_; // <0 no timeout
353 
354  int32_t device_timeout_;
355 
356 #if defined(ESP32) || defined(USE_ESP_IDF)
357  bool init_esp32_timer_();
358  void start_esp32_timer_(uint64_t alarm_value);
359 #endif
360 
361  void stop_timer_();
362 
363  void read_(); // data detected start reading
364  void start_read_timer_(); // reading timer_ to sample at 1/5 of manchester code bit length (at 5kHz)
365  void start_write_timer_(); // writing timer_ to send manchester code (at 2kHz)
366  bool check_parity_(uint32_t val);
367 
368  void bit_read_(uint8_t value);
369  ProtocolErrorType verify_stop_bit_(uint8_t value);
370  void write_bit_(uint8_t high, uint8_t clock);
371 
372 #ifdef ESP8266
373  // ESP8266 timer can accept callback with no parameters, so we have this hack to save a static instance of OpenTherm
374  static OpenTherm *instance_;
375 #endif
376 };
377 
378 } // namespace opentherm
379 } // 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:23
bool is_protocol_error()
Indicates whether last listen() or send() operation ends up with a protocol error.
Definition: opentherm.h:315
bool is_idle()
Indicates whether listinig or sending is not in progress.
Definition: opentherm.h:295
mopeka_std_values val[4]
constexpr T write_bit(T value, uint8_t bit, uint8_t bit_value)
Definition: opentherm.h:29
bool has_message()
Use this function to check whether listen() function already captured a valid data packet...
Definition: opentherm.h:249
constexpr T clear_bit(T value, uint8_t bit)
Definition: opentherm.h:27
Opentherm static class that supports either listening or sending Opentherm data packets in the same t...
Definition: opentherm.h:226
BedjetMode mode
BedJet operating mode.
Definition: bedjet_codec.h:183
constexpr T set_bit(T value, uint8_t bit)
Definition: opentherm.h:25
bool is_sent()
Use this function to check whether send() function already finished sending data packed to line...
Definition: opentherm.h:287
bool is_error()
Indicates whether last listen() or send() operation ends up with an error.
Definition: opentherm.h:303
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
Structure to hold Opentherm data packet content.
Definition: opentherm.h:176
OperationMode get_mode()
Definition: opentherm.h:319
bool is_timeout()
Indicates whether last listen() or send() operation ends up with a timeout error. ...
Definition: opentherm.h:309