ESPHome 2026.5.0-dev
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#include <cinttypes>
11#ifdef USE_ESP32
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;
23
24static const char *const TAG = "opentherm";
25
26#ifdef ESP8266
27OpenTherm *OpenTherm::instance = nullptr;
28#endif
29
30OpenTherm::OpenTherm(InternalGPIOPin *in_pin, InternalGPIOPin *out_pin, int32_t device_timeout)
31 : in_pin_(in_pin),
32 out_pin_(out_pin),
33 mode_(OperationMode::IDLE),
34 error_type_(ProtocolErrorType::NO_ERROR),
35 capture_(0),
36 clock_(0),
37 data_(0),
38 bit_pos_(0),
39 timeout_counter_(-1),
40 device_timeout_(device_timeout) {
41 this->isr_in_pin_ = in_pin->to_isr();
42 this->isr_out_pin_ = out_pin->to_isr();
43}
44
45bool OpenTherm::initialize() {
46#ifdef ESP8266
47 OpenTherm::instance = this;
48#endif
49 this->in_pin_->pin_mode(gpio::FLAG_INPUT);
50 this->in_pin_->setup();
51 this->out_pin_->pin_mode(gpio::FLAG_OUTPUT);
52 this->out_pin_->setup();
53 this->out_pin_->digital_write(true);
54
55#ifdef USE_ESP32
56 return this->init_esp32_timer_();
57#else
58 return true;
59#endif
60}
61
62void OpenTherm::listen() {
63 this->stop_timer_();
64 this->timeout_counter_ = this->device_timeout_ * 5; // timer_ ticks at 5 ticks/ms
65
66 this->mode_ = OperationMode::LISTEN;
67 this->data_ = 0;
68 this->bit_pos_ = 0;
69
70 this->start_read_timer_();
71}
72
73void OpenTherm::send(OpenthermData &data) {
74 this->stop_timer_();
75 this->data_ = data.type;
76 this->data_ = (this->data_ << 12) | data.id;
77 this->data_ = (this->data_ << 8) | data.valueHB;
78 this->data_ = (this->data_ << 8) | data.valueLB;
79 if (!check_parity_(this->data_)) {
80 this->data_ = this->data_ | 0x80000000;
81 }
82
83 this->clock_ = 1; // clock starts at HIGH
84 this->bit_pos_ = 33; // count down (33 == start bit, 32-1 data, 0 == stop bit)
85 this->mode_ = OperationMode::WRITE;
86
87 this->start_write_timer_();
88}
89
90bool OpenTherm::get_message(OpenthermData &data) {
91 if (this->mode_ == OperationMode::RECEIVED) {
92 data.type = (this->data_ >> 28) & 0x7;
93 data.id = (this->data_ >> 16) & 0xFF;
94 data.valueHB = (this->data_ >> 8) & 0xFF;
95 data.valueLB = this->data_ & 0xFF;
96 return true;
97 }
98 return false;
99}
100
101bool OpenTherm::get_protocol_error(OpenThermError &error) {
102 if (this->mode_ != OperationMode::ERROR_PROTOCOL) {
103 return false;
104 }
105
106 error.error_type = this->error_type_;
107 error.bit_pos = this->bit_pos_;
108 error.capture = this->capture_;
109 error.clock = this->clock_;
110 error.data = this->data_;
111
112 return true;
113}
114
115void OpenTherm::stop() {
116 this->stop_timer_();
117 this->mode_ = OperationMode::IDLE;
118}
119
120void IRAM_ATTR OpenTherm::read_() {
121 this->data_ = 0;
122 this->bit_pos_ = 0;
123 this->mode_ = OperationMode::READ;
124 this->capture_ = 1; // reset counter and add as if read start bit
125 this->clock_ = 1; // clock is high at the start of comm
126 this->start_read_timer_(); // get us into 1/4 of manchester code. 5 timer ticks constitute 1 ms, which is 1 bit
127 // period in OpenTherm.
128}
129
130#ifdef USE_ESP32
131bool IRAM_ATTR OpenTherm::timer_isr(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx) {
132 auto *arg = static_cast<OpenTherm *>(user_ctx);
133#else
134bool IRAM_ATTR OpenTherm::timer_isr(OpenTherm *arg) {
135#endif
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
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
229 return ProtocolErrorType::INVALID_STOP_BIT;
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#ifdef USE_ESP32
242
243bool OpenTherm::init_esp32_timer_() {
244 // 80MHz / 80 = 1MHz resolution (1µs per tick)
245 gptimer_config_t config = {
246 .clk_src = GPTIMER_CLK_SRC_DEFAULT,
247 .direction = GPTIMER_COUNT_UP,
248 .resolution_hz = 1000000,
249 };
250
251 esp_err_t result = gptimer_new_timer(&config, &this->timer_handle_);
252 if (result != ESP_OK) {
253 ESP_LOGE(TAG, "Failed to create timer: %s", esp_err_to_name(result));
254 return false;
255 }
256
257 gptimer_event_callbacks_t cbs = {
258 .on_alarm = OpenTherm::timer_isr,
259 };
260 result = gptimer_register_event_callbacks(this->timer_handle_, &cbs, this);
261 if (result != ESP_OK) {
262 ESP_LOGE(TAG, "Failed to register timer callback: %s", esp_err_to_name(result));
263 gptimer_del_timer(this->timer_handle_);
264 this->timer_handle_ = nullptr;
265 return false;
266 }
267
268 result = gptimer_enable(this->timer_handle_);
269 if (result != ESP_OK) {
270 ESP_LOGE(TAG, "Failed to enable timer: %s", esp_err_to_name(result));
271 gptimer_del_timer(this->timer_handle_);
272 this->timer_handle_ = nullptr;
273 return false;
274 }
275
276 return true;
277}
278
279void IRAM_ATTR OpenTherm::start_esp32_timer_(uint64_t alarm_value) {
280 // We will report timer errors outside of interrupt handler
281 this->timer_error_ = ESP_OK;
282 this->timer_error_type_ = TimerErrorType::NO_TIMER_ERROR;
283
284 this->alarm_config_.alarm_count = alarm_value;
285 this->timer_error_ = gptimer_set_alarm_action(this->timer_handle_, &this->alarm_config_);
286 if (this->timer_error_ != ESP_OK) {
287 this->timer_error_type_ = TimerErrorType::SET_ALARM_VALUE_ERROR;
288 return;
289 }
290 this->timer_error_ = gptimer_start(this->timer_handle_);
291 if (this->timer_error_ != ESP_OK) {
292 this->timer_error_type_ = TimerErrorType::TIMER_START_ERROR;
293 }
294}
295
296void OpenTherm::report_and_reset_timer_error() {
297 if (this->timer_error_ == ESP_OK) {
298 return;
299 }
300
301 ESP_LOGE(TAG, "Error occured while manipulating timer (%s): %s", this->timer_error_to_str(this->timer_error_type_),
302 esp_err_to_name(this->timer_error_));
303
304 this->timer_error_ = ESP_OK;
305 this->timer_error_type_ = NO_TIMER_ERROR;
306}
307
308// 5 kHz timer_
309void IRAM_ATTR OpenTherm::start_read_timer_() {
310 InterruptLock const lock;
311 this->start_esp32_timer_(200);
312}
313
314// 2 kHz timer_
315void IRAM_ATTR OpenTherm::start_write_timer_() {
316 InterruptLock const lock;
317 this->start_esp32_timer_(500);
318}
319
320void IRAM_ATTR OpenTherm::stop_timer_() {
321 InterruptLock const lock;
322 // We will report timer errors outside of interrupt handler
323 this->timer_error_ = ESP_OK;
324 this->timer_error_type_ = TimerErrorType::NO_TIMER_ERROR;
325
326 this->timer_error_ = gptimer_stop(this->timer_handle_);
327 if (this->timer_error_ != ESP_OK) {
328 this->timer_error_type_ = TimerErrorType::TIMER_PAUSE_ERROR;
329 return;
330 }
331 this->timer_error_ = gptimer_set_raw_count(this->timer_handle_, 0);
332 if (this->timer_error_ != ESP_OK) {
333 this->timer_error_type_ = TimerErrorType::SET_COUNTER_VALUE_ERROR;
334 }
335}
336
337#endif // USE_ESP32
338
339#ifdef ESP8266
340// 5 kHz timer_
341void IRAM_ATTR OpenTherm::start_read_timer_() {
342 InterruptLock const lock;
343 timer1_attachInterrupt(OpenTherm::esp8266_timer_isr);
344 timer1_enable(TIM_DIV16, TIM_EDGE, TIM_LOOP); // 5MHz (5 ticks/us - 1677721.4 us max)
345 timer1_write(1000); // 5kHz
346}
347
348// 2 kHz timer_
349void IRAM_ATTR OpenTherm::start_write_timer_() {
350 InterruptLock const lock;
351 timer1_attachInterrupt(OpenTherm::esp8266_timer_isr);
352 timer1_enable(TIM_DIV16, TIM_EDGE, TIM_LOOP); // 5MHz (5 ticks/us - 1677721.4 us max)
353 timer1_write(2500); // 2kHz
354}
355
356void IRAM_ATTR OpenTherm::stop_timer_() {
357 InterruptLock const lock;
358 timer1_disable();
359 timer1_detachInterrupt();
360}
361
362// There is nothing to report on ESP8266
363void OpenTherm::report_and_reset_timer_error() {}
364
365#endif // END ESP8266
366
367// https://stackoverflow.com/questions/21617970/how-to-check-if-value-has-even-parity-of-bits-or-odd
368bool IRAM_ATTR OpenTherm::check_parity_(uint32_t val) {
369 val ^= val >> 16;
370 val ^= val >> 8;
371 val ^= val >> 4;
372 val ^= val >> 2;
373 val ^= val >> 1;
374 return (~val) & 1;
375}
376
377#define TO_STRING_MEMBER(name) \
378 case name: \
379 return #name;
380
381const char *OpenTherm::operation_mode_to_str(OperationMode mode) {
382 switch (mode) {
383 TO_STRING_MEMBER(IDLE)
384 TO_STRING_MEMBER(LISTEN)
385 TO_STRING_MEMBER(READ)
386 TO_STRING_MEMBER(RECEIVED)
387 TO_STRING_MEMBER(WRITE)
388 TO_STRING_MEMBER(SENT)
389 TO_STRING_MEMBER(ERROR_PROTOCOL)
390 TO_STRING_MEMBER(ERROR_TIMEOUT)
391 TO_STRING_MEMBER(ERROR_TIMER)
392 default:
393 return "<INVALID>";
394 }
395}
396const char *OpenTherm::protocol_error_to_str(ProtocolErrorType error_type) {
397 switch (error_type) {
398 TO_STRING_MEMBER(NO_ERROR)
399 TO_STRING_MEMBER(NO_TRANSITION)
400 TO_STRING_MEMBER(INVALID_STOP_BIT)
401 TO_STRING_MEMBER(PARITY_ERROR)
402 TO_STRING_MEMBER(NO_CHANGE_TOO_LONG)
403 default:
404 return "<INVALID>";
405 }
406}
407const char *OpenTherm::timer_error_to_str(TimerErrorType error_type) {
408 switch (error_type) {
409 TO_STRING_MEMBER(NO_TIMER_ERROR)
410 TO_STRING_MEMBER(SET_ALARM_VALUE_ERROR)
411 TO_STRING_MEMBER(TIMER_START_ERROR)
412 TO_STRING_MEMBER(TIMER_PAUSE_ERROR)
413 TO_STRING_MEMBER(SET_COUNTER_VALUE_ERROR)
414 default:
415 return "<INVALID>";
416 }
417}
418const char *OpenTherm::message_type_to_str(MessageType message_type) {
419 switch (message_type) {
420 TO_STRING_MEMBER(READ_DATA)
421 TO_STRING_MEMBER(READ_ACK)
422 TO_STRING_MEMBER(WRITE_DATA)
423 TO_STRING_MEMBER(WRITE_ACK)
424 TO_STRING_MEMBER(INVALID_DATA)
425 TO_STRING_MEMBER(DATA_INVALID)
426 TO_STRING_MEMBER(UNKNOWN_DATAID)
427 default:
428 return "<INVALID>";
429 }
430}
431
432const char *OpenTherm::message_id_to_str(MessageId id) {
433 switch (id) {
434 TO_STRING_MEMBER(STATUS)
435 TO_STRING_MEMBER(CH_SETPOINT)
436 TO_STRING_MEMBER(CONTROLLER_CONFIG)
437 TO_STRING_MEMBER(DEVICE_CONFIG)
438 TO_STRING_MEMBER(COMMAND_CODE)
439 TO_STRING_MEMBER(FAULT_FLAGS)
440 TO_STRING_MEMBER(REMOTE)
441 TO_STRING_MEMBER(COOLING_CONTROL)
442 TO_STRING_MEMBER(CH2_SETPOINT)
443 TO_STRING_MEMBER(CH_SETPOINT_OVERRIDE)
444 TO_STRING_MEMBER(TSP_COUNT)
445 TO_STRING_MEMBER(TSP_COMMAND)
446 TO_STRING_MEMBER(FHB_SIZE)
447 TO_STRING_MEMBER(FHB_COMMAND)
448 TO_STRING_MEMBER(MAX_MODULATION_LEVEL)
449 TO_STRING_MEMBER(MAX_BOILER_CAPACITY)
450 TO_STRING_MEMBER(ROOM_SETPOINT)
451 TO_STRING_MEMBER(MODULATION_LEVEL)
452 TO_STRING_MEMBER(CH_WATER_PRESSURE)
453 TO_STRING_MEMBER(DHW_FLOW_RATE)
454 TO_STRING_MEMBER(DAY_TIME)
455 TO_STRING_MEMBER(DATE)
456 TO_STRING_MEMBER(YEAR)
457 TO_STRING_MEMBER(ROOM_SETPOINT_CH2)
458 TO_STRING_MEMBER(ROOM_TEMP)
459 TO_STRING_MEMBER(FEED_TEMP)
460 TO_STRING_MEMBER(DHW_TEMP)
461 TO_STRING_MEMBER(OUTSIDE_TEMP)
462 TO_STRING_MEMBER(RETURN_WATER_TEMP)
463 TO_STRING_MEMBER(SOLAR_STORE_TEMP)
464 TO_STRING_MEMBER(SOLAR_COLLECT_TEMP)
465 TO_STRING_MEMBER(FEED_TEMP_CH2)
466 TO_STRING_MEMBER(DHW2_TEMP)
467 TO_STRING_MEMBER(EXHAUST_TEMP)
468 TO_STRING_MEMBER(FAN_SPEED)
469 TO_STRING_MEMBER(FLAME_CURRENT)
470 TO_STRING_MEMBER(ROOM_TEMP_CH2)
471 TO_STRING_MEMBER(REL_HUMIDITY)
472 TO_STRING_MEMBER(DHW_BOUNDS)
473 TO_STRING_MEMBER(CH_BOUNDS)
474 TO_STRING_MEMBER(OTC_CURVE_BOUNDS)
475 TO_STRING_MEMBER(DHW_SETPOINT)
476 TO_STRING_MEMBER(MAX_CH_SETPOINT)
477 TO_STRING_MEMBER(OTC_CURVE_RATIO)
478 TO_STRING_MEMBER(HVAC_STATUS)
479 TO_STRING_MEMBER(REL_VENT_SETPOINT)
480 TO_STRING_MEMBER(DEVICE_VENT)
481 TO_STRING_MEMBER(HVAC_VER_ID)
482 TO_STRING_MEMBER(REL_VENTILATION)
483 TO_STRING_MEMBER(REL_HUMID_EXHAUST)
484 TO_STRING_MEMBER(EXHAUST_CO2)
485 TO_STRING_MEMBER(SUPPLY_INLET_TEMP)
486 TO_STRING_MEMBER(SUPPLY_OUTLET_TEMP)
487 TO_STRING_MEMBER(EXHAUST_INLET_TEMP)
488 TO_STRING_MEMBER(EXHAUST_OUTLET_TEMP)
489 TO_STRING_MEMBER(EXHAUST_FAN_SPEED)
490 TO_STRING_MEMBER(SUPPLY_FAN_SPEED)
491 TO_STRING_MEMBER(REMOTE_VENTILATION_PARAM)
492 TO_STRING_MEMBER(NOM_REL_VENTILATION)
493 TO_STRING_MEMBER(HVAC_NUM_TSP)
494 TO_STRING_MEMBER(HVAC_IDX_TSP)
495 TO_STRING_MEMBER(HVAC_FHB_SIZE)
496 TO_STRING_MEMBER(HVAC_FHB_IDX)
497 TO_STRING_MEMBER(RF_SIGNAL)
498 TO_STRING_MEMBER(DHW_MODE)
499 TO_STRING_MEMBER(OVERRIDE_FUNC)
500 TO_STRING_MEMBER(SOLAR_MODE_FLAGS)
501 TO_STRING_MEMBER(SOLAR_ASF)
502 TO_STRING_MEMBER(SOLAR_VERSION_ID)
503 TO_STRING_MEMBER(SOLAR_PRODUCT_ID)
504 TO_STRING_MEMBER(SOLAR_NUM_TSP)
505 TO_STRING_MEMBER(SOLAR_IDX_TSP)
506 TO_STRING_MEMBER(SOLAR_FHB_SIZE)
507 TO_STRING_MEMBER(SOLAR_FHB_IDX)
508 TO_STRING_MEMBER(SOLAR_STARTS)
509 TO_STRING_MEMBER(SOLAR_HOURS)
510 TO_STRING_MEMBER(SOLAR_ENERGY)
511 TO_STRING_MEMBER(SOLAR_TOTAL_ENERGY)
512 TO_STRING_MEMBER(FAILED_BURNER_STARTS)
513 TO_STRING_MEMBER(BURNER_FLAME_LOW)
514 TO_STRING_MEMBER(OEM_DIAGNOSTIC)
515 TO_STRING_MEMBER(BURNER_STARTS)
516 TO_STRING_MEMBER(CH_PUMP_STARTS)
517 TO_STRING_MEMBER(DHW_PUMP_STARTS)
518 TO_STRING_MEMBER(DHW_BURNER_STARTS)
519 TO_STRING_MEMBER(BURNER_HOURS)
520 TO_STRING_MEMBER(CH_PUMP_HOURS)
521 TO_STRING_MEMBER(DHW_PUMP_HOURS)
522 TO_STRING_MEMBER(DHW_BURNER_HOURS)
523 TO_STRING_MEMBER(OT_VERSION_CONTROLLER)
524 TO_STRING_MEMBER(OT_VERSION_DEVICE)
525 TO_STRING_MEMBER(VERSION_CONTROLLER)
526 TO_STRING_MEMBER(VERSION_DEVICE)
527 default:
528 return "<INVALID>";
529 }
530}
531
532void OpenTherm::debug_data(OpenthermData &data) {
533 char type_buf[9], id_buf[9], hb_buf[9], lb_buf[9];
534 ESP_LOGD(TAG, "%s %s %s %s", format_bin_to(type_buf, data.type), format_bin_to(id_buf, data.id),
535 format_bin_to(hb_buf, data.valueHB), format_bin_to(lb_buf, data.valueLB));
536 ESP_LOGD(TAG, "type: %s; id: %u; HB: %u; LB: %u; uint_16: %u; float: %f",
537 this->message_type_to_str((MessageType) data.type), data.id, data.valueHB, data.valueLB, data.u16(),
538 data.f88());
539}
540void OpenTherm::debug_error(OpenThermError &error) const {
541 ESP_LOGD(TAG, "data: 0x%08" PRIx32 "; clock: %u; capture: 0x%08" PRIx32 "; bit_pos: %u", error.data, this->clock_,
542 error.capture, error.bit_pos);
543}
544
545float OpenthermData::f88() { return ((float) this->s16()) / 256.0; }
546
547void OpenthermData::f88(float value) { this->s16((int16_t) (value * 256)); }
548
549uint16_t OpenthermData::u16() {
550 uint16_t const value = this->valueHB;
551 return (value << 8) | this->valueLB;
552}
553
554void OpenthermData::u16(uint16_t value) {
555 this->valueLB = value & 0xFF;
556 this->valueHB = (value >> 8) & 0xFF;
557}
558
559int16_t OpenthermData::s16() {
560 int16_t const value = this->valueHB;
561 return (value << 8) | this->valueLB;
562}
563
564void OpenthermData::s16(int16_t value) {
565 this->valueLB = value & 0xFF;
566 this->valueHB = (value >> 8) & 0xFF;
567}
568
569} // namespace opentherm
570} // namespace esphome
BedjetMode mode
BedJet operating mode.
OpenTherm(InternalGPIOPin *in_pin, InternalGPIOPin *out_pin, int32_t device_timeout=800)
mopeka_std_values val[3]
const std::vector< uint8_t > & data
constexpr T read_bit(T value, uint8_t bit)
Definition opentherm.h:22
const char *const TAG
Definition spi.cpp:7
const char * message_type_to_str(MessageType t)
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
char * format_bin_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length)
Format byte array as binary string to buffer.
Definition helpers.cpp:482
static void uint32_t