ESPHome 2026.6.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
23template<class T> constexpr T set_bit(T value, uint8_t bit) { return value |= (1UL << bit); }
24
25template<class T> constexpr T clear_bit(T value, uint8_t bit) { return value &= ~(1UL << bit); }
26
27template<class T> constexpr T write_bit(T value, uint8_t bit, uint8_t bit_value) {
28 return bit_value ? set_bit(value, bit) : clear_bit(value, bit);
29}
30
32 IDLE = 0, // no operation
33
34 LISTEN = 1, // waiting for transmission to start
35 READ = 2, // reading 32-bit data frame
36 RECEIVED = 3, // data frame received with valid start and stop bit
37
38 WRITE = 4, // writing data to output
39 SENT = 5, // all data written to output
40
41 ERROR_PROTOCOL = 8, // protocol error, can happed only during READ
42 ERROR_TIMEOUT = 9, // timeout while waiting for response from device, only during LISTEN
43 ERROR_TIMER = 10 // error operating the ESP32 timer
44};
45
47 NO_ERROR = 0, // No error
48 NO_TRANSITION = 1, // No transition in the middle of the bit
49 INVALID_STOP_BIT = 2, // Stop bit wasn't present when expected
50 PARITY_ERROR = 3, // Parity check didn't pass
51 NO_CHANGE_TOO_LONG = 4, // No level change for too much timer ticks
52};
53
55 NO_TIMER_ERROR = 0, // No error
56 SET_ALARM_VALUE_ERROR = 1, // No transition in the middle of the bit
57 TIMER_START_ERROR = 2, // Stop bit wasn't present when expected
58 TIMER_PAUSE_ERROR = 3, // Parity check didn't pass
59 SET_COUNTER_VALUE_ERROR = 4, // No level change for too much timer ticks
60};
61
71
73 STATUS = 0,
79 REMOTE = 6,
88 MAX_BOILER_CAPACITY = 15, // u8_hb - u8_lb gives min modulation level
94 DATE = 21,
95 YEAR = 22,
117
118 // HVAC Specific Message IDs
138
142
143 // Solar Specific Message IDs
144 SOLAR_MODE_FLAGS = 101, // hb0-2 Controller storage mode
145 // lb0 Device fault
146 // lb1-3 Device mode status
147 // lb4-5 Device status
159
174 VERSION_DEVICE = 127
176
177enum BitPositions { STOP_BIT = 33 };
178
184 uint8_t type;
185 uint8_t id;
186 uint8_t valueHB;
187 uint8_t valueLB;
188
189 OpenthermData() : type(0), id(0), valueHB(0), valueLB(0) {}
190
194 float f88();
195
199 void f88(float value);
200
204 uint16_t u16();
205
209 void u16(uint16_t value);
210
214 int16_t s16();
215
219 void s16(int16_t value);
220};
221
229
234 public:
235 OpenTherm(InternalGPIOPin *in_pin, InternalGPIOPin *out_pin, int32_t device_timeout = 800);
236
241
249 void listen();
250
256 bool has_message() { return mode_ == OperationMode::RECEIVED; }
257
266
274 void send(OpenthermData &data);
275
280 void stop();
281
288
294 bool is_sent() { return mode_ == OperationMode::SENT; }
295
302 bool is_idle() { return mode_ == OperationMode::IDLE; }
303
310 bool is_error() {
311 return mode_ == OperationMode::ERROR_TIMEOUT || mode_ == OperationMode::ERROR_PROTOCOL || mode_ == ERROR_TIMER;
312 }
313
318 bool is_timeout() { return mode_ == OperationMode::ERROR_TIMEOUT; }
319
325
330 bool is_timer_error() { return mode_ == OperationMode::ERROR_TIMER; }
331
332 bool is_active() { return mode_ == LISTEN || mode_ == READ || mode_ == WRITE; }
333
334 OperationMode get_mode() { return mode_; }
335
337 void debug_error(OpenThermError &error) const;
339
341 const char *timer_error_to_str(TimerErrorType error_type);
342 const char *message_type_to_str(MessageType message_type);
345
346#ifdef USE_ESP32
347 static bool timer_isr(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx);
348#else
349 static bool timer_isr(OpenTherm *arg);
350#endif
351
352#ifdef ESP8266
353 static void esp8266_timer_isr();
354#endif
355
356 private:
357 InternalGPIOPin *in_pin_;
358 InternalGPIOPin *out_pin_;
359 ISRInternalGPIOPin isr_in_pin_;
360 ISRInternalGPIOPin isr_out_pin_;
361
362#ifdef USE_ESP32
363 gptimer_handle_t timer_handle_{nullptr};
364 gptimer_alarm_config_t alarm_config_{
365 .alarm_count = 0,
366 .reload_count = 0,
367 .flags = {.auto_reload_on_alarm = true},
368 };
369#endif
370
371 OperationMode mode_;
372 ProtocolErrorType error_type_;
373 uint32_t capture_;
374 uint8_t clock_;
375 uint32_t data_;
376 uint8_t bit_pos_;
377 int32_t timeout_counter_; // <0 no timeout
378 int32_t device_timeout_;
379
380#ifdef USE_ESP32
381 esp_err_t timer_error_ = ESP_OK;
383
384 bool init_esp32_timer_();
385 void start_esp32_timer_(uint64_t alarm_value);
386#endif
387
388 void stop_timer_();
389
390 void read_(); // data detected start reading
391 void start_read_timer_(); // reading timer_ to sample at 1/5 of manchester code bit length (at 5kHz)
392 void start_write_timer_(); // writing timer_ to send manchester code (at 2kHz)
393 bool check_parity_(uint32_t val);
394
395 void bit_read_(uint8_t value);
396 ProtocolErrorType verify_stop_bit_(uint8_t value);
397 void write_bit_(uint8_t high, uint8_t clock);
398
399#ifdef ESP8266
400 // ESP8266 timer can accept callback with no parameters, so we have this hack to save a static instance of OpenTherm
401 static OpenTherm *instance; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
402#endif
403};
404
405} // namespace esphome::opentherm
BedjetMode mode
BedJet operating mode.
Copy of GPIOPin that is safe to use from ISRs (with no virtual functions)
Definition gpio.h:92
Opentherm static class that supports either listening or sending Opentherm data packets in the same t...
Definition opentherm.h:233
bool is_timeout()
Indicates whether last listen() or send() operation ends up with a timeout error.
Definition opentherm.h:318
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:302
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:330
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:294
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:256
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:324
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:310
mopeka_std_values val[3]
constexpr T clear_bit(T value, uint8_t bit)
Definition opentherm.h:25
constexpr T write_bit(T value, uint8_t bit, uint8_t bit_value)
Definition opentherm.h:27
constexpr T set_bit(T value, uint8_t bit)
Definition opentherm.h:23
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:183