23static const char *
const TAG =
"opentherm";
26OpenTherm *OpenTherm::instance =
nullptr;
39 device_timeout_(device_timeout) {
40 this->isr_in_pin_ = in_pin->to_isr();
41 this->isr_out_pin_ = out_pin->to_isr();
44bool OpenTherm::initialize() {
46 OpenTherm::instance =
this;
48 this->in_pin_->pin_mode(gpio::FLAG_INPUT);
49 this->in_pin_->setup();
50 this->out_pin_->pin_mode(gpio::FLAG_OUTPUT);
51 this->out_pin_->setup();
52 this->out_pin_->digital_write(
true);
55 return this->init_esp32_timer_();
61void OpenTherm::listen() {
63 this->timeout_counter_ = this->device_timeout_ * 5;
65 this->mode_ = OperationMode::LISTEN;
69 this->start_read_timer_();
72void OpenTherm::send(OpenthermData &data) {
74 this->data_ =
data.type;
75 this->data_ = (this->data_ << 12) |
data.id;
76 this->data_ = (this->data_ << 8) |
data.valueHB;
77 this->data_ = (this->data_ << 8) |
data.valueLB;
78 if (!check_parity_(this->data_)) {
79 this->data_ = this->data_ | 0x80000000;
84 this->mode_ = OperationMode::WRITE;
86 this->start_write_timer_();
89bool OpenTherm::get_message(OpenthermData &data) {
90 if (this->mode_ == OperationMode::RECEIVED) {
91 data.type = (this->data_ >> 28) & 0x7;
92 data.id = (this->data_ >> 16) & 0xFF;
93 data.valueHB = (this->data_ >> 8) & 0xFF;
94 data.valueLB = this->data_ & 0xFF;
100bool OpenTherm::get_protocol_error(OpenThermError &error) {
101 if (this->mode_ != OperationMode::ERROR_PROTOCOL) {
105 error.error_type = this->error_type_;
106 error.bit_pos = this->bit_pos_;
107 error.capture = this->capture_;
108 error.clock = this->clock_;
109 error.data = this->data_;
114void OpenTherm::stop() {
116 this->mode_ = OperationMode::IDLE;
119void IRAM_ATTR OpenTherm::read_() {
122 this->mode_ = OperationMode::READ;
125 this->start_read_timer_();
130bool IRAM_ATTR OpenTherm::timer_isr(gptimer_handle_t timer,
const gptimer_alarm_event_data_t *edata,
void *user_ctx) {
131 auto *arg =
static_cast<OpenTherm *
>(user_ctx);
133bool IRAM_ATTR OpenTherm::timer_isr(OpenTherm *arg) {
135 if (arg->mode_ == OperationMode::LISTEN) {
136 if (arg->timeout_counter_ == 0) {
137 arg->mode_ = OperationMode::ERROR_TIMEOUT;
141 bool const value = arg->isr_in_pin_.digital_read();
145 if (arg->timeout_counter_ > 0) {
146 arg->timeout_counter_--;
148 }
else if (arg->mode_ == OperationMode::READ) {
149 bool const value = arg->isr_in_pin_.digital_read();
150 uint8_t
const last = (arg->capture_ & 1);
153 if (arg->clock_ == 1 && arg->capture_ > 0xF) {
155 arg->mode_ = OperationMode::ERROR_PROTOCOL;
156 arg->error_type_ = ProtocolErrorType::NO_TRANSITION;
159 }
else if (arg->clock_ == 1 || arg->capture_ > 0xF) {
161 if (arg->bit_pos_ == BitPositions::STOP_BIT) {
163 auto stop_bit_error = arg->verify_stop_bit_(last);
164 if (stop_bit_error == ProtocolErrorType::NO_ERROR) {
165 arg->mode_ = OperationMode::RECEIVED;
170 arg->mode_ = OperationMode::ERROR_PROTOCOL;
171 arg->error_type_ = stop_bit_error;
177 arg->bit_read_(last);
185 }
else if (arg->capture_ > 0xFF) {
187 arg->mode_ = OperationMode::ERROR_PROTOCOL;
188 arg->error_type_ = ProtocolErrorType::NO_CHANGE_TOO_LONG;
192 arg->capture_ = (arg->capture_ << 1) | value;
193 }
else if (arg->mode_ == OperationMode::WRITE) {
195 if (arg->bit_pos_ == 33 || arg->bit_pos_ == 0) {
196 arg->write_bit_(1, arg->clock_);
198 arg->write_bit_(
read_bit(arg->data_, arg->bit_pos_ - 1), arg->clock_);
200 if (arg->clock_ == 0) {
201 if (arg->bit_pos_ <= 0) {
202 arg->mode_ = OperationMode::SENT;
216void IRAM_ATTR OpenTherm::esp8266_timer_isr() { OpenTherm::timer_isr(OpenTherm::instance); }
219void IRAM_ATTR OpenTherm::bit_read_(uint8_t value) {
220 this->data_ = (this->data_ << 1) | value;
226 return check_parity_(this->data_) ? ProtocolErrorType::NO_ERROR : ProtocolErrorType::PARITY_ERROR;
228 return ProtocolErrorType::INVALID_STOP_BIT;
232void IRAM_ATTR OpenTherm::write_bit_(uint8_t high, uint8_t clock) {
234 this->isr_out_pin_.digital_write(!high);
236 this->isr_out_pin_.digital_write(high);
242bool OpenTherm::init_esp32_timer_() {
244 gptimer_config_t config = {
245 .clk_src = GPTIMER_CLK_SRC_DEFAULT,
246 .direction = GPTIMER_COUNT_UP,
247 .resolution_hz = 1000000,
250 esp_err_t result = gptimer_new_timer(&config, &this->timer_handle_);
251 if (result != ESP_OK) {
252 ESP_LOGE(TAG,
"Failed to create timer: %s", esp_err_to_name(result));
256 gptimer_event_callbacks_t cbs = {
257 .on_alarm = OpenTherm::timer_isr,
259 result = gptimer_register_event_callbacks(this->timer_handle_, &cbs,
this);
260 if (result != ESP_OK) {
261 ESP_LOGE(TAG,
"Failed to register timer callback: %s", esp_err_to_name(result));
262 gptimer_del_timer(this->timer_handle_);
263 this->timer_handle_ =
nullptr;
267 result = gptimer_enable(this->timer_handle_);
268 if (result != ESP_OK) {
269 ESP_LOGE(TAG,
"Failed to enable timer: %s", esp_err_to_name(result));
270 gptimer_del_timer(this->timer_handle_);
271 this->timer_handle_ =
nullptr;
278void IRAM_ATTR OpenTherm::start_esp32_timer_(uint64_t alarm_value) {
280 this->timer_error_ = ESP_OK;
281 this->timer_error_type_ = TimerErrorType::NO_TIMER_ERROR;
283 this->alarm_config_.alarm_count = alarm_value;
284 this->timer_error_ = gptimer_set_alarm_action(this->timer_handle_, &this->alarm_config_);
285 if (this->timer_error_ != ESP_OK) {
286 this->timer_error_type_ = TimerErrorType::SET_ALARM_VALUE_ERROR;
289 this->timer_error_ = gptimer_start(this->timer_handle_);
290 if (this->timer_error_ != ESP_OK) {
291 this->timer_error_type_ = TimerErrorType::TIMER_START_ERROR;
295void OpenTherm::report_and_reset_timer_error() {
296 if (this->timer_error_ == ESP_OK) {
300 ESP_LOGE(TAG,
"Error occured while manipulating timer (%s): %s", this->timer_error_to_str(this->timer_error_type_),
301 esp_err_to_name(this->timer_error_));
303 this->timer_error_ = ESP_OK;
308void IRAM_ATTR OpenTherm::start_read_timer_() {
309 InterruptLock
const lock;
310 this->start_esp32_timer_(200);
314void IRAM_ATTR OpenTherm::start_write_timer_() {
315 InterruptLock
const lock;
316 this->start_esp32_timer_(500);
319void IRAM_ATTR OpenTherm::stop_timer_() {
320 InterruptLock
const lock;
322 this->timer_error_ = ESP_OK;
323 this->timer_error_type_ = TimerErrorType::NO_TIMER_ERROR;
325 this->timer_error_ = gptimer_stop(this->timer_handle_);
326 if (this->timer_error_ != ESP_OK) {
327 this->timer_error_type_ = TimerErrorType::TIMER_PAUSE_ERROR;
330 this->timer_error_ = gptimer_set_raw_count(this->timer_handle_, 0);
331 if (this->timer_error_ != ESP_OK) {
332 this->timer_error_type_ = TimerErrorType::SET_COUNTER_VALUE_ERROR;
340void IRAM_ATTR OpenTherm::start_read_timer_() {
341 InterruptLock
const lock;
342 timer1_attachInterrupt(OpenTherm::esp8266_timer_isr);
343 timer1_enable(TIM_DIV16, TIM_EDGE, TIM_LOOP);
348void IRAM_ATTR OpenTherm::start_write_timer_() {
349 InterruptLock
const lock;
350 timer1_attachInterrupt(OpenTherm::esp8266_timer_isr);
351 timer1_enable(TIM_DIV16, TIM_EDGE, TIM_LOOP);
355void IRAM_ATTR OpenTherm::stop_timer_() {
356 InterruptLock
const lock;
358 timer1_detachInterrupt();
362void OpenTherm::report_and_reset_timer_error() {}
367bool IRAM_ATTR OpenTherm::check_parity_(
uint32_t val) {
376#define TO_STRING_MEMBER(name) \
380const char *OpenTherm::operation_mode_to_str(OperationMode
mode) {
382 TO_STRING_MEMBER(IDLE)
383 TO_STRING_MEMBER(LISTEN)
384 TO_STRING_MEMBER(READ)
385 TO_STRING_MEMBER(RECEIVED)
386 TO_STRING_MEMBER(WRITE)
387 TO_STRING_MEMBER(SENT)
388 TO_STRING_MEMBER(ERROR_PROTOCOL)
389 TO_STRING_MEMBER(ERROR_TIMEOUT)
390 TO_STRING_MEMBER(ERROR_TIMER)
395const char *OpenTherm::protocol_error_to_str(ProtocolErrorType error_type) {
396 switch (error_type) {
397 TO_STRING_MEMBER(NO_ERROR)
398 TO_STRING_MEMBER(NO_TRANSITION)
399 TO_STRING_MEMBER(INVALID_STOP_BIT)
400 TO_STRING_MEMBER(PARITY_ERROR)
401 TO_STRING_MEMBER(NO_CHANGE_TOO_LONG)
406const char *OpenTherm::timer_error_to_str(TimerErrorType error_type) {
407 switch (error_type) {
408 TO_STRING_MEMBER(NO_TIMER_ERROR)
409 TO_STRING_MEMBER(SET_ALARM_VALUE_ERROR)
410 TO_STRING_MEMBER(TIMER_START_ERROR)
411 TO_STRING_MEMBER(TIMER_PAUSE_ERROR)
412 TO_STRING_MEMBER(SET_COUNTER_VALUE_ERROR)
417const char *OpenTherm::message_type_to_str(MessageType message_type) {
418 switch (message_type) {
419 TO_STRING_MEMBER(READ_DATA)
420 TO_STRING_MEMBER(READ_ACK)
421 TO_STRING_MEMBER(WRITE_DATA)
422 TO_STRING_MEMBER(WRITE_ACK)
423 TO_STRING_MEMBER(INVALID_DATA)
424 TO_STRING_MEMBER(DATA_INVALID)
425 TO_STRING_MEMBER(UNKNOWN_DATAID)
431const char *OpenTherm::message_id_to_str(MessageId
id) {
433 TO_STRING_MEMBER(STATUS)
434 TO_STRING_MEMBER(CH_SETPOINT)
435 TO_STRING_MEMBER(CONTROLLER_CONFIG)
436 TO_STRING_MEMBER(DEVICE_CONFIG)
437 TO_STRING_MEMBER(COMMAND_CODE)
438 TO_STRING_MEMBER(FAULT_FLAGS)
439 TO_STRING_MEMBER(REMOTE)
440 TO_STRING_MEMBER(COOLING_CONTROL)
441 TO_STRING_MEMBER(CH2_SETPOINT)
442 TO_STRING_MEMBER(CH_SETPOINT_OVERRIDE)
443 TO_STRING_MEMBER(TSP_COUNT)
444 TO_STRING_MEMBER(TSP_COMMAND)
445 TO_STRING_MEMBER(FHB_SIZE)
446 TO_STRING_MEMBER(FHB_COMMAND)
447 TO_STRING_MEMBER(MAX_MODULATION_LEVEL)
448 TO_STRING_MEMBER(MAX_BOILER_CAPACITY)
449 TO_STRING_MEMBER(ROOM_SETPOINT)
450 TO_STRING_MEMBER(MODULATION_LEVEL)
451 TO_STRING_MEMBER(CH_WATER_PRESSURE)
452 TO_STRING_MEMBER(DHW_FLOW_RATE)
453 TO_STRING_MEMBER(DAY_TIME)
454 TO_STRING_MEMBER(DATE)
455 TO_STRING_MEMBER(YEAR)
456 TO_STRING_MEMBER(ROOM_SETPOINT_CH2)
457 TO_STRING_MEMBER(ROOM_TEMP)
458 TO_STRING_MEMBER(FEED_TEMP)
459 TO_STRING_MEMBER(DHW_TEMP)
460 TO_STRING_MEMBER(OUTSIDE_TEMP)
461 TO_STRING_MEMBER(RETURN_WATER_TEMP)
462 TO_STRING_MEMBER(SOLAR_STORE_TEMP)
463 TO_STRING_MEMBER(SOLAR_COLLECT_TEMP)
464 TO_STRING_MEMBER(FEED_TEMP_CH2)
465 TO_STRING_MEMBER(DHW2_TEMP)
466 TO_STRING_MEMBER(EXHAUST_TEMP)
467 TO_STRING_MEMBER(FAN_SPEED)
468 TO_STRING_MEMBER(FLAME_CURRENT)
469 TO_STRING_MEMBER(ROOM_TEMP_CH2)
470 TO_STRING_MEMBER(REL_HUMIDITY)
471 TO_STRING_MEMBER(DHW_BOUNDS)
472 TO_STRING_MEMBER(CH_BOUNDS)
473 TO_STRING_MEMBER(OTC_CURVE_BOUNDS)
474 TO_STRING_MEMBER(DHW_SETPOINT)
475 TO_STRING_MEMBER(MAX_CH_SETPOINT)
476 TO_STRING_MEMBER(OTC_CURVE_RATIO)
477 TO_STRING_MEMBER(HVAC_STATUS)
478 TO_STRING_MEMBER(REL_VENT_SETPOINT)
479 TO_STRING_MEMBER(DEVICE_VENT)
480 TO_STRING_MEMBER(HVAC_VER_ID)
481 TO_STRING_MEMBER(REL_VENTILATION)
482 TO_STRING_MEMBER(REL_HUMID_EXHAUST)
483 TO_STRING_MEMBER(EXHAUST_CO2)
484 TO_STRING_MEMBER(SUPPLY_INLET_TEMP)
485 TO_STRING_MEMBER(SUPPLY_OUTLET_TEMP)
486 TO_STRING_MEMBER(EXHAUST_INLET_TEMP)
487 TO_STRING_MEMBER(EXHAUST_OUTLET_TEMP)
488 TO_STRING_MEMBER(EXHAUST_FAN_SPEED)
489 TO_STRING_MEMBER(SUPPLY_FAN_SPEED)
490 TO_STRING_MEMBER(REMOTE_VENTILATION_PARAM)
491 TO_STRING_MEMBER(NOM_REL_VENTILATION)
492 TO_STRING_MEMBER(HVAC_NUM_TSP)
493 TO_STRING_MEMBER(HVAC_IDX_TSP)
494 TO_STRING_MEMBER(HVAC_FHB_SIZE)
495 TO_STRING_MEMBER(HVAC_FHB_IDX)
496 TO_STRING_MEMBER(RF_SIGNAL)
497 TO_STRING_MEMBER(DHW_MODE)
498 TO_STRING_MEMBER(OVERRIDE_FUNC)
499 TO_STRING_MEMBER(SOLAR_MODE_FLAGS)
500 TO_STRING_MEMBER(SOLAR_ASF)
501 TO_STRING_MEMBER(SOLAR_VERSION_ID)
502 TO_STRING_MEMBER(SOLAR_PRODUCT_ID)
503 TO_STRING_MEMBER(SOLAR_NUM_TSP)
504 TO_STRING_MEMBER(SOLAR_IDX_TSP)
505 TO_STRING_MEMBER(SOLAR_FHB_SIZE)
506 TO_STRING_MEMBER(SOLAR_FHB_IDX)
507 TO_STRING_MEMBER(SOLAR_STARTS)
508 TO_STRING_MEMBER(SOLAR_HOURS)
509 TO_STRING_MEMBER(SOLAR_ENERGY)
510 TO_STRING_MEMBER(SOLAR_TOTAL_ENERGY)
511 TO_STRING_MEMBER(FAILED_BURNER_STARTS)
512 TO_STRING_MEMBER(BURNER_FLAME_LOW)
513 TO_STRING_MEMBER(OEM_DIAGNOSTIC)
514 TO_STRING_MEMBER(BURNER_STARTS)
515 TO_STRING_MEMBER(CH_PUMP_STARTS)
516 TO_STRING_MEMBER(DHW_PUMP_STARTS)
517 TO_STRING_MEMBER(DHW_BURNER_STARTS)
518 TO_STRING_MEMBER(BURNER_HOURS)
519 TO_STRING_MEMBER(CH_PUMP_HOURS)
520 TO_STRING_MEMBER(DHW_PUMP_HOURS)
521 TO_STRING_MEMBER(DHW_BURNER_HOURS)
522 TO_STRING_MEMBER(OT_VERSION_CONTROLLER)
523 TO_STRING_MEMBER(OT_VERSION_DEVICE)
524 TO_STRING_MEMBER(VERSION_CONTROLLER)
525 TO_STRING_MEMBER(VERSION_DEVICE)
531void OpenTherm::debug_data(OpenthermData &data) {
532 char type_buf[9], id_buf[9], hb_buf[9], lb_buf[9];
535 ESP_LOGD(TAG,
"type: %s; id: %u; HB: %u; LB: %u; uint_16: %u; float: %f",
539void OpenTherm::debug_error(OpenThermError &error)
const {
540 ESP_LOGD(TAG,
"data: 0x%08" PRIx32
"; clock: %u; capture: 0x%08" PRIx32
"; bit_pos: %u", error.data, this->clock_,
541 error.capture, error.bit_pos);
544float OpenthermData::f88() {
return ((
float) this->s16()) / 256.0; }
546void OpenthermData::f88(
float value) { this->s16((int16_t) (value * 256)); }
548uint16_t OpenthermData::u16() {
549 uint16_t
const value = this->valueHB;
550 return (value << 8) | this->valueLB;
553void OpenthermData::u16(uint16_t value) {
554 this->valueLB = value & 0xFF;
555 this->valueHB = (value >> 8) & 0xFF;
558int16_t OpenthermData::s16() {
559 int16_t
const value = this->valueHB;
560 return (value << 8) | this->valueLB;
563void OpenthermData::s16(int16_t value) {
564 this->valueLB = value & 0xFF;
565 this->valueHB = (value >> 8) & 0xFF;
BedjetMode mode
BedJet operating mode.
OpenTherm(InternalGPIOPin *in_pin, InternalGPIOPin *out_pin, int32_t device_timeout=800)
const std::vector< uint8_t > & data
constexpr T read_bit(T value, uint8_t bit)
const char * message_type_to_str(MessageType t)
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.