ESPHome 2025.5.0
Loading...
Searching...
No Matches
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"
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
19namespace esphome {
20namespace opentherm {
21
22using std::string;
23using std::to_string;
24
25static const char *const TAG = "opentherm";
26
27#ifdef ESP8266
28OpenTherm *OpenTherm::instance = nullptr;
29#endif
30
31OpenTherm::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
125void 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
135bool 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
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
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
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
217void IRAM_ATTR OpenTherm::esp8266_timer_isr() { OpenTherm::timer_isr(OpenTherm::instance); }
218#endif
219
220void IRAM_ATTR OpenTherm::bit_read_(uint8_t value) {
221 this->data_ = (this->data_ << 1) | value;
222 this->bit_pos_++;
223}
224
225ProtocolErrorType 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
233void 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
243bool 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
316void 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_
345void IRAM_ATTR OpenTherm::start_read_timer_() {
346 InterruptLock const lock;
347 this->start_esp32_timer_(200);
348}
349
350// 2 kHz timer_
351void IRAM_ATTR OpenTherm::start_write_timer_() {
352 InterruptLock const lock;
353 this->start_esp32_timer_(500);
354}
355
356void 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_
377void 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_
385void 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
392void 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
404bool 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}
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
581float OpenthermData::f88() { return ((float) this->s16()) / 256.0; }
582
583void OpenthermData::f88(float value) { this->s16((int16_t) (value * 256)); }
584
586 uint16_t const value = this->valueHB;
587 return (value << 8) | this->valueLB;
588}
589
590void 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
600void OpenthermData::s16(int16_t value) {
601 this->valueLB = value & 0xFF;
602 this->valueHB = (value >> 8) & 0xFF;
603}
604
605} // namespace opentherm
606} // namespace esphome
BedjetMode mode
BedJet operating mode.
virtual void pin_mode(gpio::Flags flags)=0
virtual void setup()=0
virtual void digital_write(bool value)=0
void digital_write(bool value)
Definition gpio.cpp:147
virtual ISRInternalGPIOPin to_isr() const =0
Helper class to disable interrupts.
Definition helpers.h:614
Opentherm static class that supports either listening or sending Opentherm data packets in the same t...
Definition opentherm.h:234
void debug_error(OpenThermError &error) const
const char * message_id_to_str(MessageId id)
void listen()
Start listening for Opentherm data packet comming from line connected to given pin.
Definition opentherm.cpp:67
bool get_message(OpenthermData &data)
Use this to retrive data packed captured by listen() function.
Definition opentherm.cpp:95
static bool timer_isr(OpenTherm *arg)
void send(OpenthermData &data)
Immediately send out Opentherm data packet to line connected on given pin.
Definition opentherm.cpp:78
const char * timer_error_to_str(TimerErrorType error_type)
bool initialize()
Setup pins.
Definition opentherm.cpp:50
const char * operation_mode_to_str(OperationMode mode)
OpenTherm(InternalGPIOPin *in_pin, InternalGPIOPin *out_pin, int32_t device_timeout=800)
Definition opentherm.cpp:31
void debug_data(OpenthermData &data)
const char * protocol_error_to_str(ProtocolErrorType error_type)
const char * message_type_to_str(MessageType message_type)
bool get_protocol_error(OpenThermError &error)
Get protocol error details in case a protocol error occured.
void stop()
Stops listening for data packet or sending out data packet and resets internal state of this class.
mopeka_std_values val[4]
@ FLAG_OUTPUT
Definition gpio.h:19
@ FLAG_INPUT
Definition gpio.h:18
constexpr T read_bit(T value, uint8_t bit)
Definition opentherm.h:22
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
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:360
std::string format_bin(const uint8_t *data, size_t length)
Format the byte array data of length len in binary.
Definition helpers.cpp:408
Structure to hold Opentherm data packet content.
Definition opentherm.h:184