ESPHome  2024.10.2
opentherm.cpp
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 #include "opentherm.h"
9 #include "esphome/core/helpers.h"
10 #if defined(ESP32) || defined(USE_ESP_IDF)
11 #include "driver/timer.h"
12 #include "esp_err.h"
13 #endif
14 #ifdef ESP8266
15 #include "Arduino.h"
16 #endif
17 #include <string>
18 #include <sstream>
19 #include <bitset>
20 
21 namespace esphome {
22 namespace opentherm {
23 
24 using std::string;
25 using std::bitset;
26 using std::stringstream;
27 using std::to_string;
28 
29 static const char *const TAG = "opentherm";
30 
31 #ifdef ESP8266
32 OpenTherm *OpenTherm::instance_ = nullptr;
33 #endif
34 
35 OpenTherm::OpenTherm(InternalGPIOPin *in_pin, InternalGPIOPin *out_pin, int32_t device_timeout)
36  : in_pin_(in_pin),
37  out_pin_(out_pin),
38 #if defined(ESP32) || defined(USE_ESP_IDF)
39  timer_group_(TIMER_GROUP_0),
40  timer_idx_(TIMER_0),
41 #endif
42  mode_(OperationMode::IDLE),
43  error_type_(ProtocolErrorType::NO_ERROR),
44  capture_(0),
45  clock_(0),
46  data_(0),
47  bit_pos_(0),
48  timeout_counter_(-1),
49  device_timeout_(device_timeout) {
50  this->isr_in_pin_ = in_pin->to_isr();
51  this->isr_out_pin_ = out_pin->to_isr();
52 }
53 
55 #ifdef ESP8266
56  OpenTherm::instance_ = this;
57 #endif
58  this->in_pin_->pin_mode(gpio::FLAG_INPUT);
59  this->out_pin_->pin_mode(gpio::FLAG_OUTPUT);
60  this->out_pin_->digital_write(true);
61 
62 #if defined(ESP32) || defined(USE_ESP_IDF)
63  return this->init_esp32_timer_();
64 #else
65  return true;
66 #endif
67 }
68 
70  this->stop_timer_();
71  this->timeout_counter_ = this->device_timeout_ * 5; // timer_ ticks at 5 ticks/ms
72 
73  this->mode_ = OperationMode::LISTEN;
74  this->data_ = 0;
75  this->bit_pos_ = 0;
76 
77  this->start_read_timer_();
78 }
79 
81  this->stop_timer_();
82  this->data_ = data.type;
83  this->data_ = (this->data_ << 12) | data.id;
84  this->data_ = (this->data_ << 8) | data.valueHB;
85  this->data_ = (this->data_ << 8) | data.valueLB;
86  if (!check_parity_(this->data_)) {
87  this->data_ = this->data_ | 0x80000000;
88  }
89 
90  this->clock_ = 1; // clock starts at HIGH
91  this->bit_pos_ = 33; // count down (33 == start bit, 32-1 data, 0 == stop bit)
92  this->mode_ = OperationMode::WRITE;
93 
94  this->start_write_timer_();
95 }
96 
98  if (this->mode_ == OperationMode::RECEIVED) {
99  data.type = (this->data_ >> 28) & 0x7;
100  data.id = (this->data_ >> 16) & 0xFF;
101  data.valueHB = (this->data_ >> 8) & 0xFF;
102  data.valueLB = this->data_ & 0xFF;
103  return true;
104  }
105  return false;
106 }
107 
109  if (this->mode_ != OperationMode::ERROR_PROTOCOL) {
110  return false;
111  }
112 
113  error.error_type = this->error_type_;
114  error.bit_pos = this->bit_pos_;
115  error.capture = this->capture_;
116  error.clock = this->clock_;
117  error.data = this->data_;
118 
119  return true;
120 }
121 
123  this->stop_timer_();
124  this->mode_ = OperationMode::IDLE;
125 }
126 
127 void IRAM_ATTR OpenTherm::read_() {
128  this->data_ = 0;
129  this->bit_pos_ = 0;
130  this->mode_ = OperationMode::READ;
131  this->capture_ = 1; // reset counter and add as if read start bit
132  this->clock_ = 1; // clock is high at the start of comm
133  this->start_read_timer_(); // get us into 1/4 of manchester code. 5 timer ticks constitute 1 ms, which is 1 bit
134  // period in OpenTherm.
135 }
136 
137 bool IRAM_ATTR OpenTherm::timer_isr(OpenTherm *arg) {
138  if (arg->mode_ == OperationMode::LISTEN) {
139  if (arg->timeout_counter_ == 0) {
140  arg->mode_ = OperationMode::ERROR_TIMEOUT;
141  arg->stop_timer_();
142  return false;
143  }
144  bool const value = arg->isr_in_pin_.digital_read();
145  if (value) { // incoming data (rising signal)
146  arg->read_();
147  }
148  if (arg->timeout_counter_ > 0) {
149  arg->timeout_counter_--;
150  }
151  } else if (arg->mode_ == OperationMode::READ) {
152  bool const value = arg->isr_in_pin_.digital_read();
153  uint8_t const last = (arg->capture_ & 1);
154  if (value != last) {
155  // transition of signal from last sampling
156  if (arg->clock_ == 1 && arg->capture_ > 0xF) {
157  // no transition in the middle of the bit
158  arg->mode_ = OperationMode::ERROR_PROTOCOL;
159  arg->error_type_ = ProtocolErrorType::NO_TRANSITION;
160  arg->stop_timer_();
161  return false;
162  } else if (arg->clock_ == 1 || arg->capture_ > 0xF) {
163  // transition in the middle of the bit OR no transition between two bit, both are valid data points
164  if (arg->bit_pos_ == BitPositions::STOP_BIT) {
165  // expecting stop bit
166  auto stop_bit_error = arg->verify_stop_bit_(last);
167  if (stop_bit_error == ProtocolErrorType::NO_ERROR) {
168  arg->mode_ = OperationMode::RECEIVED;
169  arg->stop_timer_();
170  return false;
171  } else {
172  // end of data not verified, invalid data
173  arg->mode_ = OperationMode::ERROR_PROTOCOL;
174  arg->error_type_ = stop_bit_error;
175  arg->stop_timer_();
176  return false;
177  }
178  } else {
179  // normal data point at clock high
180  arg->bit_read_(last);
181  arg->clock_ = 0;
182  }
183  } else {
184  // clock low, not a data point, switch clock
185  arg->clock_ = 1;
186  }
187  arg->capture_ = 1; // reset counter
188  } else if (arg->capture_ > 0xFF) {
189  // no change for too long, invalid mancheter encoding
190  arg->mode_ = OperationMode::ERROR_PROTOCOL;
191  arg->error_type_ = ProtocolErrorType::NO_CHANGE_TOO_LONG;
192  arg->stop_timer_();
193  return false;
194  }
195  arg->capture_ = (arg->capture_ << 1) | value;
196  } else if (arg->mode_ == OperationMode::WRITE) {
197  // write data to pin
198  if (arg->bit_pos_ == 33 || arg->bit_pos_ == 0) { // start bit
199  arg->write_bit_(1, arg->clock_);
200  } else { // data bits
201  arg->write_bit_(read_bit(arg->data_, arg->bit_pos_ - 1), arg->clock_);
202  }
203  if (arg->clock_ == 0) {
204  if (arg->bit_pos_ <= 0) { // check termination
205  arg->mode_ = OperationMode::SENT; // all data written
206  arg->stop_timer_();
207  }
208  arg->bit_pos_--;
209  arg->clock_ = 1;
210  } else {
211  arg->clock_ = 0;
212  }
213  }
214 
215  return false;
216 }
217 
218 #ifdef ESP8266
219 void IRAM_ATTR OpenTherm::esp8266_timer_isr() { OpenTherm::timer_isr(OpenTherm::instance_); }
220 #endif
221 
222 void IRAM_ATTR OpenTherm::bit_read_(uint8_t value) {
223  this->data_ = (this->data_ << 1) | value;
224  this->bit_pos_++;
225 }
226 
227 ProtocolErrorType OpenTherm::verify_stop_bit_(uint8_t value) {
228  if (value) { // stop bit detected
229  return check_parity_(this->data_) ? ProtocolErrorType::NO_ERROR : ProtocolErrorType::PARITY_ERROR;
230  } else { // no stop bit detected, error
232  }
233 }
234 
235 void IRAM_ATTR OpenTherm::write_bit_(uint8_t high, uint8_t clock) {
236  if (clock == 1) { // left part of manchester encoding
237  this->isr_out_pin_.digital_write(!high); // low means logical 1 to protocol
238  } else { // right part of manchester encoding
239  this->isr_out_pin_.digital_write(high); // high means logical 0 to protocol
240  }
241 }
242 
243 #if defined(ESP32) || defined(USE_ESP_IDF)
244 
245 bool OpenTherm::init_esp32_timer_() {
246  // Search for a free timer. Maybe unstable, we'll see.
247  int cur_timer = 0;
248  timer_group_t timer_group = TIMER_GROUP_0;
249  timer_idx_t timer_idx = TIMER_0;
250  bool timer_found = false;
251 
252  for (; cur_timer < SOC_TIMER_GROUP_TOTAL_TIMERS; cur_timer++) {
253  timer_config_t temp_config;
254  timer_group = cur_timer < 2 ? TIMER_GROUP_0 : TIMER_GROUP_1;
255  timer_idx = cur_timer < 2 ? (timer_idx_t) cur_timer : (timer_idx_t) (cur_timer - 2);
256 
257  auto err = timer_get_config(timer_group, timer_idx, &temp_config);
258  if (err == ESP_ERR_INVALID_ARG) {
259  // Error means timer was not initialized (or other things, but we are careful with our args)
260  timer_found = true;
261  break;
262  }
263 
264  ESP_LOGD(TAG, "Timer %d:%d seems to be occupied, will try another", timer_group, timer_idx);
265  }
266 
267  if (!timer_found) {
268  ESP_LOGE(TAG, "No free timer was found! OpenTherm cannot function without a timer.");
269  return false;
270  }
271 
272  ESP_LOGD(TAG, "Found free timer %d:%d", timer_group, timer_idx);
273  this->timer_group_ = timer_group;
274  this->timer_idx_ = timer_idx;
275 
276  timer_config_t const config = {
277  .alarm_en = TIMER_ALARM_EN,
278  .counter_en = TIMER_PAUSE,
279  .intr_type = TIMER_INTR_LEVEL,
280  .counter_dir = TIMER_COUNT_UP,
281  .auto_reload = TIMER_AUTORELOAD_EN,
282 #if ESP_IDF_VERSION_MAJOR >= 5
283  .clk_src = TIMER_SRC_CLK_DEFAULT,
284 #endif
285  .divider = 80,
286  };
287 
288  esp_err_t result;
289 
290  result = timer_init(this->timer_group_, this->timer_idx_, &config);
291  if (result != ESP_OK) {
292  const auto *error = esp_err_to_name(result);
293  ESP_LOGE(TAG, "Failed to init timer. Error: %s", error);
294  return false;
295  }
296 
297  result = timer_set_counter_value(this->timer_group_, this->timer_idx_, 0);
298  if (result != ESP_OK) {
299  const auto *error = esp_err_to_name(result);
300  ESP_LOGE(TAG, "Failed to set counter value. Error: %s", error);
301  return false;
302  }
303 
304  result = timer_isr_callback_add(this->timer_group_, this->timer_idx_, reinterpret_cast<bool (*)(void *)>(timer_isr),
305  this, 0);
306  if (result != ESP_OK) {
307  const auto *error = esp_err_to_name(result);
308  ESP_LOGE(TAG, "Failed to register timer interrupt. Error: %s", error);
309  return false;
310  }
311 
312  return true;
313 }
314 
315 void IRAM_ATTR OpenTherm::start_esp32_timer_(uint64_t alarm_value) {
316  esp_err_t result;
317 
318  result = timer_set_alarm_value(this->timer_group_, this->timer_idx_, alarm_value);
319  if (result != ESP_OK) {
320  const auto *error = esp_err_to_name(result);
321  ESP_LOGE(TAG, "Failed to set alarm value. Error: %s", error);
322  return;
323  }
324 
325  result = timer_start(this->timer_group_, this->timer_idx_);
326  if (result != ESP_OK) {
327  const auto *error = esp_err_to_name(result);
328  ESP_LOGE(TAG, "Failed to start the timer. Error: %s", error);
329  return;
330  }
331 }
332 
333 // 5 kHz timer_
334 void IRAM_ATTR OpenTherm::start_read_timer_() {
335  InterruptLock const lock;
336  this->start_esp32_timer_(200);
337 }
338 
339 // 2 kHz timer_
340 void IRAM_ATTR OpenTherm::start_write_timer_() {
341  InterruptLock const lock;
342  this->start_esp32_timer_(500);
343 }
344 
345 void IRAM_ATTR OpenTherm::stop_timer_() {
346  InterruptLock const lock;
347 
348  esp_err_t result;
349 
350  result = timer_pause(this->timer_group_, this->timer_idx_);
351  if (result != ESP_OK) {
352  const auto *error = esp_err_to_name(result);
353  ESP_LOGE(TAG, "Failed to pause the timer. Error: %s", error);
354  return;
355  }
356 
357  result = timer_set_counter_value(this->timer_group_, this->timer_idx_, 0);
358  if (result != ESP_OK) {
359  const auto *error = esp_err_to_name(result);
360  ESP_LOGE(TAG, "Failed to set timer counter to 0 after pausing. Error: %s", error);
361  return;
362  }
363 }
364 
365 #endif // END ESP32
366 
367 #ifdef ESP8266
368 // 5 kHz timer_
369 void OpenTherm::start_read_timer_() {
370  InterruptLock const lock;
371  timer1_attachInterrupt(OpenTherm::esp8266_timer_isr);
372  timer1_enable(TIM_DIV16, TIM_EDGE, TIM_LOOP); // 5MHz (5 ticks/us - 1677721.4 us max)
373  timer1_write(1000); // 5kHz
374 }
375 
376 // 2 kHz timer_
377 void OpenTherm::start_write_timer_() {
378  InterruptLock const lock;
379  timer1_attachInterrupt(OpenTherm::esp8266_timer_isr);
380  timer1_enable(TIM_DIV16, TIM_EDGE, TIM_LOOP); // 5MHz (5 ticks/us - 1677721.4 us max)
381  timer1_write(2500); // 2kHz
382 }
383 
384 void OpenTherm::stop_timer_() {
385  InterruptLock const lock;
386  timer1_disable();
387  timer1_detachInterrupt();
388 }
389 
390 #endif // END ESP8266
391 
392 // https://stackoverflow.com/questions/21617970/how-to-check-if-value-has-even-parity-of-bits-or-odd
393 bool OpenTherm::check_parity_(uint32_t val) {
394  val ^= val >> 16;
395  val ^= val >> 8;
396  val ^= val >> 4;
397  val ^= val >> 2;
398  val ^= val >> 1;
399  return (~val) & 1;
400 }
401 
402 #define TO_STRING_MEMBER(name) \
403  case name: \
404  return #name;
405 
407  switch (mode) {
408  TO_STRING_MEMBER(IDLE)
409  TO_STRING_MEMBER(LISTEN)
410  TO_STRING_MEMBER(READ)
411  TO_STRING_MEMBER(RECEIVED)
412  TO_STRING_MEMBER(WRITE)
413  TO_STRING_MEMBER(SENT)
414  TO_STRING_MEMBER(ERROR_PROTOCOL)
415  TO_STRING_MEMBER(ERROR_TIMEOUT)
416  default:
417  return "<INVALID>";
418  }
419 }
421  switch (error_type) {
422  TO_STRING_MEMBER(NO_ERROR)
423  TO_STRING_MEMBER(NO_TRANSITION)
424  TO_STRING_MEMBER(INVALID_STOP_BIT)
425  TO_STRING_MEMBER(PARITY_ERROR)
426  TO_STRING_MEMBER(NO_CHANGE_TOO_LONG)
427  default:
428  return "<INVALID>";
429  }
430 }
431 const char *OpenTherm::message_type_to_str(MessageType message_type) {
432  switch (message_type) {
433  TO_STRING_MEMBER(READ_DATA)
434  TO_STRING_MEMBER(READ_ACK)
435  TO_STRING_MEMBER(WRITE_DATA)
436  TO_STRING_MEMBER(WRITE_ACK)
437  TO_STRING_MEMBER(INVALID_DATA)
438  TO_STRING_MEMBER(DATA_INVALID)
439  TO_STRING_MEMBER(UNKNOWN_DATAID)
440  default:
441  return "<INVALID>";
442  }
443 }
444 
446  switch (id) {
447  TO_STRING_MEMBER(STATUS)
448  TO_STRING_MEMBER(CH_SETPOINT)
449  TO_STRING_MEMBER(CONTROLLER_CONFIG)
450  TO_STRING_MEMBER(DEVICE_CONFIG)
451  TO_STRING_MEMBER(COMMAND_CODE)
452  TO_STRING_MEMBER(FAULT_FLAGS)
453  TO_STRING_MEMBER(REMOTE)
454  TO_STRING_MEMBER(COOLING_CONTROL)
455  TO_STRING_MEMBER(CH2_SETPOINT)
456  TO_STRING_MEMBER(CH_SETPOINT_OVERRIDE)
457  TO_STRING_MEMBER(TSP_COUNT)
458  TO_STRING_MEMBER(TSP_COMMAND)
459  TO_STRING_MEMBER(FHB_SIZE)
460  TO_STRING_MEMBER(FHB_COMMAND)
461  TO_STRING_MEMBER(MAX_MODULATION_LEVEL)
462  TO_STRING_MEMBER(MAX_BOILER_CAPACITY)
463  TO_STRING_MEMBER(ROOM_SETPOINT)
464  TO_STRING_MEMBER(MODULATION_LEVEL)
465  TO_STRING_MEMBER(CH_WATER_PRESSURE)
466  TO_STRING_MEMBER(DHW_FLOW_RATE)
467  TO_STRING_MEMBER(DAY_TIME)
468  TO_STRING_MEMBER(DATE)
469  TO_STRING_MEMBER(YEAR)
470  TO_STRING_MEMBER(ROOM_SETPOINT_CH2)
471  TO_STRING_MEMBER(ROOM_TEMP)
472  TO_STRING_MEMBER(FEED_TEMP)
473  TO_STRING_MEMBER(DHW_TEMP)
474  TO_STRING_MEMBER(OUTSIDE_TEMP)
475  TO_STRING_MEMBER(RETURN_WATER_TEMP)
476  TO_STRING_MEMBER(SOLAR_STORE_TEMP)
477  TO_STRING_MEMBER(SOLAR_COLLECT_TEMP)
478  TO_STRING_MEMBER(FEED_TEMP_CH2)
479  TO_STRING_MEMBER(DHW2_TEMP)
480  TO_STRING_MEMBER(EXHAUST_TEMP)
481  TO_STRING_MEMBER(FAN_SPEED)
482  TO_STRING_MEMBER(FLAME_CURRENT)
483  TO_STRING_MEMBER(DHW_BOUNDS)
484  TO_STRING_MEMBER(CH_BOUNDS)
485  TO_STRING_MEMBER(OTC_CURVE_BOUNDS)
486  TO_STRING_MEMBER(DHW_SETPOINT)
487  TO_STRING_MEMBER(MAX_CH_SETPOINT)
488  TO_STRING_MEMBER(OTC_CURVE_RATIO)
489  TO_STRING_MEMBER(HVAC_STATUS)
490  TO_STRING_MEMBER(REL_VENT_SETPOINT)
491  TO_STRING_MEMBER(DEVICE_VENT)
492  TO_STRING_MEMBER(REL_VENTILATION)
493  TO_STRING_MEMBER(REL_HUMID_EXHAUST)
494  TO_STRING_MEMBER(SUPPLY_INLET_TEMP)
495  TO_STRING_MEMBER(SUPPLY_OUTLET_TEMP)
496  TO_STRING_MEMBER(EXHAUST_INLET_TEMP)
497  TO_STRING_MEMBER(EXHAUST_OUTLET_TEMP)
498  TO_STRING_MEMBER(NOM_REL_VENTILATION)
499  TO_STRING_MEMBER(OVERRIDE_FUNC)
500  TO_STRING_MEMBER(OEM_DIAGNOSTIC)
501  TO_STRING_MEMBER(BURNER_STARTS)
502  TO_STRING_MEMBER(CH_PUMP_STARTS)
503  TO_STRING_MEMBER(DHW_PUMP_STARTS)
504  TO_STRING_MEMBER(DHW_BURNER_STARTS)
505  TO_STRING_MEMBER(BURNER_HOURS)
506  TO_STRING_MEMBER(CH_PUMP_HOURS)
507  TO_STRING_MEMBER(DHW_PUMP_HOURS)
508  TO_STRING_MEMBER(DHW_BURNER_HOURS)
509  TO_STRING_MEMBER(OT_VERSION_CONTROLLER)
510  TO_STRING_MEMBER(OT_VERSION_DEVICE)
511  TO_STRING_MEMBER(VERSION_CONTROLLER)
512  TO_STRING_MEMBER(VERSION_DEVICE)
513  default:
514  return "<INVALID>";
515  }
516 }
517 
519  stringstream result;
520  result << bitset<8>(data.type) << " " << bitset<8>(data.id) << " " << bitset<8>(data.valueHB) << " "
521  << bitset<8>(data.valueLB) << "\n";
522  result << "type: " << this->message_type_to_str((MessageType) data.type) << "; ";
523  result << "id: " << to_string(data.id) << "; ";
524  result << "HB: " << to_string(data.valueHB) << "; ";
525  result << "LB: " << to_string(data.valueLB) << "; ";
526  result << "uint_16: " << to_string(data.u16()) << "; ";
527  result << "float: " << to_string(data.f88());
528 
529  return result.str();
530 }
532  stringstream result;
533  result << "type: " << this->protocol_error_to_to_str(error.error_type) << "; ";
534  result << "data: ";
535  result << format_hex(error.data);
536  result << "; clock: " << to_string(clock_);
537  result << "; capture: " << bitset<32>(error.capture);
538  result << "; bit_pos: " << to_string(error.bit_pos);
539 
540  return result.str();
541 }
542 
543 float OpenthermData::f88() { return ((float) this->s16()) / 256.0; }
544 
545 void OpenthermData::f88(float value) { this->s16((int16_t) (value * 256)); }
546 
547 uint16_t OpenthermData::u16() {
548  uint16_t const value = this->valueHB;
549  return (value << 8) | this->valueLB;
550 }
551 
552 void OpenthermData::u16(uint16_t value) {
553  this->valueLB = value & 0xFF;
554  this->valueHB = (value >> 8) & 0xFF;
555 }
556 
558  int16_t const value = this->valueHB;
559  return (value << 8) | this->valueLB;
560 }
561 
562 void OpenthermData::s16(int16_t value) {
563  this->valueLB = value & 0xFF;
564  this->valueHB = (value >> 8) & 0xFF;
565 }
566 
567 } // namespace opentherm
568 } // namespace esphome
void listen()
Start listening for Opentherm data packet comming from line connected to given pin.
Definition: opentherm.cpp:69
virtual void digital_write(bool value)=0
constexpr T read_bit(T value, uint8_t bit)
Definition: opentherm.h:24
std::string format_hex(const uint8_t *data, size_t length)
Format the byte array data of length len in lowercased hex.
Definition: helpers.cpp:347
void send(OpenthermData &data)
Immediately send out Opentherm data packet to line connected on given pin.
Definition: opentherm.cpp:80
const char * to_string(SHTCXType type)
Definition: shtcx.cpp:16
virtual void pin_mode(gpio::Flags flags)=0
void stop()
Stops listening for data packet or sending out data packet and resets internal state of this class...
Definition: opentherm.cpp:122
const char * operation_mode_to_str(OperationMode mode)
Definition: opentherm.cpp:406
mopeka_std_values val[4]
bool get_message(OpenthermData &data)
Use this to retrive data packed captured by listen() function.
Definition: opentherm.cpp:97
timeout while waiting to receive bytes
Definition: i2c_bus.h:16
No error found during execution of method.
Definition: i2c_bus.h:12
static bool timer_isr(OpenTherm *arg)
Definition: opentherm.cpp:137
const char * message_id_to_str(MessageId id)
Definition: opentherm.cpp:445
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
OpenTherm(InternalGPIOPin *in_pin, InternalGPIOPin *out_pin, int32_t device_timeout=800)
Definition: opentherm.cpp:35
std::string debug_data(OpenthermData &data)
Definition: opentherm.cpp:518
virtual ISRInternalGPIOPin to_isr() const =0
Helper class to disable interrupts.
Definition: helpers.h:593
std::string to_string(int value)
Definition: helpers.cpp:80
const char * protocol_error_to_to_str(ProtocolErrorType error_type)
Definition: opentherm.cpp:420
bool initialize()
Setup pins.
Definition: opentherm.cpp:54
const char * message_type_to_str(MessageType message_type)
Definition: opentherm.cpp:431
bool get_protocol_error(OpenThermError &error)
Get protocol error details in case a protocol error occured.
Definition: opentherm.cpp:108
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
Structure to hold Opentherm data packet content.
Definition: opentherm.h:144
std::string debug_error(OpenThermError &error)
Definition: opentherm.cpp:531
void digital_write(bool value)
Definition: gpio.cpp:121