Термостат OpenTherm на ESP8266

tsv_33
Offline
Зарегистрирован: 11.04.2019

Приветствую. Делаю термостат для своего газового котла BAXI SLiM, работающий по протоколу OpenTherm. Всё вроде работает. Но веб страница долго грузится. Может есть какие замечения? Как ускорить процесс загрузки? С MQTT проблем нет. 

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <OpenTherm.h>               //https://github.com/ihormelnyk/opentherm_library/

//OpenTherm input and output wires connected to 4 and 5 pins on the OpenTherm Shield
const int inPin = 4; //D2
const int outPin = 5; //D1

#define ONE_WIRE_BUS 14 //D5 Data wire is connected to 14 pin on the OpenTherm Shield
#define BUILTIN_LED 2 //D4 Встроенный LED

const char* ssid = "*******";
const char* password = "**********";
const char* mqtt_server = "***********";
const int   mqtt_port = 12345;
const char* mqtt_user = "*********";
const char* mqtt_password = "********";

ESP8266WebServer server(80); //Server on port 80
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
OpenTherm ot(inPin, outPin);
WiFiClient espClient;
PubSubClient client(espClient);
char buf[50];

float sp = 20.00, //set point
      pv = 0, //current temperature
      pv_last = 0, //prior temperature
      ierr = 0, //integral error
      dt = 0, //time between measurements
      op = 0; //PID controller output
unsigned long ts = 0, new_ts = 0; //timestamp
bool enableCentralHeating = true;
bool enableHotWater = true;
bool enableCooling = false;

int timer_off = 180; //Задержка выключения реле насоса СО 3 мин.

const char HTTP_HTML[] PROGMEM = "<!DOCTYPE html>\
<html>\
<head>\
<link rel=\"icon\" type=\"image/jpg\" href=\"data:image/jpg;base64,AAABAAEAEBAAAAEACABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAdTGwAMIKyAAA+ZwBXrtoACh4jAOv6/AA6V4MAMWWJACFQbwAOU5wAj9P/ABVmewAqdZUAHFmBABBAZAAaXYQAC0ZtAC52mABPpckAB1NwAECj+QAdYI0AJmJ4AA1SbQANWXAAIEhhAB5ukwBHm7UAE0ORADVgewADQV8ADDtWAA49UwA3c8UAJ26TAI/R+gBOlccAFD1TABc8UABFodAA2e3yADFxigBQncQAIVpzAB1afAAcYIUAClBoACRdfABInO4AIz9QAAI0SwAseqUAHGWLAA9PbgAvgvsAi6i3AE+JrQCJx+8AFlFrAM3m8AAAHDEAabT/ABVTcQAYV2sAX6zNAKbFzgAqcIgAs9v0ADBoiwBLnMIAPkdEAE+bvwC43+4AOYmuAHOlwQAfWnQAACNPABxfegBcmLYAG1qGAApKaQA5TV8AFERgAKPW5gAqPUIAeXBjAFelwgAgXY8AWKfIAGhfVQAjaIMANT8/AAAdLABttfEAIUpgADKDpgAAQF4As9XsAHCiuQBVpPUAkbW7AA45ZADX8PQAHV11ABxafgA7jK8AUaLDAB5egQBDmP8ADkttAFakyQAiZYQAJ0FPAEuh7QAUcJMAeazXAB1qigAMVHYAElFtAAtFkQA0fKQAATxcAAI2ZQAuY34AXqjMABRWcwBYjqwAUlZXAFGTxABbqu0A2unyAPT//QCNudEAHVKLAE+jxwBirvAAVGNsADR6nwAcSmIAEk9xAOX1+wAoYoUARZa8AAE4YwAAO2MAB2OAAEKcxQAKHSUAJkpiAB1DgwAbVG4AAB1AABc2SwArTF8AEWJ9AE5wpQBJnssAGlt6AE2gvwAza44AMW6OAAFLbwAidqYAJjg/AAk3hABSocIAIlt6ADyQtAAEPpAAUqPIABJcqgBVo8gAkcDVAFehywAeZ4MAJGGDAEeOwABYn+kAAD5kAEqXtwB6t98AMVyPABlVcgA8f6YAGlx1AHvE4gBOnsMACCg1AFCewwAogJgAH0BPABpejQAAL1MAEmWZAA5MagD///8AmL7QABtHZAAxfaEAYLv+AAtZdgAdU2QADk2RADhzuQAob4oAS5m+ACYxNQAdU3YAACFFACdwlgAGS18AIFpwABddggAhWHMAIVp5AA1FaAAJLDkAAytIADVvkwAjQEcAAk99AFajygAjYYUAKGJ/AAY8pwAGOFQAElV0AGqltQCCtckAW6vQABZWbgAVVXQATZe5AEmVxQBWVEwABTxpAAM/aQAYWYAAEjdjAAZBbwAdWXcATaHFABIkKwBJZXYAIFyAAAVVbAAumdIAKY7wAN37/wBYpssAlsPYABFPdQAAAAAAAAAAAAAAAAAAAAAA85kgmDFeH29f1lHbf1Tyo3nZ2Lsy7JDrEHpMXFuIPARSJpS+Zcp3pKiqCZeTzkbqybXgHMuxXTD3FMchle5VWRYkbDYjCoc5YXNjPZsGGXDfC7mAsIFx+MPDtIXF4cAlKeJaQnVQTR03BWTQPvsOirypeNF90vUCTkg4sua2wgiJRJ8u0hiRU8OMT48/uDUADCJpp3QXyPo7g2Lvi9Pnz24SVukROhNynITkoNcrnfSrR+hFt3ZgvSesShVtliyvrc2znkmu48RmKH7caEsvjXwqG6WSGkGCQ8ahNA/wZ43xhmpYjpoewfaiLb/t1aZrQPnd5brM1AEDMw1X3nsH2gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\">\
<title>Термостат</title>\
<meta charset=\"utf-8\">\
	<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\
 <script>\
   window.setInterval(\"update()\", 2000);\
    function update(a,b,c,d,e,f,g,h){\
      var a = new XMLHttpRequest();\
      a.open(\"GET\", \"/temp\", true);\
      a.onreadystatechange = function() {\
        if (a.readyState != XMLHttpRequest.DONE || a.status != 200) return;\
        document.getElementById('temp').innerHTML = a.responseText;\
      };\
      a.send();\
      var b = new XMLHttpRequest();\
      b.open(\"GET\", \"/boilertemp\", true);\
      b.onreadystatechange = function() {\
        if (b.readyState != XMLHttpRequest.DONE || b.status != 200) return;\
        document.getElementById('boilertemp').innerHTML = b.responseText;\
      };\
      b.send();\
      var c = new XMLHttpRequest();\
      c.open(\"GET\", \"/dhwttemp\", true);\
      c.onreadystatechange = function() {\
        if (c.readyState != XMLHttpRequest.DONE || c.status != 200) return;\
        document.getElementById('dhwttemp').innerHTML = c.responseText;\
      };\
      c.send();\
      var d = new XMLHttpRequest();\
      d.open(\"GET\", \"/outsidetemp\", true);\
      d.onreadystatechange = function() {\
        if (d.readyState != XMLHttpRequest.DONE || d.status != 200) return;\
        document.getElementById('outsidetemp').innerHTML = d.responseText;\
      };\
      d.send();\
      var e = new XMLHttpRequest();\
      e.open(\"GET\", \"/chon\", true);\
      e.onreadystatechange = function() {\
        if (e.readyState != XMLHttpRequest.DONE || e.status != 200) return;\
        document.getElementById('chon').innerHTML = e.responseText;\
      };\
      e.send();\
      var f = new XMLHttpRequest();\
      f.open(\"GET\", \"/hwon\", true);\
      f.onreadystatechange = function() {\
        if (f.readyState != XMLHttpRequest.DONE || f.status != 200) return;\
        document.getElementById('hwon').innerHTML = f.responseText;\
      };\
      f.send();\
      var g = new XMLHttpRequest();\
      g.open(\"GET\", \"/flameon\", true);\
      g.onreadystatechange = function() {\
        if (g.readyState != XMLHttpRequest.DONE || g.status != 200) return;\
        document.getElementById('flameon').innerHTML = g.responseText;\
      };\
      g.send();\
      var h = new XMLHttpRequest();\
      h.open(\"GET\", \"/getadc\", true);\
      h.onreadystatechange = function() {\
        if (h.readyState != XMLHttpRequest.DONE || h.status != 200) return;\
        document.getElementById('getadc').innerHTML = h.responseText;\
      };\
      h.send();\
    }\
     </script>\
</head>\
<body style=\"text-align:center\">\
<div style=\"line-height:0.5\">\
    <h1><font color=\"#1c6b72\"size=\"5\"face=\"Verdana\">Термостат</font></h1>\
    <img src=\"data:image/png;base64,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\"><p>\
    <font size=\"3\"face=\"Verdana\">OpenTherm</font><p>\
    <hr align=\"center\" size=\"3\" width=\"290px\" color=\"#1c6b72\"><br/><br/>\
    <font color=\"#1c6b72\"size=\"8\"face=\"Verdana\"><span id=\"temp\">{0}</span>°C</font><p><br/><br/>\
    <font size=\"4\"face=\"Verdana\">Котёл, подача:&nbsp <span id=\"boilertemp\">{2}</span>°C</font><p><br/>\
    <font size=\"4\"face=\"Verdana\">Горячая вода:&nbsp;&nbsp <span id=\"dhwttemp\">{3}</span>°C</font><p><br/>\
    <font size=\"4\"face=\"Verdana\">На улице:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp <span id=\"outsidetemp\">{4}</span>°C</font><p><br/>\
    <font size=\"4\"face=\"Verdana\">Модуляция:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp <span id=\"getadc\">{8}</span>%</font><p>\
    <hr align=\"center\" size=\"3\" width=\"290px\" color=\"#1c6b72\"><br/>\
    <form method=\"post\">\
    <font size=\"4\"face=\"Verdana\">Значение уставки,°C: <b><input type=\"number\" min=\"10\" max=\"30\" step=\"0.1\" name=\"sp\" value=\"{1}\" style=\"font-size:17px; width:65px; background-color:#E1E1E1\"></b></font><br/><br/><br/>\
        <input type=\"submit\"value=\"Записать\" style=\"font-size:16px; width:100px\">\
      <form>\
    </p>\
    <hr align=\"center\" size=\"3\" width=\"290px\" color=\"#1c6b72\"><br/>\
    <font size=\"3\"face=\"Verdana\">Статус СО:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp <b><span id=\"chon\">{5}</span></b></font><p><br/>\
    <font size=\"3\"face=\"Verdana\">Статус ГВС:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp <b><span id=\"hwon\">{6}</span></b></font><p><br/>\
    <font size=\"3\"face=\"Verdana\">Статус горелки:&nbsp <b><span id=\"flameon\">{7}</span></b></font><p>\
    </div>\
</body>\
</html>";

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

float getTemp() {
  return sensors.getTempCByIndex(0);
}
float getBoilerTemp() {
  return ot.getBoilerTemperature();
}
float getDHWTemp() {
  unsigned long request26 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Tdhw, 0);
  unsigned long respons26 = ot.sendRequest(request26);
  uint16_t dataValue26 = respons26 & 0xFFFF;
  float result26 = dataValue26 / 256;
  return result26;
}
float getOutsideTemp() {
  unsigned long request27 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Toutside, 0);
  unsigned long respons27 = ot.sendRequest(request27);
  uint16_t dataValue27 = respons27 & 0xFFFF;
  if (dataValue27 > 32768) {
    //negative
    float result27 = -(65536 - dataValue27) / 256;
    return result27;
  } else {
    //positive
    float result27 = dataValue27 / 256;
    return result27;
  }
}
int getADC() {
  delay(100);
  int ar = analogRead(A0);
  ar = map(ar, 26, 1023, 0, 100);
  return ar;
}
unsigned long getCentralHeatingEnabled() {
  unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
  OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
  if (responseStatus = OpenThermResponseStatus::SUCCESS)
    return ot.isCentralHeatingEnabled(response);
}
unsigned long getHotWaterEnabled() {
  unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
  OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
  if (responseStatus = OpenThermResponseStatus::SUCCESS)
    return ot.isHotWaterEnabled(response);
}
unsigned long getFlameOn() {
  unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
  OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
  if (responseStatus = OpenThermResponseStatus::SUCCESS)
    return ot.isFlameOn(response);
}
float pid(float sp, float pv, float pv_last, float& ierr, float dt) {
  float Kc = 10.0; // K / %Heater
  float tauI = 50.0; // sec
  float tauD = 1.0;  // sec
  // PID coefficients
  float KP = Kc;
  float KI = Kc / tauI;
  float KD = Kc * tauD;
  // upper and lower bounds on heater level
  float ophi = 100;
  float oplo = 0;
  // 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 D = -KD * dpv; //derivative contribution
  float op = P + I + D;
  // 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("Заданное значение температуры в помещкнии = " + String(sp) + " °C");
  Serial.println("Текущее значение температуры в помещкнии = " + String(pv) + " °C");
  Serial.println("Выхов ПИД регулятора = " + String(op));
  Serial.println("Время между измерениями = " + String(dt) + "; ПИД коэффициенты: П = " + String(P) + "; И = " + String(I) + "; Д = " + String(D));
  return op;
}
//===============================================================
// Эта процедура выполняется при открытии IP-адреса в браузере
//===============================================================
void handleRoot() {
  digitalWrite(BUILTIN_LED, 1);
  if (server.method() == HTTP_POST) {
    for (uint8_t i = 0; i < server.args(); i++) {
      if (server.argName(i) == "sp") {
        sp = server.arg(i).toFloat();
      }
    }
  }
  String page = FPSTR(HTTP_HTML);
  page.replace("{0}", String(getTemp()));
  page.replace("{1}", String((float)sp));
  page.replace("{2}", String(getBoilerTemp()));
  page.replace("{3}", String(getDHWTemp()));
  page.replace("{4}", String(getOutsideTemp()));
  page.replace("{5}", String(getCentralHeatingEnabled() ? "on" : "off"));
  page.replace("{6}", String(getHotWaterEnabled() ? "on" : "off"));
  page.replace("{7}", String(getFlameOn() ? "on" : "off"));
  page.replace("{8}", String(getADC()));
  server.send(200, "text/html", page);
  digitalWrite(BUILTIN_LED, 0);
}
void handleGetTemp() {
  //digitalWrite(BUILTIN_LED, 1);
  server.send(200, "text/plain", String(getTemp()));
  //digitalWrite(BUILTIN_LED, 0);
}
void handleGetBoilerTemp() {
  //digitalWrite(BUILTIN_LED, 1);
  server.send(200, "text/plain", String(getBoilerTemp()));
  //digitalWrite(BUILTIN_LED, 0);
}
void handleGetDHWTemp() {
  //digitalWrite(BUILTIN_LED, 1);
  server.send(200, "text/plain", String(getDHWTemp()));
  //digitalWrite(BUILTIN_LED, 0);
}
void handleGetOutsideTemp() {
  //digitalWrite(BUILTIN_LED, 1);
  server.send(200, "text/plain", String(getOutsideTemp()));
  //digitalWrite(BUILTIN_LED, 0);
}
void handleGetCentralHeatingEnabled() {
  //digitalWrite(BUILTIN_LED, 1);
  server.send(200, "text/plain", String(getCentralHeatingEnabled() ? "on" : "off"));
  //digitalWrite(BUILTIN_LED, 0);
}
void handleGetHotWaterEnabled() {
  //digitalWrite(BUILTIN_LED, 1);
  server.send(200, "text/plain", String(getHotWaterEnabled() ? "on" : "off"));
  //digitalWrite(BUILTIN_LED, 0);
}
void handleGetFlameOn() {
  //digitalWrite(BUILTIN_LED, 1);
  server.send(200, "text/plain", String(getFlameOn() ? "on" : "off"));
  //digitalWrite(BUILTIN_LED, 0);
}
void handleADC() {
  //digitalWrite(BUILTIN_LED, 1);
  server.send(200, "text/plain", String(getADC()));
  //digitalWrite(BUILTIN_LED, 0);
}
//==============================================================
//                  SETUP
//==============================================================
void setup_wifi() {
  delay(10);
  //Connect to Wi-Fi Network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password); //Connect to your Wi-Fi router
  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  //If connection successful show IP address in serial monitor
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}
void setup(void) {
  pinMode(BUILTIN_LED, OUTPUT);
  digitalWrite(BUILTIN_LED, 0);
  Serial.begin(115200);
  setup_wifi();
  if (MDNS.begin("thermostat")) {
    Serial.println("MDNS responder started");
  }
  //Initialize Webserver
  server.on("/", handleRoot); // Ответ сервера на запрос главной страницы
  server.on("/temp", handleGetTemp); // Ответ сервера на запрос температуры в помещении
  server.on("/boilertemp", handleGetBoilerTemp); // Ответ сервера на запрос температуры подачи котла
  server.on("/dhwttemp", handleGetDHWTemp); // Ответ сервера на запрос температуры горячей воды
  server.on("/outsidetemp", handleGetOutsideTemp); // Ответ сервера на запрос уличной температуры
  server.on("/chon", handleGetCentralHeatingEnabled); // Ответ сервера на запрос состояния СО
  server.on("/hwon", handleGetHotWaterEnabled); // Ответ сервера на запрос состояния ГВС
  server.on("/flameon", handleGetFlameOn); // Ответ сервера на запрос состояния горелки
  server.on("/getadc", handleADC); //Reads ADC function
  server.begin();
  Serial.println("HTTP server started");
  //==============================================================
  //                  Init DS18B20 sensor
  //==============================================================
  sensors.begin();
  sensors.requestTemperatures();
  sensors.setWaitForConversion(false); //switch to async mode
  pv, pv_last = sensors.getTempCByIndex(0);
  ts = millis();
  //==============================================================
  //                  Init OpenTherm Controller
  //==============================================================
  ot.begin(handleInterrupt);
  //==============================================================
  //                  Init MQTT Client
  //==============================================================
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);
}
void publish_temperature() {
  Serial.println("MQTT, Current Room Temperature, °C = " + String(pv));
  String(pv).toCharArray(buf, 50);
  client.publish("pv", buf);
}
void publish_boilertemp() {
  Serial.println("MQTT, CH Temperature, °C = " + String(getBoilerTemp()));
  String(getBoilerTemp()).toCharArray(buf, 50);
  client.publish("cht", buf);
}
void publish_dhwtemp() {
  Serial.println("MQTT, DHW Temperature, °C = " + String(getDHWTemp()));
  String(getDHWTemp()).toCharArray(buf, 50);
  client.publish("dhwt", buf);
}
void publish_outtemp() {
  Serial.println("MQTT, Outside Temperature, °C = " + String(getOutsideTemp()));
  String(getOutsideTemp()).toCharArray(buf, 50);
  client.publish("outt", buf);
}
void publish_statusCH() {
  Serial.println("MQTT, Status Central Heating = " + String(getCentralHeatingEnabled()));
  String(getCentralHeatingEnabled()).toCharArray(buf, 50);
  client.publish("sch", buf);
}
void publish_ADC() {
  Serial.println("MQTT, Relative Modulation Level, % = " + String(getADC()));
  String(getADC()).toCharArray(buf, 50);
  client.publish("adc", buf);
}
void publish_statusDWH() {
  Serial.println("MQTT, Status Hot Water = " + String(getHotWaterEnabled()));
  String(getHotWaterEnabled()).toCharArray(buf, 50);
  client.publish("sdwh", buf);
}
void publish_statusFlame() {
  Serial.println("MQTT, Status Central Heating = " + String(getFlameOn()));
  String(getFlameOn()).toCharArray(buf, 50);
  client.publish("sfl", buf);
}
void publish_setBoilerTemperature() {
  Serial.println("MQTT, PID Controller Output, % = Set Point CH Temperature, °C = " + String(op));
  String(op).toCharArray(buf, 50);
  client.publish("op", buf);
}
void callback(char* topic, byte* payload, unsigned int length) {
  if (strcmp(topic, "sp") != 0) return;
  String str = String();
  for (int i = 0; i < length; i++) {
    str += (char)payload[i];
  }
  Serial.println("MQTT, Setpoint Room Temperature = " + str);
  sp = str.toFloat();
}
void reconnect() {
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("ESP8266Client", mqtt_user, mqtt_password)) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      publish_temperature();
      publish_boilertemp();
      publish_dhwtemp();
      publish_outtemp();
      publish_ADC();
      publish_statusCH();
      publish_statusDWH();
      publish_statusFlame();
      publish_setBoilerTemperature();
      // ... and resubscribe
      client.subscribe("sp");
    } else {
      Serial.print("failed, rc =");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}
//==============================================================
//                     LOOP
//==============================================================
void loop(void) {
  new_ts = millis();
  if (new_ts - ts > 1000) {
    unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
    OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
    if (responseStatus = OpenThermResponseStatus::SUCCESS) {
      //Serial.println("Error: Invalid boiler response " + String(response, HEX));
      Serial.println("Текущий статус системы отопления: " + String(ot.isCentralHeatingEnabled(response) ? "on" : "off"));
      Serial.println("Текущий статус горячей воды: " + String(ot.isHotWaterEnabled(response) ? "on" : "off"));
      Serial.println("Текущий статус горелки: " + String(ot.isFlameOn(response) ? "on" : "off"));
      Serial.println("Индикация состояния неисправности: " + String(ot.isFault(response) ? "fault" : "no fault"));
      Serial.println("Диагностическая индикация: " + String(ot.isDiagnostic(response) ? "diagnostics" : "no diagnostics"));
    }
    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");
    }
    pv = sensors.getTempCByIndex(0);
    dt = (new_ts - ts) / 1000.0;
    ts = new_ts;
    if (responseStatus == OpenThermResponseStatus::SUCCESS) {
      op = pid(sp, pv, pv_last, ierr, dt);
      //Set CH Temperature
      ot.setBoilerTemperature(op);
      pv_last = pv;
      sensors.requestTemperatures(); //async temperature request
    }
    //Print Temperature

    Serial.println("Текущая температура контура СО = " + String(getBoilerTemp()) + " °C");
    Serial.println("Текущая температура контура ГВС = " + String(getDHWTemp()) + " °C");
    Serial.println("Температура на улице = " + String(getOutsideTemp()) + " °C");
    Serial.println("Текущий уровень модуляции горелки  = " + String (getADC()) + " %");
    Serial.println("ADC = " + String (analogRead(A0)));

    publish_temperature();
    publish_boilertemp();
    publish_dhwtemp();
    publish_outtemp();
    publish_ADC();
    publish_statusCH();
    publish_statusDWH();
    publish_statusFlame();
    publish_setBoilerTemperature();
  }
  //MQTT Loop
  if (!client.connected()) {
    reconnect();
  }
  client.loop();
  server.handleClient(); //handle http requests
}

 

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

Посмотрел по диагонали, возможно не прав, у вас получение температуры в самом выводе html странички.

Вообще все плохо, получение статусов котла температуры и прочих данных должно быть неблокирующим кодом по таймерам, а вывод в страничку из переменных а не динамически получать. Кстати зачем страничку в progmem? Неужели места в esp не хватило.

andycat
andycat аватар
Offline
Зарегистрирован: 07.09.2017

ЕвгенийП пишет:

andycat пишет:
Кстати зачем страничку в progmem?

А в строке №241 эта самая страничка целиком закачивается в RAM


И на это тоже тратится драгоценное процессорное время

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

строка 223 и 224 орфографические ошибки, это, что бросилось в глаза

tsv_33
Offline
Зарегистрирован: 11.04.2019

ua6em пишет:

строка 223 и 224 орфографические ошибки, это, что бросилось в глаза

Спасибо, исправил. 

Алексей.
Алексей. аватар
Offline
Зарегистрирован: 02.02.2018

Давно такого не видел, чтоб статику, по определению размещенную в PROGMEM, там ей и место, перегружать в стринг и в этом стринге реплейсить.
Для чего настраивать интервал, чтоб каждые 2 секунды асинхронно выполнялись куча запросов (броузер их параллельно ведь выполняет).
/temp /boilertemp /dhwttemp /outsidetemp /chon /hwon /flameon /getadc
ни ужели нельзя обойтись одним запросом, получать всё необходимое сразу.
 

tsv_33
Offline
Зарегистрирован: 11.04.2019

Алексей. пишет:

Давно такого не видел, чтоб статику, по определению размещенную в PROGMEM, там ей и место, перегружать в стринг и в этом стринге реплейсить.
Для чего настраивать интервал, чтоб каждые 2 секунды асинхронно выполнялись куча запросов (броузер их параллельно ведь выполняет).
/temp /boilertemp /dhwttemp /outsidetemp /chon /hwon /flameon /getadc
ни ужели нельзя обойтись одним запросом, получать всё необходимое сразу.

Алексей., пробовал и одним запросом, как выше написали, но переменные не обновляются без перезагрузки страницы...

Алексей.
Алексей. аватар
Offline
Зарегистрирован: 02.02.2018

Что значит не обновляются? Есп их не отправляет? А по отдельности отправляет? Целую страницу отправляет, а десяток переменных не может? Да быть такого не может. Вы увеличиваете нагрузку, отправляя с броузера сразу несколько запросов, по одному для каждого параметра.

VOVA_iS
Offline
Зарегистрирован: 09.07.2019

tsv_33 пишет:

Приветствую. Делаю термостат для своего газового котла BAXI SLiM, работающий по протоколу OpenTherm. Всё вроде работает. Но веб страница долго грузится. Может есть какие замечения? Как ускорить процесс загрузки? С MQTT проблем нет. 


 

Здравствуйте 

Загрузил ваш скейтч

У меня котел не отключает помпу отопления. У Вас все нормально с этим

И возможно ли дописать скейтч для регулирования температуры горячей воды(можно только через mqtt)

Спасибо

b707
Offline
Зарегистрирован: 26.05.2017

VOVA_iS - не надо этот код брать, он откровенно плохо написан. Не шутите с газом.

VOVA_iS
Offline
Зарегистрирован: 09.07.2019

b707 пишет:

VOVA_iS - не надо этот код брать, он откровенно плохо написан. Не шутите с газом.

Тут криво веб сервер написан

По работе котла код скопирован с примера библиотеки OpenTherm. 

Mqtt работает хорошо

tsv_33
Offline
Зарегистрирован: 11.04.2019

VOVA_iS, да, помпа молотит постоянно, повесил на термостат своё реле для её управления. Горячую воду то же сделал. Вообще много чего добавил и переделал, и продолжаю делать...

P.S. Да, сервер кривой :-(

tsv_33
Offline
Зарегистрирован: 11.04.2019

VOVA_iS, от веб сервера решил отказаться, оставлю только конфигурационную страничку и всё, а для рулёжки достаточно mqtt. 

VOVA_iS
Offline
Зарегистрирован: 09.07.2019

tsv_33 пишет:

VOVA_iS, да, помпа молотит постоянно, повесил на термостат своё реле для её управления. Горячую воду то же сделал. Вообще много чего добавил и переделал, и продолжаю делать...

P.S. Да, сервер кривой :-(

про помпу можно подробней:)?

скетч можно с горячей водой?

VOVA_iS
Offline
Зарегистрирован: 09.07.2019

tsv_33 пишет:

VOVA_iS, от веб сервера решил отказаться, оставлю только конфигурационную страничку и всё, а для рулёжки достаточно mqtt. 

Да я согласен по MQTT управления выше крыши. И умный дом можно добавить полноценно

tsv_33
Offline
Зарегистрирован: 11.04.2019

VOVA_iS пишет:

про помпу можно подробней:)?

скетч можно с горячей водой?

Для помпы использую библиотеку TimingRelay.h:

#define PIN_RELAY 12                       // D6 На внешнее реле управления насосом СО и внешнюю индикацию работы насоса СО
TimingRelay relay(PIN_RELAY, 0, 180000);   // Создаем реле времени (назначенный порт, задержка включения, задержка выключения).

В setup:

pinMode(PIN_RELAY, OUTPUT);
 relay.autoReset = true;      // Автоматический запуск таймера задержки выключения.

В loop реле работает по условию:

// обработка индикации и реле насоса СО.
  if (status_flame == 1 && status_dhw == 0) {    // Если состояния горелки = 1 и статус состояния ГВС = 0 включаем реле.
    relay.on();
  } else {
    relay.off();
  }
  relay.loop();                                  // Обрабатываем (обязательно).
// конец обработки индикации и реле насоса СО.

С ГВС не так всё просто... Можно и по играться уставкой температуры ГВС. :-) При работе от внешнего термостата, а рекомендованный это QAA73, именно он задаёт режим работы ГВС, т.е. когда включить по таймеру (1 раз в неделю) прогрев системы до 65 гр., чтобы убить бацилы. При отсутствии внешнего это делает автоматика котла. Если в термостате этого нет, то ручками и по календарю. У меня пока не получилось автоматом, а может и получилось, полноценно не проверял, здесь, на форуме помогли.

Вот по порядку:

#define DAY(x)    ((x) * 24 * MIN(60))
#define MIN(x)    ((x) * 10000ul)
#define TIME1      DAY(7)
#define TIME2      MIN(60)
#define VAL1       50.
#define VAL2       60.

float spdhw = VAL1;  // точка отсчета температуры горячей воды контура ГВС
unsigned int hex56 = (spdhw * 256 * 16 / 16); // точка отсчета температуры горячей воды контура ГВС, но в (HEX)

// Обработчик входящих тем и полезных нагрузок MQTT
void callback(char* topic, byte* payload, unsigned int length) {
  char buffer[length + 1];
  memcpy(buffer, payload, length);
  buffer[length] = '\0';
  float a = atof((char*)buffer);
  if (a <= 29) {
    sp = a;
  } else {
    spdhw = a;
  }
  //Serial.println("MQTT,topic = " + String(a));
}

В reconnect добавить строку 

client.subscribe("sp");  // эта уже есть
client.subscribe("spdhw");  // добавить

В loop

//==============================================================================
// Активации функции Антилегионелла ГВС 65°C на 1 час каждые 7 дней по кругу.
//==============================================================================

  static bool state;
  static uint32_t old_millis = millis();
  if (state == false) {
    if (millis() - old_millis >= TIME1) {
      old_millis = millis();
      spdhw = VAL1;
      state = true;
    }
  } else if (millis() - old_millis >= TIME2) {
    old_millis = millis();
    spdhw = VAL2;
    state = false;
  }

 

VOVA_iS
Offline
Зарегистрирован: 09.07.2019

А как параметр spdhw передаётся в котёл? не вижу

tsv_33
Offline
Зарегистрирован: 11.04.2019

VOVA_iS пишет:

А как параметр spdhw передаётся в котёл? не вижу

Извиняюсь!

float setDHWTemp() {
  unsigned long request56 = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::TdhwSet, hex56);
  unsigned long respons56 = ot.sendRequest(request56);
  uint16_t dataValue56 = respons56 & 0xFFFF;
  float result56 = dataValue56 / 256;
  return result56;
}

В loop после 466 строки (код в начале) добавить:

// Заданная температура ГВС
      hex56 = (spdhw * 256 * 16 / 16);
      unsigned int setDHWTemp(hex56);

 

VOVA_iS
Offline
Зарегистрирован: 09.07.2019

Ещё вырежу всю веб морду:-)

спасибо завтра попробую скомпилировать скетч

tsv_33
Offline
Зарегистрирован: 11.04.2019

VOVA_iS, есть ещё одна задумка. В слимах по ОТ не передаётся уровень модуляции горелки.  Можно сделать используя у ESP 8266 А0. 

VOVA_iS
Offline
Зарегистрирован: 09.07.2019

Интересная идея:-) только я на сколько помню на А0  может  быть максимум 3.3v

надо сначала замеры сделать у котла:-)

 

tsv_33
Offline
Зарегистрирован: 11.04.2019

VOVA_iS, вполне достаточно. С котла 9,10 контакты А5 с катушки модуляции идёт ШИМ. Надо ставить операционник, преобразовать ШИМ в линейные 0-5В, короче с него через делитель на А0. Программно потребуется ремапинг АЦП.

VOVA_iS
Offline
Зарегистрирован: 09.07.2019

tsv_33 пишет:

VOVA_iS пишет:

про помпу можно подробней:)?

скетч можно с горячей водой?

Для помпы использую библиотеку TimingRelay.h:

#define PIN_RELAY 12                       // D6 На внешнее реле управления насосом СО и внешнюю индикацию работы насоса СО
TimingRelay relay(PIN_RELAY, 0, 180000);   // Создаем реле времени (назначенный порт, задержка включения, задержка выключения).

В setup:

pinMode(PIN_RELAY, OUTPUT);
 relay.autoReset = true;      // Автоматический запуск таймера задержки выключения.

В loop реле работает по условию:

// обработка индикации и реле насоса СО.
  if (status_flame == 1 && status_dhw == 0) {    // Если состояния горелки = 1 и статус состояния ГВС = 0 включаем реле.
    relay.on();
  } else {
    relay.off();
  }
  relay.loop();                                  // Обрабатываем (обязательно).
// конец обработки индикации и реле насоса СО.

 

Не могу скомпилировать идут ошибки

в коде данные переменные status_flame status_dhw не опредлены 

 

tsv_33
Offline
Зарегистрирован: 11.04.2019

Всё верно, статусы не задекларированы, вставьте это в loop выше индикации и состояния насоса:

// обработка индикации состояния ГВС.
  bool status_dhw = getHotWaterEnabled();
  if (status_dhw == 1) {
    digitalWrite(EXTERNAL_LED, 1);
  } else {
    digitalWrite(EXTERNAL_LED, 0);
  }
  // обработка индикации состояния горелки.
  bool status_flame = getFlameOn();
  if (status_flame == 1) {
    digitalWrite(EXTERNAL_LED_1, 1);
  } else {
    digitalWrite(EXTERNAL_LED_1, 0);
  }

Условия для индикации можно отключить, а то опять ругаться будет при компиляции...

VOVA_iS
Offline
Зарегистрирован: 09.07.2019

я так понимаю нужно это 


void callback(char* topic, byte* payload, unsigned int length) {
  if (strcmp(topic, "sp") != 0) return;
  String str = String();
  for (int i = 0; i < length; i++) {
    str += (char)payload[i];
  }
Serial.println("MQTT, Setpoint Room Temperature = " + str);
sp = str.toFloat();
}

Заменить на это 

// Обработчик входящих тем и полезных нагрузок MQTT
void callback(char* topic, byte* payload, unsigned int length) {
  char buffer[length + 1];
  memcpy(buffer, payload, length);
  buffer[length] = '\0';
  float a = atof((char*)buffer);
  if (a <= 29) {
    sp = a;
  } else {
    spdhw = a;
  }
   
   

 

tsv_33
Offline
Зарегистрирован: 11.04.2019

Да, верно.

VOVA_iS
Offline
Зарегистрирован: 09.07.2019
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <WiFiClient.h>
#include <TimingRelay.h>
#include <ESP8266mDNS.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <OpenTherm.h>               //<a href="https://github.com/ihormelnyk/opentherm_library/" rel="nofollow">https://github.com/ihormelnyk/opentherm_library/</a>

//OpenTherm input and output wires connected to 4 and 5 pins on the OpenTherm Shield
const int inPin = 4; //D2
const int outPin = 5; //D1

#define ONE_WIRE_BUS 14 //D5 Data wire is connected to 14 pin on the OpenTherm Shield
#define BUILTIN_LED 2 //D4 Встроенный LED
#define PIN_RELAY 12                       // D6 На внешнее реле управления насосом СО и внешнюю индикацию работы насоса СО
TimingRelay relay(PIN_RELAY, 0, 180000);   // Создаем реле времени (назначенный порт, задержка включения, задержка выключения).


const char* ssid = "-------";
const char* password = "----------";
const char* mqtt_server = "192.168.10.1";
const int   mqtt_port = 1883;
const char* mqtt_user = "";
const char* mqtt_password = "";


//ESP8266WebServer server(80); //Server on port 80
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
OpenTherm ot(inPin, outPin);
WiFiClient espClient;
PubSubClient client(espClient);
char buf[50];

float sp = 20.00, //set point
      pv = 0, //current temperature
      pv_last = 0, //prior temperature
      ierr = 0, //integral error
      dt = 0, //time between measurements
      op = 0; //PID controller output
unsigned long ts = 0, new_ts = 0; //timestamp
bool enableCentralHeating = true;
bool enableHotWater = true;
bool enableCooling = false;

int timer_off = 180; //Задержка выключения реле насоса СО 3 мин.



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

float spdhw = 40;  // точка отсчета температуры горячей воды контура ГВС
unsigned int hex56 = (spdhw * 256 * 16 / 16); // точка отсчета температуры горячей воды контура ГВС, но в (HEX)

float getTemp() {
  return sensors.getTempCByIndex(0);
}
float getBoilerTemp() {
  return ot.getBoilerTemperature();
}
float setDHWTemp() {
  unsigned long request56 = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::TdhwSet, hex56);
  unsigned long respons56 = ot.sendRequest(request56);
  uint16_t dataValue56 = respons56 & 0xFFFF;
  float result56 = dataValue56 / 256;
  return result56;
}
float getDHWTemp() {
  unsigned long request26 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Tdhw, 0);
  unsigned long respons26 = ot.sendRequest(request26);
  uint16_t dataValue26 = respons26 & 0xFFFF;
  float result26 = dataValue26 / 256;
  return result26;
}
float getOutsideTemp() {
  unsigned long request27 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Toutside, 0);
  unsigned long respons27 = ot.sendRequest(request27);
  uint16_t dataValue27 = respons27 & 0xFFFF;
  if (dataValue27 > 32768) {
    //negative
    float result27 = -(65536 - dataValue27) / 256;
    return result27;
  } else {
    //positive
    float result27 = dataValue27 / 256;
    return result27;
  }
}
int getADC() {
  delay(100);
  int ar = analogRead(A0);
  ar = map(ar, 26, 1023, 0, 100);
  return ar;
}
unsigned long getCentralHeatingEnabled() {
  unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
  OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
  if (responseStatus = OpenThermResponseStatus::SUCCESS)
    return ot.isCentralHeatingActive(response);
}
unsigned long getHotWaterEnabled() {
  unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
  OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
  if (responseStatus = OpenThermResponseStatus::SUCCESS)
    return ot.isHotWaterActive(response);
}
unsigned long getFlameOn() {
  unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
  OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
  if (responseStatus = OpenThermResponseStatus::SUCCESS)
    return ot.isFlameOn(response);
}
float pid(float sp, float pv, float pv_last, float& ierr, float dt) {
  float Kc = 10.0; // K / %Heater
  float tauI = 50.0; // sec
  float tauD = 1.0;  // sec
  // PID coefficients
  float KP = Kc;
  float KI = Kc / tauI;
  float KD = Kc * tauD;
  // upper and lower bounds on heater level
  float ophi = 85;
  float oplo = 30;
  // 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 D = -KD * dpv; //derivative contribution
  float op = P + I + D;
  // 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("Заданное значение температуры в помещкнии = " + String(sp) + " °C");
  Serial.println("Текущее значение температуры в помещкнии = " + String(pv) + " °C");
  Serial.println("Выхов ПИД регулятора = " + String(op));
  Serial.println("Время между измерениями = " + String(dt) + "; ПИД коэффициенты: П = " + String(P) + "; И = " + String(I) + "; Д = " + String(D));
  return op;
}
//===============================================================
// Эта процедура выполняется при открытии IP-адреса в браузере
//===============================================================

//==============================================================
//                  SETUP
//==============================================================
 
void setup_wifi() {
  delay(10);
  //Connect to Wi-Fi Network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password); //Connect to your Wi-Fi router
  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  //If connection successful show IP address in serial monitor
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}
void setup(void) {
  pinMode(BUILTIN_LED, OUTPUT);
  digitalWrite(BUILTIN_LED, 0);
  Serial.begin(115200);
  setup_wifi();
  pinMode (12, OUTPUT);
  relay.autoReset = true;      // Автоматический запуск таймера задержки выключения.
  if (MDNS.begin("thermostat")) {
    Serial.println("MDNS responder started");
  }
  //Initialize Webserver
  
  //==============================================================
  //                  Init DS18B20 sensor
  //==============================================================
  sensors.begin();
  sensors.requestTemperatures();
  sensors.setWaitForConversion(false); //switch to async mode
  pv, pv_last = sensors.getTempCByIndex(0);
  ts = millis();
  //==============================================================
  //                  Init OpenTherm Controller
  //==============================================================
  ot.begin(handleInterrupt);
  //==============================================================
  //                  Init MQTT Client
  //==============================================================
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);
}
void publish_temperature() {
  Serial.println("MQTT, Current Room Temperature, °C = " + String(pv));
  String(pv).toCharArray(buf, 50);
  client.publish("pv", buf);
}
void publish_boilertemp() {
  Serial.println("MQTT, CH Temperature, °C = " + String(getBoilerTemp()));
  String(getBoilerTemp()).toCharArray(buf, 50);
  client.publish("cht", buf);
}
void publish_dhwtemp() {
  Serial.println("MQTT, DHW Temperature, °C = " + String(getDHWTemp()));
  String(getDHWTemp()).toCharArray(buf, 50);
  client.publish("dhwt", buf);
}
void publish_outtemp() {
  Serial.println("MQTT, Outside Temperature, °C = " + String(getOutsideTemp()));
  String(getOutsideTemp()).toCharArray(buf, 50);
  client.publish("outt", buf);
}
void publish_statusCH() {
  Serial.println("MQTT, Status Central Heating = " + String(getCentralHeatingEnabled()));
  String(getCentralHeatingEnabled()).toCharArray(buf, 50);
  client.publish("sch", buf);
}
void publish_ADC() {
  Serial.println("MQTT, Relative Modulation Level, % = " + String(getADC()));
  String(getADC()).toCharArray(buf, 50);
  client.publish("adc", buf);
}
void publish_statusDWH() {
  Serial.println("MQTT, Status Hot Water = " + String(getHotWaterEnabled()));
  String(getHotWaterEnabled()).toCharArray(buf, 50);
  client.publish("sdwh", buf);
}
void publish_statusFlame() {
  Serial.println("MQTT, Status Central Heating = " + String(getFlameOn()));
  String(getFlameOn()).toCharArray(buf, 50);
  client.publish("sfl", buf);
}
void publish_setBoilerTemperature() {
  Serial.println("MQTT, PID Controller Output, % = Set Point CH Temperature, °C = " + String(op));
  String(op).toCharArray(buf, 50);
  client.publish("op", buf);
}
void callback(char* topic, byte* payload, unsigned int length) {
  char buffer[length + 1];
  memcpy(buffer, payload, length);
  buffer[length] = '\0';
  float a = atof((char*)buffer);
  if (a <= 29) {
    sp = a;
  } else {
    spdhw = a;
  }
//  Serial.println("MQTT, Setpoint Room Temperature = " + str);
//  sp = str.toFloat();
}
void reconnect() {
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("ESP8266Client", mqtt_user, mqtt_password)) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      publish_temperature();
      publish_boilertemp();
      publish_dhwtemp();
      publish_outtemp();
      publish_ADC();
      publish_statusCH();
      publish_statusDWH();
      publish_statusFlame();
      publish_setBoilerTemperature();
      // ... and resubscribe
      client.subscribe("sp");
      client.subscribe("spdhw"); 
    } else {
      Serial.print("failed, rc =");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}
//==============================================================
//                     LOOP
//==============================================================
void loop(void) {
  new_ts = millis();
  if (new_ts - ts > 1000) {
    unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
    OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
    if (responseStatus = OpenThermResponseStatus::SUCCESS) {
      //Serial.println("Error: Invalid boiler response " + String(response, HEX));
      Serial.println("Текущий статус системы отопления: " + String(ot.isCentralHeatingActive(response) ? "on" : "off"));
      Serial.println("Текущий статус горячей воды: " + String(ot.isHotWaterActive(response) ? "on" : "off"));
      Serial.println("Текущий статус горелки: " + String(ot.isFlameOn(response) ? "on" : "off"));
      Serial.println("Индикация состояния неисправности: " + String(ot.isFault(response) ? "fault" : "no fault"));
      Serial.println("Диагностическая индикация: " + String(ot.isDiagnostic(response) ? "diagnostics" : "no diagnostics"));
    }
    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");
    }
    pv = sensors.getTempCByIndex(0);
    dt = (new_ts - ts) / 1000.0;
    ts = new_ts;
    if (responseStatus == OpenThermResponseStatus::SUCCESS) {
      op = pid(sp, pv, pv_last, ierr, dt);
      //Set CH Temperature
      ot.setBoilerTemperature(op);
      hex56 = (spdhw * 256 * 16 / 16);
      unsigned int setDHWTemp(hex56);
      pv_last = pv;
      sensors.requestTemperatures(); //async temperature request
    }
    // обработка индикации состояния ГВС.
  bool status_dhw = getHotWaterEnabled();
   // обработка индикации состояния горелки.
  bool status_flame = getFlameOn();
  //обработка индикации и реле насоса СО.
 if (status_flame == 1 && status_dhw == 0) {    // Если состояния горелки = 1 и статус состояния ГВС = 0 включаем реле.
    relay.on();
 } else {
    relay.off();
  }
  relay.loop();                                  // Обрабатываем (обязательно).
 //конец обработки индикации и реле насоса СО.
  
    //Print Temperature

    Serial.println("Текущая температура контура СО = " + String(getBoilerTemp()) + " °C");
    Serial.println("Текущая температура контура ГВС = " + String(getDHWTemp()) + " °C");
    Serial.println("Температура на улице = " + String(getOutsideTemp()) + " °C");
    Serial.println("Текущий уровень модуляции горелки  = " + String (getADC()) + " %");
    Serial.println("ADC = " + String (analogRead(A0)));

    publish_temperature();
    publish_boilertemp();
    publish_dhwtemp();
    publish_outtemp();
    publish_ADC();
    publish_statusCH();
    publish_statusDWH();
    publish_statusFlame();
    publish_setBoilerTemperature();
  }
  //MQTT Loop
  if (!client.connected()) {
    reconnect();
  }
  client.loop();
//  server.handleClient(); //handle http requests
}

Скомпилировал код выше.

Увы температура горяей воды не передается.

Посмотрите где может быть ошибка?

tsv_33
Offline
Зарегистрирован: 11.04.2019

ОК! Посмотрю. У меня сейчас проблема после обновления менеджера плат до версии 2.5.2. esp-шка ругается в мониторе "ISR not in IRAM!", ребутится постоянно. До обновления была версия 2.5.0. Копать дальше? Или откатиться и забыть? После декодирования стека пишет:

Decoding stack results
0x401001b8: millis() at C:\Users\Stas\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.5.2\cores\esp8266\core_esp8266_wiring.cpp line 186
0x40201038: handleInterrupt() at C:\Users\Stas\Documents\Arduino\OpenTherm_thermostat_v6.01_WEB/OpenTherm_thermostat_v6.01_WEB.ino line 213
0x401004be: __attachInterrupt(uint8_t, voidFuncPtr, int) at C:\Users\Stas\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.5.2\cores\esp8266\core_esp8266_wiring_digital.cpp line 206
0x4020c8f0: OpenTherm::begin(void (*)(), void (*)(unsigned long, OpenThermResponseStatus)) at C:\Users\Stas\Documents\Arduino\libraries\opentherm_library-master\src\OpenTherm.cpp line 28
0x4020c91c: OpenTherm::begin(void (*)()) at C:\Users\Stas\Documents\Arduino\libraries\opentherm_library-master\src\OpenTherm.cpp line 36
0x40203922: setup() at C:\Users\Stas\Documents\Arduino\OpenTherm_thermostat_v6.01_WEB/OpenTherm_thermostat_v6.01_WEB.ino line 642
0x40201018: saveConfigCallback() at C:\Users\Stas\Documents\Arduino\OpenTherm_thermostat_v6.01_WEB/OpenTherm_thermostat_v6.01_WEB.ino line 430
0x402011e0: std::_Function_handler ::_M_invoke(const std::_Any_data &) at c:\users\stas\appdata\local\arduino15\packages\esp8266\tools\xtensa-lx106-elf-gcc\2.5.0-3-20ed2b9\xtensa-lx106-elf\include\c++\4.8.2/functional line 2069
0x402103c4: loop_wrapper() at C:\Users\Stas\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.5.2\cores\esp8266\core_esp8266_main.cpp line 122
 
Ладно, если бы на ошибке в коде..., а то и на библиотеки жалуется...
VOVA_iS
Offline
Зарегистрирован: 09.07.2019

tsv_33 пишет:

ОК! Посмотрю. У меня сейчас проблема после обновления менеджера плат до версии 2.5.2. esp-шка ругается в мониторе "ISR not in IRAM!", ребутится постоянно. До обновления была версия 2.5.0. Копать дальше? Или откатиться и забыть? После декодирования стека пишет:

Откатись и забудь.

У меня тоже после обновления много каких проектов перестало собираться.

Откатился и все норм

tsv_33
Offline
Зарегистрирован: 11.04.2019

Это понятно, но всё же не случайно это, что то с прерываниями не то... Где то надо добавить ICACHE_RAM_ATTR перед функцией ISR, что бы правильно было.

tsv_33
Offline
Зарегистрирован: 11.04.2019

Хотя ругани в сети по этому поводу полно...:-)

tsv_33
Offline
Зарегистрирован: 11.04.2019

Так будет проще, выкладываю один из рабочих кодов без веб страницы, сервер из кода сами удалите и принты в монитор тоже, не нужны они для работы с MQTT.

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <OpenTherm.h>
#include <TimingRelay.h>

//Входные и выходные контакты OpenTherm, подключены к 4 и 5 контактам платы
const int inPin = 4;            //D2
const int outPin = 5;           //D1

#define ONE_WIRE_BUS 14         // D5 Data wire is connected to 14 pin on the OpenTherm Shield
#define BUILTIN_LED 2           // D4 Встроенный LED
#define PIN_RELAY 12            // D6 На внешнее реле управления насосом СО и внешнюю индикацию работы насоса СО
#define EXTERNAL_LED 13         // D7 На внешний LED индикации работы ГВС
#define EXTERNAL_LED_1 15       // D8 На внешний LED индикации работы горелки
#define EXTERNAL_LED_2 0        // D3 На внешний LED индикации работы авария

//#define DAY(x)            ((x) * 10000ul) // тест
#define DAY(x)            ((x) * 24 * MIN(60))
#define MIN(x)            ((x) * 10000ul)
#define TIME1             DAY(7)
#define TIME2             MIN(60)
#define VAL1              50.
#define VAL2              60.

const char* ssid = "********";
const char* password = "*********";
const char* mqtt_server = "***********";
const int   mqtt_port = 12345;
const char* mqtt_user = "***********";
const char* mqtt_password = "*************";

ESP8266WebServer server(80);                     // Сервер на порту 80
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
OpenTherm ot(inPin, outPin);
WiFiClient espClient;
PubSubClient client(espClient);
char buf[50];

float sp = 20,                                   // точка отсчета комнатной температуры
      pv = 0,                                    // текущая температура в комнате
      pv_last = 0,                               // предыдущая температура
      ierr = 0,                                  // интегральная погрешность
      dt = 0,                                    // время между измерениями
      op = 0,                                    // выход ПИД контроллера
      spdhw = VAL1;                              // точка отсчета температуры горячей воды контура ГВС
unsigned long ts = 0, new_ts = 0;                // отметки времени
unsigned int hex56 = (spdhw * 256 * 16 / 16);    // точка отсчета температуры горячей воды контура ГВС, но в (HEX)
unsigned int data126 = 0x013F;                   // тип и версия термостата (HEX)
bool enableCentralHeating = true;
bool enableHotWater = true;
bool enableCooling = false;

TimingRelay relay(PIN_RELAY, 0, 180000);         // Создаем реле времени (назначенный порт, задержка включения, задержка выключения).

//===============================================================
//               Обрабатываем внешние прерывания
//===============================================================
void handleInterrupt() {
  ot.handleInterrupt();
}

float getTemp() {
  return sensors.getTempCByIndex(0);
}
float getBoilerTemp() {
  return ot.getBoilerTemperature();
}
float getDHWTemp() {
  unsigned long request26 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Tdhw, 0);
  unsigned long respons26 = ot.sendRequest(request26);
  uint16_t dataValue26 = respons26 & 0xFFFF;
  float result26 = dataValue26 / 256;
  return result26;
}
float getOutsideTemp() {
  unsigned long request27 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Toutside, 0);
  unsigned long respons27 = ot.sendRequest(request27);
  uint16_t dataValue27 = respons27 & 0xFFFF;
  if (dataValue27 > 32768) {
    //negative
    float result27 = -(65536 - dataValue27) / 256;
    return result27;
  } else {
    //positive
    float result27 = dataValue27 / 256;
    return result27;
  }
}
int getADC() {
  delay(100);
  int ar = analogRead(A0);
  ar = map(ar, 0, 1023, 0, 100);
  return ar;
}
unsigned long getFault() {
  unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
  OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
  if (responseStatus = OpenThermResponseStatus::SUCCESS)
    return ot.isFault(response);
}
unsigned long getCentralHeatingEnabled() {
  unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
  OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
  if (responseStatus = OpenThermResponseStatus::SUCCESS)
    return ot.isCentralHeatingEnabled(response);
}
unsigned long getHotWaterEnabled() {
  unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
  OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
  if (responseStatus = OpenThermResponseStatus::SUCCESS)
    return ot.isHotWaterEnabled(response);
}
unsigned long getFlameOn() {
  unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
  OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
  if (responseStatus = OpenThermResponseStatus::SUCCESS)
    return ot.isFlameOn(response);
}
unsigned int getSlaveVersion_type() {
  unsigned long request127 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::SlaveVersion, 0);
  unsigned long respons127 = ot.sendRequest(request127);
  uint16_t dataValue127 = respons127 & 0xFFFF;
  unsigned result127 = dataValue127 / 256;
  return result127;
}
unsigned int getSlaveVersion_num() {
  unsigned long request127 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::SlaveVersion, 0);
  unsigned long respons127 = ot.sendRequest(request127);
  uint8_t dataValue127_ = respons127 & 0xFF;
  unsigned result127_ = dataValue127_;
  return result127_;
}
unsigned int setMasterVersion_type() {
  unsigned long request126 = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::MasterVersion, data126);
  unsigned long respons126 = ot.sendRequest(request126);
  uint16_t dataValue126 = respons126 & 0xFFFF;
  unsigned result126 = dataValue126 / 256;
  return result126;
}
unsigned int setMasterVersion_num() {
  unsigned long request126 = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::MasterVersion, data126);
  unsigned long respons126 = ot.sendRequest(request126);
  uint8_t dataValue126_ = respons126 & 0xFF;
  unsigned result126_ = dataValue126_;
  return result126_;
}
float setDHWTemp() {
  unsigned long request56 = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::TdhwSet, hex56);
  unsigned long respons56 = ot.sendRequest(request56);
  uint16_t dataValue56 = respons56 & 0xFFFF;
  float result56 = dataValue56 / 256;
  return result56;
}
unsigned int getFaultCode() {
  unsigned long request5 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::ASFflags, 0);
  unsigned long respons5 = ot.sendRequest(request5);
  uint8_t dataValue5 = respons5 & 0xFF;
  unsigned result5 = dataValue5;
  return result5;
}
//===============================================================
//              Вычисляем коэффициенты ПИД регулятора
//===============================================================
float pid(float sp, float pv, float pv_last, float& ierr, float dt) {
  float Kc = 10.0; // K / %Heater
  float tauI = 50.0; // sec
  float tauD = 1.0;  // sec
  // ПИД коэффициенты
  float KP = Kc;
  float KI = Kc / tauI;
  float KD = Kc * tauD;
  // верхняя и нижняя границы уровня нагрева
  float ophi = 100;
  float oplo = 0;
  // вычислить ошибку
  float error = sp - pv;
  // calculate the integral error
  ierr = ierr + KI * error * dt;
  // вычислить производную измерения
  float dpv = (pv - pv_last) / dt;
  // рассчитать выход ПИД регулятора
  float P = KP * error;                      // пропорциональная составляющая
  float I = ierr;                            // интегральная составляющая
  float D = -KD * dpv;                       // дифференциальная составляющая
  float op = P + I + D;
  // защита от сброса
  if ((op < oplo) || (op > ophi)) {
    I = I - KI * error * dt;
    // выход регулятора, он же уставка для ID-1 (температура теплоносителя контура СО котла)
    op = max(oplo, min(ophi, op));
  }
  ierr = I;
  Serial.println("Заданное значение температуры в помещении = " + String(sp) + " °C");
  Serial.println("Текущее значение температуры в помещении = " + String(pv) + " °C");
  Serial.println("Выхов ПИД регулятора = " + String(op));
  Serial.println("Время между измерениями = " + String(dt) + "; ПИД коэффициенты: П = " + String(P) + "; И = " + String(I) + "; Д = " + String(D));
  return op;
}
//===============================================================
// Эта процедура выполняется при открытии IP-адреса в браузере
//===============================================================
void handleRoot() {
  digitalWrite(BUILTIN_LED, 1);
  if (server.method() == HTTP_POST) {
    for (uint8_t i = 0; i < server.args(); i++) {
      if (server.argName(i) == "sp") {
        sp = server.arg(i).toFloat();
      }
      if (server.argName(i) == "spdhw") {
        spdhw = server.arg(i).toFloat();
      }
    }
  }
  String page = FPSTR(HTTP_HTML);
  page.replace("{0}", String(getTemp()));
  page.replace("{1}", String((float)sp));
  page.replace("{2}", String(getBoilerTemp()));
  page.replace("{3}", String(getDHWTemp()));
  page.replace("{4}", String(getOutsideTemp()));
  page.replace("{5}", String(getCentralHeatingEnabled() ? "on" : "off"));
  page.replace("{6}", String(getHotWaterEnabled() ? "on" : "off"));
  page.replace("{7}", String(getFlameOn() ? "on" : "off"));
  page.replace("{8}", String(getADC()));
  page.replace("{9}", String((float)spdhw));
  server.send(200, "text/html", page);
  digitalWrite(BUILTIN_LED, 0);
}
void handleGetTemp() {
  digitalWrite(BUILTIN_LED, 1);
  server.send(200, "text/plain", String(getTemp()));
  digitalWrite(BUILTIN_LED, 0);
}
void handleGetBoilerTemp() {
  digitalWrite(BUILTIN_LED, 1);
  server.send(200, "text/plain", String(getBoilerTemp()));
  digitalWrite(BUILTIN_LED, 0);
}
void handleGetDHWTemp() {
  digitalWrite(BUILTIN_LED, 1);
  server.send(200, "text/plain", String(getDHWTemp()));
  digitalWrite(BUILTIN_LED, 0);
}
void handleGetOutsideTemp() {
  digitalWrite(BUILTIN_LED, 1);
  server.send(200, "text/plain", String(getOutsideTemp()));
  digitalWrite(BUILTIN_LED, 0);
}
void handleGetCentralHeatingEnabled() {
  digitalWrite(BUILTIN_LED, 1);
  server.send(200, "text/plain", String(getCentralHeatingEnabled() ? "on" : "off"));
  digitalWrite(BUILTIN_LED, 0);
}
void handleGetHotWaterEnabled() {
  digitalWrite(BUILTIN_LED, 1);
  server.send(200, "text/plain", String(getHotWaterEnabled() ? "on" : "off"));
  digitalWrite(BUILTIN_LED, 0);
}
void handleGetFlameOn() {
  digitalWrite(BUILTIN_LED, 1);
  server.send(200, "text/plain", String(getFlameOn() ? "on" : "off"));
  digitalWrite(BUILTIN_LED, 0);
}
void handleADC() {
  digitalWrite(BUILTIN_LED, 1);
  server.send(200, "text/plain", String(getADC()));
  digitalWrite(BUILTIN_LED, 0);
}
//==============================================================
//                  Подключаемся к сети WiFi
//==============================================================
void setup_wifi() {
  delay(10);
  // подключение к сети Wi-Fi
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);                // Подключение к маршрутизатору Wi-Fi
  // дождаться соединения
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // если соединение успешно, показывает IP-адрес в мониторе
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}
//==============================================================
//                  Функция SETUP
//==============================================================
void setup() {
  pinMode(BUILTIN_LED, OUTPUT);
  pinMode(EXTERNAL_LED, OUTPUT);
  pinMode(EXTERNAL_LED_1, OUTPUT);
  pinMode(EXTERNAL_LED_2, OUTPUT);
  pinMode(PIN_RELAY, OUTPUT);
  digitalWrite(BUILTIN_LED, 0);
  digitalWrite(EXTERNAL_LED, 0);
  digitalWrite(EXTERNAL_LED_1, 0);
  digitalWrite(EXTERNAL_LED_2, 0);
  relay.autoReset = true;                             // Автоматический запуск таймера задержки выключения.
  Serial.begin(115200);
  setup_wifi();
  if (MDNS.begin("thermostat")) {
    Serial.println("MDNS responder started");
  }
  // инициализация web сервера
  server.on("/", handleRoot);                         // Ответ сервера на запрос главной страницы
  server.on("/temp", handleGetTemp);                  // Ответ сервера на запрос температуры в помещении
  server.on("/boilertemp", handleGetBoilerTemp);      // Ответ сервера на запрос температуры подачи котла
  server.on("/dhwttemp", handleGetDHWTemp);           // Ответ сервера на запрос температуры горячей воды
  server.on("/outsidetemp", handleGetOutsideTemp);    // Ответ сервера на запрос уличной температуры
  server.on("/chon", handleGetCentralHeatingEnabled); // Ответ сервера на запрос состояния СО
  server.on("/hwon", handleGetHotWaterEnabled);       // Ответ сервера на запрос состояния ГВС
  server.on("/flameon", handleGetFlameOn);            // Ответ сервера на запрос состояния горелки
  server.on("/getadc", handleADC);                    // Ответ сервера на запрос уровня модуляции горелки
  server.begin();
  Serial.println("HTTP server started");

  //==============================================================
  //          Инициализация датчика температуры DS18B20
  //==============================================================
  sensors.begin();
  sensors.requestTemperatures();
  sensors.setWaitForConversion(false);                // Переключиться в асинхронный режим
  pv, pv_last = sensors.getTempCByIndex(0);
  ts = millis();
  //==============================================================
  //          Инициализация OpenTherm
  //==============================================================
  ot.begin(handleInterrupt);
  //==============================================================
  //          Инициализация MQTT клиента
  //==============================================================
  client.setServer(mqtt_server, mqtt_port);   // подключаемся к MQTT
  client.setCallback(callback);               // функция получения топиков с брокера
}
void publish_temperature() {
  Serial.println("MQTT, Current Room Temperature, °C = " + String(pv));
  String(pv).toCharArray(buf, 50);
  client.publish("pv", buf);
}
void publish_boilertemp() {
  Serial.println("MQTT, CH Temperature, °C = " + String(getBoilerTemp()));
  String(getBoilerTemp()).toCharArray(buf, 50);
  client.publish("cht", buf);
}
void publish_dhwtemp() {
  Serial.println("MQTT, DHW Temperature, °C = " + String(getDHWTemp()));
  String(getDHWTemp()).toCharArray(buf, 50);
  client.publish("dhwt", buf);
}
void publish_outtemp() {
  Serial.println("MQTT, Outside Temperature, °C = " + String(getOutsideTemp()));
  String(getOutsideTemp()).toCharArray(buf, 50);
  client.publish("outt", buf);
}
void publish_statusCH() {
  Serial.println("MQTT, Status Central Heating = " + String(getCentralHeatingEnabled()));
  String(getCentralHeatingEnabled()).toCharArray(buf, 50);
  client.publish("sch", buf);
}
void publish_ADC() {
  Serial.println("MQTT, Relative Modulation Level, % = " + String(getADC()));
  String(getADC()).toCharArray(buf, 50);
  client.publish("adc", buf);
}
void publish_statusDWH() {
  Serial.println("MQTT, Status Hot Water = " + String(getHotWaterEnabled()));
  String(getHotWaterEnabled()).toCharArray(buf, 50);
  client.publish("sdwh", buf);
}
void publish_statusFlame() {
  Serial.println("MQTT, Status Central Heating = " + String(getFlameOn()));
  String(getFlameOn()).toCharArray(buf, 50);
  client.publish("sfl", buf);
}
void publish_setBoilerTemperature() {
  Serial.println("MQTT, PID Controller Output, % = Setpoint CH Temperature, °C = " + String(op));
  String(op).toCharArray(buf, 50);
  client.publish("op", buf);
}
// функция обратного вызова, чтение топиков
void callback(char* topic, byte* payload, unsigned int length) {
  char buffer[length + 1];
  memcpy(buffer, payload, length);
  buffer[length] = '\0';
  float a = atof((char*)buffer);
  if (a <= 29) {
    sp = a;
  } else {
    spdhw = a;
  }
  Serial.println("MQTT,topic = " + String(a));
}
void reconnect() {
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("ESP8266Client", mqtt_user, mqtt_password)) {
      Serial.println("connected");
      // после подключения публикуем объявление...
      publish_temperature();
      publish_boilertemp();
      publish_dhwtemp();
      publish_outtemp();
      publish_ADC();
      publish_statusCH();
      publish_statusDWH();
      publish_statusFlame();
      publish_setBoilerTemperature();
      // ... и перезаписываем
      client.subscribe("sp");
      client.subscribe("spdhw");
    } else {
      Serial.print("failed, rc =");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Подождать 5 сек. перед повторной попыткой
      delay(5000);
    }
  }
}
//==============================================================
//                     Функция LOOP
//==============================================================
void loop() {
  new_ts = millis();
  if (new_ts - ts > 1000) {
    unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
    OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
    if (responseStatus = OpenThermResponseStatus::SUCCESS) {
      //Serial.println("Error: Invalid boiler response " + String(response, HEX));
    }
    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");
    }
    pv = sensors.getTempCByIndex(0);
    dt = (new_ts - ts) / 1000.0;
    ts = new_ts;
    if (responseStatus == OpenThermResponseStatus::SUCCESS) {
      op = pid(sp, pv, pv_last, ierr, dt);
      // Заданная температура СО
      ot.setBoilerTemperature(op);
      // Заданная температура ГВС
      hex56 = (spdhw * 256 * 16 / 16);
      unsigned int setDHWTemp(hex56);
      pv_last = pv;
      sensors.requestTemperatures();                     //Асинхронный запрос температуры
    }
    // Записать ID-2; мастер-код MemberID
    unsigned int data = 0x0004;
    unsigned long request = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::MConfigMMemberIDcode, data);
    ot.sendRequest(request);   
   // unsigned long request5 = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::ASFflags, 0);
   // unsigned long respons5 = ot.sendRequest(request5);
   // uint8_t dataValue5 = respons5 & 0xFF;
   // unsigned result5 = dataValue5;
    // Выводим в монитор
    Serial.println("Текущий статус системы отопления: " + String(ot.isCentralHeatingEnabled(response) ? "on" : "off"));
    Serial.println("Текущий статус горячей воды: " + String(ot.isHotWaterEnabled(response) ? "on" : "off"));
    Serial.println("Текущий статус горелки: " + String(ot.isFlameOn(response) ? "on" : "off"));
    Serial.println("Индикация состояния неисправности: " + String(ot.isFault(response) ? "fault" : "no fault"));
    Serial.println("Диагностическая индикация: " + String(ot.isDiagnostic(response) ? "diagnostics" : "no diagnostics"));
    Serial.println("Текущая температура контура СО = " + String(getBoilerTemp()) + " °C");
    Serial.println("Текущая температура контура ГВС = " + String(getDHWTemp()) + " °C");
    Serial.println("Температура на улице = " + String(getOutsideTemp()) + " °C");
    Serial.println("Текуший уровень модуляции горелки  = " + String (getADC()) + " %");
    Serial.println("ADC = " + String (analogRead(A0)));
    Serial.println("Уставка температуры ГВС = " + String(setDHWTemp()) + " °C");
    Serial.println("Тип и версия термостата: тип " + String(setMasterVersion_type()) + ", версия " + String(setMasterVersion_num()));
    Serial.println("Тип и версия котла: тип " + String(getSlaveVersion_type()) + ", версия " + String(getSlaveVersion_num()));
  //=================================================================================================
  //    Индикация и реле насоса СО c откл. с задержкой 3 мин., индикация работы ГВС, горелки
  //=================================================================================================
  int status_error = getFault();                          // индикация состояния авария.
  if (status_error == 1) {
    Serial.println("Код неисправности: E " + String(getFaultCode()));
    digitalWrite(EXTERNAL_LED_2, 1);
  } else {
    digitalWrite(EXTERNAL_LED_2, 0);
  }
  int status_dhw = getHotWaterEnabled();                  // индикация состояния ГВС.
  if (status_dhw == 1) {
    digitalWrite(EXTERNAL_LED, 1);
  } else {
    digitalWrite(EXTERNAL_LED, 0);
  }
  int status_flame = getFlameOn();                        // индикация состояния горелки.
  if (status_flame == 1) {
    digitalWrite(EXTERNAL_LED_1, 1);
  } else {
    digitalWrite(EXTERNAL_LED_1, 0);
  }
  if (status_flame == 1 && status_dhw == 0) {             // Если состояния горелки = 1 и статус состояния ГВС = 0 включаем реле.
    relay.on();
  } else {
    relay.off();
  }
  relay.loop();                                           // Обрабатываем (обязательно).
    publish_temperature();
    publish_boilertemp();
    publish_dhwtemp();
    publish_outtemp();
    publish_ADC();
    publish_statusCH();
    publish_statusDWH();
    publish_statusFlame();
    publish_setBoilerTemperature();
    //publish_setDHWTemperature();
  }
  // MQTT Loop
  if (!client.connected()) {
    reconnect();
  }
  client.loop();
  server.handleClient();     // обработка http-запросов
  //=================================================================================================
  //             Активации функции Антилегионелла ГВС 65°C на 1 час каждые 7 дней по кругу.
  //=================================================================================================
  static bool state;
  static uint32_t old_millis = millis();
  if (state == false) {
    if (millis() - old_millis >= TIME1) {
      old_millis = millis();
      spdhw = VAL1;
      state = true;
    }
  } else if (millis() - old_millis >= TIME2) {
    old_millis = millis();
    spdhw = VAL2;
    state = false;
  }
}
tsv_33
Offline
Зарегистрирован: 11.04.2019

P.S.

Так и есть, атрибут ICACHE_RAM_ATTR теперь обязателен в коде, функциях, вызываемых по прерываниям. Проблема работы с 2.5.2 разрешилась.

VOVA_iS
Offline
Зарегистрирован: 09.07.2019

Проблему с помпой решил немного по другому

в секцию loop добавил 

if (pv >= sp) {
   enableCentralHeating = false;
   } else {
   enableCentralHeating = true;  } 

После добавления этого кода котёл по достижению заданной температуры выключает режим отопления и соответсвенно насос, через заданное время в настройке котла

Проблема с горячей водой так и осталось. Регулирование нету

даже в логах пишется что заданная температура 0

 

но если изменить код вот так

float setDHWTemp() {
 unsigned long request56 = ot.buildRequest(OpenThermRequestType::WRITE_ACK, OpenThermMessageID::TdhwSet, hex56);
 unsigned long respons56 = ot.sendRequest(request56);
 uint16_t dataValue56 = respons56 & 0xFFFF;
 float result56 = dataValue56 / 256;
 return result56;

То заданная температура отображается(температура гвс установлена до подключение платы), но не устанавливается

tsv_33
Offline
Зарегистрирован: 11.04.2019

VOVA_iS, с помпой понятно, а я то начал городить огород с доп. реле, простенько, сам бы не догадался...

Пишет 0, но только когда котёл не подключен к термостату, когда же котёл представится термостату и тот ответит ему, то всё ОК.

P.S.

Вообще библиотека ОТ неплохая, но попытка скрестить ужа и ежа провалилась, я про web и mqtt в одном флаконе. Жуткие тормоза при совместной работе двух библиотек, причём только web. Приглядываюсь к такому решению https://www.youtube.com/watch?v=QQ7apgKBgNM

VOVA_iS
Offline
Зарегистрирован: 09.07.2019

tsv_33 пишет:

VOVA_iS, с помпой понятно, а я то начал городить огород с доп. реле, простенько, сам бы не догадался...

Пишет 0, но только когда котёл не подключен к термостату, когда же котёл представится термостату и тот ответит ему, то всё ОК.

Котёл с термостатом представились друг другу.

А проблема что все данные с котла приходят кроме установленной температуры ГВС 

да не нужен этот web

mqtt более чем достаточно

VOVA_iS
Offline
Зарегистрирован: 09.07.2019

tsv_33 пишет:

VOVA_iS, с помпой понятно, а я то начал городить огород с доп. реле, простенько, сам бы не догадался...

Пишет 0, но только когда котёл не подключен к термостату, когда же котёл представится термостату и тот ответит ему, то всё ОК.

Котёл с термостатом представились друг другу.

А проблема что все данные с котла приходят кроме установленной температуры ГВС 

да не нужен этот web

mqtt более чем достаточно

tsv_33
Offline
Зарегистрирован: 11.04.2019

А в mqtt брокере переменная spdhw видна?

VOVA_iS
Offline
Зарегистрирован: 09.07.2019

но если изменить код вот так

1 float setDHWTemp() {
2  unsigned long request56 = ot.buildRequest(OpenThermRequestType::WRITE_ACK, OpenThermMessageID::TdhwSet, hex56);
3  unsigned long respons56 = ot.sendRequest(request56);
4  uint16_t dataValue56 = respons56 & 0xFFFF;
5  float result56 = dataValue56 / 256;
6

 return result56;

 

Тогда видна

tsv_33
Offline
Зарегистрирован: 11.04.2019

В протоколе ОТ ID Msg 56 имеет | R  W | DHW setpoint | f8.8 | 0..127 | Domestic hot water temperature setpoint (°C) 

Можно прочитать, что записалось и вывести в монитор, отдельно я этого не делал, просто проверял ранее и всё было ОК. Точно не помню, как переменную обзывал...

float getDHWTemp() {
  unsigned long request56 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::TdhwSet, hex56);
  unsigned long respons56 = ot.sendRequest(request56);
  uint16_t dataValue56 = respons56 & 0xFFFF;
  float result56 = dataValue56 / 256;
  return result56;
}

 

tsv_33
Offline
Зарегистрирован: 11.04.2019

VOVA_iS, а как определили, что в котёл не ушла уставка?

tsv_33
Offline
Зарегистрирован: 11.04.2019

 

if (pv >= sp) {
   enableCentralHeating = false;
   } else {
   enableCentralHeating = true;  } 

С этим кодом работы помпы ОТ отключается...т.е. интерфейс активируется только при этом условии, а если ОТ не активен, то целевую температуру контур ГВС не примет, стало быть, котёл работает в этом промежутке, в обычном режиме, ON/OFF (перемычка). Хотя, могу и ошибаться, погонять надо...

VOVA_iS
Offline
Зарегистрирован: 09.07.2019

tsv_33 пишет:

VOVA_iS, а как определили, что в котёл не ушла уставка?

 

Банально включаю воду и смотрю на сколько нагрел котел. Было 38 ставлю 45. Все равно греет до 38. 

VOVA_iS
Offline
Зарегистрирован: 09.07.2019

tsv_33 пишет:

 

if (pv >= sp) {
   enableCentralHeating = false;
   } else {
   enableCentralHeating = true;  } 

С этим кодом работы помпы ОТ отключается...т.е. интерфейс активируется только при этом условии, а если ОТ не активен, то целевую температуру контур ГВС не примет, стало быть, котёл работает в этом промежутке, в обычном режиме, ON/OFF (перемычка). Хотя, могу и ошибаться, погонять надо...

Это решение было найдено после проблемы с регулировкой температуры.

С параметром READ и WRITE_ACK установленную температуру читает.

 

Вообщем добавил свою переменную в MQTT

void publish_setDHWTemp() {
  Serial.println("MQTT, DHW Temperature Set, °C = " + String(setDHWTemp()));
  String(setDHWTemp()).toCharArray(buf, 50);
  client.publish("dhws", buf);

Она почему-то в консоли появляется один раз.

 

Лог с WRITE

Connecting to HomeN16
....
WiFi connected
IP address: 
192.168.10.110
MDNS responder started
Attempting MQTT connection...connected
MQTT, DHW Temperature Set, °C = 0.00
MQTT, Current Room Temperature, °C = 0.00
MQTT, CH Temperature, °C = 28.00
MQTT, DHW Temperature, °C = 28.00
MQTT, Outside Temperature, °C = 0.00
MQTT, Relative Modulation Level, % = 0
MQTT, Status Central Heating = 1
MQTT, Status Hot Water = 0
MQTT, Status Central Heating = 0
MQTT, PID Controller Output, % = Setpoint CH Temperature, °C = 0.00
Заданное значение температуры в помещении = 24.50 °C
Текущее значение температуры в помещении = 28.44 °C
Выхов ПИД регулятора = 45.00
Время между измерениями = 3.88; ПИД коэффициенты: П = -39.38; И = 0.00; Д = 0.00
Текущий статус системы отопления: on
Текущий статус горячей воды: off
Текущий статус горелки: off
Индикация состояния неисправности: no fault
Диагностическая индикация: no diagnostics
Текущая температура контура СО = 28.00 °C
Текущая температура контура ГВС = 28.00 °C
Температура на улице = 0.00 °C
Текуший уровень модуляции горелки  = 0 %
ADC = 3
Установка температуры ГВС = 0.00 °C
Тип и версия термостата: тип 0, версия 0
Тип и версия котла: тип 0, версия 0
MQTT, Current Room Temperature, °C = 28.44
MQTT, CH Temperature, °C = 28.00
MQTT, DHW Temperature, °C = 28.00
MQTT, Outside Temperature, °C = 0.00
MQTT, Relative Modulation Level, % = 0
MQTT, Status Central Heating = 0
MQTT, Status Hot Water = 0
MQTT, Status Central Heating = 0
MQTT, PID Controller Output, % = Setpoint CH Temperature, °C = 45.00
Заданное значение температуры в помещении = 24.50 °C
Текущее значение температуры в помещении = 28.44 °C
Выхов ПИД регулятора = 45.00
Время между измерениями = 5.32; ПИД коэффициенты: П = -39.38; И = 0.00; Д = 0.00
Текущий статус системы отопления: off
Текущий статус горячей воды: off
Текущий статус горелки: off
Индикация состояния неисправности: no fault
Диагностическая индикация: no diagnostics
Текущая температура контура СО = 28.00 °C
Текущая температура контура ГВС = 28.00 °C
Температура на улице = 0.00 °C
Текуший уровень модуляции горелки  = 0 %
ADC = 10
Установка температуры ГВС = 0.00 °C
Тип и версия термостата: тип 0, версия 0
Тип и версия котла: тип 0, версия 0
MQTT, Current Room Temperature, °C = 28.44
MQTT, CH Temperature, °C = 28.00
MQTT, DHW Temperature, °C = 28.00
MQTT, Outside Temperature, °C = 0.00
MQTT, Relative Modulation Level, % = 0
MQTT, Status Central Heating = 0
MQTT, Status Hot Water = 0
MQTT, Status Central Heating = 0
MQTT, PID Controller Output, % = Setpoint CH Temperature, °C = 45.00
MQTT,topic = 45.80
 
 
Лог с READ
 
Connecting to HomeN16
....
WiFi connected
IP address: 
192.168.10.110
MDNS responder started
Attempting MQTT connection...connected
MQTT, DHW Temperature Set, °C = 40.00
MQTT, Current Room Temperature, °C = 0.00
MQTT, CH Temperature, °C = 29.00
MQTT, DHW Temperature, °C = 28.00
MQTT, Outside Temperature, °C = 0.00
MQTT, Relative Modulation Level, % = 0
MQTT, Status Central Heating = 1
MQTT, Status Hot Water = 0
MQTT, Status Central Heating = 0
MQTT, PID Controller Output, % = Setpoint CH Temperature, °C = 0.00
Заданное значение температуры в помещении = 24.50 °C
Текущее значение температуры в помещении = 28.38 °C
Выхов ПИД регулятора = 45.00
Время между измерениями = 3.88; ПИД коэффициенты: П = -38.75; И = 0.00; Д = 0.00
Текущий статус системы отопления: on
Текущий статус горячей воды: off
Текущий статус горелки: off
Индикация состояния неисправности: no fault
Диагностическая индикация: no diagnostics
Текущая температура контура СО = 28.00 °C
Текущая температура контура ГВС = 29.00 °C
Температура на улице = 0.00 °C
Текуший уровень модуляции горелки  = 0 %
ADC = 3
Установка температуры ГВС = 40.00 °C
Тип и версия термостата: тип 0, версия 0
Тип и версия котла: тип 0, версия 0
MQTT, Current Room Temperature, °C = 28.38
MQTT, CH Temperature, °C = 29.00
MQTT, DHW Temperature, °C = 29.00
MQTT, Outside Temperature, °C = 0.00
MQTT, Relative Modulation Level, % = 0
MQTT, Status Central Heating = 0
MQTT, Status Hot Water = 0
MQTT, Status Central Heating = 0
MQTT, PID Controller Output, % = Setpoint CH Temperature, °C = 45.00
Заданное значение температуры в помещении = 24.50 °C
Текущее значение температуры в помещении = 28.38 °C
Выхов ПИД регулятора = 45.00
Время между измерениями = 5.32; ПИД коэффициенты: П = -38.75; И = 0.00; Д = 0.00
Текущий статус системы отопления: off
Текущий статус горячей воды: off
Текущий статус горелки: off
Индикация состояния неисправности: no fault
Диагностическая индикация: no diagnostics
Текущая температура контура СО = 29.00 °C
Текущая температура контура ГВС = 29.00 °C
Температура на улице = 0.00 °C
Текуший уровень модуляции горелки  = 0 %
ADC = 3
Установка температуры ГВС = 40.00 °C
Тип и версия термостата: тип 0, версия 0
Тип и версия котла: тип 0, версия 0
MQTT, Current Room Temperature, °C = 28.38
MQTT, CH Temperature, °C = 29.00
MQTT, DHW Temperature, °C = 28.00
MQTT, Outside Temperature, °C = 0.00
MQTT, Relative Modulation Level, % = 0
MQTT, Status Central Heating = 0
MQTT, Status Hot Water = 0
MQTT, Status Central Heating = 0
MQTT, PID Controller Output, % = Setpoint CH Temperature, °C = 45.00
MQTT,topic = 45.80
Заданное значение температуры в помещении = 24.50 °C
Текущее значение температуры в помещении = 28.38 °C
Выхов ПИД регулятора = 45.00
Время между измерениями = 5.32; ПИД коэффициенты: П = -38.75; И = 0.00; Д = 0.00
Текущий статус системы отопления: off
Текущий статус горячей воды: off
Текущий статус горелки: off
Индикация состояния неисправности: no fault
Диагностическая индикация: no diagnostics
Текущая температура контура СО = 29.00 °C
Текущая температура контура ГВС = 29.00 °C
Температура на улице = 0.00 °C
Текуший уровень модуляции горелки  = 0 %
ADC = 3
Установка температуры ГВС = 40.00 °C
Тип и версия термостата: тип 0, версия 0
Тип и версия котла: тип 0, версия 0
MQTT, Current Room Temperature, °C = 28.38
MQTT, CH Temperature, °C = 28.00
MQTT, DHW Temperature, °C = 29.00
MQTT, Outside Temperature, °C = 0.00
MQTT, Relative Modulation Level, % = 0
MQTT, Status Central Heating = 0
MQTT, Status Hot Water = 0
MQTT, Status Central Heating = 0
MQTT, PID Controller Output, % = Setpoint CH Temperature, °C = 45.00
 
 
tsv_33
Offline
Зарегистрирован: 11.04.2019

VOVA_iS пишет:

tsv_33 пишет:

VOVA_iS, а как определили, что в котёл не ушла уставка?

 

Банально включаю воду и смотрю на сколько нагрел котел. Было 38 ставлю 45. Все равно греет до 38. 

Гистерезис большой, не зацикливаюсь. Я сейчас откопал свой рабочий код без WEB, внёс в него вашу правку помпы, проверил работает. Только обновите библиотеку ОТ. Там слово Enable заменено на Active (код правленный).

/*
  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
*/

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

//Входные и выходные контакты OpenTherm, подключены к 4 и 5 контактам платы
const int inPin = 4;            //D2
const int outPin = 5;           //D1

#define ONE_WIRE_BUS 14         // D5 Data wire is connected to 14 pin on the OpenTherm Shield
#define BUILTIN_LED 2           // D4 Встроенный LED
#define EXTERNAL_LED_3 12       // D6 На внешний LED индикации работы СО
#define EXTERNAL_LED 13         // D7 На внешний LED индикации работы ГВС
#define EXTERNAL_LED_1 15       // D8 На внешний LED индикации работы горелки
#define EXTERNAL_LED_2 0        // D3 На внешний LED индикации работы авария

//#define DAY(x)            ((x) * 10000ul) // тест
#define DAY(x)            ((x) * 24 * MIN(60))
#define MIN(x)            ((x) * 10000ul)
#define TIME1             DAY(7)
#define TIME2             MIN(60)
#define VAL1              50.
#define VAL2              60.

const char* ssid = "";
const char* password = "";
const char* mqtt_server = "";
const int   mqtt_port = 12345;
const char* mqtt_user = "";
const char* mqtt_password = "";

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
OpenTherm ot(inPin, outPin);
WiFiClient espClient;
PubSubClient client(espClient);
char buf[50];

float sp = 20,                                   // точка отсчета комнатной температуры
      pv = 0,                                    // текущая температура в комнате
      pv_last = 0,                               // предыдущая температура
      ierr = 0,                                  // интегральная погрешность
      dt = 0,                                    // время между измерениями
      op = 0,                                    // выход ПИД контроллера
      spdhw = VAL1;                              // точка отсчета температуры горячей воды контура ГВС
unsigned long ts = 0, new_ts = 0;                // отметки времени
unsigned int hex56 = (spdhw * 256 * 16 / 16);    // точка отсчета температуры горячей воды контура ГВС, но в (HEX)
unsigned int data126 = 0x013F;                   // тип и версия термостата (HEX)
bool enableCentralHeating = true;
bool enableHotWater = true;
bool enableCooling = false;

//===============================================================
//               Обрабатываем внешние прерывания
//===============================================================
void handleInterrupt() {
  ot.handleInterrupt();
}

float getTemp() {
  return sensors.getTempCByIndex(0);
}
float getBoilerTemp() {
  return ot.getBoilerTemperature();
}
float getDHWTemp() {
  unsigned long request26 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Tdhw, 0);
  unsigned long respons26 = ot.sendRequest(request26);
  uint16_t dataValue26 = respons26 & 0xFFFF;
  float result26 = dataValue26 / 256;
  return result26;
}
float getOutsideTemp() {
  unsigned long request27 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Toutside, 0);
  unsigned long respons27 = ot.sendRequest(request27);
  uint16_t dataValue27 = respons27 & 0xFFFF;
  if (dataValue27 > 32768) {
    //negative
    float result27 = -(65536 - dataValue27) / 256;
    return result27;
  } else {
    //positive
    float result27 = dataValue27 / 256;
    return result27;
  }
}
int getADC() {
  delay(100);
  int ar = analogRead(A0);
  ar = map(ar, 0, 1023, 0, 100);
  return ar;
}
unsigned long getFault() {
  unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
  OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
  if (responseStatus = OpenThermResponseStatus::SUCCESS)
    return ot.isFault(response);
}
unsigned long getCentralHeatingEnabled() {
  unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
  OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
  if (responseStatus = OpenThermResponseStatus::SUCCESS)
    return ot.isCentralHeatingActive(response);
}
unsigned long getHotWaterEnabled() {
  unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
  OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
  if (responseStatus = OpenThermResponseStatus::SUCCESS)
    return ot.isHotWaterActive(response);
}
unsigned long getFlameOn() {
  unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
  OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
  if (responseStatus = OpenThermResponseStatus::SUCCESS)
    return ot.isFlameOn(response);
}
unsigned int getSlaveVersion_type() {
  unsigned long request127 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::SlaveVersion, 0);
  unsigned long respons127 = ot.sendRequest(request127);
  uint16_t dataValue127 = respons127 & 0xFFFF;
  unsigned result127 = dataValue127 / 256;
  return result127;
}
unsigned int getSlaveVersion_num() {
  unsigned long request127 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::SlaveVersion, 0);
  unsigned long respons127 = ot.sendRequest(request127);
  uint8_t dataValue127_ = respons127 & 0xFF;
  unsigned result127_ = dataValue127_;
  return result127_;
}
unsigned int setMasterVersion_type() {
  unsigned long request126 = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::MasterVersion, data126);
  unsigned long respons126 = ot.sendRequest(request126);
  uint16_t dataValue126 = respons126 & 0xFFFF;
  unsigned result126 = dataValue126 / 256;
  return result126;
}
unsigned int setMasterVersion_num() {
  unsigned long request126 = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::MasterVersion, data126);
  unsigned long respons126 = ot.sendRequest(request126);
  uint8_t dataValue126_ = respons126 & 0xFF;
  unsigned result126_ = dataValue126_;
  return result126_;
}
float setDHWTemp() {
  unsigned long request56 = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::TdhwSet, hex56);
  unsigned long respons56 = ot.sendRequest(request56);
  uint16_t dataValue56 = respons56 & 0xFFFF;
  float result56 = dataValue56 / 256;
  return result56;
}
unsigned int getFaultCode() {
  unsigned long request5 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::ASFflags, 0);
  unsigned long respons5 = ot.sendRequest(request5);
  uint8_t dataValue5 = respons5 & 0xFF;
  unsigned result5 = dataValue5;
  return result5;
}
//===============================================================
//              Вычисляем коэффициенты ПИД регулятора
//===============================================================
float pid(float sp, float pv, float pv_last, float& ierr, float dt) {
  float Kc = 10.0; // K / %Heater
  float tauI = 50.0; // sec
  float tauD = 1.0;  // sec
  // ПИД коэффициенты
  float KP = Kc;
  float KI = Kc / tauI;
  float KD = Kc * tauD;
  // верхняя и нижняя границы уровня нагрева
  float ophi = 100;
  float oplo = 0;
  // вычислить ошибку
  float error = sp - pv;
  // calculate the integral error
  ierr = ierr + KI * error * dt;
  // вычислить производную измерения
  float dpv = (pv - pv_last) / dt;
  // рассчитать выход ПИД регулятора
  float P = KP * error;                      // пропорциональная составляющая
  float I = ierr;                            // интегральная составляющая
  float D = -KD * dpv;                       // дифференциальная составляющая
  float op = P + I + D;
  // защита от сброса
  if ((op < oplo) || (op > ophi)) {
    I = I - KI * error * dt;
    // выход регулятора, он же уставка для ID-1 (температура теплоносителя контура СО котла)
    op = max(oplo, min(ophi, op));
  }
  ierr = I;
  Serial.println("Заданное значение температуры в помещении = " + String(sp) + " °C");
  Serial.println("Текущее значение температуры в помещении = " + String(pv) + " °C");
  Serial.println("Выхов ПИД регулятора = " + String(op));
  Serial.println("Время между измерениями = " + String(dt) + "; ПИД коэффициенты: П = " + String(P) + "; И = " + String(I) + "; Д = " + String(D));
  return op;
}
//==============================================================
//                  Подключаемся к сети WiFi
//==============================================================
void setup_wifi() {
  delay(10);
  // подключение к сети Wi-Fi
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);                // Подключение к маршрутизатору Wi-Fi
  // дождаться соединения
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // если соединение успешно, показывает IP-адрес в мониторе
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}
//==============================================================
//                  Функция SETUP
//==============================================================

void setup() {
  pinMode(BUILTIN_LED, OUTPUT);
  pinMode(EXTERNAL_LED, OUTPUT);
  pinMode(EXTERNAL_LED_1, OUTPUT);
  pinMode(EXTERNAL_LED_2, OUTPUT);
  pinMode(EXTERNAL_LED_3, OUTPUT);
  digitalWrite(BUILTIN_LED, LOW);
  digitalWrite(EXTERNAL_LED, LOW);
  digitalWrite(EXTERNAL_LED_1, LOW);
  digitalWrite(EXTERNAL_LED_2, LOW);
  digitalWrite(EXTERNAL_LED_3, LOW);
  Serial.begin(115200);
  setup_wifi();
  //==============================================================
  //          Инициализация датчика температуры DS18B20
  //==============================================================
  sensors.begin();
  sensors.requestTemperatures();
  sensors.setWaitForConversion(false);                // Переключиться в асинхронный режим
  pv, pv_last = sensors.getTempCByIndex(0);
  ts = millis();
  //==============================================================
  //          Инициализация OpenTherm
  //==============================================================
  ot.begin(handleInterrupt);
  //==============================================================
  //          Инициализация MQTT клиента
  //==============================================================
  client.setServer(mqtt_server, mqtt_port);   // подключаемся к MQTT
  client.setCallback(callback);               // функция получения топиков с брокера
}
void publish_temperature() {
  Serial.println("MQTT, Current Room Temperature, °C = " + String(pv));
  String(pv).toCharArray(buf, 50);
  client.publish("pv", buf);
}
void publish_boilertemp() {
  Serial.println("MQTT, CH Temperature, °C = " + String(getBoilerTemp()));
  String(getBoilerTemp()).toCharArray(buf, 50);
  client.publish("cht", buf);
}
void publish_dhwtemp() {
  Serial.println("MQTT, DHW Temperature, °C = " + String(getDHWTemp()));
  String(getDHWTemp()).toCharArray(buf, 50);
  client.publish("dhwt", buf);
}
void publish_outtemp() {
  Serial.println("MQTT, Outside Temperature, °C = " + String(getOutsideTemp()));
  String(getOutsideTemp()).toCharArray(buf, 50);
  client.publish("outt", buf);
}
void publish_statusCH() {
  Serial.println("MQTT, Status Central Heating = " + String(getCentralHeatingEnabled()));
  String(getCentralHeatingEnabled()).toCharArray(buf, 50);
  client.publish("sch", buf);
}
void publish_ADC() {
  Serial.println("MQTT, Relative Modulation Level, % = " + String(getADC()));
  String(getADC()).toCharArray(buf, 50);
  client.publish("adc", buf);
}
void publish_statusDWH() {
  Serial.println("MQTT, Status Hot Water = " + String(getHotWaterEnabled()));
  String(getHotWaterEnabled()).toCharArray(buf, 50);
  client.publish("sdwh", buf);
}
void publish_statusFlame() {
  Serial.println("MQTT, Status Central Heating = " + String(getFlameOn()));
  String(getFlameOn()).toCharArray(buf, 50);
  client.publish("sfl", buf);
}
void publish_setBoilerTemperature() {
  Serial.println("MQTT, PID Controller Output, % = Setpoint CH Temperature, °C = " + String(op));
  String(op).toCharArray(buf, 50);
  client.publish("op", buf);
}
// функция обратного вызова, чтение топиков

void callback(char* topic, byte* payload, unsigned int length) {
  char buffer[length + 1];
  memcpy(buffer, payload, length);
  buffer[length] = '\0';
  float a = atof((char*)buffer);
  if (a <= 31) {
    sp = a;
  } else {
    spdhw = a;
  }
  Serial.println("MQTT,topic = " + String(a));
}

void reconnect() {
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("ESP8266Client", mqtt_user, mqtt_password)) {
      Serial.println("connected");

      // после подключения публикуем объявление...

      publish_temperature();
      publish_boilertemp();
      publish_dhwtemp();
      publish_outtemp();
      publish_ADC();
      publish_statusCH();
      publish_statusDWH();
      publish_statusFlame();
      publish_setBoilerTemperature();

      // ... и перезаписываем

      client.subscribe("sp");
      client.subscribe("spdhw");
    } else {
      Serial.print("failed, rc =");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");

      // Подождать 5 сек. перед повторной попыткой

      delay(5000);
    }
  }
}
//==============================================================
//                     Функция LOOP
//==============================================================
void loop() {
  new_ts = millis();
  if (new_ts - ts > 1000) {
   
    unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
    OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
    if (responseStatus = OpenThermResponseStatus::SUCCESS) {
      //Serial.println("Error: Invalid boiler response " + String(response, HEX));
    }
    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");
    }

    pv = sensors.getTempCByIndex(0);
    dt = (new_ts - ts) / 1000.0;
    ts = new_ts;
    if (responseStatus == OpenThermResponseStatus::SUCCESS) {
      op = pid(sp, pv, pv_last, ierr, dt);

      // Заданная температура СО
      ot.setBoilerTemperature(op);

      // Заданная температура ГВС
      hex56 = (spdhw * 256 * 16 / 16);
      unsigned int setDHWTemp(hex56);

      pv_last = pv;
      sensors.requestTemperatures();                     //Асинхронный запрос температуры
    }

    // Записать ID-2; мастер-код MemberID
    unsigned int data = 0x0004;
    unsigned long request = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::MConfigMMemberIDcode, data);
    ot.sendRequest(request);
    
   // unsigned long request5 = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::ASFflags, 0);
   // unsigned long respons5 = ot.sendRequest(request5);
   // uint8_t dataValue5 = respons5 & 0xFF;
  //  unsigned result5 = dataValue5;

    // Выводим в монитор
    Serial.println("Текущий статус системы отопления: " + String(ot.isCentralHeatingActive(response) ? "on" : "off"));
    Serial.println("Текущий статус горячей воды: " + String(ot.isHotWaterActive(response) ? "on" : "off"));
    Serial.println("Текущий статус горелки: " + String(ot.isFlameOn(response) ? "on" : "off"));
    Serial.println("Индикация состояния неисправности: " + String(ot.isFault(response) ? "fault" : "no fault"));
    Serial.println("Диагностическая индикация: " + String(ot.isDiagnostic(response) ? "diagnostics" : "no diagnostics"));
    Serial.println("Текущая температура контура СО = " + String(getBoilerTemp()) + " °C");
    Serial.println("Текущая температура контура ГВС = " + String(getDHWTemp()) + " °C");
    Serial.println("Температура на улице = " + String(getOutsideTemp()) + " °C");
    Serial.println("Текуший уровень модуляции горелки  = " + String (getADC()) + " %");
    Serial.println("ADC = " + String (analogRead(A0)));
    Serial.println("Уставка температуры ГВС = " + String(setDHWTemp()) + " °C");
    Serial.println("Тип и версия термостата: тип " + String(setMasterVersion_type()) + ", версия " + String(setMasterVersion_num()));
    Serial.println("Тип и версия котла: тип " + String(getSlaveVersion_type()) + ", версия " + String(getSlaveVersion_num()));

  //=================================================================================================
  //    Индикация и реле насоса СО c откл. с задержкой 3 мин., индикация работы ГВС, горелки
  //=================================================================================================
  int status_error = getFault();                          // индикация состояния авария.
  if (status_error == 1) {
    Serial.println("Код неисправности: E " + String(getFaultCode()));
    digitalWrite(EXTERNAL_LED_2, HIGH);
  } else {
    digitalWrite(EXTERNAL_LED_2, LOW);
  }
  int status_dhw = getHotWaterEnabled();                  // индикация состояния ГВС.
  if (status_dhw == 1) {
    digitalWrite(EXTERNAL_LED, HIGH);
  } else {
    digitalWrite(EXTERNAL_LED, LOW);
  }
  int status_flame = getFlameOn();                        // индикация состояния горелки.
  if (status_flame == 1) {
    digitalWrite(EXTERNAL_LED_1, HIGH);
  } else {
    digitalWrite(EXTERNAL_LED_1, LOW);
  }
  
  if (pv >= sp) {                                    // индикация состояния СО.
   enableCentralHeating = false;
   digitalWrite(EXTERNAL_LED_3, LOW);
   } else {
   enableCentralHeating = true;
   digitalWrite(EXTERNAL_LED_3, HIGH);  
   }
    
    publish_temperature();
    publish_boilertemp();
    publish_dhwtemp();
    publish_outtemp();
    publish_ADC();
    publish_statusCH();
    publish_statusDWH();
    publish_statusFlame();
    publish_setBoilerTemperature();
  }
  // MQTT Loop
  if (!client.connected()) {
    reconnect();
  }
  client.loop();
  //=================================================================================================
  //             Активации функции Антилегионелла ГВС 65°C на 1 час каждые 7 дней по кругу.
  //=================================================================================================

  static bool state;
  static uint32_t old_millis = millis();

  if (state == false) {
    if (millis() - old_millis >= TIME1) {
      old_millis = millis();
      spdhw = VAL1;
      state = true;
    }
  } else if (millis() - old_millis >= TIME2) {
    old_millis = millis();
    spdhw = VAL2;
    state = false;
  }
}

 

tsv_33
Offline
Зарегистрирован: 11.04.2019

Ещё, в приложении (не знаю каким пользуетесь) целевую уставку температуры в помещении делайте не более 30 гр., а целевую уставку ГВС не менее 32 гр. Условие описано в этом куске кода, строка 367:

if (a <= 31) {
    sp = a;
  } else {
    spdhw = a;
  }

P.S. Кстати, у вас BAXI SLiM? В интерфейсной плате к LMU33 котла назначение реле какое? Есть предположения? Я интерфейсную плату сам делал, вместо реле повесил светодиод, для индикации, зажигается только при работе контура СО, на ГВС не активируется. При отключении СО, сразу гаснет.

VOVA_iS
Offline
Зарегистрирован: 09.07.2019

Вообщем такая же фигня... Не проходит установка температуры.

Может проблема с версией термостата.

 

Тип и версия термостата: тип 0, версия 0
Тип и версия котла: тип 0, версия 0

У вас что выдает в логах по этим параметрам. Думаю может он представиться нормально не может. Либо представился без регулировки ГВС.

И откуда можно узнать какие варианты есть представления для котла?

VOVA_iS
Offline
Зарегистрирован: 09.07.2019

tsv_33 пишет:

Ещё, в приложении (не знаю каким пользуетесь) целевую уставку температуры в помещении делайте не более 30 гр., а целевую уставку ГВС не менее 32 гр. Условие описано в этом куске кода, строка 367:

if (a <= 31) {
    sp = a;
  } else {
    spdhw = a;
  }

P.S. Кстати, у вас BAXI SLiM? В интерфейсной плате к LMU33 котла назначение реле какое? Есть предположения? Я интерфейсную плату сам делал, вместо реле повесил светодиод, для индикации, зажигается только при работе контура СО, на ГВС не активируется. При отключении СО, сразу гаснет.

 

У меня KOREASTAR Premium. В этом вопросе не помогу.

tsv_33
Offline
Зарегистрирован: 11.04.2019

Загрузите мой последний код, посмотрим, что у вас. У меня котел представляется 1.51. если не ошибаюсь. просто я принты отключил.

tsv_33
Offline
Зарегистрирован: 11.04.2019

VOVA_iS пишет:

tsv_33 пишет:

Ещё, в приложении (не знаю каким пользуетесь) целевую уставку температуры в помещении делайте не более 30 гр., а целевую уставку ГВС не менее 32 гр. Условие описано в этом куске кода, строка 367:

if (a <= 31) {
    sp = a;
  } else {
    spdhw = a;
  }

P.S. Кстати, у вас BAXI SLiM? В интерфейсной плате к LMU33 котла назначение реле какое? Есть предположения? Я интерфейсную плату сам делал, вместо реле повесил светодиод, для индикации, зажигается только при работе контура СО, на ГВС не активируется. При отключении СО, сразу гаснет.

У меня KOREASTAR Premium. В этом вопросе не помогу.

Понял, тогда и с уровнем модуляции вам разбираться надо. Мой котёл по ОТ этот ID Msg "RelModLevel" не отдаёт, потому сам вычисляю, а не читаю его из котла.

VOVA_iS
Offline
Зарегистрирован: 09.07.2019
Заданное значение температуры в помещении = 20.00 °C
Текущее значение температуры в помещении = 28.56 °C
Выхов ПИД регулятора = 0.00
Время между измерениями = 5.32; ПИД коэффициенты: П = -85.62; И = 0.00; Д = 0.00
Текущий статус системы отопления: off
Текущий статус горячей воды: off
Текущий статус горелки: off
Индикация состояния неисправности: no fault
Диагностическая индикация: no diagnostics
Текущая температура контура СО = 31.00 °C
Текущая температура контура ГВС = 36.00 °C
Температура на улице = 0.00 °C
Текуший уровень модуляции горелки  = 0 %
ADC = 10
Уставка температуры ГВС = 0.00 °C
Тип и версия термостата: тип 0, версия 0
Тип и версия котла: тип 0, версия 0
MQTT, Current Room Temperature, °C = 28.56
MQTT, CH Temperature, °C = 31.00
MQTT, DHW Temperature, °C = 36.00
MQTT, Outside Temperature, °C = 0.00
MQTT, Relative Modulation Level, % = 0
MQTT, Status Central Heating = 0
MQTT, Status Hot Water = 0
MQTT, Status Central Heating = 0
MQTT, PID Controller Output, % = Setpoint CH Temperature, °C = 0.00
Заданное значение температуры в помещении = 20.00 °C
Текущее значение температуры в помещении = 28.56 °C
Выхов ПИД регулятора = 0.00
Время между измерениями = 5.32; ПИД коэффициенты: П = -85.62; И = 0.00; Д = 0.00
Текущий статус системы отопления: off
Текущий статус горячей воды: off
Текущий статус горелки: off
Индикация состояния неисправности: no fault
Диагностическая индикация: no diagnostics
Текущая температура контура СО = 31.00 °C
Текущая температура контура ГВС = 36.00 °C
Температура на улице = 0.00 °C
Текуший уровень модуляции горелки  = 0 %
ADC = 3
Уставка температуры ГВС = 0.00 °C
Тип и версия термостата: тип 0, версия 0
Тип и версия котла: тип 0, версия 0
MQTT, Current Room Temperature, °C = 28.56
MQTT, CH Temperature, °C = 31.00
MQTT, DHW Temperature, °C = 36.00
MQTT, Outside Temperature, °C = 0.00
MQTT, Relative Modulation Level, % = 0
MQTT, Status Central Heating = 0
MQTT, Status Hot Water = 0
MQTT, Status Central Heating = 0
MQTT, PID Controller Output, % = Setpoint CH Temperature, °C = 0.00
Заданное значение температуры в помещении = 20.00 °C
Текущее значение температуры в помещении = 28.56 °C
Выхов ПИД регулятора = 0.00
Время между измерениями = 5.32; ПИД коэффициенты: П = -85.62; И = 0.00; Д = 0.00
Текущий статус системы отопления: off
Текущий статус горячей воды: off
Текущий статус горелки: off
Индикация состояния неисправности: no fault
Диагностическая индикация: no diagnostics
Текущая температура контура СО = 31.00 °C
Текущая температура контура ГВС = 36.00 °C
Температура на улице = 0.00 °C
Текуший уровень модуляции горелки  = 0 %
ADC = 10
Уставка температуры ГВС = 0.00 °C
Тип и версия термостата: тип 0, версия 0
Тип и версия котла: тип 0, версия 0
MQTT, Current Room Temperature, °C = 28.56
MQTT, CH Temperature, °C = 31.00
MQTT, DHW Temperature, °C = 36.00
MQTT, Outside Temperature, °C = 0.00
MQTT, Relative Modulation Level, % = 0
MQTT, Status Central Heating = 0
MQTT, Status Hot Water = 0
MQTT, Status Central Heating = 0
MQTT, PID Controller Output, % = Setpoint CH Temperature, °C = 0.00
Заданное значение температуры в помещении = 20.00 °C
Текущее значение температуры в помещении = 28.56 °C
Выхов ПИД регулятора = 0.00
Время между измерениями = 5.32; ПИД коэффициенты: П = -85.62; И = 0.00; Д = 0.00
Текущий статус системы отопления: off
Текущий статус горячей воды: off
Текущий статус горелки: off
Индикация состояния неисправности: no fault
Диагностическая индикация: no diagnostics
Текущая температура контура СО = 31.00 °C
Текущая температура контура ГВС = 36.00 °C
Температура на улице = 0.00 °C
Текуший уровень модуляции горелки  = 0 %
ADC = 3
Уставка температуры ГВС = 0.00 °C
Тип и версия термостата: тип 0, версия 0
Тип и версия котла: тип 0, версия 0
MQTT, Current Room Temperature, °C = 28.56
MQTT, CH Temperature, °C = 31.00
MQTT, DHW Temperature, °C = 36.00
MQTT, Outside Temperature, °C = 0.00
MQTT, Relative Modulation Level, % = 0
MQTT, Status Central Heating = 0
MQTT, Status Hot Water = 0
MQTT, Status Central Heating = 0
MQTT, PID Controller Output, % = Setpoint CH Temperature, °C = 0.00
Заданное значение температуры в помещении = 20.00 °C
Текущее значение температуры в помещении = 28.56 °C
Выхов ПИД регулятора = 0.00
Время между измерениями = 5.32; ПИД коэффициенты: П = -85.62; И = 0.00; Д = 0.00
Текущий статус системы отопления: off
Текущий статус горячей воды: off
Текущий статус горелки: off
Индикация состояния неисправности: no fault
Диагностическая индикация: no diagnostics
Текущая температура контура СО = 31.00 °C
Текущая температура контура ГВС = 36.00 °C
Температура на улице = 0.00 °C
Текуший уровень модуляции горелки  = 0 %
ADC = 10
Уставка температуры ГВС = 0.00 °C
Тип и версия термостата: тип 0, версия 0
Тип и версия котла: тип 0, версия 0
MQTT, Current Room Temperature, °C = 28.56
MQTT, CH Temperature, °C = 31.00
MQTT, DHW Temperature, °C = 36.00
MQTT, Outside Temperature, °C = 0.00
MQTT, Relative Modulation Level, % = 0
MQTT, Status Central Heating = 0
MQTT, Status Hot Water = 0
MQTT, Status Central Heating = 0
MQTT, PID Controller Output, % = Setpoint CH Temperature, °C = 0.00
Заданное значение температуры в помещении = 20.00 °C
Текущее значение температуры в помещении = 28.56 °C
Выхов ПИД регулятора = 0.00
Время между измерениями = 5.32; ПИД коэффициенты: П = -85.62; И = 0.00; Д = 0.00
Текущий статус системы отопления: off
Текущий статус горячей воды: off
Текущий статус горелки: off
Индикация состояния неисправности: no fault
Диагностическая индикация: no diagnostics
Текущая температура контура СО = 30.00 °C
Текущая температура контура ГВС = 36.00 °C
Температура на улице = 0.00 °C
Текуший уровень модуляции горелки  = 0 %
ADC = 10
Уставка температуры ГВС = 0.00 °C
Тип и версия термостата: тип 0, версия 0
Тип и версия котла: тип 0, версия 0