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