14#include "driver/timer.h"
27static const char *
const TAG =
"opentherm";
30OpenTherm *OpenTherm::instance =
nullptr;
37 timer_group_(TIMER_GROUP_0),
47 device_timeout_(device_timeout) {
48 this->isr_in_pin_ = in_pin->
to_isr();
49 this->isr_out_pin_ = out_pin->
to_isr();
54 OpenTherm::instance =
this;
57 this->in_pin_->
setup();
59 this->out_pin_->
setup();
63 return this->init_esp32_timer_();
71 this->timeout_counter_ = this->device_timeout_ * 5;
77 this->start_read_timer_();
82 this->data_ = data.
type;
83 this->data_ = (this->data_ << 12) | data.
id;
84 this->data_ = (this->data_ << 8) | data.
valueHB;
85 this->data_ = (this->data_ << 8) | data.
valueLB;
86 if (!check_parity_(this->data_)) {
87 this->data_ = this->data_ | 0x80000000;
94 this->start_write_timer_();
99 data.
type = (this->data_ >> 28) & 0x7;
100 data.
id = (this->data_ >> 16) & 0xFF;
101 data.
valueHB = (this->data_ >> 8) & 0xFF;
102 data.
valueLB = this->data_ & 0xFF;
114 error.
bit_pos = this->bit_pos_;
115 error.
capture = this->capture_;
116 error.
clock = this->clock_;
117 error.
data = this->data_;
127void IRAM_ATTR OpenTherm::read_() {
133 this->start_read_timer_();
139 if (arg->timeout_counter_ == 0) {
148 if (arg->timeout_counter_ > 0) {
149 arg->timeout_counter_--;
153 uint8_t
const last = (arg->capture_ & 1);
156 if (arg->clock_ == 1 && arg->capture_ > 0xF) {
162 }
else if (arg->clock_ == 1 || arg->capture_ > 0xF) {
166 auto stop_bit_error = arg->verify_stop_bit_(last);
174 arg->error_type_ = stop_bit_error;
180 arg->bit_read_(last);
188 }
else if (arg->capture_ > 0xFF) {
195 arg->capture_ = (arg->capture_ << 1) | value;
198 if (arg->bit_pos_ == 33 || arg->bit_pos_ == 0) {
199 arg->write_bit_(1, arg->clock_);
201 arg->write_bit_(
read_bit(arg->data_, arg->bit_pos_ - 1), arg->clock_);
203 if (arg->clock_ == 0) {
204 if (arg->bit_pos_ <= 0) {
222void IRAM_ATTR OpenTherm::bit_read_(uint8_t value) {
223 this->data_ = (this->data_ << 1) | value;
235void IRAM_ATTR OpenTherm::write_bit_(uint8_t high, uint8_t clock) {
245bool OpenTherm::init_esp32_timer_() {
248 timer_group_t timer_group = TIMER_GROUP_0;
249 timer_idx_t timer_idx = TIMER_0;
250 bool timer_found =
false;
252 for (; cur_timer < SOC_TIMER_GROUP_TOTAL_TIMERS; cur_timer++) {
253 timer_config_t temp_config;
254 timer_group = cur_timer < 2 ? TIMER_GROUP_0 : TIMER_GROUP_1;
255 timer_idx = cur_timer < 2 ? (timer_idx_t) cur_timer : (timer_idx_t) (cur_timer - 2);
257 auto err = timer_get_config(timer_group, timer_idx, &temp_config);
258 if (err == ESP_ERR_INVALID_ARG) {
264 ESP_LOGD(TAG,
"Timer %d:%d seems to be occupied, will try another", timer_group, timer_idx);
268 ESP_LOGE(TAG,
"No free timer was found! OpenTherm cannot function without a timer.");
272 ESP_LOGD(TAG,
"Found free timer %d:%d", timer_group, timer_idx);
273 this->timer_group_ = timer_group;
274 this->timer_idx_ = timer_idx;
276 timer_config_t
const config = {
277 .alarm_en = TIMER_ALARM_EN,
278 .counter_en = TIMER_PAUSE,
279 .intr_type = TIMER_INTR_LEVEL,
280 .counter_dir = TIMER_COUNT_UP,
281 .auto_reload = TIMER_AUTORELOAD_EN,
282 .clk_src = TIMER_SRC_CLK_DEFAULT,
288 result = timer_init(this->timer_group_, this->timer_idx_, &config);
289 if (result != ESP_OK) {
290 const auto *error = esp_err_to_name(result);
291 ESP_LOGE(TAG,
"Failed to init timer. Error: %s", error);
295 result = timer_set_counter_value(this->timer_group_, this->timer_idx_, 0);
296 if (result != ESP_OK) {
297 const auto *error = esp_err_to_name(result);
298 ESP_LOGE(TAG,
"Failed to set counter value. Error: %s", error);
302 result = timer_isr_callback_add(this->timer_group_, this->timer_idx_,
reinterpret_cast<bool (*)(
void *)
>(
timer_isr),
304 if (result != ESP_OK) {
305 const auto *error = esp_err_to_name(result);
306 ESP_LOGE(TAG,
"Failed to register timer interrupt. Error: %s", error);
313void IRAM_ATTR OpenTherm::start_esp32_timer_(uint64_t alarm_value) {
315 this->timer_error_ = ESP_OK;
318 this->timer_error_ = timer_set_alarm_value(this->timer_group_, this->timer_idx_, alarm_value);
319 if (this->timer_error_ != ESP_OK) {
323 this->timer_error_ = timer_start(this->timer_group_, this->timer_idx_);
324 if (this->timer_error_ != ESP_OK) {
330 if (this->timer_error_ == ESP_OK) {
334 ESP_LOGE(TAG,
"Error occured while manipulating timer (%s): %s", this->
timer_error_to_str(this->timer_error_type_),
335 esp_err_to_name(this->timer_error_));
337 this->timer_error_ = ESP_OK;
342void IRAM_ATTR OpenTherm::start_read_timer_() {
344 this->start_esp32_timer_(200);
348void IRAM_ATTR OpenTherm::start_write_timer_() {
350 this->start_esp32_timer_(500);
353void IRAM_ATTR OpenTherm::stop_timer_() {
354 InterruptLock
const lock;
356 this->timer_error_ = ESP_OK;
359 this->timer_error_ = timer_pause(this->timer_group_, this->timer_idx_);
360 if (this->timer_error_ != ESP_OK) {
364 this->timer_error_ = timer_set_counter_value(this->timer_group_, this->timer_idx_, 0);
365 if (this->timer_error_ != ESP_OK) {
374void IRAM_ATTR OpenTherm::start_read_timer_() {
375 InterruptLock
const lock;
377 timer1_enable(TIM_DIV16, TIM_EDGE, TIM_LOOP);
382void IRAM_ATTR OpenTherm::start_write_timer_() {
383 InterruptLock
const lock;
385 timer1_enable(TIM_DIV16, TIM_EDGE, TIM_LOOP);
389void IRAM_ATTR OpenTherm::stop_timer_() {
390 InterruptLock
const lock;
392 timer1_detachInterrupt();
401bool IRAM_ATTR OpenTherm::check_parity_(uint32_t
val) {
410#define TO_STRING_MEMBER(name) \
416 TO_STRING_MEMBER(
IDLE)
418 TO_STRING_MEMBER(
READ)
420 TO_STRING_MEMBER(
WRITE)
421 TO_STRING_MEMBER(
SENT)
430 switch (error_type) {
441 switch (error_type) {
452 switch (message_type) {
488 TO_STRING_MEMBER(
DATE)
489 TO_STRING_MEMBER(
YEAR)
566 char type_buf[9], id_buf[9], hb_buf[9], lb_buf[9];
569 ESP_LOGD(TAG,
"type: %s; id: %u; HB: %u; LB: %u; uint_16: %u; float: %f",
574 ESP_LOGD(TAG,
"data: 0x%08" PRIx32
"; clock: %u; capture: 0x%08" PRIx32
"; bit_pos: %u", error.
data, this->clock_,
583 uint16_t
const value = this->
valueHB;
584 return (value << 8) | this->
valueLB;
589 this->
valueHB = (value >> 8) & 0xFF;
593 int16_t
const value = this->
valueHB;
594 return (value << 8) | this->
valueLB;
599 this->
valueHB = (value >> 8) & 0xFF;
BedjetMode mode
BedJet operating mode.
virtual void pin_mode(gpio::Flags flags)=0
virtual void digital_write(bool value)=0
void digital_write(bool value)
virtual ISRInternalGPIOPin to_isr() const =0
Helper class to disable interrupts.
Opentherm static class that supports either listening or sending Opentherm data packets in the same t...
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.
void report_and_reset_timer_error()
bool get_message(OpenthermData &data)
Use this to retrive data packed captured by listen() function.
static bool timer_isr(OpenTherm *arg)
void send(OpenthermData &data)
Immediately send out Opentherm data packet to line connected on given pin.
const char * timer_error_to_str(TimerErrorType error_type)
bool initialize()
Setup pins.
const char * operation_mode_to_str(OperationMode mode)
OpenTherm(InternalGPIOPin *in_pin, InternalGPIOPin *out_pin, int32_t device_timeout=800)
void debug_data(OpenthermData &data)
const char * protocol_error_to_str(ProtocolErrorType error_type)
static void esp8266_timer_isr()
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.
@ SET_COUNTER_VALUE_ERROR
constexpr T read_bit(T value, uint8_t bit)
@ REMOTE_VENTILATION_PARAM
Providing packet encoding functions for exchanging data with a remote host.
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.
ProtocolErrorType error_type
Structure to hold Opentherm data packet content.