OpenTherm Library


Posted on Sunday, April 1, 2018 at 12:00 AM, 39516 views


OpenTherm Arduino Library

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 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.

This version of library uses interrupts to achieve better stability and synchronization with boiler. For educational puprouses and to get better undestanding of OpenTherm protocol there is OpenTherm Controller based on simple sketch implemented using delays. So if you want control and integrate your boiler into home automation system, without proprietary thermostats, this library for you.

Installation:

  • Install Arduino Software (IDE)
  • Open the Arduino IDE and click to the "Sketch" menu and then Include Library > Manage Libraries.
    Manage Libraries in Arduino IDE
  • Type "OpenTherm" into filter field in Library Manager window. Choose "OpenTherm Library by Ihor Melnyk". Click Install.
    Install OpenTherm Library
  • Open Demo Example
    OpenTherm Example
Also you can find souce code of Arduino OpenTherm Library on GitHub

Configuration and Usage:

  • First of all you need to include OpenTherm Library:
    #include <OpenTherm.h>
  • You have to choose 2 controller GPIO pins which will be used for communication and connected to OpenTherm Adapter. Controller(Arduino/ESP8266) input pin should support interrupts.
    Arduino digital pins usable for interrupts: Uno, Nano, Mini: 2,3; Mega: 2, 3, 18, 19, 20, 21
    ESP8266: Interrupts may be attached to any GPIO pin except GPIO16, 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.
    So let's choose 2,3 for Arduino or 4,5 for ESP8266:
    const int inPin = 2; //4
    const int outPin = 3; //5
    
    Controller output pin should be connected to OpenTherm Adapter input pin and vise versa.
  • Define OpenTherm class instance using constructor which accepts as arguments pin numbers:
    OpenTherm ot(inPin, outPin);
  • Define interrupts handler function for specified above instance:
    void ICACHE_RAM_ATTR handleInterrupt() {
    	ot.handleInterrupt();
    }
    
  • Use begin function to initialize OpenTherm instance, specify interrupts handler function as argument
    void setup()
    {
        ot.begin(handleInterrupt);
    }
    
  • According to OpenTherm Protocol specification master (controller) must communicate at least every 1 sec. So lets make some requests in loop function:
    void loop()
    {	
        //Set/Get Boiler Status
        ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
        //Set Boiler Temperature to 64 degrees C
        ot.setBoilerTemperature(64);
        //Get Boiler Temperature
        float temperature = ot.getBoilerTemperature();
        delay(1000);
    }
    

Full Sample:

#include <Arduino.h>
#include <OpenTherm.h>
const int inPin = 2; //4
const int outPin = 3; //5
OpenTherm ot(inPin, outPin);
void ICACHE_RAM_ATTR handleInterrupt() {
	ot.handleInterrupt();
}
void setup()
{
	Serial.begin(115200);
	Serial.println("Start");
	
	ot.begin(handleInterrupt);
}
void loop()
{	
	//Set/Get Boiler Status
	bool enableCentralHeating = true;
	bool enableHotWater = true;
	bool enableCooling = false;
	unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
	OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
	if (responseStatus == OpenThermResponseStatus::SUCCESS) {		
		Serial.println("Central Heating: " + 
            String(ot.isCentralHeatingActive(response) ? "on" : "off"));
		Serial.println("Hot Water: " + 
            String(ot.isHotWaterActive(response) ? "on" : "off"));
		Serial.println("Flame: " + 
            String(ot.isFlameOn(response) ? "on" : "off"));
	}
	if (responseStatus == OpenThermResponseStatus::NONE) {
		Serial.println("Error: OpenTherm is not initialized");
	}
	else if (responseStatus == OpenThermResponseStatus::INVALID) {
		Serial.println("Error: Invalid response " + String(response, HEX));
	}
	else if (responseStatus == OpenThermResponseStatus::TIMEOUT) {
		Serial.println("Error: Response timeout");
	}
	//Set Boiler Temperature to 64 degrees C
	ot.setBoilerTemperature(64);
	//Get Boiler Temperature
	float temperature = ot.getBoilerTemperature();
	Serial.println("Boiler temperature is " + String(temperature) + " degrees C");	
	Serial.println();
	delay(1000);
}

Serial port output:

Start
Central Heating: on
Hot Water: on
Flame: on
Boiler temperature is 64 degrees C

Advanced Requests:

To send advanced request you have to:
  • Prepare 16-bit data if required
  • Build request using buildRequest() function, which accepts 3 arguments:
    - request type: Read/Write
    - message id: OpenThermMessageID enum
    - data: 16-bit data
    unsigned long buildRequest(
        OpenThermRequestType type,
        OpenThermMessageID id,
        unsigned int data);
    
  • Make request using sendRequest() function:
    unsigned long response = ot.sendRequest(request);
    
  • Parse response data according to OpenTherm protocol specification

Advanced Request Sample:

//Read Relative Modulation Level:
unsigned int data = 0xFFFF;
unsigned long request = ot.buildRequest(
    OpenThermRequestType::READ,
    OpenThermMessageID::RelModLevel,
    data);
unsigned long response = ot.sendRequest(request);

Asynchronous Requests:

To send requests asynchronously you have to:
  • Write callback function to handle responses:
    void processResponse(unsigned long response, OpenThermResponseStatus status) {
        if (status == OpenThermResponseStatus::SUCCESS) {
            //handle response
        }
    }
  • Use begin() function with 2 arguments to pass processResponse handler function:
    void begin(
         void(*handleInterruptCallback)(void),
         void(*processResponseCallback)(unsigned long, OpenThermResponseStatus));
    
  • Use sendRequestAync() function to make asynchronous request:
    bool sendRequestAync(unsigned long request);
  • Call process() function in loop function so OpenTherm Library can operate and handle response.
    ot.process();
  • Check whether OpenTherm Library is ready to send next request:
    bool isReady = ot.isReady();

Back to List