Mqtt Opentherm controller, need help adapting code

I’m using the code below to control my central heating system.
Everything works fine, except the fact that the hot water production is allways on.

Could anyone help me adapting the code so I can switch on and off the hot water production over mqtt.

/*************************************************************
  This example runs directly on ESP8266 chip.

  Please be sure to select the right ESP8266 module
  in the Tools -> Board -> WeMos D1 Mini

  Adjust settings in Config.h before run
 *************************************************************/

#include <ESP8266WiFi.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <PubSubClient.h>
#include <OpenTherm.h>

// Your WiFi credentials.
// Set password to "" for open networks.
const char* ssid = "YOUR_WIFI";
const char* pass = "YOUR_PASSWORD";

// Your MQTT broker address and credentials
const char* mqtt_server = "xxx.xxx.xxx.xxx";
const char* mqtt_user = "mqtt_user";
const char* mqtt_password = "mqtt_password";
const int mqtt_port = 1883;

// Master OpenTherm Shield pins configuration
const int OT_IN_PIN = 4;  //for Arduino, 4 for ESP8266 (D2), 21 for ESP32
const int OT_OUT_PIN = 5; //for Arduino, 5 for ESP8266 (D1), 22 for ESP32

// Temperature sensor pin
const int ROOM_TEMP_SENSOR_PIN = 14; //for Arduino, 14 for ESP8266 (D5), 18 for ESP32

// MQTT topics
const char* CURRENT_TEMP_GET_TOPIC = "opentherm-thermostat/current-temperature/get";
const char* CURRENT_TEMP_SET_TOPIC = "opentherm-thermostat/current-temperature/set";
const char* TEMP_SETPOINT_GET_TOPIC = "opentherm-thermostat/setpoint-temperature/get";
const char* TEMP_SETPOINT_SET_TOPIC = "opentherm-thermostat/setpoint-temperature/set";
const char* MODE_GET_TOPIC = "opentherm-thermostat/mode/get";
const char* MODE_SET_TOPIC = "opentherm-thermostat/mode/set";
const char* TEMP_BOILER_GET_TOPIC = "opentherm-thermostat/boiler-temperature/get";
const char* TEMP_BOILER_TARGET_GET_TOPIC = "opentherm-thermostat/boiler-target-temperature/get";


const unsigned long extTempTimeout_ms = 60 * 1000;
const unsigned long statusUpdateInterval_ms = 1000;

float  sp = 15, //set point
       t = 15, //current temperature
       t_last = 0, //prior temperature
       ierr = 25, //integral error
       dt = 0, //time between measurements
       op = 0; //PID controller output
unsigned long ts = 0, new_ts = 0; //timestamp
unsigned long lastUpdate = 0;
unsigned long lastTempSet = 0;

bool heatingEnabled = true;

#define MSG_BUFFER_SIZE  (50)
char msg[MSG_BUFFER_SIZE];

OneWire oneWire(ROOM_TEMP_SENSOR_PIN);
DallasTemperature sensors(&oneWire);
OpenTherm ot(OT_IN_PIN, OT_OUT_PIN);
WiFiClient espClient;
PubSubClient client(espClient);

void ICACHE_RAM_ATTR handleInterrupt() {
  ot.handleInterrupt();
}

float getTemp() {
  unsigned long now = millis();
  if (now - lastTempSet > extTempTimeout_ms)
    return sensors.getTempCByIndex(0);
  else
    return t;
}

float pid(float sp, float pv, float pv_last, float& ierr, float dt) {
  float KP = 10;
  float KI = 0.02;
  // upper and lower bounds on heater level
  float ophi = 63;
  float oplo = 20;
  // calculate the error
  float error = sp - pv;
  // calculate the integral error
  ierr = ierr + KI * error * dt;
  // calculate the measurement derivative
  //float dpv = (pv - pv_last) / dt;
  // calculate the PID output
  float P = KP * error; //proportional contribution
  float I = ierr; //integral contribution
  float op = P + I;
  // implement anti-reset windup
  if ((op < oplo) || (op > ophi)) {
    I = I - KI * error * dt;
    // clip output
    op = max(oplo, min(ophi, op));
  }
  ierr = I;

  Serial.println("sp=" + String(sp) + " pv=" + String(pv) + " dt=" + String(dt) + " op=" + String(op) + " P=" + String(P) + " I=" + String(I));

  return op;
}

// This function calculates temperature and sends data to MQTT every second.
void updateData()
{
  //Set/Get Boiler Status
  bool enableHotWater = true;
  bool enableCooling = false;
  unsigned long response = ot.setBoilerStatus(heatingEnabled, enableHotWater, enableCooling);
  OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
  if (responseStatus != OpenThermResponseStatus::SUCCESS) {
    Serial.println("Error: Invalid boiler response " + String(response, HEX));
  }

  t = getTemp();
  new_ts = millis();
  dt = (new_ts - ts) / 1000.0;
  ts = new_ts;
  if (responseStatus == OpenThermResponseStatus::SUCCESS) {
    op = pid(sp, t, t_last, ierr, dt);
    ot.setBoilerTemperature(op);
  }
  t_last = t;
  sensors.requestTemperatures(); //async temperature request

  snprintf (msg, MSG_BUFFER_SIZE, "%s", String(op).c_str());
  client.publish(TEMP_BOILER_TARGET_GET_TOPIC, msg);

  snprintf (msg, MSG_BUFFER_SIZE, "%s", String(t).c_str());
  client.publish(CURRENT_TEMP_GET_TOPIC, msg);

  float bt = ot.getBoilerTemperature();
  snprintf (msg, MSG_BUFFER_SIZE, "%s", String(bt).c_str());
  client.publish(TEMP_BOILER_GET_TOPIC, msg);

  snprintf (msg, MSG_BUFFER_SIZE, "%s", String(sp).c_str());
  client.publish(TEMP_SETPOINT_GET_TOPIC, msg);

  snprintf (msg, MSG_BUFFER_SIZE, "%s", heatingEnabled ? "heat" : "off");
  client.publish(MODE_GET_TOPIC, msg);

  Serial.print("Current temperature: " + String(t) + " °C ");
  String tempSource = (millis() - lastTempSet > extTempTimeout_ms)
                      ? "(internal sensor)"
                      : "(external sensor)";
  Serial.println(tempSource);
}

String convertPayloadToStr(byte* payload, unsigned int length) {
  char s[length + 1];
  s[length] = 0;
  for (int i = 0; i < length; ++i)
    s[i] = payload[i];
  String tempRequestStr(s);
  return tempRequestStr;
}

const String setpointSetTopic(TEMP_SETPOINT_SET_TOPIC);
const String currentTempSetTopic(CURRENT_TEMP_SET_TOPIC);
const String modeSetTopic(MODE_SET_TOPIC);

void callback(char* topic, byte* payload, unsigned int length) {
  const String topicStr(topic);

  String payloadStr = convertPayloadToStr(payload, length);

  if (topicStr == setpointSetTopic) {
    Serial.println("Set target temperature: " + payloadStr);
    sp = payloadStr.toFloat();
  }
  else if (topicStr == currentTempSetTopic) {
    t = payloadStr.toFloat();
    lastTempSet = millis();
  }
  else if (topicStr == modeSetTopic) {
    Serial.println("Set mode: " + payloadStr);
    if (payloadStr == "heat")
      heatingEnabled = true;
    else if (payloadStr == "off")
      heatingEnabled = false;
    else
      Serial.println("Unknown mode");
  }
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    const char* clientId = "opentherm-thermostat-test";
    if (client.connect(clientId, mqtt_user, mqtt_password)) {
      Serial.println("ok");

      client.subscribe(TEMP_SETPOINT_SET_TOPIC);
      client.subscribe(MODE_SET_TOPIC);
      client.subscribe(CURRENT_TEMP_SET_TOPIC);
    } else {
      Serial.print(" failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void setup()
{
  Serial.begin(115200);

  Serial.println("Connecting to " + String(ssid));
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, pass);

  int deadCounter = 20;
  while (WiFi.status() != WL_CONNECTED && deadCounter-- > 0) {
    delay(500);
    Serial.print(".");
  }

  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("Failed to connect to " + String(ssid));
    while (true);
  }
  else {
    Serial.println("ok");
  }

  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);

  ot.begin(handleInterrupt);

  //Init DS18B20 sensor
  sensors.begin();
  sensors.requestTemperatures();
  sensors.setWaitForConversion(false); //switch to async mode
  t, t_last = sensors.getTempCByIndex(0);
  ts = millis();
  lastTempSet = -extTempTimeout_ms;
}

void loop()
{
  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  unsigned long now = millis();
  if (now - lastUpdate > statusUpdateInterval_ms) {
    lastUpdate = now;
    updateData();
  }
}

Blind code.
It adds a opentherm-thermostat/hotwater-switch/set topic where you publish “on” or “off”.

EDIT: Added opentherm-thermostat/hotwater-switch/get for the current value as well

/*************************************************************
  This example runs directly on ESP8266 chip.

  Please be sure to select the right ESP8266 module
  in the Tools -> Board -> WeMos D1 Mini

  Adjust settings in Config.h before run
 *************************************************************/

#include <ESP8266WiFi.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <PubSubClient.h>
#include <OpenTherm.h>

// Your WiFi credentials.
// Set password to "" for open networks.
const char* ssid = "YOUR_WIFI";
const char* pass = "YOUR_PASSWORD";

// Your MQTT broker address and credentials
const char* mqtt_server = "xxx.xxx.xxx.xxx";
const char* mqtt_user = "mqtt_user";
const char* mqtt_password = "mqtt_password";
const int mqtt_port = 1883;

// Master OpenTherm Shield pins configuration
const int OT_IN_PIN = 4;  //for Arduino, 4 for ESP8266 (D2), 21 for ESP32
const int OT_OUT_PIN = 5; //for Arduino, 5 for ESP8266 (D1), 22 for ESP32

// Temperature sensor pin
const int ROOM_TEMP_SENSOR_PIN = 14; //for Arduino, 14 for ESP8266 (D5), 18 for ESP32

// MQTT topics
const char* CURRENT_TEMP_GET_TOPIC = "opentherm-thermostat/current-temperature/get";
const char* CURRENT_TEMP_SET_TOPIC = "opentherm-thermostat/current-temperature/set";
const char* TEMP_SETPOINT_GET_TOPIC = "opentherm-thermostat/setpoint-temperature/get";
const char* TEMP_SETPOINT_SET_TOPIC = "opentherm-thermostat/setpoint-temperature/set";
const char* MODE_GET_TOPIC = "opentherm-thermostat/mode/get";
const char* MODE_SET_TOPIC = "opentherm-thermostat/mode/set";
const char* TEMP_BOILER_GET_TOPIC = "opentherm-thermostat/boiler-temperature/get";
const char* TEMP_BOILER_TARGET_GET_TOPIC = "opentherm-thermostat/boiler-target-temperature/get";
const char* HOTWATER_SWITCH_TOPIC = "opentherm-thermostat/hotwater-switch/set";
const char* HOTWATER_GET_TOPIC = "opentherm-thermostat/hotwater-switch/get";


const unsigned long extTempTimeout_ms = 60 * 1000;
const unsigned long statusUpdateInterval_ms = 1000;

float  sp = 15, //set point
       t = 15, //current temperature
       t_last = 0, //prior temperature
       ierr = 25, //integral error
       dt = 0, //time between measurements
       op = 0; //PID controller output
unsigned long ts = 0, new_ts = 0; //timestamp
unsigned long lastUpdate = 0;
unsigned long lastTempSet = 0;

bool heatingEnabled = true;
bool hotwaterEnabled = true;

#define MSG_BUFFER_SIZE  (50)
char msg[MSG_BUFFER_SIZE];

OneWire oneWire(ROOM_TEMP_SENSOR_PIN);
DallasTemperature sensors(&oneWire);
OpenTherm ot(OT_IN_PIN, OT_OUT_PIN);
WiFiClient espClient;
PubSubClient client(espClient);

void ICACHE_RAM_ATTR handleInterrupt() {
  ot.handleInterrupt();
}

float getTemp() {
  unsigned long now = millis();
  if (now - lastTempSet > extTempTimeout_ms)
    return sensors.getTempCByIndex(0);
  else
    return t;
}

float pid(float sp, float pv, float pv_last, float& ierr, float dt) {
  float KP = 10;
  float KI = 0.02;
  // upper and lower bounds on heater level
  float ophi = 63;
  float oplo = 20;
  // calculate the error
  float error = sp - pv;
  // calculate the integral error
  ierr = ierr + KI * error * dt;
  // calculate the measurement derivative
  //float dpv = (pv - pv_last) / dt;
  // calculate the PID output
  float P = KP * error; //proportional contribution
  float I = ierr; //integral contribution
  float op = P + I;
  // implement anti-reset windup
  if ((op < oplo) || (op > ophi)) {
    I = I - KI * error * dt;
    // clip output
    op = max(oplo, min(ophi, op));
  }
  ierr = I;

  Serial.println("sp=" + String(sp) + " pv=" + String(pv) + " dt=" + String(dt) + " op=" + String(op) + " P=" + String(P) + " I=" + String(I));

  return op;
}

// This function calculates temperature and sends data to MQTT every second.
void updateData()
{
  //Set/Get Boiler Status
  bool enableHotWater = true;
  bool enableCooling = false;
  unsigned long response = ot.setBoilerStatus(heatingEnabled, hotwaterEnabled, enableCooling);
  OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
  if (responseStatus != OpenThermResponseStatus::SUCCESS) {
    Serial.println("Error: Invalid boiler response " + String(response, HEX));
  }

  t = getTemp();
  new_ts = millis();
  dt = (new_ts - ts) / 1000.0;
  ts = new_ts;
  if (responseStatus == OpenThermResponseStatus::SUCCESS) {
    op = pid(sp, t, t_last, ierr, dt);
    ot.setBoilerTemperature(op);
  }
  t_last = t;
  sensors.requestTemperatures(); //async temperature request

  snprintf (msg, MSG_BUFFER_SIZE, "%s", String(op).c_str());
  client.publish(TEMP_BOILER_TARGET_GET_TOPIC, msg);

  snprintf (msg, MSG_BUFFER_SIZE, "%s", String(t).c_str());
  client.publish(CURRENT_TEMP_GET_TOPIC, msg);

  float bt = ot.getBoilerTemperature();
  snprintf (msg, MSG_BUFFER_SIZE, "%s", String(bt).c_str());
  client.publish(TEMP_BOILER_GET_TOPIC, msg);

  snprintf (msg, MSG_BUFFER_SIZE, "%s", String(sp).c_str());
  client.publish(TEMP_SETPOINT_GET_TOPIC, msg);

  snprintf (msg, MSG_BUFFER_SIZE, "%s", heatingEnabled ? "heat" : "off");
  client.publish(MODE_GET_TOPIC, msg);

  snprintf (msg, MSG_BUFFER_SIZE, "%s", hotwaterEnabled ? "on" : "off");
  client.publish(HOTWATER_GET_TOPIC, msg);

  Serial.print("Current temperature: " + String(t) + " °C ");
  String tempSource = (millis() - lastTempSet > extTempTimeout_ms)
                      ? "(internal sensor)"
                      : "(external sensor)";
  Serial.println(tempSource);
}

String convertPayloadToStr(byte* payload, unsigned int length) {
  char s[length + 1];
  s[length] = 0;
  for (int i = 0; i < length; ++i)
    s[i] = payload[i];
  String tempRequestStr(s);
  return tempRequestStr;
}

const String setpointSetTopic(TEMP_SETPOINT_SET_TOPIC);
const String currentTempSetTopic(CURRENT_TEMP_SET_TOPIC);
const String modeSetTopic(MODE_SET_TOPIC);
const String hotwaterSwitchTopic(HOTWATER_SWITCH_TOPIC);

void callback(char* topic, byte* payload, unsigned int length) {
  const String topicStr(topic);

  String payloadStr = convertPayloadToStr(payload, length);

  if (topicStr == setpointSetTopic) {
    Serial.println("Set target temperature: " + payloadStr);
    sp = payloadStr.toFloat();
  }
  else if (topicStr == currentTempSetTopic) {
    t = payloadStr.toFloat();
    lastTempSet = millis();
  }
  else if (topicStr == modeSetTopic) {
    Serial.println("Set mode: " + payloadStr);
    if (payloadStr == "heat")
      heatingEnabled = true;
    else if (payloadStr == "off")
      heatingEnabled = false;
    else
      Serial.println("Unknown mode");
  }
  else if (topicStr == hotwaterSwitchTopic) {
    Serial.println("Hot Water switch: " + payloadStr);
    if (payloadStr == "on")
      hotwaterEnabled = true;
    else if (payloadStr == "off")
      hotwaterEnabled = false;
    else
      Serial.println("Unknown mode");
  }
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    const char* clientId = "opentherm-thermostat-test";
    if (client.connect(clientId, mqtt_user, mqtt_password)) {
      Serial.println("ok");

      client.subscribe(TEMP_SETPOINT_SET_TOPIC);
      client.subscribe(MODE_SET_TOPIC);
      client.subscribe(CURRENT_TEMP_SET_TOPIC);
      client.subscribe(BOILER_SWITCH_TOPIC);
    } else {
      Serial.print(" failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void setup()
{
  Serial.begin(115200);

  Serial.println("Connecting to " + String(ssid));
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, pass);

  int deadCounter = 20;
  while (WiFi.status() != WL_CONNECTED && deadCounter-- > 0) {
    delay(500);
    Serial.print(".");
  }

  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("Failed to connect to " + String(ssid));
    while (true);
  }
  else {
    Serial.println("ok");
  }

  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);

  ot.begin(handleInterrupt);

  //Init DS18B20 sensor
  sensors.begin();
  sensors.requestTemperatures();
  sensors.setWaitForConversion(false); //switch to async mode
  t, t_last = sensors.getTempCByIndex(0);
  ts = millis();
  lastTempSet = -extTempTimeout_ms;
}

void loop()
{
  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  unsigned long now = millis();
  if (now - lastUpdate > statusUpdateInterval_ms) {
    lastUpdate = now;
    updateData();
  }
}

Guys, what is this statement ‘const String setpointSetTopic(TEMP_SETPOINT_SET_TOPIC);’.

If I understand this correctly, it assigns TEMP_SETPOINT_SET_TOPIC value to setpointSetTopic.
But, couldn’t we use TEMP_SETPOINT_SET_TOPIC in the condition?

Hi @marchano, I´m about to dive in the deep end with this project as well :slight_smile:

Did you use Chris B´s code in the end and if yes, did it do the trick?

I have the problem that the target temperature (setpoint) is always set to zero when restarting.
Can someone help me there?

type or paste code here/*************************************************************
  This example runs directly on ESP32 chip.

Switch on hot water, send back some information
 *************************************************************/

#include <WiFi.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <PubSubClient.h>
#include <OpenTherm.h>

// Your WiFi credentials.
// Set password to "" for open networks.
const char* ssid = "7";
const char* pass = "T7";

// Your MQTT broker address and credentials
const char* mqtt_server = "192.168.1.63";
const char* mqtt_user = "Jn";
const char* mqtt_password = "D0";
const int mqtt_port = 1883;

// Master OpenTherm Shield pins configuration
const int OT_IN_PIN = 21;  //for Arduino, 4 for ESP8266 (D2), 21 for ESP32
const int OT_OUT_PIN = 22; //for Arduino, 5 for ESP8266 (D1), 22 for ESP32

// Temperature sensor pin
const int ROOM_TEMP_SENSOR_PIN = 18; //for Arduino, 14 for ESP8266 (D5), 18 for ESP32

// MQTT topics
const char* CURRENT_TEMP_GET_TOPIC = "opentherm-thermostat/current-temperature/get";
const char* CURRENT_TEMP_SET_TOPIC = "opentherm-thermostat/current-temperature/set";
const char* TEMP_SETPOINT_GET_TOPIC = "opentherm-thermostat/setpoint-temperature/get";
const char* TEMP_SETPOINT_SET_TOPIC = "opentherm-thermostat/setpoint-temperature/set";
const char* MODE_GET_TOPIC = "opentherm-thermostat/mode/get";
const char* MODE_SET_TOPIC = "opentherm-thermostat/mode/set";
const char* TEMP_BOILER_GET_TOPIC = "opentherm-thermostat/boiler-temperature/get";
const char* TEMP_BOILER_TARGET_GET_TOPIC = "opentherm-thermostat/boiler-target-temperature/get";
const char* TEMP_Hotwater_GET_TOPIC = "opentherm-thermostat/hotwater-temperature/get";
const char* retour_GET_TOPIC = "opentherm-thermostat/ISFOULT_GET_TOPIC/get";
const char* HOTWATER_SWITCH_TOPIC = "opentherm-thermostat/hotwater-switch/set";
const char* HOTWATER_GET_TOPIC = "opentherm-thermostat/hotwater-switch/get";



const unsigned long extTempTimeout_ms = 60 * 1000;
const unsigned long statusUpdateInterval_ms = 1000;

float  sp = 22, //set point
       t = 15, //current temperature
       t_last = 0, //prior temperature
       ierr = 25, //integral error
       dt = 0, //time between measurements
       op = 0; //PID controller output
unsigned long ts = 0, new_ts = 0; //timestamp
unsigned long lastUpdate = 0;
unsigned long lastTempSet = 0;

bool heatingEnabled = true;
bool enableHotWater = true;
bool enableCooling = false;

#define MSG_BUFFER_SIZE  (50)
char msg[MSG_BUFFER_SIZE];

OneWire oneWire(ROOM_TEMP_SENSOR_PIN);
DallasTemperature sensors(&oneWire);
OpenTherm ot(OT_IN_PIN, OT_OUT_PIN);
WiFiClient espClient;
PubSubClient client(espClient);

void ICACHE_RAM_ATTR handleInterrupt() {
  ot.handleInterrupt();
}

float getTemp() {
  unsigned long now = millis();
  if (now - lastTempSet > extTempTimeout_ms)
    return sensors.getTempCByIndex(0);
  else
    return t;
}

float pid(float sp, float pv, float pv_last, float& ierr, float dt) {
  float KP = 10;
  float KI = 0.02;
  // upper and lower bounds on heater level
  float ophi = 63;
  float oplo = 20;
  // calculate the error
  float error = sp - pv;
  // calculate the integral error
  ierr = ierr + KI * error * dt;
  // calculate the measurement derivative
  //float dpv = (pv - pv_last) / dt;
  // calculate the PID output
  float P = KP * error; //proportional contribution
  float I = ierr; //integral contribution
  float op = P + I;
  // implement anti-reset windup
  if ((op < oplo) || (op > ophi)) {
    I = I - KI * error * dt;
    // clip output
    op = max(oplo, min(ophi, op));
  }
  ierr = I;

  Serial.println("sp=" + String(sp) + " pv=" + String(pv) + " dt=" + String(dt) + " op=" + String(op) + " P=" + String(P) + " I=" + String(I));

  return op;
}

// This function calculates temperature and sends data to MQTT every second.
void updateData()
{
  //Set/Get Boiler Status
  
  unsigned long response = ot.setBoilerStatus(heatingEnabled, enableHotWater, enableCooling);
  OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
  if (responseStatus != OpenThermResponseStatus::SUCCESS) {
    Serial.println("Error: Invalid boiler response " + String(response, HEX));
 
  }

  t = getTemp();
  new_ts = millis();
  dt = (new_ts - ts) / 1000.0;
  ts = new_ts;
  if (responseStatus == OpenThermResponseStatus::SUCCESS) {
    op = pid(sp, t, t_last, ierr, dt);
    ot.setBoilerTemperature(op);
     }
  t_last = t;
  sensors.requestTemperatures(); //async temperature request

  snprintf (msg, MSG_BUFFER_SIZE, "%s", String(op).c_str());
  client.publish(TEMP_BOILER_TARGET_GET_TOPIC, msg);

  snprintf (msg, MSG_BUFFER_SIZE, "%s", String(t).c_str());
  client.publish(CURRENT_TEMP_GET_TOPIC, msg);

  float bt = ot.getBoilerTemperature();
  snprintf (msg, MSG_BUFFER_SIZE, "%s", String(bt).c_str());
  client.publish(TEMP_BOILER_GET_TOPIC, msg);

  snprintf (msg, MSG_BUFFER_SIZE, "%s", String(sp).c_str());
  client.publish(TEMP_SETPOINT_GET_TOPIC, msg);

  snprintf (msg, MSG_BUFFER_SIZE, "%s", heatingEnabled ? "heat" : "off");
  client.publish(MODE_GET_TOPIC, msg);

  snprintf (msg, MSG_BUFFER_SIZE, "%s", String(response, HEX));
  client.publish(retour_GET_TOPIC, msg);

  float hw = ot.getDHWTemperature();
  snprintf (msg, MSG_BUFFER_SIZE, "%s", String(hw).c_str());
  client.publish(TEMP_Hotwater_GET_TOPIC, msg);

 
  snprintf (msg, MSG_BUFFER_SIZE, "%s", enableHotWater ? "on" : "off");
  client.publish(HOTWATER_GET_TOPIC, msg);

  Serial.print("Current temperature: " + String(t) + " °C ");
  String tempSource = (millis() - lastTempSet > extTempTimeout_ms)
                      ? "(internal sensor)"
                      : "(external sensor)";
  Serial.println(tempSource);
}

String convertPayloadToStr(byte* payload, unsigned int length) {
  char s[length + 1];
  s[length] = 0;
  for (int i = 0; i < length; ++i)
    s[i] = payload[i];
  String tempRequestStr(s);
  return tempRequestStr;
}

const String setpointSetTopic(TEMP_SETPOINT_SET_TOPIC);
const String currentTempSetTopic(CURRENT_TEMP_SET_TOPIC);
const String modeSetTopic(MODE_SET_TOPIC);
const String hotwaterSwitchTopic(HOTWATER_SWITCH_TOPIC);

void callback(char* topic, byte* payload, unsigned int length) {
  const String topicStr(topic);

  String payloadStr = convertPayloadToStr(payload, length);

  if (topicStr == setpointSetTopic) {
    Serial.println("Set target temperature: " + payloadStr);
    sp = payloadStr.toFloat();
  }
  else if (topicStr == currentTempSetTopic) {
    t = payloadStr.toFloat();
    lastTempSet = millis();
  }
  else if (topicStr == modeSetTopic) {
    Serial.println("Set mode: " + payloadStr);
    if (payloadStr == "heat")
      heatingEnabled = true;
    else if (payloadStr == "off")
      heatingEnabled = false;
    else
      Serial.println("Unknown mode");
  }
  else if (topicStr == hotwaterSwitchTopic) {
    Serial.println("Hot Water switch: " + payloadStr);
    if (payloadStr == "on")
      enableHotWater = true;
    else if (payloadStr == "off")
      enableHotWater = false;
    else
      Serial.println("Unknown mode");
  }
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    const char* clientId = "opentherm-thermostat-test";
    if (client.connect(clientId, mqtt_user, mqtt_password)) {
      Serial.println("ok");

      client.subscribe(TEMP_SETPOINT_SET_TOPIC);
      client.subscribe(MODE_SET_TOPIC);
      client.subscribe(CURRENT_TEMP_SET_TOPIC);
      client.subscribe(HOTWATER_SWITCH_TOPIC);
    } else {
      Serial.print(" failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void setup()
{
  Serial.begin(115200);

  Serial.println("Connecting to " + String(ssid));
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, pass);

  int deadCounter = 20;
  while (WiFi.status() != WL_CONNECTED && deadCounter-- > 0) {
    delay(500);
    Serial.print(".");
  }

  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("Failed to connect to " + String(ssid));
    while (true);
  }
  else {
    Serial.println("ok");
  }

  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);

  ot.begin(handleInterrupt);

  //Init DS18B20 sensor
  sensors.begin();
  sensors.requestTemperatures();
  sensors.setWaitForConversion(false); //switch to async mode
  t, t_last = sensors.getTempCByIndex(0);
  ts = millis();
  lastTempSet = -extTempTimeout_ms;
}

void loop()
{
  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  unsigned long now = millis();
  if (now - lastUpdate > statusUpdateInterval_ms) {
    lastUpdate = now;
    updateData();
  }
}