├── CMakeLists.txt ├── LICENSE ├── README.md ├── examples ├── OpenThermGatewayMonitor_Demo │ └── OpenThermGatewayMonitor_Demo.ino ├── OpenThermMaster_Demo │ └── OpenThermMaster_Demo.ino └── OpenThermSlave_Demo │ └── OpenThermSlave_Demo.ino ├── keywords.txt ├── library.properties └── src ├── OpenTherm.cpp └── OpenTherm.h /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | idf_component_register( 4 | SRCS "src/OpenTherm.cpp" 5 | INCLUDE_DIRS "." "src" 6 | PRIV_REQUIRES arduino 7 | ) 8 | 9 | project (OpenTherm) 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ihor Melnyk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://vshymanskyy.github.io/StandWithUkraine) 2 | 3 | # OpenTherm Arduino/ESP8266/ESP32 Library 4 | 5 | This library provides implementation of OpenTherm protocol. 6 | 7 | OpenTherm Library is based on OpenTherm protocol specification v2.2 and works with all OpenTherm compatible boilers. Library can be easily installed into Arduino IDE and compiled for Arduino, ESP8266/ESP32 and other similar controllers. 8 | 9 | OpenTherm protocol requires simple low voltage twowire connection to boiler, but voltage levels (7..15V) still much higher than Arduino/ESP8266 levels, which requires [OpenTherm Adapter](http://ihormelnyk.com/opentherm_adapter). 10 | 11 | This version of library uses interrupts to achieve better stability and synchronization with boiler. 12 | 13 | ## Using OpenTherm Library you will be able: 14 | - control your boiler remotely (get status, switch on/off heating/water heating, set water temperature and much more) 15 | - make custom thermostat 16 | 17 | ## Configuration and Usage: 18 | ```c 19 | #include 20 | ``` 21 | You have to choose 2 controller GPIO pins which will be used for communication and connected to [OpenTherm Adapter](http://ihormelnyk.com/opentherm_adapter). Controller(Arduino/ESP8266) input pin should support interrupts. 22 | Controller output pin should be connected to OpenTherm Adapter input pin and vise versa. 23 | ```c 24 | const int inPin = 2; 25 | const int outPin = 3; 26 | ``` 27 | Define OpenTherm class instance using constructor which accepts as arguments pin numbers: 28 | ```c 29 | OpenTherm ot(inPin, outPin); 30 | ``` 31 | Define interrupts handler function for specified above instance: 32 | ```c 33 | void handleInterrupt() { 34 | ot.handleInterrupt(); 35 | } 36 | ``` 37 | Use begin function to initialize OpenTherm instance, specify interrupts handler function as argument 38 | ```c 39 | void setup() 40 | { 41 | ot.begin(handleInterrupt); 42 | } 43 | ``` 44 | According to OpenTherm Protocol specification master (controller) must communicate at least every 1 sec. So lets make some requests in loop function: 45 | ```c 46 | void loop() 47 | { 48 | //Set/Get Boiler Status 49 | ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling); 50 | //Set Boiler Temperature to 64 degrees C 51 | ot.setBoilerTemperature(64); 52 | //Get Boiler Temperature 53 | float temperature = ot.getBoilerTemperature(); 54 | delay(1000); 55 | } 56 | ``` 57 | 58 | In details [OpenTherm Library](http://ihormelnyk.com/opentherm_library) described [here](http://ihormelnyk.com/opentherm_library). 59 | 60 | ## OpenTherm Adapter Schematic 61 | ![opentherm adapter schmetic](http://ihormelnyk.com/Content/Pages/opentherm_adapter/opentherm_adapter_schematic.png) 62 | 63 | ## Arduino UNO Connection 64 | ![opentherm adapter arduino](http://ihormelnyk.com/Content/Pages/opentherm_adapter/opentherm_adapter_arduino_connection.png) 65 | 66 | ## License 67 | Copyright (c) 2018 [Ihor Melnyk](http://ihormelnyk.com). Licensed under the [MIT license](/LICENSE?raw=true). 68 | -------------------------------------------------------------------------------- /examples/OpenThermGatewayMonitor_Demo/OpenThermGatewayMonitor_Demo.ino: -------------------------------------------------------------------------------- 1 | /* 2 | OpenTherm Gateway/Monitor Example 3 | By: Ihor Melnyk 4 | Date: May 1st, 2019 5 | http://ihormelnyk.com 6 | */ 7 | 8 | #include 9 | #include 10 | 11 | const int mInPin = 2; // for Arduino, 4 for ESP8266 (D2), 21 for ESP32 12 | const int mOutPin = 4; // for Arduino, 5 for ESP8266 (D1), 22 for ESP32 13 | 14 | const int sInPin = 3; // for Arduino, 12 for ESP8266 (D6), 19 for ESP32 15 | const int sOutPin = 5; // for Arduino, 13 for ESP8266 (D7), 23 for ESP32 16 | 17 | OpenTherm mOT(mInPin, mOutPin); 18 | OpenTherm sOT(sInPin, sOutPin, true); 19 | 20 | void IRAM_ATTR mHandleInterrupt() 21 | { 22 | mOT.handleInterrupt(); 23 | } 24 | 25 | void IRAM_ATTR sHandleInterrupt() 26 | { 27 | sOT.handleInterrupt(); 28 | } 29 | 30 | void processRequest(unsigned long request, OpenThermResponseStatus status) 31 | { 32 | Serial.println("T" + String(request, HEX)); // master/thermostat request 33 | unsigned long response = mOT.sendRequest(request); 34 | if (response) 35 | { 36 | Serial.println("B" + String(response, HEX)); // slave/boiler response 37 | sOT.sendResponse(response); 38 | } 39 | } 40 | 41 | void setup() 42 | { 43 | Serial.begin(9600); // 9600 supported by OpenTherm Monitor App 44 | mOT.begin(mHandleInterrupt); // for ESP ot.begin(); without interrupt handler can be used 45 | sOT.begin(sHandleInterrupt, processRequest); 46 | } 47 | 48 | void loop() 49 | { 50 | sOT.process(); 51 | } 52 | -------------------------------------------------------------------------------- /examples/OpenThermMaster_Demo/OpenThermMaster_Demo.ino: -------------------------------------------------------------------------------- 1 | /* 2 | OpenTherm Master Communication Example 3 | By: Ihor Melnyk 4 | Date: January 19th, 2018 5 | 6 | Uses the OpenTherm library to get/set boiler status and water temperature 7 | Open serial monitor at 115200 baud to see output. 8 | 9 | Hardware Connections (OpenTherm Adapter (http://ihormelnyk.com/pages/OpenTherm) to Arduino/ESP8266): 10 | -OT1/OT2 = Boiler X1/X2 11 | -VCC = 5V or 3.3V 12 | -GND = GND 13 | -IN = Arduino (3) / ESP8266 (5) Output Pin 14 | -OUT = Arduino (2) / ESP8266 (4) Input Pin 15 | 16 | Controller(Arduino/ESP8266) input pin should support interrupts. 17 | Arduino digital pins usable for interrupts: Uno, Nano, Mini: 2,3; Mega: 2, 3, 18, 19, 20, 21 18 | ESP8266: Interrupts may be attached to any GPIO pin except GPIO16, 19 | but since GPIO6-GPIO11 are typically used to interface with the flash memory ICs on most esp8266 modules, applying interrupts to these pins are likely to cause problems 20 | */ 21 | 22 | #include 23 | #include 24 | 25 | const int inPin = 2; // for Arduino, 4 for ESP8266 (D2), 21 for ESP32 26 | const int outPin = 3; // for Arduino, 5 for ESP8266 (D1), 22 for ESP32 27 | OpenTherm ot(inPin, outPin); 28 | 29 | void IRAM_ATTR handleInterrupt() 30 | { 31 | ot.handleInterrupt(); 32 | } 33 | 34 | void setup() 35 | { 36 | Serial.begin(9600); 37 | Serial.println("Start"); 38 | 39 | ot.begin(handleInterrupt); // for ESP ot.begin(); without interrupt handler can be used 40 | } 41 | 42 | void loop() 43 | { 44 | // Set/Get Boiler Status 45 | bool enableCentralHeating = true; 46 | bool enableHotWater = true; 47 | bool enableCooling = false; 48 | unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling); 49 | OpenThermResponseStatus responseStatus = ot.getLastResponseStatus(); 50 | if (responseStatus == OpenThermResponseStatus::SUCCESS) 51 | { 52 | Serial.println("Central Heating: " + String(ot.isCentralHeatingActive(response) ? "on" : "off")); 53 | Serial.println("Hot Water: " + String(ot.isHotWaterActive(response) ? "on" : "off")); 54 | Serial.println("Flame: " + String(ot.isFlameOn(response) ? "on" : "off")); 55 | } 56 | if (responseStatus == OpenThermResponseStatus::NONE) 57 | { 58 | Serial.println("Error: OpenTherm is not initialized"); 59 | } 60 | else if (responseStatus == OpenThermResponseStatus::INVALID) 61 | { 62 | Serial.println("Error: Invalid response " + String(response, HEX)); 63 | } 64 | else if (responseStatus == OpenThermResponseStatus::TIMEOUT) 65 | { 66 | Serial.println("Error: Response timeout"); 67 | } 68 | 69 | // Set Boiler Temperature to 64 degrees C 70 | ot.setBoilerTemperature(64); 71 | 72 | // Get Boiler Temperature 73 | float ch_temperature = ot.getBoilerTemperature(); 74 | Serial.println("CH temperature is " + String(ch_temperature) + " degrees C"); 75 | 76 | // Set DHW setpoint to 40 degrees C 77 | ot.setDHWSetpoint(40); 78 | 79 | // Get DHW Temperature 80 | float dhw_temperature = ot.getDHWTemperature(); 81 | Serial.println("DHW temperature is " + String(dhw_temperature) + " degrees C"); 82 | 83 | Serial.println(); 84 | delay(1000); 85 | } 86 | -------------------------------------------------------------------------------- /examples/OpenThermSlave_Demo/OpenThermSlave_Demo.ino: -------------------------------------------------------------------------------- 1 | /* 2 | OpenTherm Slave Example 3 | By: Ihor Melnyk 4 | Date: May 1st, 2019 5 | http://ihormelnyk.com 6 | */ 7 | 8 | #include 9 | #include 10 | 11 | const int inPin = 2; // for Arduino, 12 for ESP8266 (D6), 19 for ESP32 12 | const int outPin = 3; // for Arduino, 13 for ESP8266 (D7), 23 for ESP32 13 | OpenTherm ot(inPin, outPin, true); 14 | 15 | void IRAM_ATTR handleInterrupt() 16 | { 17 | ot.handleInterrupt(); 18 | } 19 | 20 | void processRequest(unsigned long request, OpenThermResponseStatus status) 21 | { 22 | Serial.println("T" + String(request, HEX)); // master/thermostat request 23 | 24 | unsigned long response = 0; 25 | OpenThermMessageID id = ot.getDataID(request); 26 | uint16_t data = ot.getUInt(request); 27 | float f = ot.getFloat(request); 28 | switch (id) 29 | { 30 | case OpenThermMessageID::Status: 31 | { 32 | uint8_t statusRequest = data >> 8; 33 | uint8_t chEnable = statusRequest & 0x1; 34 | uint8_t dhwEnable = statusRequest & 0x2; 35 | data &= 0xFF00; 36 | // data |= 0x01; //fault indication 37 | if (chEnable) 38 | data |= 0x02; // CH active 39 | if (dhwEnable) 40 | data |= 0x04; // DHW active 41 | if (chEnable || dhwEnable) 42 | data |= 0x08; // flame on 43 | // data |= 0x10; //cooling active 44 | // data |= 0x20; //CH2 active 45 | // data |= 0x40; //diagnostic/service event 46 | // data |= 0x80; //electricity production on 47 | 48 | response = ot.buildResponse(OpenThermMessageType::READ_ACK, id, data); 49 | break; 50 | } 51 | case OpenThermMessageID::TSet: 52 | { 53 | response = ot.buildResponse(OpenThermMessageType::WRITE_ACK, id, data); 54 | break; 55 | } 56 | case OpenThermMessageID::Tboiler: 57 | { 58 | data = ot.temperatureToData(45); 59 | response = ot.buildResponse(OpenThermMessageType::READ_ACK, id, data); 60 | break; 61 | } 62 | default: 63 | { 64 | // build UNKNOWN-DATAID response 65 | response = ot.buildResponse(OpenThermMessageType::UNKNOWN_DATA_ID, ot.getDataID(request), 0); 66 | } 67 | } 68 | Serial.println("B" + String(response, HEX)); // slave/boiler response 69 | 70 | // send response 71 | delay(20); // 20..400ms, usually 100ms 72 | ot.sendResponse(response); 73 | } 74 | 75 | void setup() 76 | { 77 | Serial.begin(9600); 78 | Serial.println("Start"); 79 | 80 | ot.begin(handleInterrupt, processRequest); // for ESP ot.begin(); without interrupt handler can be used 81 | } 82 | 83 | void loop() 84 | { 85 | ot.process(); 86 | } 87 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For OpenTherm 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | OpenTherm KEYWORD1 10 | OpenThermStatus KEYWORD1 11 | OpenThermResponseStatus KEYWORD1 12 | OpenThermRequestType KEYWORD1 13 | OpenThermMessageID KEYWORD1 14 | 15 | ####################################### 16 | # Methods and Functions (KEYWORD2) 17 | ####################################### 18 | 19 | begin KEYWORD2 20 | isReady KEYWORD2 21 | sendRequest KEYWORD2 22 | sendRequestAync KEYWORD2 23 | buildRequest KEYWORD2 24 | getLastResponseStatus KEYWORD2 25 | handleInterrupt KEYWORD2 26 | process KEYWORD2 27 | end KEYWORD2 28 | doSomething KEYWORD2 29 | 30 | setBoilerStatus KEYWORD2 31 | setBoilerTemperature KEYWORD2 32 | getBoilerTemperature KEYWORD2 33 | setDHWSetpoint KEYWORD2 34 | getDHWTemperature KEYWORD2 35 | 36 | ####################################### 37 | # Instances (KEYWORD2) 38 | ####################################### 39 | 40 | ####################################### 41 | # Constants (LITERAL1) 42 | ####################################### -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=OpenTherm Library 2 | version=1.1.5 3 | author=Ihor Melnyk 4 | maintainer=Ihor Melnyk 5 | sentence=OpenTherm Library for HVAC system control communication using Arduino and ESP8266/ESP32 hardware. 6 | paragraph=OpenTherm Library is based on OpenTherm protocol specification v2.2 and works with all OpenTherm compatible boilers. 7 | category=Communication 8 | url=https://github.com/ihormelnyk/opentherm_library 9 | architectures=* 10 | includes=OpenTherm.h 11 | -------------------------------------------------------------------------------- /src/OpenTherm.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | OpenTherm.cpp - OpenTherm Communication Library For Arduino, ESP8266, ESP32 3 | Copyright 2023, Ihor Melnyk 4 | */ 5 | 6 | #include "OpenTherm.h" 7 | #if !defined(__AVR__) 8 | #include "FunctionalInterrupt.h" 9 | #endif 10 | 11 | OpenTherm::OpenTherm(int inPin, int outPin, bool isSlave) : 12 | status(OpenThermStatus::NOT_INITIALIZED), 13 | inPin(inPin), 14 | outPin(outPin), 15 | isSlave(isSlave), 16 | response(0), 17 | responseStatus(OpenThermResponseStatus::NONE), 18 | responseTimestamp(0), 19 | processResponseCallback(NULL) 20 | { 21 | } 22 | 23 | void OpenTherm::begin(void (*handleInterruptCallback)(void)) 24 | { 25 | pinMode(inPin, INPUT); 26 | pinMode(outPin, OUTPUT); 27 | if (handleInterruptCallback != NULL) 28 | { 29 | attachInterrupt(digitalPinToInterrupt(inPin), handleInterruptCallback, CHANGE); 30 | } 31 | else 32 | { 33 | #if !defined(__AVR__) 34 | attachInterruptArg( 35 | digitalPinToInterrupt(inPin), 36 | OpenTherm::handleInterruptHelper, 37 | this, 38 | CHANGE 39 | ); 40 | #endif 41 | } 42 | activateBoiler(); 43 | status = OpenThermStatus::READY; 44 | } 45 | 46 | void OpenTherm::begin(void (*handleInterruptCallback)(void), void (*processResponseCallback)(unsigned long, OpenThermResponseStatus)) 47 | { 48 | begin(handleInterruptCallback); 49 | this->processResponseCallback = processResponseCallback; 50 | } 51 | 52 | #if !defined(__AVR__) 53 | void OpenTherm::begin() 54 | { 55 | begin(NULL); 56 | } 57 | 58 | void OpenTherm::begin(std::function processResponseFunction) 59 | { 60 | begin(); 61 | this->processResponseFunction = processResponseFunction; 62 | } 63 | #endif 64 | 65 | bool IRAM_ATTR OpenTherm::isReady() 66 | { 67 | return status == OpenThermStatus::READY; 68 | } 69 | 70 | int IRAM_ATTR OpenTherm::readState() 71 | { 72 | return digitalRead(inPin); 73 | } 74 | 75 | void OpenTherm::setActiveState() 76 | { 77 | digitalWrite(outPin, LOW); 78 | } 79 | 80 | void OpenTherm::setIdleState() 81 | { 82 | digitalWrite(outPin, HIGH); 83 | } 84 | 85 | void OpenTherm::activateBoiler() 86 | { 87 | setIdleState(); 88 | delay(1000); 89 | } 90 | 91 | void OpenTherm::sendBit(bool high) 92 | { 93 | if (high) 94 | setActiveState(); 95 | else 96 | setIdleState(); 97 | delayMicroseconds(500); 98 | if (high) 99 | setIdleState(); 100 | else 101 | setActiveState(); 102 | delayMicroseconds(500); 103 | } 104 | 105 | bool OpenTherm::sendRequestAsync(unsigned long request) 106 | { 107 | noInterrupts(); 108 | const bool ready = isReady(); 109 | 110 | if (!ready) 111 | { 112 | interrupts(); 113 | return false; 114 | } 115 | 116 | status = OpenThermStatus::REQUEST_SENDING; 117 | response = 0; 118 | responseStatus = OpenThermResponseStatus::NONE; 119 | 120 | #ifdef INC_FREERTOS_H 121 | BaseType_t schedulerState = xTaskGetSchedulerState(); 122 | if (schedulerState == taskSCHEDULER_RUNNING) 123 | { 124 | vTaskSuspendAll(); 125 | } 126 | #endif 127 | 128 | interrupts(); 129 | 130 | sendBit(HIGH); // start bit 131 | for (int i = 31; i >= 0; i--) 132 | { 133 | sendBit(bitRead(request, i)); 134 | } 135 | sendBit(HIGH); // stop bit 136 | setIdleState(); 137 | 138 | responseTimestamp = micros(); 139 | status = OpenThermStatus::RESPONSE_WAITING; 140 | 141 | #ifdef INC_FREERTOS_H 142 | if (schedulerState == taskSCHEDULER_RUNNING) { 143 | xTaskResumeAll(); 144 | } 145 | #endif 146 | 147 | return true; 148 | } 149 | 150 | unsigned long OpenTherm::sendRequest(unsigned long request) 151 | { 152 | if (!sendRequestAsync(request)) 153 | { 154 | return 0; 155 | } 156 | 157 | while (!isReady()) 158 | { 159 | process(); 160 | yield(); 161 | } 162 | return response; 163 | } 164 | 165 | bool OpenTherm::sendResponse(unsigned long request) 166 | { 167 | noInterrupts(); 168 | const bool ready = isReady(); 169 | 170 | if (!ready) 171 | { 172 | interrupts(); 173 | return false; 174 | } 175 | 176 | status = OpenThermStatus::REQUEST_SENDING; 177 | response = 0; 178 | responseStatus = OpenThermResponseStatus::NONE; 179 | 180 | #ifdef INC_FREERTOS_H 181 | BaseType_t schedulerState = xTaskGetSchedulerState(); 182 | if (schedulerState == taskSCHEDULER_RUNNING) 183 | { 184 | vTaskSuspendAll(); 185 | } 186 | #endif 187 | 188 | interrupts(); 189 | 190 | sendBit(HIGH); // start bit 191 | for (int i = 31; i >= 0; i--) 192 | { 193 | sendBit(bitRead(request, i)); 194 | } 195 | sendBit(HIGH); // stop bit 196 | setIdleState(); 197 | status = OpenThermStatus::READY; 198 | 199 | #ifdef INC_FREERTOS_H 200 | if (schedulerState == taskSCHEDULER_RUNNING) { 201 | xTaskResumeAll(); 202 | } 203 | #endif 204 | 205 | return true; 206 | } 207 | 208 | unsigned long OpenTherm::getLastResponse() 209 | { 210 | return response; 211 | } 212 | 213 | OpenThermResponseStatus OpenTherm::getLastResponseStatus() 214 | { 215 | return responseStatus; 216 | } 217 | 218 | void IRAM_ATTR OpenTherm::handleInterrupt() 219 | { 220 | if (isReady()) 221 | { 222 | if (isSlave && readState() == HIGH) 223 | { 224 | status = OpenThermStatus::RESPONSE_WAITING; 225 | } 226 | else 227 | { 228 | return; 229 | } 230 | } 231 | 232 | unsigned long newTs = micros(); 233 | if (status == OpenThermStatus::RESPONSE_WAITING) 234 | { 235 | if (readState() == HIGH) 236 | { 237 | status = OpenThermStatus::RESPONSE_START_BIT; 238 | responseTimestamp = newTs; 239 | } 240 | else 241 | { 242 | status = OpenThermStatus::RESPONSE_INVALID; 243 | responseTimestamp = newTs; 244 | } 245 | } 246 | else if (status == OpenThermStatus::RESPONSE_START_BIT) 247 | { 248 | if ((newTs - responseTimestamp < 750) && readState() == LOW) 249 | { 250 | status = OpenThermStatus::RESPONSE_RECEIVING; 251 | responseTimestamp = newTs; 252 | responseBitIndex = 0; 253 | } 254 | else 255 | { 256 | status = OpenThermStatus::RESPONSE_INVALID; 257 | responseTimestamp = newTs; 258 | } 259 | } 260 | else if (status == OpenThermStatus::RESPONSE_RECEIVING) 261 | { 262 | if ((newTs - responseTimestamp) > 750) 263 | { 264 | if (responseBitIndex < 32) 265 | { 266 | response = (response << 1) | !readState(); 267 | responseTimestamp = newTs; 268 | responseBitIndex = responseBitIndex + 1; 269 | } 270 | else 271 | { // stop bit 272 | status = OpenThermStatus::RESPONSE_READY; 273 | responseTimestamp = newTs; 274 | } 275 | } 276 | } 277 | } 278 | 279 | #if !defined(__AVR__) 280 | void IRAM_ATTR OpenTherm::handleInterruptHelper(void* ptr) 281 | { 282 | static_cast(ptr)->handleInterrupt(); 283 | } 284 | #endif 285 | 286 | void OpenTherm::processResponse() 287 | { 288 | if (processResponseCallback != NULL) 289 | { 290 | processResponseCallback(response, responseStatus); 291 | } 292 | #if !defined(__AVR__) 293 | if (this->processResponseFunction != NULL) 294 | { 295 | processResponseFunction(response, responseStatus); 296 | } 297 | #endif 298 | } 299 | 300 | void OpenTherm::process() 301 | { 302 | noInterrupts(); 303 | OpenThermStatus st = status; 304 | unsigned long ts = responseTimestamp; 305 | interrupts(); 306 | 307 | if (st == OpenThermStatus::READY) 308 | return; 309 | unsigned long newTs = micros(); 310 | if (st != OpenThermStatus::NOT_INITIALIZED && st != OpenThermStatus::DELAY && (newTs - ts) > 1000000) 311 | { 312 | status = OpenThermStatus::READY; 313 | responseStatus = OpenThermResponseStatus::TIMEOUT; 314 | processResponse(); 315 | } 316 | else if (st == OpenThermStatus::RESPONSE_INVALID) 317 | { 318 | status = OpenThermStatus::DELAY; 319 | responseStatus = OpenThermResponseStatus::INVALID; 320 | processResponse(); 321 | } 322 | else if (st == OpenThermStatus::RESPONSE_READY) 323 | { 324 | status = OpenThermStatus::DELAY; 325 | responseStatus = (isSlave ? isValidRequest(response) : isValidResponse(response)) ? OpenThermResponseStatus::SUCCESS : OpenThermResponseStatus::INVALID; 326 | processResponse(); 327 | } 328 | else if (st == OpenThermStatus::DELAY) 329 | { 330 | if ((newTs - ts) > (isSlave ? 20000 : 100000)) 331 | { 332 | status = OpenThermStatus::READY; 333 | } 334 | } 335 | } 336 | 337 | bool OpenTherm::parity(unsigned long frame) // odd parity 338 | { 339 | byte p = 0; 340 | while (frame > 0) 341 | { 342 | if (frame & 1) 343 | p++; 344 | frame = frame >> 1; 345 | } 346 | return (p & 1); 347 | } 348 | 349 | OpenThermMessageType OpenTherm::getMessageType(unsigned long message) 350 | { 351 | OpenThermMessageType msg_type = static_cast((message >> 28) & 7); 352 | return msg_type; 353 | } 354 | 355 | OpenThermMessageID OpenTherm::getDataID(unsigned long frame) 356 | { 357 | return (OpenThermMessageID)((frame >> 16) & 0xFF); 358 | } 359 | 360 | unsigned long OpenTherm::buildRequest(OpenThermMessageType type, OpenThermMessageID id, unsigned int data) 361 | { 362 | unsigned long request = data; 363 | if (type == OpenThermMessageType::WRITE_DATA) 364 | { 365 | request |= 1ul << 28; 366 | } 367 | request |= ((unsigned long)id) << 16; 368 | if (parity(request)) 369 | request |= (1ul << 31); 370 | return request; 371 | } 372 | 373 | unsigned long OpenTherm::buildResponse(OpenThermMessageType type, OpenThermMessageID id, unsigned int data) 374 | { 375 | unsigned long response = data; 376 | response |= ((unsigned long)type) << 28; 377 | response |= ((unsigned long)id) << 16; 378 | if (parity(response)) 379 | response |= (1ul << 31); 380 | return response; 381 | } 382 | 383 | bool OpenTherm::isValidResponse(unsigned long response) 384 | { 385 | if (parity(response)) 386 | return false; 387 | byte msgType = (response << 1) >> 29; 388 | return msgType == (byte)OpenThermMessageType::READ_ACK || msgType == (byte)OpenThermMessageType::WRITE_ACK; 389 | } 390 | 391 | bool OpenTherm::isValidRequest(unsigned long request) 392 | { 393 | if (parity(request)) 394 | return false; 395 | byte msgType = (request << 1) >> 29; 396 | return msgType == (byte)OpenThermMessageType::READ_DATA || msgType == (byte)OpenThermMessageType::WRITE_DATA; 397 | } 398 | 399 | void OpenTherm::end() 400 | { 401 | detachInterrupt(digitalPinToInterrupt(inPin)); 402 | } 403 | 404 | OpenTherm::~OpenTherm() 405 | { 406 | end(); 407 | } 408 | 409 | const char *OpenTherm::statusToString(OpenThermResponseStatus status) 410 | { 411 | switch (status) 412 | { 413 | case OpenThermResponseStatus::NONE: 414 | return "NONE"; 415 | case OpenThermResponseStatus::SUCCESS: 416 | return "SUCCESS"; 417 | case OpenThermResponseStatus::INVALID: 418 | return "INVALID"; 419 | case OpenThermResponseStatus::TIMEOUT: 420 | return "TIMEOUT"; 421 | default: 422 | return "UNKNOWN"; 423 | } 424 | } 425 | 426 | const char *OpenTherm::messageTypeToString(OpenThermMessageType message_type) 427 | { 428 | switch (message_type) 429 | { 430 | case OpenThermMessageType::READ_DATA: 431 | return "READ_DATA"; 432 | case OpenThermMessageType::WRITE_DATA: 433 | return "WRITE_DATA"; 434 | case OpenThermMessageType::INVALID_DATA: 435 | return "INVALID_DATA"; 436 | case OpenThermMessageType::RESERVED: 437 | return "RESERVED"; 438 | case OpenThermMessageType::READ_ACK: 439 | return "READ_ACK"; 440 | case OpenThermMessageType::WRITE_ACK: 441 | return "WRITE_ACK"; 442 | case OpenThermMessageType::DATA_INVALID: 443 | return "DATA_INVALID"; 444 | case OpenThermMessageType::UNKNOWN_DATA_ID: 445 | return "UNKNOWN_DATA_ID"; 446 | default: 447 | return "UNKNOWN"; 448 | } 449 | } 450 | 451 | // building requests 452 | 453 | unsigned long OpenTherm::buildSetBoilerStatusRequest(bool enableCentralHeating, bool enableHotWater, bool enableCooling, bool enableOutsideTemperatureCompensation, bool enableCentralHeating2) 454 | { 455 | unsigned int data = enableCentralHeating | (enableHotWater << 1) | (enableCooling << 2) | (enableOutsideTemperatureCompensation << 3) | (enableCentralHeating2 << 4); 456 | data <<= 8; 457 | return buildRequest(OpenThermMessageType::READ_DATA, OpenThermMessageID::Status, data); 458 | } 459 | 460 | unsigned long OpenTherm::buildSetBoilerTemperatureRequest(float temperature) 461 | { 462 | unsigned int data = temperatureToData(temperature); 463 | return buildRequest(OpenThermMessageType::WRITE_DATA, OpenThermMessageID::TSet, data); 464 | } 465 | 466 | unsigned long OpenTherm::buildGetBoilerTemperatureRequest() 467 | { 468 | return buildRequest(OpenThermMessageType::READ_DATA, OpenThermMessageID::Tboiler, 0); 469 | } 470 | 471 | // parsing responses 472 | bool OpenTherm::isFault(unsigned long response) 473 | { 474 | return response & 0x1; 475 | } 476 | 477 | bool OpenTherm::isCentralHeatingActive(unsigned long response) 478 | { 479 | return response & 0x2; 480 | } 481 | 482 | bool OpenTherm::isHotWaterActive(unsigned long response) 483 | { 484 | return response & 0x4; 485 | } 486 | 487 | bool OpenTherm::isFlameOn(unsigned long response) 488 | { 489 | return response & 0x8; 490 | } 491 | 492 | bool OpenTherm::isCoolingActive(unsigned long response) 493 | { 494 | return response & 0x10; 495 | } 496 | 497 | bool OpenTherm::isDiagnostic(unsigned long response) 498 | { 499 | return response & 0x40; 500 | } 501 | 502 | uint16_t OpenTherm::getUInt(const unsigned long response) 503 | { 504 | const uint16_t u88 = response & 0xffff; 505 | return u88; 506 | } 507 | 508 | float OpenTherm::getFloat(const unsigned long response) 509 | { 510 | const uint16_t u88 = getUInt(response); 511 | const float f = (u88 & 0x8000) ? -(0x10000L - u88) / 256.0f : u88 / 256.0f; 512 | return f; 513 | } 514 | 515 | unsigned int OpenTherm::temperatureToData(float temperature) 516 | { 517 | if (temperature < 0) 518 | temperature = 0; 519 | if (temperature > 100) 520 | temperature = 100; 521 | unsigned int data = (unsigned int)(temperature * 256); 522 | return data; 523 | } 524 | 525 | // basic requests 526 | 527 | unsigned long OpenTherm::setBoilerStatus(bool enableCentralHeating, bool enableHotWater, bool enableCooling, bool enableOutsideTemperatureCompensation, bool enableCentralHeating2) 528 | { 529 | return sendRequest(buildSetBoilerStatusRequest(enableCentralHeating, enableHotWater, enableCooling, enableOutsideTemperatureCompensation, enableCentralHeating2)); 530 | } 531 | 532 | bool OpenTherm::setBoilerTemperature(float temperature) 533 | { 534 | unsigned long response = sendRequest(buildSetBoilerTemperatureRequest(temperature)); 535 | return isValidResponse(response); 536 | } 537 | 538 | float OpenTherm::getBoilerTemperature() 539 | { 540 | unsigned long response = sendRequest(buildGetBoilerTemperatureRequest()); 541 | return isValidResponse(response) ? getFloat(response) : 0; 542 | } 543 | 544 | float OpenTherm::getReturnTemperature() 545 | { 546 | unsigned long response = sendRequest(buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Tret, 0)); 547 | return isValidResponse(response) ? getFloat(response) : 0; 548 | } 549 | 550 | bool OpenTherm::setDHWSetpoint(float temperature) 551 | { 552 | unsigned int data = temperatureToData(temperature); 553 | unsigned long response = sendRequest(buildRequest(OpenThermMessageType::WRITE_DATA, OpenThermMessageID::TdhwSet, data)); 554 | return isValidResponse(response); 555 | } 556 | 557 | float OpenTherm::getDHWTemperature() 558 | { 559 | unsigned long response = sendRequest(buildRequest(OpenThermMessageType::READ_DATA, OpenThermMessageID::Tdhw, 0)); 560 | return isValidResponse(response) ? getFloat(response) : 0; 561 | } 562 | 563 | float OpenTherm::getModulation() 564 | { 565 | unsigned long response = sendRequest(buildRequest(OpenThermRequestType::READ, OpenThermMessageID::RelModLevel, 0)); 566 | return isValidResponse(response) ? getFloat(response) : 0; 567 | } 568 | 569 | float OpenTherm::getPressure() 570 | { 571 | unsigned long response = sendRequest(buildRequest(OpenThermRequestType::READ, OpenThermMessageID::CHPressure, 0)); 572 | return isValidResponse(response) ? getFloat(response) : 0; 573 | } 574 | 575 | unsigned char OpenTherm::getFault() 576 | { 577 | return ((sendRequest(buildRequest(OpenThermRequestType::READ, OpenThermMessageID::ASFflags, 0)) >> 8) & 0xff); 578 | } 579 | -------------------------------------------------------------------------------- /src/OpenTherm.h: -------------------------------------------------------------------------------- 1 | /* 2 | OpenTherm.h - OpenTherm Library for the ESP8266/ESP32/Arduino platform 3 | https://github.com/ihormelnyk/OpenTherm 4 | http://ihormelnyk.com/pages/OpenTherm 5 | Licensed under MIT license 6 | Copyright 2023, Ihor Melnyk 7 | 8 | Frame Structure: 9 | P MGS-TYPE SPARE DATA-ID DATA-VALUE 10 | 0 000 0000 00000000 00000000 00000000 11 | */ 12 | 13 | #ifndef OpenTherm_h 14 | #define OpenTherm_h 15 | 16 | #include 17 | #include 18 | 19 | enum class OpenThermResponseStatus : byte 20 | { 21 | NONE, 22 | SUCCESS, 23 | INVALID, 24 | TIMEOUT 25 | }; 26 | 27 | enum class OpenThermMessageType : byte 28 | { 29 | /* Master to Slave */ 30 | READ_DATA = 0b000, 31 | READ = READ_DATA, // for backwared compatibility 32 | WRITE_DATA = 0b001, 33 | WRITE = WRITE_DATA, // for backwared compatibility 34 | INVALID_DATA = 0b010, 35 | RESERVED = 0b011, 36 | /* Slave to Master */ 37 | READ_ACK = 0b100, 38 | WRITE_ACK = 0b101, 39 | DATA_INVALID = 0b110, 40 | UNKNOWN_DATA_ID = 0b111 41 | }; 42 | 43 | typedef OpenThermMessageType OpenThermRequestType; // for backwared compatibility 44 | 45 | enum class OpenThermMessageID : byte 46 | { 47 | Status = 0, // flag8/flag8 Master and Slave Status flags. 48 | TSet = 1, // f8.8 Control Setpoint i.e.CH water temperature Setpoint(°C) 49 | MConfigMMemberIDcode = 2, // flag8/u8 Master Configuration Flags / Master MemberID Code 50 | SConfigSMemberIDcode = 3, // flag8/u8 Slave Configuration Flags / Slave MemberID Code 51 | RemoteRequest = 4, // u8/u8 Remote Request 52 | ASFflags = 5, // flag8/u8 Application - specific fault flags and OEM fault code 53 | RBPflags = 6, // flag8/flag8 Remote boiler parameter transfer - enable & read / write flags 54 | CoolingControl = 7, // f8.8 Cooling control signal(%) 55 | TsetCH2 = 8, // f8.8 Control Setpoint for 2e CH circuit(°C) 56 | TrOverride = 9, // f8.8 Remote override room Setpoint 57 | TSP = 10, // u8/u8 Number of Transparent - Slave - Parameters supported by slave 58 | TSPindexTSPvalue = 11, // u8/u8 Index number / Value of referred - to transparent slave parameter. 59 | FHBsize = 12, // u8/u8 Size of Fault - History - Buffer supported by slave 60 | FHBindexFHBvalue = 13, // u8/u8 Index number / Value of referred - to fault - history buffer entry. 61 | MaxRelModLevelSetting = 14, // f8.8 Maximum relative modulation level setting(%) 62 | MaxCapacityMinModLevel = 15, // u8/u8 Maximum boiler capacity(kW) / Minimum boiler modulation level(%) 63 | TrSet = 16, // f8.8 Room Setpoint(°C) 64 | RelModLevel = 17, // f8.8 Relative Modulation Level(%) 65 | CHPressure = 18, // f8.8 Water pressure in CH circuit(bar) 66 | DHWFlowRate = 19, // f8.8 Water flow rate in DHW circuit. (litres / minute) 67 | DayTime = 20, // special/u8 Day of Week and Time of Day 68 | Date = 21, // u8/u8 Calendar date 69 | Year = 22, // u16 Calendar year 70 | TrSetCH2 = 23, // f8.8 Room Setpoint for 2nd CH circuit(°C) 71 | Tr = 24, // f8.8 Room temperature(°C) 72 | Tboiler = 25, // f8.8 Boiler flow water temperature(°C) 73 | Tdhw = 26, // f8.8 DHW temperature(°C) 74 | Toutside = 27, // f8.8 Outside temperature(°C) 75 | Tret = 28, // f8.8 Return water temperature(°C) 76 | Tstorage = 29, // f8.8 Solar storage temperature(°C) 77 | Tcollector = 30, // f8.8 Solar collector temperature(°C) 78 | TflowCH2 = 31, // f8.8 Flow water temperature CH2 circuit(°C) 79 | Tdhw2 = 32, // f8.8 Domestic hot water temperature 2 (°C) 80 | Texhaust = 33, // s16 Boiler exhaust temperature(°C) 81 | TboilerHeatExchanger = 34, // f8.8 Boiler heat exchanger temperature(°C) 82 | BoilerFanSpeedSetpointAndActual = 35, // u8/u8 Boiler fan speed Setpoint and actual value 83 | FlameCurrent = 36, // f8.8 Electrical current through burner flame[μA] 84 | TrCH2 = 37, // f8.8 Room temperature for 2nd CH circuit(°C) 85 | RelativeHumidity = 38, // f8.8 Actual relative humidity as a percentage 86 | TrOverride2 = 39, // f8.8 Remote Override Room Setpoint 2 87 | TdhwSetUBTdhwSetLB = 48, // s8/s8 DHW Setpoint upper & lower bounds for adjustment(°C) 88 | MaxTSetUBMaxTSetLB = 49, // s8/s8 Max CH water Setpoint upper & lower bounds for adjustment(°C) 89 | TdhwSet = 56, // f8.8 DHW Setpoint(°C) (Remote parameter 1) 90 | MaxTSet = 57, // f8.8 Max CH water Setpoint(°C) (Remote parameters 2) 91 | StatusVentilationHeatRecovery = 70, // flag8/flag8 Master and Slave Status flags ventilation / heat - recovery 92 | Vset = 71, // -/u8 Relative ventilation position (0-100%). 0% is the minimum set ventilation and 100% is the maximum set ventilation. 93 | ASFflagsOEMfaultCodeVentilationHeatRecovery = 72, // flag8/u8 Application-specific fault flags and OEM fault code ventilation / heat-recovery 94 | OEMDiagnosticCodeVentilationHeatRecovery = 73, // u16 An OEM-specific diagnostic/service code for ventilation / heat-recovery system 95 | SConfigSMemberIDCodeVentilationHeatRecovery = 74, // flag8/u8 Slave Configuration Flags / Slave MemberID Code ventilation / heat-recovery 96 | OpenThermVersionVentilationHeatRecovery = 75, // f8.8 The implemented version of the OpenTherm Protocol Specification in the ventilation / heat-recovery system. 97 | VentilationHeatRecoveryVersion = 76, // u8/u8 Ventilation / heat-recovery product version number and type 98 | RelVentLevel = 77, // -/u8 Relative ventilation (0-100%) 99 | RHexhaust = 78, // -/u8 Relative humidity exhaust air (0-100%) 100 | CO2exhaust = 79, // u16 CO2 level exhaust air (0-2000 ppm) 101 | Tsi = 80, // f8.8 Supply inlet temperature (°C) 102 | Tso = 81, // f8.8 Supply outlet temperature (°C) 103 | Tei = 82, // f8.8 Exhaust inlet temperature (°C) 104 | Teo = 83, // f8.8 Exhaust outlet temperature (°C) 105 | RPMexhaust = 84, // u16 Exhaust fan speed in rpm 106 | RPMsupply = 85, // u16 Supply fan speed in rpm 107 | RBPflagsVentilationHeatRecovery = 86, // flag8/flag8 Remote ventilation / heat-recovery parameter transfer-enable & read/write flags 108 | NominalVentilationValue = 87, // u8/- Nominal relative value for ventilation (0-100 %) 109 | TSPventilationHeatRecovery = 88, // u8/u8 Number of Transparent-Slave-Parameters supported by TSP’s ventilation / heat-recovery 110 | TSPindexTSPvalueVentilationHeatRecovery = 89, // u8/u8 Index number / Value of referred-to transparent TSP’s ventilation / heat-recovery parameter. 111 | FHBsizeVentilationHeatRecovery = 90, // u8/u8 Size of Fault-History-Buffer supported by ventilation / heat-recovery 112 | FHBindexFHBvalueVentilationHeatRecovery = 91, // u8/u8 Index number / Value of referred-to fault-history buffer entry ventilation / heat-recovery 113 | Brand = 93, // u8/u8 Index number of the character in the text string ASCII character referenced by the above index number 114 | BrandVersion = 94, // u8/u8 Index number of the character in the text string ASCII character referenced by the above index number 115 | BrandSerialNumber = 95, // u8/u8 Index number of the character in the text string ASCII character referenced by the above index number 116 | CoolingOperationHours = 96, // u16 Number of hours that the slave is in Cooling Mode. Reset by zero is optional for slave 117 | PowerCycles = 97, // u16 Number of Power Cycles of a slave (wake-up after Reset), Reset by zero is optional for slave 118 | RFsensorStatusInformation = 98, // special/special For a specific RF sensor the RF strength and battery level is written 119 | RemoteOverrideOperatingModeHeatingDHW = 99, // special/special Operating Mode HC1, HC2/ Operating Mode DHW 120 | RemoteOverrideFunction = 100, // flag8/- Function of manual and program changes in master and remote room Setpoint 121 | StatusSolarStorage = 101, // flag8/flag8 Master and Slave Status flags Solar Storage 122 | ASFflagsOEMfaultCodeSolarStorage = 102, // flag8/u8 Application-specific fault flags and OEM fault code Solar Storage 123 | SConfigSMemberIDcodeSolarStorage = 103, // flag8/u8 Slave Configuration Flags / Slave MemberID Code Solar Storage 124 | SolarStorageVersion = 104, // u8/u8 Solar Storage product version number and type 125 | TSPSolarStorage = 105, // u8/u8 Number of Transparent - Slave - Parameters supported by TSP’s Solar Storage 126 | TSPindexTSPvalueSolarStorage = 106, // u8/u8 Index number / Value of referred - to transparent TSP’s Solar Storage parameter. 127 | FHBsizeSolarStorage = 107, // u8/u8 Size of Fault - History - Buffer supported by Solar Storage 128 | FHBindexFHBvalueSolarStorage = 108, // u8/u8 Index number / Value of referred - to fault - history buffer entry Solar Storage 129 | ElectricityProducerStarts = 109, // U16 Number of start of the electricity producer. 130 | ElectricityProducerHours = 110, // U16 Number of hours the electricity produces is in operation 131 | ElectricityProduction = 111, // U16 Current electricity production in Watt. 132 | CumulativElectricityProduction = 112, // U16 Cumulative electricity production in KWh. 133 | UnsuccessfulBurnerStarts = 113, // u16 Number of un - successful burner starts 134 | FlameSignalTooLowNumber = 114, // u16 Number of times flame signal was too low 135 | OEMDiagnosticCode = 115, // u16 OEM - specific diagnostic / service code 136 | SuccessfulBurnerStarts = 116, // u16 Number of succesful starts burner 137 | CHPumpStarts = 117, // u16 Number of starts CH pump 138 | DHWPumpValveStarts = 118, // u16 Number of starts DHW pump / valve 139 | DHWBurnerStarts = 119, // u16 Number of starts burner during DHW mode 140 | BurnerOperationHours = 120, // u16 Number of hours that burner is in operation(i.e.flame on) 141 | CHPumpOperationHours = 121, // u16 Number of hours that CH pump has been running 142 | DHWPumpValveOperationHours = 122, // u16 Number of hours that DHW pump has been running or DHW valve has been opened 143 | DHWBurnerOperationHours = 123, // u16 Number of hours that burner is in operation during DHW mode 144 | OpenThermVersionMaster = 124, // f8.8 The implemented version of the OpenTherm Protocol Specification in the master. 145 | OpenThermVersionSlave = 125, // f8.8 The implemented version of the OpenTherm Protocol Specification in the slave. 146 | MasterVersion = 126, // u8/u8 Master product version number and type 147 | SlaveVersion = 127, // u8/u8 Slave product version number and type 148 | }; 149 | 150 | enum class OpenThermStatus : byte 151 | { 152 | NOT_INITIALIZED, 153 | READY, 154 | DELAY, 155 | REQUEST_SENDING, 156 | RESPONSE_WAITING, 157 | RESPONSE_START_BIT, 158 | RESPONSE_RECEIVING, 159 | RESPONSE_READY, 160 | RESPONSE_INVALID 161 | }; 162 | 163 | class OpenTherm 164 | { 165 | public: 166 | OpenTherm(int inPin = 4, int outPin = 5, bool isSlave = false); 167 | ~OpenTherm(); 168 | volatile OpenThermStatus status; 169 | void begin(void (*handleInterruptCallback)(void)); 170 | void begin(void (*handleInterruptCallback)(void), void (*processResponseCallback)(unsigned long, OpenThermResponseStatus)); 171 | #if !defined(__AVR__) 172 | void begin(); 173 | void begin(std::function processResponseFunction); 174 | #endif 175 | bool isReady(); 176 | unsigned long sendRequest(unsigned long request); 177 | bool sendResponse(unsigned long request); 178 | bool sendRequestAsync(unsigned long request); 179 | [[deprecated("Use OpenTherm::sendRequestAsync(unsigned long) instead")]] 180 | bool sendRequestAync(unsigned long request) { 181 | return sendRequestAsync(request); 182 | } 183 | static unsigned long buildRequest(OpenThermMessageType type, OpenThermMessageID id, unsigned int data); 184 | static unsigned long buildResponse(OpenThermMessageType type, OpenThermMessageID id, unsigned int data); 185 | unsigned long getLastResponse(); 186 | OpenThermResponseStatus getLastResponseStatus(); 187 | static const char *statusToString(OpenThermResponseStatus status); 188 | void handleInterrupt(); 189 | #if !defined(__AVR__) 190 | static void handleInterruptHelper(void* ptr); 191 | #endif 192 | void process(); 193 | void end(); 194 | 195 | static bool parity(unsigned long frame); 196 | static OpenThermMessageType getMessageType(unsigned long message); 197 | static OpenThermMessageID getDataID(unsigned long frame); 198 | static const char *messageTypeToString(OpenThermMessageType message_type); 199 | static bool isValidRequest(unsigned long request); 200 | static bool isValidResponse(unsigned long response); 201 | 202 | // requests 203 | static unsigned long buildSetBoilerStatusRequest(bool enableCentralHeating, bool enableHotWater = false, bool enableCooling = false, bool enableOutsideTemperatureCompensation = false, bool enableCentralHeating2 = false); 204 | static unsigned long buildSetBoilerTemperatureRequest(float temperature); 205 | static unsigned long buildGetBoilerTemperatureRequest(); 206 | 207 | // responses 208 | static bool isFault(unsigned long response); 209 | static bool isCentralHeatingActive(unsigned long response); 210 | static bool isHotWaterActive(unsigned long response); 211 | static bool isFlameOn(unsigned long response); 212 | static bool isCoolingActive(unsigned long response); 213 | static bool isDiagnostic(unsigned long response); 214 | static uint16_t getUInt(const unsigned long response); 215 | static float getFloat(const unsigned long response); 216 | static unsigned int temperatureToData(float temperature); 217 | 218 | // basic requests 219 | unsigned long setBoilerStatus(bool enableCentralHeating, bool enableHotWater = false, bool enableCooling = false, bool enableOutsideTemperatureCompensation = false, bool enableCentralHeating2 = false); 220 | bool setBoilerTemperature(float temperature); 221 | float getBoilerTemperature(); 222 | float getReturnTemperature(); 223 | bool setDHWSetpoint(float temperature); 224 | float getDHWTemperature(); 225 | float getModulation(); 226 | float getPressure(); 227 | unsigned char getFault(); 228 | 229 | private: 230 | const int inPin; 231 | const int outPin; 232 | const bool isSlave; 233 | 234 | volatile unsigned long response; 235 | volatile OpenThermResponseStatus responseStatus; 236 | volatile unsigned long responseTimestamp; 237 | volatile byte responseBitIndex; 238 | 239 | int readState(); 240 | void setActiveState(); 241 | void setIdleState(); 242 | void activateBoiler(); 243 | 244 | void sendBit(bool high); 245 | void processResponse(); 246 | void (*processResponseCallback)(unsigned long, OpenThermResponseStatus); 247 | #if !defined(__AVR__) 248 | std::function processResponseFunction; 249 | #endif 250 | }; 251 | 252 | #ifndef ICACHE_RAM_ATTR 253 | #define ICACHE_RAM_ATTR 254 | #endif 255 | 256 | #ifndef IRAM_ATTR 257 | #define IRAM_ATTR ICACHE_RAM_ATTR 258 | #endif 259 | 260 | #endif // OpenTherm_h 261 | --------------------------------------------------------------------------------