Latest opentherm (#23704)

* Revert "Build firmware from Master branch"

* Updated workflows

* Added ENS160 (Air quality) and ENS210 (Temperature & Humidity) sensor

* Revert "Added ENS160 (Air quality) and ENS210 (Temperature & Humidity) sensor"

* pre-release 9.3.0

* Update README.md

* Update xsns_05_ds18x20.ino

Fix DS18x20 driver timing issue (#11270)

* Prep release 9.4.0

* Prep 9.4.0

* Update Spanish and Italian

* Update languages

* Push rebuild

* Update changelog

* Update tasmota_version.h

* Update CHANGELOG.md

* Update README.md

* Prep v10.1.0

* revert xlgt_01_ws2812

* Update xsns_69_opentherm.ino

Add variable overrides

* Revert "Update xsns_69_opentherm.ino"

* Prep release

* Prep release

* Prep release 11.1

* Prep release 12.0

* Fix resolving MQTT and NTP servers

- Fix resolving MQTT and NTP servers (#15816)
- Bump version to v12.0.1

* Update RELEASENOTES.md

* Update CHANGELOG.md

* Release 12.1

* Release 12.1

* Revert camera changes

* Prep v12.1.1

* Prep v12.1.1

* Prep v12.1.1

* OT bugfix

* Prep release v12.2.0

* Prep v12.3.0 release

* Revert Tuya change

* add safeboot to release (#17393)

* Update Tasmota_build_master.yml

* Fix ESP8266 zifbee exception 3

* Update RELEASENOTES.md

* fix needed depend. base32-images (#17406)

* Fix ESP32 uploads

* Create TASMOTA_FullLogo_Vector_White.svg

* Fix support for non-sequential buttons and switches

Fix support for non-sequential buttons and switches (#17967)

* Fix duplicate EnergyTotal update

* Update README.md

* New workflow for release (#18722)

* Update CHANGELOG.md

* Update CHANGELOG.md

* fix rs485 transmit

* fix modbus

* prep v13.1

* Prep v13.2.0

* Prep v13.3

* Update to v13.4.0

* Prep release v14.0.0

* Prep release v14.0.0

* Prep v14.1.0

* Prep v14.2.0

* Update CHANGELOG.md

* Prep 14.4

* Update changelog

* Prep v14.5.0

* remove abs from analog sensor

* Update CHANGELOG.md

* Prep release v14.6.0

* Update CHANGELOG.md

* Prep v15.0.0

* change opentherm library

* fixes for new open therm library

* remove changes

* remove changes

---------

Co-authored-by: Jason2866 <24528715+Jason2866@users.noreply.github.com>
Co-authored-by: Theo Arends <11044339+arendst@users.noreply.github.com>
Co-authored-by: chrfriese123 <christoph@frieseonline.de>
Co-authored-by: Alexey Pavlov <ap@profi.ru>
Co-authored-by: Serge <60098151+Xjeater@users.noreply.github.com>
This commit is contained in:
Alexey Pavlov 2025-07-20 16:29:10 +03:00 committed by GitHub
parent aa7d74dec0
commit 6080bdc472
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 858 additions and 656 deletions

View File

@ -1,410 +0,0 @@
/*
OpenTherm.cpp - OpenTherm Communication Library For Arduino, ESP8266
Copyright 2018, Ihor Melnyk
*/
#include "OpenTherm.h"
OpenTherm::OpenTherm(int inPin, int outPin, bool isSlave):
status(OpenThermStatus::OPTH_NOT_INITIALIZED),
inPin(inPin),
outPin(outPin),
isSlave(isSlave),
response(0),
responseStatus(OpenThermResponseStatus::OPTH_NONE),
responseTimestamp(0),
handleInterruptCallback(NULL),
processResponseCallback(NULL)
{
}
void OpenTherm::begin(void(*handleInterruptCallback)(void), void(*processResponseCallback)(unsigned long, int))
{
pinMode(inPin, INPUT);
pinMode(outPin, OUTPUT);
if (handleInterruptCallback != NULL) {
this->handleInterruptCallback = handleInterruptCallback;
attachInterrupt(digitalPinToInterrupt(inPin), handleInterruptCallback, CHANGE);
}
activateBoiler();
status = OpenThermStatus::OPTH_READY;
this->processResponseCallback = processResponseCallback;
}
void OpenTherm::begin(void(*handleInterruptCallback)(void))
{
begin(handleInterruptCallback, NULL);
}
bool ICACHE_RAM_ATTR OpenTherm::isReady()
{
return status == OpenThermStatus::OPTH_READY;
}
int ICACHE_RAM_ATTR OpenTherm::readState() {
return digitalRead(inPin);
}
void OpenTherm::setActiveState() {
digitalWrite(outPin, LOW);
}
void OpenTherm::setIdleState() {
digitalWrite(outPin, HIGH);
}
void OpenTherm::activateBoiler() {
setIdleState();
delay(1000);
}
void OpenTherm::sendBit(bool high) {
if (high) setActiveState(); else setIdleState();
delayMicroseconds(500);
if (high) setIdleState(); else setActiveState();
delayMicroseconds(500);
}
bool OpenTherm::sendRequestAync(unsigned long request)
{
//Serial.println("Request: " + String(request, HEX));
noInterrupts();
const bool ready = isReady();
interrupts();
if (!ready)
return false;
status = OpenThermStatus::OPTH_REQUEST_SENDING;
response = 0;
responseStatus = OpenThermResponseStatus::OPTH_NONE;
sendBit(HIGH); //start bit
for (int i = 31; i >= 0; i--) {
sendBit(bitRead(request, i));
}
sendBit(HIGH); //stop bit
setIdleState();
status = OpenThermStatus::OPTH_RESPONSE_WAITING;
responseTimestamp = micros();
return true;
}
unsigned long OpenTherm::sendRequest(unsigned long request)
{
if (!sendRequestAync(request)) return 0;
while (!isReady()) {
process();
yield();
}
return response;
}
bool OpenTherm::sendResponse(unsigned long request)
{
status = OpenThermStatus::OPTH_REQUEST_SENDING;
response = 0;
responseStatus = OpenThermResponseStatus::OPTH_NONE;
sendBit(HIGH); //start bit
for (int i = 31; i >= 0; i--) {
sendBit(bitRead(request, i));
}
sendBit(HIGH); //stop bit
setIdleState();
status = OpenThermStatus::OPTH_READY;
return true;
}
OpenThermResponseStatus OpenTherm::getLastResponseStatus()
{
return responseStatus;
}
void ICACHE_RAM_ATTR OpenTherm::handleInterrupt()
{
if (isReady())
{
if (isSlave && readState() == HIGH) {
status = OpenThermStatus::OPTH_RESPONSE_WAITING;
}
else {
return;
}
}
unsigned long newTs = micros();
if (status == OpenThermStatus::OPTH_RESPONSE_WAITING) {
if (readState() == HIGH) {
status = OpenThermStatus::OPTH_RESPONSE_START_BIT;
responseTimestamp = newTs;
}
else {
status = OpenThermStatus::OPTH_RESPONSE_INVALID;
responseTimestamp = newTs;
}
}
else if (status == OpenThermStatus::OPTH_RESPONSE_START_BIT) {
if ((newTs - responseTimestamp < 750) && readState() == LOW) {
status = OpenThermStatus::OPTH_RESPONSE_RECEIVING;
responseTimestamp = newTs;
responseBitIndex = 0;
}
else {
status = OpenThermStatus::OPTH_RESPONSE_INVALID;
responseTimestamp = newTs;
}
}
else if (status == OpenThermStatus::OPTH_RESPONSE_RECEIVING) {
if ((newTs - responseTimestamp) > 750) {
if (responseBitIndex < 32) {
response = (response << 1) | !readState();
responseTimestamp = newTs;
responseBitIndex++;
}
else { //stop bit
status = OpenThermStatus::OPTH_RESPONSE_READY;
responseTimestamp = newTs;
}
}
}
}
void OpenTherm::process()
{
noInterrupts();
OpenThermStatus st = status;
unsigned long ts = responseTimestamp;
interrupts();
if (st == OpenThermStatus::OPTH_READY) return;
unsigned long newTs = micros();
if (st != OpenThermStatus::OPTH_NOT_INITIALIZED && (newTs - ts) > 1000000) {
status = OpenThermStatus::OPTH_READY;
responseStatus = OpenThermResponseStatus::OPTH_TIMEOUT;
if (processResponseCallback != NULL) {
processResponseCallback(response, responseStatus);
}
}
else if (st == OpenThermStatus::OPTH_RESPONSE_INVALID) {
status = OpenThermStatus::OPTH_DELAY;
responseStatus = OpenThermResponseStatus::OPTH_INVALID;
if (processResponseCallback != NULL) {
processResponseCallback(response, responseStatus);
}
}
else if (st == OpenThermStatus::OPTH_RESPONSE_READY) {
status = OpenThermStatus::OPTH_DELAY;
responseStatus = (isSlave ? isValidRequest(response) : isValidResponse(response)) ? OpenThermResponseStatus::OPTH_SUCCESS : OpenThermResponseStatus::OPTH_INVALID;
if (processResponseCallback != NULL) {
processResponseCallback(response, responseStatus);
}
}
else if (st == OpenThermStatus::OPTH_DELAY) {
if ((newTs - ts) > 100000) {
status = OpenThermStatus::OPTH_READY;
}
}
}
bool OpenTherm::parity(unsigned long frame) //odd parity
{
byte p = 0;
while (frame > 0)
{
if (frame & 1) p++;
frame = frame >> 1;
}
return (p & 1);
}
OpenThermMessageType OpenTherm::getMessageType(unsigned long message)
{
OpenThermMessageType msg_type = static_cast<OpenThermMessageType>((message >> 28) & 7);
return msg_type;
}
OpenThermMessageID OpenTherm::getDataID(unsigned long frame)
{
return (OpenThermMessageID)((frame >> 16) & 0xFF);
}
unsigned long OpenTherm::buildRequest(OpenThermMessageType type, OpenThermMessageID id, unsigned int data)
{
unsigned long request = data;
if (type == OpenThermMessageType::OPTH_WRITE_DATA) {
request |= 1ul << 28;
}
request |= ((unsigned long)id) << 16;
if (OpenTherm::parity(request)) request |= (1ul << 31);
return request;
}
unsigned long OpenTherm::buildResponse(OpenThermMessageType type, OpenThermMessageID id, unsigned int data)
{
unsigned long response = data;
response |= type << 28;
response |= ((unsigned long)id) << 16;
if (OpenTherm::parity(response)) response |= (1ul << 31);
return response;
}
bool OpenTherm::isValidResponse(unsigned long response)
{
if (OpenTherm::parity(response)) return false;
byte msgType = (response << 1) >> 29;
return msgType == OPTH_READ_ACK || msgType == OPTH_WRITE_ACK;
}
bool OpenTherm::isValidRequest(unsigned long request)
{
if (OpenTherm::parity(request)) return false;
byte msgType = (request << 1) >> 29;
return msgType == OPTH_READ_DATA || msgType == OPTH_WRITE_DATA;
}
void OpenTherm::end() {
if (this->handleInterruptCallback != NULL) {
detachInterrupt(digitalPinToInterrupt(inPin));
}
}
const char *OpenTherm::statusToString(OpenThermResponseStatus status)
{
switch (status) {
case OPTH_NONE: return "NONE";
case OPTH_SUCCESS: return "SUCCESS";
case OPTH_INVALID: return "INVALID";
case OPTH_TIMEOUT: return "TIMEOUT";
default: return "UNKNOWN";
}
}
const char *OpenTherm::messageTypeToString(OpenThermMessageType message_type)
{
switch (message_type) {
case OPTH_READ_DATA: return "READ_DATA";
case OPTH_WRITE_DATA: return "WRITE_DATA";
case OPTH_INVALID_DATA: return "INVALID_DATA";
case OPTH_RESERVED: return "RESERVED";
case OPTH_READ_ACK: return "READ_ACK";
case OPTH_WRITE_ACK: return "WRITE_ACK";
case OPTH_DATA_INVALID: return "DATA_INVALID";
case OPTH_UNKNOWN_DATA_ID: return "UNKNOWN_DATA_ID";
default: return "UNKNOWN";
}
}
//building requests
unsigned long OpenTherm::buildSetBoilerStatusRequest(bool enableCentralHeating, bool enableHotWater, bool enableCooling, bool enableOutsideTemperatureCompensation, bool enableCentralHeating2) {
unsigned int data = enableCentralHeating | (enableHotWater << 1) | (enableCooling << 2) | (enableOutsideTemperatureCompensation << 3) | (enableCentralHeating2 << 4);
data <<= 8;
return buildRequest(OpenThermMessageType::OPTH_READ_DATA, OpenThermMessageID::Status, data);
}
unsigned long OpenTherm::buildSetBoilerTemperatureRequest(float temperature) {
unsigned int data = temperatureToData(temperature);
return buildRequest(OpenThermMessageType::OPTH_WRITE_DATA, OpenThermMessageID::TSet, data);
}
unsigned long OpenTherm::buildSetHotWaterTemperatureRequest(float temperature) {
unsigned int data = temperatureToData(temperature);
return buildRequest(OpenThermMessageType::OPTH_WRITE_DATA, OpenThermMessageID::TdhwSet, data);
}
unsigned long OpenTherm::buildGetBoilerTemperatureRequest() {
return buildRequest(OpenThermMessageType::OPTH_READ_DATA, OpenThermMessageID::Tboiler, 0);
}
unsigned long OpenTherm::buildSlaveConfigurationRequest() {
return buildRequest(OpenThermMessageType::OPTH_READ_DATA, OpenThermMessageID::SConfigSMemberIDcode, 0);
}
//parsing responses
bool OpenTherm::isFault(unsigned long response) {
return response & 0x1;
}
bool OpenTherm::isCentralHeatingActive(unsigned long response) {
return response & 0x2;
}
bool OpenTherm::isHotWaterActive(unsigned long response) {
return response & 0x4;
}
bool OpenTherm::isFlameOn(unsigned long response) {
return response & 0x8;
}
bool OpenTherm::isCoolingActive(unsigned long response) {
return response & 0x10;
}
bool OpenTherm::isDiagnostic(unsigned long response) {
return response & 0x40;
}
uint16_t OpenTherm::getUInt(const unsigned long response) {
const uint16_t u88 = response & 0xffff;
return u88;
}
float OpenTherm::getFloat(const unsigned long response) {
const uint16_t u88 = getUInt(response);
const float f = (u88 & 0x8000) ? -(0x10000L - u88) / 256.0f : u88 / 256.0f;
return f;
}
unsigned int OpenTherm::temperatureToData(float temperature) {
if (temperature < 0) temperature = 0;
if (temperature > 100) temperature = 100;
unsigned int data = (unsigned int)(temperature * 256);
return data;
}
//basic requests
unsigned long OpenTherm::setBoilerStatus(bool enableCentralHeating, bool enableHotWater, bool enableCooling, bool enableOutsideTemperatureCompensation, bool enableCentralHeating2) {
return sendRequest(buildSetBoilerStatusRequest(enableCentralHeating, enableHotWater, enableCooling, enableOutsideTemperatureCompensation, enableCentralHeating2));
}
bool OpenTherm::setBoilerTemperature(float temperature) {
unsigned long response = sendRequest(buildSetBoilerTemperatureRequest(temperature));
return isValidResponse(response);
}
bool OpenTherm::setHotWaterTemperature(float temperature) {
unsigned long response = sendRequest(buildSetHotWaterTemperatureRequest(temperature));
return isValidResponse(response);
}
float OpenTherm::getBoilerTemperature() {
unsigned long response = sendRequest(buildGetBoilerTemperatureRequest());
return isValidResponse(response) ? getFloat(response) : 0;
}
float OpenTherm::getReturnTemperature() {
unsigned long response = sendRequest(buildRequest(OpenThermRequestType::OPTH_READ, OpenThermMessageID::Tret, 0));
return isValidResponse(response) ? getFloat(response) : 0;
}
float OpenTherm::getModulation() {
unsigned long response = sendRequest(buildRequest(OpenThermRequestType::OPTH_READ, OpenThermMessageID::RelModLevel, 0));
return isValidResponse(response) ? getFloat(response) : 0;
}
float OpenTherm::getPressure() {
unsigned long response = sendRequest(buildRequest(OpenThermRequestType::OPTH_READ, OpenThermMessageID::CHPressure, 0));
return isValidResponse(response) ? getFloat(response) : 0;
}
unsigned char OpenTherm::getFault() {
return ((sendRequest(buildRequest(OpenThermRequestType::OPTH_READ, OpenThermMessageID::ASFflags, 0)) >> 8) & 0xff);
}
unsigned long OpenTherm::getSlaveConfiguration() {
return sendRequest(buildSlaveConfigurationRequest());
}

View File

@ -1,193 +0,0 @@
/*
OpenTherm.h - OpenTherm Library for the ESP8266/Arduino platform
https://github.com/ihormelnyk/OpenTherm
http://ihormelnyk.com/pages/OpenTherm
Licensed under MIT license
Copyright 2018, Ihor Melnyk
Frame Structure:
P MGS-TYPE SPARE DATA-ID DATA-VALUE
0 000 0000 00000000 00000000 00000000
*/
#ifndef OpenTherm_h
#define OpenTherm_h
#include <stdint.h>
#include <Arduino.h>
enum OpenThermResponseStatus {
OPTH_NONE,
OPTH_SUCCESS,
OPTH_INVALID,
OPTH_TIMEOUT
};
enum OpenThermMessageType {
/* Master to Slave */
OPTH_READ_DATA = B000,
OPTH_READ = OPTH_READ_DATA, // for backwared compatibility
OPTH_WRITE_DATA = B001,
OPTH_WRITE = OPTH_WRITE_DATA, // for backwared compatibility
OPTH_INVALID_DATA = B010,
OPTH_RESERVED = B011,
/* Slave to Master */
OPTH_READ_ACK = B100,
OPTH_WRITE_ACK = B101,
OPTH_DATA_INVALID = B110,
OPTH_UNKNOWN_DATA_ID = B111
};
typedef OpenThermMessageType OpenThermRequestType; // for backwared compatibility
enum OpenThermMessageID {
Status, // flag8 / flag8 Master and Slave Status flags.
TSet, // f8.8 Control setpoint ie CH water temperature setpoint (°C)
MConfigMMemberIDcode, // flag8 / u8 Master Configuration Flags / Master MemberID Code
SConfigSMemberIDcode, // flag8 / u8 Slave Configuration Flags / Slave MemberID Code
Command, // u8 / u8 Remote Command
ASFflags, // / OEM-fault-code flag8 / u8 Application-specific fault flags and OEM fault code
RBPflags, // flag8 / flag8 Remote boiler parameter transfer-enable & read/write flags
CoolingControl, // f8.8 Cooling control signal (%)
TsetCH2, // f8.8 Control setpoint for 2e CH circuit (°C)
TrOverride, // f8.8 Remote override room setpoint
TSP, // u8 / u8 Number of Transparent-Slave-Parameters supported by slave
TSPindexTSPvalue, // u8 / u8 Index number / Value of referred-to transparent slave parameter.
FHBsize, // u8 / u8 Size of Fault-History-Buffer supported by slave
FHBindexFHBvalue, // u8 / u8 Index number / Value of referred-to fault-history buffer entry.
MaxRelModLevelSetting, // f8.8 Maximum relative modulation level setting (%)
MaxCapacityMinModLevel, // u8 / u8 Maximum boiler capacity (kW) / Minimum boiler modulation level(%)
TrSet, // f8.8 Room Setpoint (°C)
RelModLevel, // f8.8 Relative Modulation Level (%)
CHPressure, // f8.8 Water pressure in CH circuit (bar)
DHWFlowRate, // f8.8 Water flow rate in DHW circuit. (litres/minute)
DayTime, // special / u8 Day of Week and Time of Day
Date, // u8 / u8 Calendar date
Year, // u16 Calendar year
TrSetCH2, // f8.8 Room Setpoint for 2nd CH circuit (°C)
Tr, // f8.8 Room temperature (°C)
Tboiler, // f8.8 Boiler flow water temperature (°C)
Tdhw, // f8.8 DHW temperature (°C)
Toutside, // f8.8 Outside temperature (°C)
Tret, // f8.8 Return water temperature (°C)
Tstorage, // f8.8 Solar storage temperature (°C)
Tcollector, // f8.8 Solar collector temperature (°C)
TflowCH2, // f8.8 Flow water temperature CH2 circuit (°C)
Tdhw2, // f8.8 Domestic hot water temperature 2 (°C)
Texhaust, // s16 Boiler exhaust temperature (°C)
TdhwSetUBTdhwSetLB = 48, // s8 / s8 DHW setpoint upper & lower bounds for adjustment (°C)
MaxTSetUBMaxTSetLB, // s8 / s8 Max CH water setpoint upper & lower bounds for adjustment (°C)
HcratioUBHcratioLB, // s8 / s8 OTC heat curve ratio upper & lower bounds for adjustment
TdhwSet = 56, // f8.8 DHW setpoint (°C) (Remote parameter 1)
MaxTSet, // f8.8 Max CH water setpoint (°C) (Remote parameters 2)
Hcratio, // f8.8 OTC heat curve ratio (°C) (Remote parameter 3)
RemoteOverrideFunction = 100, // flag8 / - Function of manual and program changes in master and remote room setpoint.
OEMDiagnosticCode = 115, // u16 OEM-specific diagnostic/service code
BurnerStarts, // u16 Number of starts burner
CHPumpStarts, // u16 Number of starts CH pump
DHWPumpValveStarts, // u16 Number of starts DHW pump/valve
DHWBurnerStarts, // u16 Number of starts burner during DHW mode
BurnerOperationHours, // u16 Number of hours that burner is in operation (i.e. flame on)
CHPumpOperationHours, // u16 Number of hours that CH pump has been running
DHWPumpValveOperationHours, // u16 Number of hours that DHW pump has been running or DHW valve has been opened
DHWBurnerOperationHours, // u16 Number of hours that burner is in operation during DHW mode
OpenThermVersionMaster, // f8.8 The implemented version of the OpenTherm Protocol Specification in the master.
OpenThermVersionSlave, // f8.8 The implemented version of the OpenTherm Protocol Specification in the slave.
MasterVersion, // u8 / u8 Master product version number and type
SlaveVersion, // u8 / u8 Slave product version number and type
};
enum OpenThermStatus {
OPTH_NOT_INITIALIZED,
OPTH_READY,
OPTH_DELAY,
OPTH_REQUEST_SENDING,
OPTH_RESPONSE_WAITING,
OPTH_RESPONSE_START_BIT,
OPTH_RESPONSE_RECEIVING,
OPTH_RESPONSE_READY,
OPTH_RESPONSE_INVALID
};
class OpenTherm
{
public:
OpenTherm(int inPin = 4, int outPin = 5, bool isSlave = false);
volatile OpenThermStatus status;
void begin(void(*handleInterruptCallback)(void));
void begin(void(*handleInterruptCallback)(void), void(*processResponseCallback)(unsigned long, int));
bool isReady();
unsigned long sendRequest(unsigned long request);
bool sendResponse(unsigned long request);
bool sendRequestAync(unsigned long request);
static unsigned long buildRequest(OpenThermMessageType type, OpenThermMessageID id, unsigned int data);
static unsigned long buildResponse(OpenThermMessageType type, OpenThermMessageID id, unsigned int data);
OpenThermResponseStatus getLastResponseStatus();
const char *statusToString(OpenThermResponseStatus status);
void handleInterrupt();
void process();
void end();
static bool parity(unsigned long frame);
OpenThermMessageType getMessageType(unsigned long message);
OpenThermMessageID getDataID(unsigned long frame);
const char *messageTypeToString(OpenThermMessageType message_type);
bool isValidRequest(unsigned long request);
bool isValidResponse(unsigned long response);
//requests
unsigned long buildSetBoilerStatusRequest(bool enableCentralHeating, bool enableHotWater = false, bool enableCooling = false, bool enableOutsideTemperatureCompensation = false, bool enableCentralHeating2 = false);
unsigned long buildSetBoilerTemperatureRequest(float temperature);
unsigned long buildGetBoilerTemperatureRequest();
unsigned long buildSetHotWaterTemperatureRequest(float temperature);
unsigned long buildSlaveConfigurationRequest();
//responses
static bool isFault(unsigned long response);
static bool isCentralHeatingActive(unsigned long response);
static bool isHotWaterActive(unsigned long response);
static bool isFlameOn(unsigned long response);
static bool isCoolingActive(unsigned long response);
static bool isDiagnostic(unsigned long response);
static uint16_t getUInt(const unsigned long response);
static float getFloat(const unsigned long response);
static unsigned int temperatureToData(float temperature);
//basic requests
unsigned long setBoilerStatus(bool enableCentralHeating, bool enableHotWater = false, bool enableCooling = false, bool enableOutsideTemperatureCompensation = false, bool enableCentralHeating2 = false);
bool setBoilerTemperature(float temperature);
bool setHotWaterTemperature(float temperature);
float getBoilerTemperature();
float getReturnTemperature();
float getModulation();
float getPressure();
unsigned char getFault();
unsigned long getSlaveConfiguration();
private:
const int inPin;
const int outPin;
const bool isSlave;
volatile unsigned long response;
volatile OpenThermResponseStatus responseStatus;
volatile unsigned long responseTimestamp;
volatile byte responseBitIndex;
int readState();
void setActiveState();
void setIdleState();
void activateBoiler();
void sendBit(bool high);
void(*handleInterruptCallback)();
void(*processResponseCallback)(unsigned long, int);
};
#ifndef ICACHE_RAM_ATTR
#define ICACHE_RAM_ATTR
#endif
#endif // OpenTherm_h

View File

@ -1,29 +0,0 @@
Attention when updating library. Changes in lib needed!!
All OpenTherm constants shall be prepended with `OPTH_` to avoid conflicts with other libs.
See commit https://github.com/arendst/Tasmota/commit/960291729ccc7cb4da50108e5299d44a79cb06de
As of OpenTherm-0.9.0, hte list is:
OPTH_NONE
OPTH_SUCCESS
OPTH_INVALID
OPTH_TIMEOUT
OPTH_READ_DATA
OPTH_READ
OPTH_WRITE_DATA
OPTH_WRITE
OPTH_INVALID_DATA
OPTH_RESERVED
OPTH_READ_ACK
OPTH_WRITE_ACK
OPTH_DATA_INVALID
OPTH_UNKNOWN_DATA_ID
OPTH_NOT_INITIALIZED
OPTH_READY
OPTH_DELAY
OPTH_REQUEST_SENDING
OPTH_RESPONSE_WAITING
OPTH_RESPONSE_START_BIT
OPTH_RESPONSE_RECEIVING
OPTH_RESPONSE_READY
OPTH_RESPONSE_INVALID

View File

@ -1,8 +1,8 @@
# OpenTherm Arduino/ESP8266 Library
# OpenTherm Arduino/ESP8266/ESP32 Library
This library provides implementation of OpenTherm protocol.
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 and other similar controllers.
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.
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).

View File

@ -30,6 +30,8 @@ doSomething KEYWORD2
setBoilerStatus KEYWORD2
setBoilerTemperature KEYWORD2
getBoilerTemperature KEYWORD2
setDHWSetpoint KEYWORD2
getDHWTemperature KEYWORD2
#######################################
# Instances (KEYWORD2)

View File

@ -1,8 +1,8 @@
name=OpenTherm Library
version=0.9.0
version=1.1.5
author=Ihor Melnyk <ihor.melnyk@gmail.com>
maintainer=Ihor Melnyk <ihor.melnyk@gmail.com>
sentence=OpenTherm Library for HVAC system control communication using Arduino and ESP8266 hardware.
sentence=OpenTherm Library for HVAC system control communication using Arduino and ESP8266/ESP32 hardware.
paragraph=OpenTherm Library is based on OpenTherm protocol specification v2.2 and works with all OpenTherm compatible boilers.
category=Communication
url=https://github.com/ihormelnyk/opentherm_library

View File

@ -0,0 +1,578 @@
/*
OpenTherm.cpp - OpenTherm Communication Library For Arduino, ESP8266, ESP32
Copyright 2023, Ihor Melnyk
*/
#include "OpenTherm.h"
#if !defined(__AVR__)
#include "FunctionalInterrupt.h"
#endif
OpenTherm::OpenTherm(int inPin, int outPin, bool isSlave) :
status(OpenThermStatus::NOT_INITIALIZED),
inPin(inPin),
outPin(outPin),
isSlave(isSlave),
response(0),
responseStatus(OpenThermResponseStatus::NONE),
responseTimestamp(0),
processResponseCallback(NULL)
{
}
void OpenTherm::begin(void (*handleInterruptCallback)(void))
{
pinMode(inPin, INPUT);
pinMode(outPin, OUTPUT);
if (handleInterruptCallback != NULL)
{
attachInterrupt(digitalPinToInterrupt(inPin), handleInterruptCallback, CHANGE);
}
else
{
#if !defined(__AVR__)
attachInterruptArg(
digitalPinToInterrupt(inPin),
OpenTherm::handleInterruptHelper,
this,
CHANGE
);
#endif
}
activateBoiler();
status = OpenThermStatus::READY;
}
void OpenTherm::begin(void (*handleInterruptCallback)(void), void (*processResponseCallback)(unsigned long, int))
{
begin(handleInterruptCallback);
this->processResponseCallback = processResponseCallback;
}
#if !defined(__AVR__)
void OpenTherm::begin()
{
begin(NULL);
}
void OpenTherm::begin(std::function<void(unsigned long, OpenThermResponseStatus)> processResponseFunction)
{
begin();
this->processResponseFunction = processResponseFunction;
}
#endif
bool IRAM_ATTR OpenTherm::isReady()
{
return status == OpenThermStatus::READY;
}
int IRAM_ATTR OpenTherm::readState()
{
return digitalRead(inPin);
}
void OpenTherm::setActiveState()
{
digitalWrite(outPin, LOW);
}
void OpenTherm::setIdleState()
{
digitalWrite(outPin, HIGH);
}
void OpenTherm::activateBoiler()
{
setIdleState();
delay(1000);
}
void OpenTherm::sendBit(bool high)
{
if (high)
setActiveState();
else
setIdleState();
delayMicroseconds(500);
if (high)
setIdleState();
else
setActiveState();
delayMicroseconds(500);
}
bool OpenTherm::sendRequestAsync(unsigned long request)
{
noInterrupts();
const bool ready = isReady();
if (!ready)
{
interrupts();
return false;
}
status = OpenThermStatus::REQUEST_SENDING;
response = 0;
responseStatus = OpenThermResponseStatus::NONE;
#ifdef INC_FREERTOS_H
BaseType_t schedulerState = xTaskGetSchedulerState();
if (schedulerState == taskSCHEDULER_RUNNING)
{
vTaskSuspendAll();
}
#endif
interrupts();
sendBit(HIGH); // start bit
for (int i = 31; i >= 0; i--)
{
sendBit(bitRead(request, i));
}
sendBit(HIGH); // stop bit
setIdleState();
responseTimestamp = micros();
status = OpenThermStatus::RESPONSE_WAITING;
#ifdef INC_FREERTOS_H
if (schedulerState == taskSCHEDULER_RUNNING) {
xTaskResumeAll();
}
#endif
return true;
}
unsigned long OpenTherm::sendRequest(unsigned long request)
{
if (!sendRequestAsync(request))
{
return 0;
}
while (!isReady())
{
process();
yield();
}
return response;
}
bool OpenTherm::sendResponse(unsigned long request)
{
noInterrupts();
const bool ready = isReady();
if (!ready)
{
interrupts();
return false;
}
status = OpenThermStatus::REQUEST_SENDING;
response = 0;
responseStatus = OpenThermResponseStatus::NONE;
#ifdef INC_FREERTOS_H
BaseType_t schedulerState = xTaskGetSchedulerState();
if (schedulerState == taskSCHEDULER_RUNNING)
{
vTaskSuspendAll();
}
#endif
interrupts();
sendBit(HIGH); // start bit
for (int i = 31; i >= 0; i--)
{
sendBit(bitRead(request, i));
}
sendBit(HIGH); // stop bit
setIdleState();
status = OpenThermStatus::READY;
#ifdef INC_FREERTOS_H
if (schedulerState == taskSCHEDULER_RUNNING) {
xTaskResumeAll();
}
#endif
return true;
}
unsigned long OpenTherm::getLastResponse()
{
return response;
}
OpenThermResponseStatus OpenTherm::getLastResponseStatus()
{
return responseStatus;
}
void IRAM_ATTR OpenTherm::handleInterrupt()
{
if (isReady())
{
if (isSlave && readState() == HIGH)
{
status = OpenThermStatus::RESPONSE_WAITING;
}
else
{
return;
}
}
unsigned long newTs = micros();
if (status == OpenThermStatus::RESPONSE_WAITING)
{
if (readState() == HIGH)
{
status = OpenThermStatus::RESPONSE_START_BIT;
responseTimestamp = newTs;
}
else
{
status = OpenThermStatus::RESPONSE_INVALID;
responseTimestamp = newTs;
}
}
else if (status == OpenThermStatus::RESPONSE_START_BIT)
{
if ((newTs - responseTimestamp < 750) && readState() == LOW)
{
status = OpenThermStatus::RESPONSE_RECEIVING;
responseTimestamp = newTs;
responseBitIndex = 0;
}
else
{
status = OpenThermStatus::RESPONSE_INVALID;
responseTimestamp = newTs;
}
}
else if (status == OpenThermStatus::RESPONSE_RECEIVING)
{
if ((newTs - responseTimestamp) > 750)
{
if (responseBitIndex < 32)
{
response = (response << 1) | !readState();
responseTimestamp = newTs;
responseBitIndex = responseBitIndex + 1;
}
else
{ // stop bit
status = OpenThermStatus::RESPONSE_READY;
responseTimestamp = newTs;
}
}
}
}
#if !defined(__AVR__)
void IRAM_ATTR OpenTherm::handleInterruptHelper(void* ptr)
{
static_cast<OpenTherm*>(ptr)->handleInterrupt();
}
#endif
void OpenTherm::processResponse()
{
if (processResponseCallback != NULL)
{
processResponseCallback(response, (int)responseStatus);
}
#if !defined(__AVR__)
if (this->processResponseFunction != NULL)
{
processResponseFunction(response, responseStatus);
}
#endif
}
void OpenTherm::process()
{
noInterrupts();
OpenThermStatus st = status;
unsigned long ts = responseTimestamp;
interrupts();
if (st == OpenThermStatus::READY)
return;
unsigned long newTs = micros();
if (st != OpenThermStatus::NOT_INITIALIZED && st != OpenThermStatus::DELAY && (newTs - ts) > 1000000)
{
status = OpenThermStatus::READY;
responseStatus = OpenThermResponseStatus::TIMEOUT;
processResponse();
}
else if (st == OpenThermStatus::RESPONSE_INVALID)
{
status = OpenThermStatus::DELAY;
responseStatus = OpenThermResponseStatus::INVALID;
processResponse();
}
else if (st == OpenThermStatus::RESPONSE_READY)
{
status = OpenThermStatus::DELAY;
responseStatus = (isSlave ? isValidRequest(response) : isValidResponse(response)) ? OpenThermResponseStatus::SUCCESS : OpenThermResponseStatus::INVALID;
processResponse();
}
else if (st == OpenThermStatus::DELAY)
{
if ((newTs - ts) > (isSlave ? 20000 : 100000))
{
status = OpenThermStatus::READY;
}
}
}
bool OpenTherm::parity(unsigned long frame) // odd parity
{
byte p = 0;
while (frame > 0)
{
if (frame & 1)
p++;
frame = frame >> 1;
}
return (p & 1);
}
OpenThermMessageType OpenTherm::getMessageType(unsigned long message)
{
OpenThermMessageType msg_type = static_cast<OpenThermMessageType>((message >> 28) & 7);
return msg_type;
}
OpenThermMessageID OpenTherm::getDataID(unsigned long frame)
{
return (OpenThermMessageID)((frame >> 16) & 0xFF);
}
unsigned long OpenTherm::buildRequest(OpenThermMessageType type, OpenThermMessageID id, unsigned int data)
{
unsigned long request = data;
if (type == OpenThermMessageType::WRITE_DATA)
{
request |= 1ul << 28;
}
request |= ((unsigned long)id) << 16;
if (parity(request))
request |= (1ul << 31);
return request;
}
unsigned long OpenTherm::buildResponse(OpenThermMessageType type, OpenThermMessageID id, unsigned int data)
{
unsigned long response = data;
response |= ((unsigned long)type) << 28;
response |= ((unsigned long)id) << 16;
if (parity(response))
response |= (1ul << 31);
return response;
}
bool OpenTherm::isValidResponse(unsigned long response)
{
if (parity(response))
return false;
byte msgType = (response << 1) >> 29;
return msgType == (byte)OpenThermMessageType::READ_ACK || msgType == (byte)OpenThermMessageType::WRITE_ACK;
}
bool OpenTherm::isValidRequest(unsigned long request)
{
if (parity(request))
return false;
byte msgType = (request << 1) >> 29;
return msgType == (byte)OpenThermMessageType::READ_DATA || msgType == (byte)OpenThermMessageType::WRITE_DATA;
}
void OpenTherm::end()
{
detachInterrupt(digitalPinToInterrupt(inPin));
}
OpenTherm::~OpenTherm()
{
end();
}
const char *OpenTherm::statusToString(OpenThermResponseStatus status)
{
switch (status)
{
case OpenThermResponseStatus::NONE:
return "NONE";
case OpenThermResponseStatus::SUCCESS:
return "SUCCESS";
case OpenThermResponseStatus::INVALID:
return "INVALID";
case OpenThermResponseStatus::TIMEOUT:
return "TIMEOUT";
default:
return "UNKNOWN";
}
}
const char *OpenTherm::messageTypeToString(OpenThermMessageType message_type)
{
switch (message_type)
{
case OpenThermMessageType::READ_DATA:
return "READ_DATA";
case OpenThermMessageType::WRITE_DATA:
return "WRITE_DATA";
case OpenThermMessageType::INVALID_DATA:
return "INVALID_DATA";
case OpenThermMessageType::RESERVED:
return "RESERVED";
case OpenThermMessageType::READ_ACK:
return "READ_ACK";
case OpenThermMessageType::WRITE_ACK:
return "WRITE_ACK";
case OpenThermMessageType::DATA_INVALID:
return "DATA_INVALID";
case OpenThermMessageType::UNKNOWN_DATA_ID:
return "UNKNOWN_DATA_ID";
default:
return "UNKNOWN";
}
}
// building requests
unsigned long OpenTherm::buildSetBoilerStatusRequest(bool enableCentralHeating, bool enableHotWater, bool enableCooling, bool enableOutsideTemperatureCompensation, bool enableCentralHeating2)
{
unsigned int data = enableCentralHeating | (enableHotWater << 1) | (enableCooling << 2) | (enableOutsideTemperatureCompensation << 3) | (enableCentralHeating2 << 4);
data <<= 8;
return buildRequest(OpenThermMessageType::READ_DATA, OpenThermMessageID::Status, data);
}
unsigned long OpenTherm::buildSetBoilerTemperatureRequest(float temperature)
{
unsigned int data = temperatureToData(temperature);
return buildRequest(OpenThermMessageType::WRITE_DATA, OpenThermMessageID::TSet, data);
}
unsigned long OpenTherm::buildGetBoilerTemperatureRequest()
{
return buildRequest(OpenThermMessageType::READ_DATA, OpenThermMessageID::Tboiler, 0);
}
// parsing responses
bool OpenTherm::isFault(unsigned long response)
{
return response & 0x1;
}
bool OpenTherm::isCentralHeatingActive(unsigned long response)
{
return response & 0x2;
}
bool OpenTherm::isHotWaterActive(unsigned long response)
{
return response & 0x4;
}
bool OpenTherm::isFlameOn(unsigned long response)
{
return response & 0x8;
}
bool OpenTherm::isCoolingActive(unsigned long response)
{
return response & 0x10;
}
bool OpenTherm::isDiagnostic(unsigned long response)
{
return response & 0x40;
}
uint16_t OpenTherm::getUInt(const unsigned long response)
{
const uint16_t u88 = response & 0xffff;
return u88;
}
float OpenTherm::getFloat(const unsigned long response)
{
const uint16_t u88 = getUInt(response);
const float f = (u88 & 0x8000) ? -(0x10000L - u88) / 256.0f : u88 / 256.0f;
return f;
}
unsigned int OpenTherm::temperatureToData(float temperature)
{
if (temperature < 0)
temperature = 0;
if (temperature > 100)
temperature = 100;
unsigned int data = (unsigned int)(temperature * 256);
return data;
}
// basic requests
unsigned long OpenTherm::setBoilerStatus(bool enableCentralHeating, bool enableHotWater, bool enableCooling, bool enableOutsideTemperatureCompensation, bool enableCentralHeating2)
{
return sendRequest(buildSetBoilerStatusRequest(enableCentralHeating, enableHotWater, enableCooling, enableOutsideTemperatureCompensation, enableCentralHeating2));
}
bool OpenTherm::setBoilerTemperature(float temperature)
{
unsigned long response = sendRequest(buildSetBoilerTemperatureRequest(temperature));
return isValidResponse(response);
}
float OpenTherm::getBoilerTemperature()
{
unsigned long response = sendRequest(buildGetBoilerTemperatureRequest());
return isValidResponse(response) ? getFloat(response) : 0;
}
float OpenTherm::getReturnTemperature()
{
unsigned long response = sendRequest(buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Tret, 0));
return isValidResponse(response) ? getFloat(response) : 0;
}
bool OpenTherm::setDHWSetpoint(float temperature)
{
unsigned int data = temperatureToData(temperature);
unsigned long response = sendRequest(buildRequest(OpenThermMessageType::WRITE_DATA, OpenThermMessageID::TdhwSet, data));
return isValidResponse(response);
}
float OpenTherm::getDHWTemperature()
{
unsigned long response = sendRequest(buildRequest(OpenThermMessageType::READ_DATA, OpenThermMessageID::Tdhw, 0));
return isValidResponse(response) ? getFloat(response) : 0;
}
float OpenTherm::getModulation()
{
unsigned long response = sendRequest(buildRequest(OpenThermRequestType::READ, OpenThermMessageID::RelModLevel, 0));
return isValidResponse(response) ? getFloat(response) : 0;
}
float OpenTherm::getPressure()
{
unsigned long response = sendRequest(buildRequest(OpenThermRequestType::READ, OpenThermMessageID::CHPressure, 0));
return isValidResponse(response) ? getFloat(response) : 0;
}
unsigned char OpenTherm::getFault()
{
return ((sendRequest(buildRequest(OpenThermRequestType::READ, OpenThermMessageID::ASFflags, 0)) >> 8) & 0xff);
}

View File

@ -0,0 +1,256 @@
/*
OpenTherm.h - OpenTherm Library for the ESP8266/ESP32/Arduino platform
https://github.com/ihormelnyk/OpenTherm
http://ihormelnyk.com/pages/OpenTherm
Licensed under MIT license
Copyright 2023, Ihor Melnyk
Frame Structure:
P MGS-TYPE SPARE DATA-ID DATA-VALUE
0 000 0000 00000000 00000000 00000000
*/
#ifndef OpenTherm_h
#define OpenTherm_h
#include <stdint.h>
#include <Arduino.h>
enum class OpenThermResponseStatus : byte
{
NONE,
SUCCESS,
INVALID,
TIMEOUT
};
enum class OpenThermMessageType : byte
{
/* Master to Slave */
READ_DATA = 0b000,
READ = READ_DATA, // for backwared compatibility
WRITE_DATA = 0b001,
WRITE = WRITE_DATA, // for backwared compatibility
INVALID_DATA = 0b010,
RESERVED = 0b011,
/* Slave to Master */
READ_ACK = 0b100,
WRITE_ACK = 0b101,
DATA_INVALID = 0b110,
UNKNOWN_DATA_ID = 0b111
};
typedef OpenThermMessageType OpenThermRequestType; // for backwared compatibility
enum class OpenThermMessageID : byte
{
Status = 0, // flag8/flag8 Master and Slave Status flags.
TSet = 1, // f8.8 Control Setpoint i.e.CH water temperature Setpoint(°C)
MConfigMMemberIDcode = 2, // flag8/u8 Master Configuration Flags / Master MemberID Code
SConfigSMemberIDcode = 3, // flag8/u8 Slave Configuration Flags / Slave MemberID Code
RemoteRequest = 4, // u8/u8 Remote Request
ASFflags = 5, // flag8/u8 Application - specific fault flags and OEM fault code
RBPflags = 6, // flag8/flag8 Remote boiler parameter transfer - enable & read / write flags
CoolingControl = 7, // f8.8 Cooling control signal(%)
TsetCH2 = 8, // f8.8 Control Setpoint for 2e CH circuit(°C)
TrOverride = 9, // f8.8 Remote override room Setpoint
TSP = 10, // u8/u8 Number of Transparent - Slave - Parameters supported by slave
TSPindexTSPvalue = 11, // u8/u8 Index number / Value of referred - to transparent slave parameter.
FHBsize = 12, // u8/u8 Size of Fault - History - Buffer supported by slave
FHBindexFHBvalue = 13, // u8/u8 Index number / Value of referred - to fault - history buffer entry.
MaxRelModLevelSetting = 14, // f8.8 Maximum relative modulation level setting(%)
MaxCapacityMinModLevel = 15, // u8/u8 Maximum boiler capacity(kW) / Minimum boiler modulation level(%)
TrSet = 16, // f8.8 Room Setpoint(°C)
RelModLevel = 17, // f8.8 Relative Modulation Level(%)
CHPressure = 18, // f8.8 Water pressure in CH circuit(bar)
DHWFlowRate = 19, // f8.8 Water flow rate in DHW circuit. (litres / minute)
DayTime = 20, // special/u8 Day of Week and Time of Day
Date = 21, // u8/u8 Calendar date
Year = 22, // u16 Calendar year
TrSetCH2 = 23, // f8.8 Room Setpoint for 2nd CH circuit(°C)
Tr = 24, // f8.8 Room temperature(°C)
Tboiler = 25, // f8.8 Boiler flow water temperature(°C)
Tdhw = 26, // f8.8 DHW temperature(°C)
Toutside = 27, // f8.8 Outside temperature(°C)
Tret = 28, // f8.8 Return water temperature(°C)
Tstorage = 29, // f8.8 Solar storage temperature(°C)
Tcollector = 30, // f8.8 Solar collector temperature(°C)
TflowCH2 = 31, // f8.8 Flow water temperature CH2 circuit(°C)
Tdhw2 = 32, // f8.8 Domestic hot water temperature 2 (°C)
Texhaust = 33, // s16 Boiler exhaust temperature(°C)
TboilerHeatExchanger = 34, // f8.8 Boiler heat exchanger temperature(°C)
BoilerFanSpeedSetpointAndActual = 35, // u8/u8 Boiler fan speed Setpoint and actual value
FlameCurrent = 36, // f8.8 Electrical current through burner flame[μA]
TrCH2 = 37, // f8.8 Room temperature for 2nd CH circuit(°C)
RelativeHumidity = 38, // f8.8 Actual relative humidity as a percentage
TrOverride2 = 39, // f8.8 Remote Override Room Setpoint 2
TdhwSetUBTdhwSetLB = 48, // s8/s8 DHW Setpoint upper & lower bounds for adjustment(°C)
MaxTSetUBMaxTSetLB = 49, // s8/s8 Max CH water Setpoint upper & lower bounds for adjustment(°C)
TdhwSet = 56, // f8.8 DHW Setpoint(°C) (Remote parameter 1)
MaxTSet = 57, // f8.8 Max CH water Setpoint(°C) (Remote parameters 2)
StatusVentilationHeatRecovery = 70, // flag8/flag8 Master and Slave Status flags ventilation / heat - recovery
Vset = 71, // -/u8 Relative ventilation position (0-100%). 0% is the minimum set ventilation and 100% is the maximum set ventilation.
ASFflagsOEMfaultCodeVentilationHeatRecovery = 72, // flag8/u8 Application-specific fault flags and OEM fault code ventilation / heat-recovery
OEMDiagnosticCodeVentilationHeatRecovery = 73, // u16 An OEM-specific diagnostic/service code for ventilation / heat-recovery system
SConfigSMemberIDCodeVentilationHeatRecovery = 74, // flag8/u8 Slave Configuration Flags / Slave MemberID Code ventilation / heat-recovery
OpenThermVersionVentilationHeatRecovery = 75, // f8.8 The implemented version of the OpenTherm Protocol Specification in the ventilation / heat-recovery system.
VentilationHeatRecoveryVersion = 76, // u8/u8 Ventilation / heat-recovery product version number and type
RelVentLevel = 77, // -/u8 Relative ventilation (0-100%)
RHexhaust = 78, // -/u8 Relative humidity exhaust air (0-100%)
CO2exhaust = 79, // u16 CO2 level exhaust air (0-2000 ppm)
Tsi = 80, // f8.8 Supply inlet temperature (°C)
Tso = 81, // f8.8 Supply outlet temperature (°C)
Tei = 82, // f8.8 Exhaust inlet temperature (°C)
Teo = 83, // f8.8 Exhaust outlet temperature (°C)
RPMexhaust = 84, // u16 Exhaust fan speed in rpm
RPMsupply = 85, // u16 Supply fan speed in rpm
RBPflagsVentilationHeatRecovery = 86, // flag8/flag8 Remote ventilation / heat-recovery parameter transfer-enable & read/write flags
NominalVentilationValue = 87, // u8/- Nominal relative value for ventilation (0-100 %)
TSPventilationHeatRecovery = 88, // u8/u8 Number of Transparent-Slave-Parameters supported by TSPs ventilation / heat-recovery
TSPindexTSPvalueVentilationHeatRecovery = 89, // u8/u8 Index number / Value of referred-to transparent TSPs ventilation / heat-recovery parameter.
FHBsizeVentilationHeatRecovery = 90, // u8/u8 Size of Fault-History-Buffer supported by ventilation / heat-recovery
FHBindexFHBvalueVentilationHeatRecovery = 91, // u8/u8 Index number / Value of referred-to fault-history buffer entry ventilation / heat-recovery
Brand = 93, // u8/u8 Index number of the character in the text string ASCII character referenced by the above index number
BrandVersion = 94, // u8/u8 Index number of the character in the text string ASCII character referenced by the above index number
BrandSerialNumber = 95, // u8/u8 Index number of the character in the text string ASCII character referenced by the above index number
CoolingOperationHours = 96, // u16 Number of hours that the slave is in Cooling Mode. Reset by zero is optional for slave
PowerCycles = 97, // u16 Number of Power Cycles of a slave (wake-up after Reset), Reset by zero is optional for slave
RFsensorStatusInformation = 98, // special/special For a specific RF sensor the RF strength and battery level is written
RemoteOverrideOperatingModeHeatingDHW = 99, // special/special Operating Mode HC1, HC2/ Operating Mode DHW
RemoteOverrideFunction = 100, // flag8/- Function of manual and program changes in master and remote room Setpoint
StatusSolarStorage = 101, // flag8/flag8 Master and Slave Status flags Solar Storage
ASFflagsOEMfaultCodeSolarStorage = 102, // flag8/u8 Application-specific fault flags and OEM fault code Solar Storage
SConfigSMemberIDcodeSolarStorage = 103, // flag8/u8 Slave Configuration Flags / Slave MemberID Code Solar Storage
SolarStorageVersion = 104, // u8/u8 Solar Storage product version number and type
TSPSolarStorage = 105, // u8/u8 Number of Transparent - Slave - Parameters supported by TSPs Solar Storage
TSPindexTSPvalueSolarStorage = 106, // u8/u8 Index number / Value of referred - to transparent TSPs Solar Storage parameter.
FHBsizeSolarStorage = 107, // u8/u8 Size of Fault - History - Buffer supported by Solar Storage
FHBindexFHBvalueSolarStorage = 108, // u8/u8 Index number / Value of referred - to fault - history buffer entry Solar Storage
ElectricityProducerStarts = 109, // U16 Number of start of the electricity producer.
ElectricityProducerHours = 110, // U16 Number of hours the electricity produces is in operation
ElectricityProduction = 111, // U16 Current electricity production in Watt.
CumulativElectricityProduction = 112, // U16 Cumulative electricity production in KWh.
UnsuccessfulBurnerStarts = 113, // u16 Number of un - successful burner starts
FlameSignalTooLowNumber = 114, // u16 Number of times flame signal was too low
OEMDiagnosticCode = 115, // u16 OEM - specific diagnostic / service code
SuccessfulBurnerStarts = 116, // u16 Number of succesful starts burner
CHPumpStarts = 117, // u16 Number of starts CH pump
DHWPumpValveStarts = 118, // u16 Number of starts DHW pump / valve
DHWBurnerStarts = 119, // u16 Number of starts burner during DHW mode
BurnerOperationHours = 120, // u16 Number of hours that burner is in operation(i.e.flame on)
CHPumpOperationHours = 121, // u16 Number of hours that CH pump has been running
DHWPumpValveOperationHours = 122, // u16 Number of hours that DHW pump has been running or DHW valve has been opened
DHWBurnerOperationHours = 123, // u16 Number of hours that burner is in operation during DHW mode
OpenThermVersionMaster = 124, // f8.8 The implemented version of the OpenTherm Protocol Specification in the master.
OpenThermVersionSlave = 125, // f8.8 The implemented version of the OpenTherm Protocol Specification in the slave.
MasterVersion = 126, // u8/u8 Master product version number and type
SlaveVersion = 127, // u8/u8 Slave product version number and type
};
enum class OpenThermStatus : byte
{
NOT_INITIALIZED,
READY,
DELAY,
REQUEST_SENDING,
RESPONSE_WAITING,
RESPONSE_START_BIT,
RESPONSE_RECEIVING,
RESPONSE_READY,
RESPONSE_INVALID
};
class OpenTherm
{
public:
OpenTherm(int inPin = 4, int outPin = 5, bool isSlave = false);
~OpenTherm();
volatile OpenThermStatus status;
void begin(void (*handleInterruptCallback)(void));
void begin(void (*handleInterruptCallback)(void), void (*processResponseCallback)(unsigned long, int));
#if !defined(__AVR__)
void begin();
void begin(std::function<void(unsigned long, OpenThermResponseStatus)> processResponseFunction);
#endif
bool isReady();
unsigned long sendRequest(unsigned long request);
bool sendResponse(unsigned long request);
bool sendRequestAsync(unsigned long request);
static unsigned long buildRequest(OpenThermMessageType type, OpenThermMessageID id, unsigned int data);
static unsigned long buildResponse(OpenThermMessageType type, OpenThermMessageID id, unsigned int data);
unsigned long getLastResponse();
OpenThermResponseStatus getLastResponseStatus();
static const char *statusToString(OpenThermResponseStatus status);
void handleInterrupt();
#if !defined(__AVR__)
static void handleInterruptHelper(void* ptr);
#endif
void process();
void end();
static bool parity(unsigned long frame);
static OpenThermMessageType getMessageType(unsigned long message);
static OpenThermMessageID getDataID(unsigned long frame);
static const char *messageTypeToString(OpenThermMessageType message_type);
static bool isValidRequest(unsigned long request);
static bool isValidResponse(unsigned long response);
// requests
static unsigned long buildSetBoilerStatusRequest(bool enableCentralHeating, bool enableHotWater = false, bool enableCooling = false, bool enableOutsideTemperatureCompensation = false, bool enableCentralHeating2 = false);
static unsigned long buildSetBoilerTemperatureRequest(float temperature);
static unsigned long buildGetBoilerTemperatureRequest();
// responses
static bool isFault(unsigned long response);
static bool isCentralHeatingActive(unsigned long response);
static bool isHotWaterActive(unsigned long response);
static bool isFlameOn(unsigned long response);
static bool isCoolingActive(unsigned long response);
static bool isDiagnostic(unsigned long response);
static uint16_t getUInt(const unsigned long response);
static float getFloat(const unsigned long response);
static unsigned int temperatureToData(float temperature);
// basic requests
unsigned long setBoilerStatus(bool enableCentralHeating, bool enableHotWater = false, bool enableCooling = false, bool enableOutsideTemperatureCompensation = false, bool enableCentralHeating2 = false);
bool setBoilerTemperature(float temperature);
float getBoilerTemperature();
float getReturnTemperature();
bool setDHWSetpoint(float temperature);
float getDHWTemperature();
float getModulation();
float getPressure();
unsigned char getFault();
private:
const int inPin;
const int outPin;
const bool isSlave;
volatile unsigned long response;
volatile OpenThermResponseStatus responseStatus;
volatile unsigned long responseTimestamp;
volatile byte responseBitIndex;
int readState();
void setActiveState();
void setIdleState();
void activateBoiler();
void sendBit(bool high);
void processResponse();
void (*processResponseCallback)(unsigned long, int);
#if !defined(__AVR__)
std::function<void(unsigned long, OpenThermResponseStatus)> processResponseFunction;
#endif
};
#ifndef ICACHE_RAM_ATTR
#define ICACHE_RAM_ATTR
#endif
#ifndef IRAM_ATTR
#define IRAM_ATTR ICACHE_RAM_ATTR
#endif
#endif // OpenTherm_h

View File

@ -191,7 +191,7 @@ void sns_opentherm_processResponseCallback(unsigned long response, int st)
switch (status)
{
case OpenThermResponseStatus::OPTH_SUCCESS:
case OpenThermResponseStatus::SUCCESS:
if (sns_ot_master->isValidResponse(response))
{
sns_opentherm_process_success_response(&sns_ot_boiler_status, response);
@ -200,7 +200,7 @@ void sns_opentherm_processResponseCallback(unsigned long response, int st)
sns_ot_timeout_before_disconnect = SNS_OT_MAX_TIMEOUTS_BEFORE_DISCONNECT;
break;
case OpenThermResponseStatus::OPTH_INVALID:
case OpenThermResponseStatus::INVALID:
sns_opentherm_check_retry_request();
sns_ot_connection_status = OpenThermConnectionStatus::OTC_READY;
sns_ot_timeout_before_disconnect = SNS_OT_MAX_TIMEOUTS_BEFORE_DISCONNECT;
@ -210,7 +210,7 @@ void sns_opentherm_processResponseCallback(unsigned long response, int st)
// In this case we do reconnect.
// If this command will timeout multiple times, it will be excluded from the rotation later on
// after couple of failed attempts. See sns_opentherm_check_retry_request logic
case OpenThermResponseStatus::OPTH_TIMEOUT:
case OpenThermResponseStatus::TIMEOUT:
sns_opentherm_check_retry_request();
if (--sns_ot_timeout_before_disconnect == 0)
{
@ -326,8 +326,8 @@ void sns_ot_start_handshake()
sns_opentherm_protocol_reset();
sns_ot_master->sendRequestAync(
OpenTherm::buildRequest(OpenThermMessageType::OPTH_READ_DATA, OpenThermMessageID::SConfigSMemberIDcode, 0));
sns_ot_master->sendRequestAsync(
OpenTherm::buildRequest(OpenThermMessageType::READ_DATA, OpenThermMessageID::SConfigSMemberIDcode, 0));
sns_ot_connection_status = OpenThermConnectionStatus::OTC_HANDSHAKE;
}
@ -335,8 +335,7 @@ void sns_ot_start_handshake()
void sns_ot_process_handshake(unsigned long response, int st)
{
OpenThermResponseStatus status = (OpenThermResponseStatus)st;
if (status != OpenThermResponseStatus::OPTH_SUCCESS || !sns_ot_master->isValidResponse(response))
if (status != OpenThermResponseStatus::SUCCESS || !sns_ot_master->isValidResponse(response))
{
AddLog(LOG_LEVEL_ERROR,
PSTR("[OTH]: getSlaveConfiguration failed. Status=%s"),
@ -609,7 +608,7 @@ bool Xsns69(uint32_t function)
unsigned long request = sns_opentherm_get_next_request(&sns_ot_boiler_status);
if (-1 != request)
{
sns_ot_master->sendRequestAync(request);
sns_ot_master->sendRequestAsync(request);
sns_ot_connection_status = OpenThermConnectionStatus::OTC_INFLIGHT;
}
}

View File

@ -16,7 +16,6 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef USE_OPENTHERM
#include "OpenTherm.h"
@ -214,7 +213,7 @@ OpenThermCommand sns_opentherm_commands[] = {
.m_ot_appent_telemetry = sns_opentherm_tele_generic_u16},
{// Number of starts burner
.m_command_name = "OT116",
.m_command_code = (uint8_t)OpenThermMessageID::BurnerStarts,
.m_command_code = (uint8_t)OpenThermMessageID::SuccessfulBurnerStarts,
.m_flags = 0,
.m_results = {{.m_u8 = 0}, {.m_u8 = 0}},
.m_ot_make_request = sns_opentherm_get_generic_u16,
@ -246,7 +245,7 @@ OpenThermCommand sns_opentherm_commands[] = {
.m_ot_appent_telemetry = sns_opentherm_tele_generic_u16},
{// Boiler Lock-out Reset command
.m_command_name = "BLOR",
.m_command_code = (uint8_t)OpenThermMessageID::Command,
.m_command_code = (uint8_t)OpenThermMessageID::RemoteRequest,
.m_flags = {.skip = 1},
.m_results = {{.m_u8 = 0}, {.m_u8 = 0}},
.m_ot_make_request = sns_opentherm_send_blor,
@ -284,7 +283,7 @@ unsigned long sns_opentherm_set_slave_flags(struct OpenThermCommandT *self, stru
data <<= 8;
return OpenTherm::buildRequest(OpenThermRequestType::OPTH_READ, OpenThermMessageID::Status, data);
return OpenTherm::buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Status, data);
}
void sns_opentherm_parse_slave_flags(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *boilerStatus, unsigned long response)
@ -330,7 +329,7 @@ unsigned long sns_opentherm_set_boiler_temperature(struct OpenThermCommandT *sel
self->m_results[0].m_float = status->m_boilerSetpoint;
unsigned int data = OpenTherm::temperatureToData(status->m_boilerSetpoint);
return OpenTherm::buildRequest(OpenThermMessageType::OPTH_WRITE_DATA, OpenThermMessageID::TSet, data);
return OpenTherm::buildRequest(OpenThermMessageType::WRITE_DATA, OpenThermMessageID::TSet, data);
}
void sns_opentherm_parse_set_boiler_temperature(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *boilerStatus, unsigned long response)
{
@ -370,7 +369,7 @@ unsigned long sns_opentherm_set_boiler_dhw_temperature(struct OpenThermCommandT
self->m_results[0].m_float = status->m_hotWaterSetpoint;
unsigned int data = OpenTherm::temperatureToData(status->m_hotWaterSetpoint);
return OpenTherm::buildRequest(OpenThermMessageType::OPTH_WRITE_DATA, OpenThermMessageID::TdhwSet, data);
return OpenTherm::buildRequest(OpenThermMessageType::WRITE_DATA, OpenThermMessageID::TdhwSet, data);
}
void sns_opentherm_parse_boiler_dhw_temperature(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *boilerStatus, unsigned long response)
{
@ -391,7 +390,7 @@ void sns_opentherm_tele_boiler_dhw_temperature(struct OpenThermCommandT *self)
/////////////////////////////////// App Specific Fault Flags //////////////////////////////////////////////////
unsigned long sns_opentherm_get_flags(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *)
{
return OpenTherm::buildRequest(OpenThermRequestType::OPTH_READ, OpenThermMessageID::ASFflags, 0);
return OpenTherm::buildRequest(OpenThermRequestType::READ, OpenThermMessageID::ASFflags, 0);
}
void sns_opentherm_parse_flags(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *boilerStatus, unsigned long response)
@ -426,7 +425,7 @@ void sns_opentherm_tele_u16(struct OpenThermCommandT *self)
/////////////////////////////////// OEM Diag Code //////////////////////////////////////////////////
unsigned long sns_opentherm_get_oem_diag(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *)
{
return OpenTherm::buildRequest(OpenThermRequestType::OPTH_READ, OpenThermMessageID::OEMDiagnosticCode, 0);
return OpenTherm::buildRequest(OpenThermRequestType::READ, OpenThermMessageID::OEMDiagnosticCode, 0);
}
void sns_opentherm_parse_oem_diag(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *boilerStatus, unsigned long response)
@ -450,7 +449,7 @@ unsigned long sns_opentherm_send_blor(struct OpenThermCommandT *self, struct OT_
unsigned int data = 1; //1 : “BLOR”= Boiler Lock-out Reset command
data <<= 8;
return OpenTherm::buildRequest(OpenThermMessageType::OPTH_WRITE_DATA, OpenThermMessageID::Command, data);
return OpenTherm::buildRequest(OpenThermMessageType::WRITE_DATA, OpenThermMessageID::RemoteRequest, data);
}
bool sns_opentherm_call_blor()
@ -470,7 +469,7 @@ bool sns_opentherm_call_blor()
/////////////////////////////////// Generic Single Float /////////////////////////////////////////////////
unsigned long sns_opentherm_get_generic_float(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *)
{
return OpenTherm::buildRequest(OpenThermRequestType::OPTH_READ, (OpenThermMessageID)self->m_command_code, 0);
return OpenTherm::buildRequest(OpenThermRequestType::READ, (OpenThermMessageID)self->m_command_code, 0);
}
void sns_opentherm_parse_generic_float(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *boilerStatus, unsigned long response)
@ -488,7 +487,7 @@ void sns_opentherm_tele_generic_float(struct OpenThermCommandT *self)
/////////////////////////////////// Generic U16 /////////////////////////////////////////////////
unsigned long sns_opentherm_get_generic_u16(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *)
{
return OpenTherm::buildRequest(OpenThermRequestType::OPTH_READ, (OpenThermMessageID)self->m_command_code, 0);
return OpenTherm::buildRequest(OpenThermRequestType::READ, (OpenThermMessageID)self->m_command_code, 0);
}
void sns_opentherm_parse_generic_u16(struct OpenThermCommandT *self, struct OT_BOILER_STATUS_T *boilerStatus, unsigned long response)
@ -559,7 +558,7 @@ void sns_opentherm_check_retry_request()
bool canRetry = ++cmd->m_flags.retryCount < 3;
// In case of last retry and if this command never respond successfully, set notSupported flag
if (!canRetry && !cmd->m_flags.supported)
if (!cmd->m_flags.supported)
{
cmd->m_flags.notSupported = true;
AddLog(LOG_LEVEL_ERROR,