Термостат OpenTherm на ESP8266
- Войдите на сайт для отправки комментариев
Сб, 20/04/2019 - 16:43
Приветствую. Делаю термостат для своего газового котла 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\">Котёл, подача:  <span id=\"boilertemp\">{2}</span>°C</font><p><br/>\ <font size=\"4\"face=\"Verdana\">Горячая вода:   <span id=\"dhwttemp\">{3}</span>°C</font><p><br/>\ <font size=\"4\"face=\"Verdana\">На улице:   <span id=\"outsidetemp\">{4}</span>°C</font><p><br/>\ <font size=\"4\"face=\"Verdana\">Модуляция:   <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\">Статус СО:   <b><span id=\"chon\">{5}</span></b></font><p><br/>\ <font size=\"3\"face=\"Verdana\">Статус ГВС:   <b><span id=\"hwon\">{6}</span></b></font><p><br/>\ <font size=\"3\"face=\"Verdana\">Статус горелки:  <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 }
Посмотрел по диагонали, возможно не прав, у вас получение температуры в самом выводе html странички.
Вообще все плохо, получение статусов котла температуры и прочих данных должно быть неблокирующим кодом по таймерам, а вывод в страничку из переменных а не динамически получать. Кстати зачем страничку в progmem? Неужели места в esp не хватило.
А в строке №241 эта самая страничка целиком закачивается в RAM
И на это тоже тратится драгоценное процессорное время
строка 223 и 224 орфографические ошибки, это, что бросилось в глаза
строка 223 и 224 орфографические ошибки, это, что бросилось в глаза
Спасибо, исправил.
Давно такого не видел, чтоб статику, по определению размещенную в PROGMEM, там ей и место, перегружать в стринг и в этом стринге реплейсить.
Для чего настраивать интервал, чтоб каждые 2 секунды асинхронно выполнялись куча запросов (броузер их параллельно ведь выполняет).
/temp /boilertemp /dhwttemp /outsidetemp /chon /hwon /flameon /getadc
ни ужели нельзя обойтись одним запросом, получать всё необходимое сразу.
Давно такого не видел, чтоб статику, по определению размещенную в PROGMEM, там ей и место, перегружать в стринг и в этом стринге реплейсить.
Для чего настраивать интервал, чтоб каждые 2 секунды асинхронно выполнялись куча запросов (броузер их параллельно ведь выполняет).
/temp /boilertemp /dhwttemp /outsidetemp /chon /hwon /flameon /getadc
ни ужели нельзя обойтись одним запросом, получать всё необходимое сразу.
Алексей., пробовал и одним запросом, как выше написали, но переменные не обновляются без перезагрузки страницы...
Что значит не обновляются? Есп их не отправляет? А по отдельности отправляет? Целую страницу отправляет, а десяток переменных не может? Да быть такого не может. Вы увеличиваете нагрузку, отправляя с броузера сразу несколько запросов, по одному для каждого параметра.
Приветствую. Делаю термостат для своего газового котла BAXI SLiM, работающий по протоколу OpenTherm. Всё вроде работает. Но веб страница долго грузится. Может есть какие замечения? Как ускорить процесс загрузки? С MQTT проблем нет.
Здравствуйте
Загрузил ваш скейтч
У меня котел не отключает помпу отопления. У Вас все нормально с этим
И возможно ли дописать скейтч для регулирования температуры горячей воды(можно только через mqtt)
Спасибо
VOVA_iS - не надо этот код брать, он откровенно плохо написан. Не шутите с газом.
VOVA_iS - не надо этот код брать, он откровенно плохо написан. Не шутите с газом.
Тут криво веб сервер написан
По работе котла код скопирован с примера библиотеки OpenTherm.
Mqtt работает хорошо
VOVA_iS, да, помпа молотит постоянно, повесил на термостат своё реле для её управления. Горячую воду то же сделал. Вообще много чего добавил и переделал, и продолжаю делать...
P.S. Да, сервер кривой :-(
VOVA_iS, от веб сервера решил отказаться, оставлю только конфигурационную страничку и всё, а для рулёжки достаточно mqtt.
VOVA_iS, да, помпа молотит постоянно, повесил на термостат своё реле для её управления. Горячую воду то же сделал. Вообще много чего добавил и переделал, и продолжаю делать...
P.S. Да, сервер кривой :-(
про помпу можно подробней:)?
скетч можно с горячей водой?
VOVA_iS, от веб сервера решил отказаться, оставлю только конфигурационную страничку и всё, а для рулёжки достаточно mqtt.
Да я согласен по MQTT управления выше крыши. И умный дом можно добавить полноценно
про помпу можно подробней:)?
скетч можно с горячей водой?
Для помпы использую библиотеку TimingRelay.h:
В setup:
В 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; }А как параметр spdhw передаётся в котёл? не вижу
А как параметр 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, есть ещё одна задумка. В слимах по ОТ не передаётся уровень модуляции горелки. Можно сделать используя у ESP 8266 А0.
Интересная идея:-) только я на сколько помню на А0 может быть максимум 3.3v
надо сначала замеры сделать у котла:-)
VOVA_iS, вполне достаточно. С котла 9,10 контакты А5 с катушки модуляции идёт ШИМ. Надо ставить операционник, преобразовать ШИМ в линейные 0-5В, короче с него через делитель на А0. Программно потребуется ремапинг АЦП.
про помпу можно подробней:)?
скетч можно с горячей водой?
Для помпы использую библиотеку TimingRelay.h:
В setup:
В loop реле работает по условию:
// обработка индикации и реле насоса СО. if (status_flame == 1 && status_dhw == 0) { // Если состояния горелки = 1 и статус состояния ГВС = 0 включаем реле. relay.on(); } else { relay.off(); } relay.loop(); // Обрабатываем (обязательно). // конец обработки индикации и реле насоса СО.Не могу скомпилировать идут ошибки
в коде данные переменные status_flame status_dhw не опредлены
Всё верно, статусы не задекларированы, вставьте это в 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); }Условия для индикации можно отключить, а то опять ругаться будет при компиляции...
я так понимаю нужно это
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; }Да, верно.
Скомпилировал код выше.
Увы температура горяей воды не передается.
Посмотрите где может быть ошибка?
ОК! Посмотрю. У меня сейчас проблема после обновления менеджера плат до версии 2.5.2. esp-шка ругается в мониторе "ISR not in IRAM!", ребутится постоянно. До обновления была версия 2.5.0. Копать дальше? Или откатиться и забыть? После декодирования стека пишет:
ОК! Посмотрю. У меня сейчас проблема после обновления менеджера плат до версии 2.5.2. esp-шка ругается в мониторе "ISR not in IRAM!", ребутится постоянно. До обновления была версия 2.5.0. Копать дальше? Или откатиться и забыть? После декодирования стека пишет:
Откатись и забудь.
У меня тоже после обновления много каких проектов перестало собираться.
Откатился и все норм
Это понятно, но всё же не случайно это, что то с прерываниями не то... Где то надо добавить ICACHE_RAM_ATTR перед функцией ISR, что бы правильно было.
Хотя ругани в сети по этому поводу полно...:-)
Так будет проще, выкладываю один из рабочих кодов без веб страницы, сервер из кода сами удалите и принты в монитор тоже, не нужны они для работы с 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; } }P.S.
Так и есть, атрибут ICACHE_RAM_ATTR теперь обязателен в коде, функциях, вызываемых по прерываниям. Проблема работы с 2.5.2 разрешилась.
Проблему с помпой решил немного по другому
в секцию 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;То заданная температура отображается(температура гвс установлена до подключение платы), но не устанавливается
VOVA_iS, с помпой понятно, а я то начал городить огород с доп. реле, простенько, сам бы не догадался...
Пишет 0, но только когда котёл не подключен к термостату, когда же котёл представится термостату и тот ответит ему, то всё ОК.
P.S.
Вообще библиотека ОТ неплохая, но попытка скрестить ужа и ежа провалилась, я про web и mqtt в одном флаконе. Жуткие тормоза при совместной работе двух библиотек, причём только web. Приглядываюсь к такому решению https://www.youtube.com/watch?v=QQ7apgKBgNM
VOVA_iS, с помпой понятно, а я то начал городить огород с доп. реле, простенько, сам бы не догадался...
Пишет 0, но только когда котёл не подключен к термостату, когда же котёл представится термостату и тот ответит ему, то всё ОК.
Котёл с термостатом представились друг другу.
А проблема что все данные с котла приходят кроме установленной температуры ГВС
да не нужен этот web
mqtt более чем достаточно
VOVA_iS, с помпой понятно, а я то начал городить огород с доп. реле, простенько, сам бы не догадался...
Пишет 0, но только когда котёл не подключен к термостату, когда же котёл представится термостату и тот ответит ему, то всё ОК.
Котёл с термостатом представились друг другу.
А проблема что все данные с котла приходят кроме установленной температуры ГВС
да не нужен этот web
mqtt более чем достаточно
А в mqtt брокере переменная spdhw видна?
но если изменить код вот так
1floatsetDHWTemp() {2unsignedlongrequest56 = ot.buildRequest(OpenThermRequestType::WRITE_ACK, OpenThermMessageID::TdhwSet, hex56);3unsignedlongrespons56 = ot.sendRequest(request56);4uint16_t dataValue56 = respons56 & 0xFFFF;5floatresult56 = dataValue56 / 256;6returnresult56;Тогда видна
В протоколе ОТ 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; }VOVA_iS, а как определили, что в котёл не ушла уставка?
if (pv >= sp) { enableCentralHeating = false; } else { enableCentralHeating = true; }С этим кодом работы помпы ОТ отключается...т.е. интерфейс активируется только при этом условии, а если ОТ не активен, то целевую температуру контур ГВС не примет, стало быть, котёл работает в этом промежутке, в обычном режиме, ON/OFF (перемычка). Хотя, могу и ошибаться, погонять надо...
VOVA_iS, а как определили, что в котёл не ушла уставка?
Банально включаю воду и смотрю на сколько нагрел котел. Было 38 ставлю 45. Все равно греет до 38.
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
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; } }Ещё, в приложении (не знаю каким пользуетесь) целевую уставку температуры в помещении делайте не более 30 гр., а целевую уставку ГВС не менее 32 гр. Условие описано в этом куске кода, строка 367:
if (a <= 31) { sp = a; } else { spdhw = a; }P.S. Кстати, у вас BAXI SLiM? В интерфейсной плате к LMU33 котла назначение реле какое? Есть предположения? Я интерфейсную плату сам делал, вместо реле повесил светодиод, для индикации, зажигается только при работе контура СО, на ГВС не активируется. При отключении СО, сразу гаснет.
Вообщем такая же фигня... Не проходит установка температуры.
Может проблема с версией термостата.
У вас что выдает в логах по этим параметрам. Думаю может он представиться нормально не может. Либо представился без регулировки ГВС.
И откуда можно узнать какие варианты есть представления для котла?
Ещё, в приложении (не знаю каким пользуетесь) целевую уставку температуры в помещении делайте не более 30 гр., а целевую уставку ГВС не менее 32 гр. Условие описано в этом куске кода, строка 367:
if (a <= 31) { sp = a; } else { spdhw = a; }P.S. Кстати, у вас BAXI SLiM? В интерфейсной плате к LMU33 котла назначение реле какое? Есть предположения? Я интерфейсную плату сам делал, вместо реле повесил светодиод, для индикации, зажигается только при работе контура СО, на ГВС не активируется. При отключении СО, сразу гаснет.
У меня KOREASTAR Premium. В этом вопросе не помогу.
Загрузите мой последний код, посмотрим, что у вас. У меня котел представляется 1.51. если не ошибаюсь. просто я принты отключил.
Ещё, в приложении (не знаю каким пользуетесь) целевую уставку температуры в помещении делайте не более 30 гр., а целевую уставку ГВС не менее 32 гр. Условие описано в этом куске кода, строка 367:
if (a <= 31) { sp = a; } else { spdhw = a; }P.S. Кстати, у вас BAXI SLiM? В интерфейсной плате к LMU33 котла назначение реле какое? Есть предположения? Я интерфейсную плату сам делал, вместо реле повесил светодиод, для индикации, зажигается только при работе контура СО, на ГВС не активируется. При отключении СО, сразу гаснет.
У меня KOREASTAR Premium. В этом вопросе не помогу.
Понял, тогда и с уровнем модуляции вам разбираться надо. Мой котёл по ОТ этот ID Msg "RelModLevel" не отдаёт, потому сам вычисляю, а не читаю его из котла.