ESPHome 2026.3.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// TODO: Migrate from legacy timer API (driver/timer.h) to GPTimer API (driver/gptimer.h)
16// The legacy timer API is deprecated in ESP-IDF 5.x. Migration would allow removing the
17// "driver" IDF component dependency. See:
18// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/migration-guides/release-5.x/5.0/peripherals.html#id4
19#ifdef USE_ESP32
20#include "driver/timer.h"
21#endif
22
23namespace esphome {
24namespace opentherm {
25
26template<class T> constexpr T read_bit(T value, uint8_t bit) { return (value >> bit) & 0x01; }
27
28template<class T> constexpr T set_bit(T value, uint8_t bit) { return value |= (1UL << bit); }
29
30template<class T> constexpr T clear_bit(T value, uint8_t bit) { return value &= ~(1UL << bit); }
31
32template<class T> constexpr T write_bit(T value, uint8_t bit, uint8_t bit_value) {
33 return bit_value ? set_bit(value, bit) : clear_bit(value, bit);
34}
35
37 IDLE = 0, // no operation
38
39 LISTEN = 1, // waiting for transmission to start
40 READ = 2, // reading 32-bit data frame
41 RECEIVED = 3, // data frame received with valid start and stop bit
42
43 WRITE = 4, // writing data to output
44 SENT = 5, // all data written to output
45
46 ERROR_PROTOCOL = 8, // protocol error, can happed only during READ
47 ERROR_TIMEOUT = 9, // timeout while waiting for response from device, only during LISTEN
48 ERROR_TIMER = 10 // error operating the ESP32 timer
49};
50
52 NO_ERROR = 0, // No error
53 NO_TRANSITION = 1, // No transition in the middle of the bit
54 INVALID_STOP_BIT = 2, // Stop bit wasn't present when expected
55 PARITY_ERROR = 3, // Parity check didn't pass
56 NO_CHANGE_TOO_LONG = 4, // No level change for too much timer ticks
57};
58
60 NO_TIMER_ERROR = 0, // No error
61 SET_ALARM_VALUE_ERROR = 1, // No transition in the middle of the bit
62 TIMER_START_ERROR = 2, // Stop bit wasn't present when expected
63 TIMER_PAUSE_ERROR = 3, // Parity check didn't pass
64 SET_COUNTER_VALUE_ERROR = 4, // No level change for too much timer ticks
65};
66
76
78 STATUS = 0,
84 REMOTE = 6,
93 MAX_BOILER_CAPACITY = 15, // u8_hb - u8_lb gives min modulation level
99 DATE = 21,
100 YEAR = 22,
122
123 // HVAC Specific Message IDs
143
147
148 // Solar Specific Message IDs
149 SOLAR_MODE_FLAGS = 101, // hb0-2 Controller storage mode
150 // lb0 Device fault
151 // lb1-3 Device mode status
152 // lb4-5 Device status
164
179 VERSION_DEVICE = 127
181
182enum BitPositions { STOP_BIT = 33 };
183
189 uint8_t type;
190 uint8_t id;
191 uint8_t valueHB;
192 uint8_t valueLB;
193
194 OpenthermData() : type(0), id(0), valueHB(0), valueLB(0) {}
195
199 float f88();
200
204 void f88(float value);
205
209 uint16_t u16();
210
214 void u16(uint16_t value);
215
219 int16_t s16();
220
224 void s16(int16_t value);
225};
226
229 uint32_t capture;
230 uint8_t clock;
231 uint32_t data;
232 uint8_t bit_pos;
233};
234
239 public:
240 OpenTherm(InternalGPIOPin *in_pin, InternalGPIOPin *out_pin, int32_t device_timeout = 800);
241
245 bool initialize();
246
254 void listen();
255
261 bool has_message() { return mode_ == OperationMode::RECEIVED; }
262
270 bool get_message(OpenthermData &data);
271
279 void send(OpenthermData &data);
280
285 void stop();
286
293
299 bool is_sent() { return mode_ == OperationMode::SENT; }
300
307 bool is_idle() { return mode_ == OperationMode::IDLE; }
308
315 bool is_error() {
316 return mode_ == OperationMode::ERROR_TIMEOUT || mode_ == OperationMode::ERROR_PROTOCOL || mode_ == ERROR_TIMER;
317 }
318
323 bool is_timeout() { return mode_ == OperationMode::ERROR_TIMEOUT; }
324
330
335 bool is_timer_error() { return mode_ == OperationMode::ERROR_TIMER; }
336
337 bool is_active() { return mode_ == LISTEN || mode_ == READ || mode_ == WRITE; }
338
339 OperationMode get_mode() { return mode_; }
340
341 void debug_data(OpenthermData &data);
342 void debug_error(OpenThermError &error) const;
344
345 const char *protocol_error_to_str(ProtocolErrorType error_type);
346 const char *timer_error_to_str(TimerErrorType error_type);
347 const char *message_type_to_str(MessageType message_type);
349 const char *message_id_to_str(MessageId id);
350
351 static bool timer_isr(OpenTherm *arg);
352
353#ifdef ESP8266
354 static void esp8266_timer_isr();
355#endif
356
357 private:
358 InternalGPIOPin *in_pin_;
359 InternalGPIOPin *out_pin_;
360 ISRInternalGPIOPin isr_in_pin_;
361 ISRInternalGPIOPin isr_out_pin_;
362
363#ifdef USE_ESP32
364 timer_group_t timer_group_;
365 timer_idx_t timer_idx_;
366#endif
367
368 OperationMode mode_;
369 ProtocolErrorType error_type_;
370 uint32_t capture_;
371 uint8_t clock_;
372 uint32_t data_;
373 uint8_t bit_pos_;
374 int32_t timeout_counter_; // <0 no timeout
375 int32_t device_timeout_;
376
377#ifdef USE_ESP32
378 esp_err_t timer_error_ = ESP_OK;
380
381 bool init_esp32_timer_();
382 void start_esp32_timer_(uint64_t alarm_value);
383#endif
384
385 void stop_timer_();
386
387 void read_(); // data detected start reading
388 void start_read_timer_(); // reading timer_ to sample at 1/5 of manchester code bit length (at 5kHz)
389 void start_write_timer_(); // writing timer_ to send manchester code (at 2kHz)
390 bool check_parity_(uint32_t val);
391
392 void bit_read_(uint8_t value);
393 ProtocolErrorType verify_stop_bit_(uint8_t value);
394 void write_bit_(uint8_t high, uint8_t clock);
395
396#ifdef ESP8266
397 // ESP8266 timer can accept callback with no parameters, so we have this hack to save a static instance of OpenTherm
398 static OpenTherm *instance; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
399#endif
400};
401
402} // namespace opentherm
403} // namespace esphome
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:238
bool is_timeout()
Indicates whether last listen() or send() operation ends up with a timeout error.
Definition opentherm.h:323
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.
Definition opentherm.cpp:69
bool is_idle()
Indicates whether listinig or sending is not in progress.
Definition opentherm.h:307
bool get_message(OpenthermData &data)
Use this to retrive data packed captured by listen() function.
Definition opentherm.cpp:97
static bool timer_isr(OpenTherm *arg)
void send(OpenthermData &data)
Immediately send out Opentherm data packet to line connected on given pin.
Definition opentherm.cpp:80
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:335
bool initialize()
Setup pins.
Definition opentherm.cpp:52
bool is_sent()
Use this function to check whether send() function already finished sending data packed to line.
Definition opentherm.h:299
const char * operation_mode_to_str(OperationMode mode)
OpenTherm(InternalGPIOPin *in_pin, InternalGPIOPin *out_pin, int32_t device_timeout=800)
Definition opentherm.cpp:33
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:261
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:329
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:315
mopeka_std_values val[4]
constexpr T clear_bit(T value, uint8_t bit)
Definition opentherm.h:30
constexpr T write_bit(T value, uint8_t bit, uint8_t bit_value)
Definition opentherm.h:32
constexpr T set_bit(T value, uint8_t bit)
Definition opentherm.h:28
constexpr T read_bit(T value, uint8_t bit)
Definition opentherm.h:26
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
Structure to hold Opentherm data packet content.
Definition opentherm.h:188