ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
opentherm.h
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#pragma once
9
10#include <string>
11#include "esphome/core/hal.h"
13#include "esphome/core/log.h"
14
15#ifdef USE_ESP32
16#include "driver/gptimer.h"
17#endif
18
19namespace esphome::opentherm {
20
21template<class T> constexpr T read_bit(T value, uint8_t bit) { return (value >> bit) & 0x01; }
22
24 IDLE = 0, // no operation
25
26 LISTEN = 1, // waiting for transmission to start
27 READ = 2, // reading 32-bit data frame
28 RECEIVED = 3, // data frame received with valid start and stop bit
29
30 WRITE = 4, // writing data to output
31 SENT = 5, // all data written to output
32
33 ERROR_PROTOCOL = 8, // protocol error, can happed only during READ
34 ERROR_TIMEOUT = 9, // timeout while waiting for response from device, only during LISTEN
35 ERROR_TIMER = 10 // error operating the ESP32 timer
36};
37
39 NO_ERROR = 0, // No error
40 NO_TRANSITION = 1, // No transition in the middle of the bit
41 INVALID_STOP_BIT = 2, // Stop bit wasn't present when expected
42 PARITY_ERROR = 3, // Parity check didn't pass
43 NO_CHANGE_TOO_LONG = 4, // No level change for too much timer ticks
44};
45
47 NO_TIMER_ERROR = 0, // No error
48 SET_ALARM_VALUE_ERROR = 1, // No transition in the middle of the bit
49 TIMER_START_ERROR = 2, // Stop bit wasn't present when expected
50 TIMER_PAUSE_ERROR = 3, // Parity check didn't pass
51 SET_COUNTER_VALUE_ERROR = 4, // No level change for too much timer ticks
52};
53
63
65 STATUS = 0,
71 REMOTE = 6,
80 MAX_BOILER_CAPACITY = 15, // u8_hb - u8_lb gives min modulation level
86 DATE = 21,
87 YEAR = 22,
109
110 // HVAC Specific Message IDs
130
134
135 // Solar Specific Message IDs
136 SOLAR_MODE_FLAGS = 101, // hb0-2 Controller storage mode
137 // lb0 Device fault
138 // lb1-3 Device mode status
139 // lb4-5 Device status
151
166 VERSION_DEVICE = 127
168
169enum BitPositions { STOP_BIT = 33 };
170
176 uint8_t type;
177 uint8_t id;
178 uint8_t valueHB;
179 uint8_t valueLB;
180
181 OpenthermData() : type(0), id(0), valueHB(0), valueLB(0) {}
182
186 float get_f88();
187
191 void set_f88(float value);
192
196 uint16_t get_u16();
197
201 void set_u16(uint16_t value);
202
206 int16_t get_s16();
207
211 void set_s16(int16_t value);
212};
213
221
226 public:
227 OpenTherm(InternalGPIOPin *in_pin, InternalGPIOPin *out_pin, int32_t device_timeout = 800);
228
233
241 void listen();
242
248 bool has_message() { return mode_ == OperationMode::RECEIVED; }
249
258
266 void send(OpenthermData &data);
267
272 void stop();
273
280
286 bool is_sent() { return mode_ == OperationMode::SENT; }
287
294 bool is_idle() { return mode_ == OperationMode::IDLE; }
295
302 bool is_error() {
303 return mode_ == OperationMode::ERROR_TIMEOUT || mode_ == OperationMode::ERROR_PROTOCOL || mode_ == ERROR_TIMER;
304 }
305
310 bool is_timeout() { return mode_ == OperationMode::ERROR_TIMEOUT; }
311
317
322 bool is_timer_error() { return mode_ == OperationMode::ERROR_TIMER; }
323
324 bool is_active() { return mode_ == LISTEN || mode_ == READ || mode_ == WRITE; }
325
326 OperationMode get_mode() { return mode_; }
327
329 void debug_error(OpenThermError &error) const;
331
333 const char *timer_error_to_str(TimerErrorType error_type);
334 const char *message_type_to_str(MessageType message_type);
337
338#ifdef USE_ESP32
339 static bool timer_isr(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx);
340#else
341 static bool timer_isr(OpenTherm *arg);
342#endif
343
344#ifdef ESP8266
345 static void esp8266_timer_isr();
346#endif
347
348 private:
349 InternalGPIOPin *in_pin_;
350 InternalGPIOPin *out_pin_;
351 ISRInternalGPIOPin isr_in_pin_;
352 ISRInternalGPIOPin isr_out_pin_;
353
354#ifdef USE_ESP32
355 gptimer_handle_t timer_handle_{nullptr};
356 gptimer_alarm_config_t alarm_config_{
357 .alarm_count = 0,
358 .reload_count = 0,
359 .flags = {.auto_reload_on_alarm = true},
360 };
361#endif
362
363 OperationMode mode_;
364 ProtocolErrorType error_type_;
365 uint32_t capture_;
366 uint8_t clock_;
367 uint32_t data_;
368 uint8_t bit_pos_;
369 int32_t timeout_counter_; // <0 no timeout
370 int32_t device_timeout_;
371
372#ifdef USE_ESP32
373 esp_err_t timer_error_ = ESP_OK;
375
376 bool init_esp32_timer_();
377 void start_esp32_timer_(uint64_t alarm_value);
378#endif
379
380 void stop_timer_();
381
382 void read_(); // data detected start reading
383 void start_read_timer_(); // reading timer_ to sample at 1/5 of manchester code bit length (at 5kHz)
384 void start_write_timer_(); // writing timer_ to send manchester code (at 2kHz)
385 bool check_parity_(uint32_t val);
386
387 void bit_read_(uint8_t value);
388 ProtocolErrorType verify_stop_bit_(uint8_t value);
389 void write_bit_(uint8_t high, uint8_t clock);
390
391#ifdef ESP8266
392 // ESP8266 timer can accept callback with no parameters, so we have this hack to save a static instance of OpenTherm
393 static OpenTherm *instance; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
394#endif
395};
396
397} // namespace esphome::opentherm
BedjetMode mode
BedJet operating mode.
Copy of GPIOPin that is safe to use from ISRs (with no virtual functions)
Definition gpio_pin.h:98
Opentherm static class that supports either listening or sending Opentherm data packets in the same t...
Definition opentherm.h:225
bool is_timeout()
Indicates whether last listen() or send() operation ends up with a timeout error.
Definition opentherm.h:310
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.
static bool timer_isr(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
bool is_idle()
Indicates whether listinig or sending is not in progress.
Definition opentherm.h:294
bool get_message(OpenthermData &data)
Use this to retrive data packed captured by listen() function.
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 is_timer_error()
Indicates whether start_esp32_timer_() or stop_timer_() had an error.
Definition opentherm.h:322
bool initialize()
Setup pins.
bool is_sent()
Use this function to check whether send() function already finished sending data packed to line.
Definition opentherm.h:286
static bool timer_isr(OpenTherm *arg)
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)
bool has_message()
Use this function to check whether listen() function already captured a valid data packet.
Definition opentherm.h:248
const char * protocol_error_to_str(ProtocolErrorType error_type)
bool is_protocol_error()
Indicates whether last listen() or send() operation ends up with a protocol error.
Definition opentherm.h:316
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.
bool is_error()
Indicates whether last listen() or send() operation ends up with an error.
Definition opentherm.h:302
mopeka_std_values val[3]
constexpr T read_bit(T value, uint8_t bit)
Definition opentherm.h:21
static void uint32_t
Structure to hold Opentherm data packet content.
Definition opentherm.h:175
void set_u16(uint16_t value)