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