Термостат OpenTherm на ESP8266
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- …
- следующая ›
- последняя »
- Войдите на сайт для отправки комментариев
Сб, 20/04/2019 - 16:43
Приветствую. Делаю термостат для своего газового котла BAXI SLiM, работающий по протоколу OpenTherm. Всё вроде работает. Но веб страница долго грузится. Может есть какие замечения? Как ускорить процесс загрузки? С MQTT проблем нет.
001 | #include <ESP8266WiFi.h> |
002 | #include <PubSubClient.h> |
003 | #include <WiFiClient.h> |
004 | #include <ESP8266WebServer.h> |
005 | #include <ESP8266mDNS.h> |
006 | #include <OneWire.h> |
007 | #include <DallasTemperature.h> |
008 | #include <OpenTherm.h> //<a href="https://github.com/ihormelnyk/opentherm_library/" rel="nofollow">https://github.com/ihormelnyk/opentherm_library/</a> |
009 |
010 | //OpenTherm input and output wires connected to 4 and 5 pins on the OpenTherm Shield |
011 | const int inPin = 4; //D2 |
012 | const int outPin = 5; //D1 |
013 |
014 | #define ONE_WIRE_BUS 14 //D5 Data wire is connected to 14 pin on the OpenTherm Shield |
015 | #define BUILTIN_LED 2 //D4 Встроенный LED |
016 |
017 | const char * ssid = "*******" ; |
018 | const char * password = "**********" ; |
019 | const char * mqtt_server = "***********" ; |
020 | const int mqtt_port = 12345; |
021 | const char * mqtt_user = "*********" ; |
022 | const char * mqtt_password = "********" ; |
023 |
024 | ESP8266WebServer server(80); //Server on port 80 |
025 | OneWire oneWire(ONE_WIRE_BUS); |
026 | DallasTemperature sensors(&oneWire); |
027 | OpenTherm ot(inPin, outPin); |
028 | WiFiClient espClient; |
029 | PubSubClient client(espClient); |
030 | char buf[50]; |
031 |
032 | float sp = 20.00, //set point |
033 | pv = 0, //current temperature |
034 | pv_last = 0, //prior temperature |
035 | ierr = 0, //integral error |
036 | dt = 0, //time between measurements |
037 | op = 0; //PID controller output |
038 | unsigned long ts = 0, new_ts = 0; //timestamp |
039 | bool enableCentralHeating = true ; |
040 | bool enableHotWater = true ; |
041 | bool enableCooling = false ; |
042 |
043 | int timer_off = 180; //Задержка выключения реле насоса СО 3 мин. |
044 |
045 | const char HTTP_HTML[] PROGMEM = "<!DOCTYPE html>\ |
046 | <html>\ |
047 | <head>\ |
048 | <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=\">\ |
049 | <title>Термостат</title>\ |
050 | <meta charset=\"utf-8\">\ |
051 | <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\ |
052 | <script>\ |
053 | window.setInterval(\"update()\", 2000);\ |
054 | function update(a,b,c,d,e,f,g,h){\ |
055 | var a = new XMLHttpRequest();\ |
056 | a.open(\"GET\", \"/temp\", true );\ |
057 | a.onreadystatechange = function() {\ |
058 | if (a.readyState != XMLHttpRequest.DONE || a.status != 200) return ;\ |
059 | document.getElementById( 'temp' ).innerHTML = a.responseText;\ |
060 | };\ |
061 | a.send();\ |
062 | var b = new XMLHttpRequest();\ |
063 | b.open(\"GET\", \"/boilertemp\", true );\ |
064 | b.onreadystatechange = function() {\ |
065 | if (b.readyState != XMLHttpRequest.DONE || b.status != 200) return ;\ |
066 | document.getElementById( 'boilertemp' ).innerHTML = b.responseText;\ |
067 | };\ |
068 | b.send();\ |
069 | var c = new XMLHttpRequest();\ |
070 | c.open(\"GET\", \"/dhwttemp\", true );\ |
071 | c.onreadystatechange = function() {\ |
072 | if (c.readyState != XMLHttpRequest.DONE || c.status != 200) return ;\ |
073 | document.getElementById( 'dhwttemp' ).innerHTML = c.responseText;\ |
074 | };\ |
075 | c.send();\ |
076 | var d = new XMLHttpRequest();\ |
077 | d.open(\"GET\", \"/outsidetemp\", true );\ |
078 | d.onreadystatechange = function() {\ |
079 | if (d.readyState != XMLHttpRequest.DONE || d.status != 200) return ;\ |
080 | document.getElementById( 'outsidetemp' ).innerHTML = d.responseText;\ |
081 | };\ |
082 | d.send();\ |
083 | var e = new XMLHttpRequest();\ |
084 | e.open(\"GET\", \"/chon\", true );\ |
085 | e.onreadystatechange = function() {\ |
086 | if (e.readyState != XMLHttpRequest.DONE || e.status != 200) return ;\ |
087 | document.getElementById( 'chon' ).innerHTML = e.responseText;\ |
088 | };\ |
089 | e.send();\ |
090 | var f = new XMLHttpRequest();\ |
091 | f.open(\"GET\", \"/hwon\", true );\ |
092 | f.onreadystatechange = function() {\ |
093 | if (f.readyState != XMLHttpRequest.DONE || f.status != 200) return ;\ |
094 | document.getElementById( 'hwon' ).innerHTML = f.responseText;\ |
095 | };\ |
096 | f.send();\ |
097 | var g = new XMLHttpRequest();\ |
098 | g.open(\"GET\", \"/flameon\", true );\ |
099 | g.onreadystatechange = function() {\ |
100 | if (g.readyState != XMLHttpRequest.DONE || g.status != 200) return ;\ |
101 | document.getElementById( 'flameon' ).innerHTML = g.responseText;\ |
102 | };\ |
103 | g.send();\ |
104 | var h = new XMLHttpRequest();\ |
105 | h.open(\"GET\", \"/getadc\", true );\ |
106 | h.onreadystatechange = function() {\ |
107 | if (h.readyState != XMLHttpRequest.DONE || h.status != 200) return ;\ |
108 | document.getElementById( 'getadc' ).innerHTML = h.responseText;\ |
109 | };\ |
110 | h.send();\ |
111 | }\ |
112 | </script>\ |
113 | </head>\ |
114 | <body style=\"text-align:center\">\ |
115 | <div style=\"line-height:0.5\">\ |
116 | <h1><font color=\"#1c6b72\"size=\"5\"face=\"Verdana\">Термостат</font></h1>\ |
117 | <img src=\"data:image/png;base64,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\"><p>\ |
118 | <font size=\"3\"face=\"Verdana\">OpenTherm</font><p>\ |
119 | <hr align=\"center\" size=\"3\" width=\"290px\" color=\"#1c6b72\"><br/><br/>\ |
120 | <font color=\"#1c6b72\"size=\"8\"face=\"Verdana\"><span id=\"temp\">{0}</span>°C</font><p><br/><br/>\ |
121 | <font size=\"4\"face=\"Verdana\">Котёл, подача:  <span id=\"boilertemp\">{2}</span>°C</font><p><br/>\ |
122 | <font size=\"4\"face=\"Verdana\">Горячая вода:   <span id=\"dhwttemp\">{3}</span>°C</font><p><br/>\ |
123 | <font size=\"4\"face=\"Verdana\">На улице:   <span id=\"outsidetemp\">{4}</span>°C</font><p><br/>\ |
124 | <font size=\"4\"face=\"Verdana\">Модуляция:   <span id=\"getadc\">{8}</span>%</font><p>\ |
125 | <hr align=\"center\" size=\"3\" width=\"290px\" color=\"#1c6b72\"><br/>\ |
126 | <form method=\"post\">\ |
127 | <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/>\ |
128 | <input type=\"submit\"value=\"Записать\" style=\"font-size:16px; width:100px\">\ |
129 | <form>\ |
130 | </p>\ |
131 | <hr align=\"center\" size=\"3\" width=\"290px\" color=\"#1c6b72\"><br/>\ |
132 | <font size=\"3\"face=\"Verdana\">Статус СО:   <b><span id=\"chon\">{5}</span></b></font><p><br/>\ |
133 | <font size=\"3\"face=\"Verdana\">Статус ГВС:   <b><span id=\"hwon\">{6}</span></b></font><p><br/>\ |
134 | <font size=\"3\"face=\"Verdana\">Статус горелки:  <b><span id=\"flameon\">{7}</span></b></font><p>\ |
135 | </div>\ |
136 | </body>\ |
137 | </html>"; |
138 |
139 | void handleInterrupt() { |
140 | ot.handleInterrupt(); |
141 | } |
142 |
143 | float getTemp() { |
144 | return sensors.getTempCByIndex(0); |
145 | } |
146 | float getBoilerTemp() { |
147 | return ot.getBoilerTemperature(); |
148 | } |
149 | float getDHWTemp() { |
150 | unsigned long request26 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Tdhw, 0); |
151 | unsigned long respons26 = ot.sendRequest(request26); |
152 | uint16_t dataValue26 = respons26 & 0xFFFF; |
153 | float result26 = dataValue26 / 256; |
154 | return result26; |
155 | } |
156 | float getOutsideTemp() { |
157 | unsigned long request27 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Toutside, 0); |
158 | unsigned long respons27 = ot.sendRequest(request27); |
159 | uint16_t dataValue27 = respons27 & 0xFFFF; |
160 | if (dataValue27 > 32768) { |
161 | //negative |
162 | float result27 = -(65536 - dataValue27) / 256; |
163 | return result27; |
164 | } else { |
165 | //positive |
166 | float result27 = dataValue27 / 256; |
167 | return result27; |
168 | } |
169 | } |
170 | int getADC() { |
171 | delay(100); |
172 | int ar = analogRead(A0); |
173 | ar = map(ar, 26, 1023, 0, 100); |
174 | return ar; |
175 | } |
176 | unsigned long getCentralHeatingEnabled() { |
177 | unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling); |
178 | OpenThermResponseStatus responseStatus = ot.getLastResponseStatus(); |
179 | if (responseStatus = OpenThermResponseStatus::SUCCESS) |
180 | return ot.isCentralHeatingEnabled(response); |
181 | } |
182 | unsigned long getHotWaterEnabled() { |
183 | unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling); |
184 | OpenThermResponseStatus responseStatus = ot.getLastResponseStatus(); |
185 | if (responseStatus = OpenThermResponseStatus::SUCCESS) |
186 | return ot.isHotWaterEnabled(response); |
187 | } |
188 | unsigned long getFlameOn() { |
189 | unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling); |
190 | OpenThermResponseStatus responseStatus = ot.getLastResponseStatus(); |
191 | if (responseStatus = OpenThermResponseStatus::SUCCESS) |
192 | return ot.isFlameOn(response); |
193 | } |
194 | float pid( float sp, float pv, float pv_last, float & ierr, float dt) { |
195 | float Kc = 10.0; // K / %Heater |
196 | float tauI = 50.0; // sec |
197 | float tauD = 1.0; // sec |
198 | // PID coefficients |
199 | float KP = Kc; |
200 | float KI = Kc / tauI; |
201 | float KD = Kc * tauD; |
202 | // upper and lower bounds on heater level |
203 | float ophi = 100; |
204 | float oplo = 0; |
205 | // calculate the error |
206 | float error = sp - pv; |
207 | // calculate the integral error |
208 | ierr = ierr + KI * error * dt; |
209 | // calculate the measurement derivative |
210 | float dpv = (pv - pv_last) / dt; |
211 | // calculate the PID output |
212 | float P = KP * error; //proportional contribution |
213 | float I = ierr; //integral contribution |
214 | float D = -KD * dpv; //derivative contribution |
215 | float op = P + I + D; |
216 | // implement anti-reset windup |
217 | if ((op < oplo) || (op > ophi)) { |
218 | I = I - KI * error * dt; |
219 | // clip output |
220 | op = max(oplo, min(ophi, op)); |
221 | } |
222 | ierr = I; |
223 | Serial .println( "Заданное значение температуры в помещкнии = " + String(sp) + " °C" ); |
224 | Serial .println( "Текущее значение температуры в помещкнии = " + String(pv) + " °C" ); |
225 | Serial .println( "Выхов ПИД регулятора = " + String(op)); |
226 | Serial .println( "Время между измерениями = " + String(dt) + "; ПИД коэффициенты: П = " + String(P) + "; И = " + String(I) + "; Д = " + String(D)); |
227 | return op; |
228 | } |
229 | //=============================================================== |
230 | // Эта процедура выполняется при открытии IP-адреса в браузере |
231 | //=============================================================== |
232 | void handleRoot() { |
233 | digitalWrite(BUILTIN_LED, 1); |
234 | if (server.method() == HTTP_POST) { |
235 | for (uint8_t i = 0; i < server.args(); i++) { |
236 | if (server.argName(i) == "sp" ) { |
237 | sp = server.arg(i).toFloat(); |
238 | } |
239 | } |
240 | } |
241 | String page = FPSTR(HTTP_HTML); |
242 | page.replace( "{0}" , String(getTemp())); |
243 | page.replace( "{1}" , String(( float )sp)); |
244 | page.replace( "{2}" , String(getBoilerTemp())); |
245 | page.replace( "{3}" , String(getDHWTemp())); |
246 | page.replace( "{4}" , String(getOutsideTemp())); |
247 | page.replace( "{5}" , String(getCentralHeatingEnabled() ? "on" : "off" )); |
248 | page.replace( "{6}" , String(getHotWaterEnabled() ? "on" : "off" )); |
249 | page.replace( "{7}" , String(getFlameOn() ? "on" : "off" )); |
250 | page.replace( "{8}" , String(getADC())); |
251 | server.send(200, "text/html" , page); |
252 | digitalWrite(BUILTIN_LED, 0); |
253 | } |
254 | void handleGetTemp() { |
255 | //digitalWrite(BUILTIN_LED, 1); |
256 | server.send(200, "text/plain" , String(getTemp())); |
257 | //digitalWrite(BUILTIN_LED, 0); |
258 | } |
259 | void handleGetBoilerTemp() { |
260 | //digitalWrite(BUILTIN_LED, 1); |
261 | server.send(200, "text/plain" , String(getBoilerTemp())); |
262 | //digitalWrite(BUILTIN_LED, 0); |
263 | } |
264 | void handleGetDHWTemp() { |
265 | //digitalWrite(BUILTIN_LED, 1); |
266 | server.send(200, "text/plain" , String(getDHWTemp())); |
267 | //digitalWrite(BUILTIN_LED, 0); |
268 | } |
269 | void handleGetOutsideTemp() { |
270 | //digitalWrite(BUILTIN_LED, 1); |
271 | server.send(200, "text/plain" , String(getOutsideTemp())); |
272 | //digitalWrite(BUILTIN_LED, 0); |
273 | } |
274 | void handleGetCentralHeatingEnabled() { |
275 | //digitalWrite(BUILTIN_LED, 1); |
276 | server.send(200, "text/plain" , String(getCentralHeatingEnabled() ? "on" : "off" )); |
277 | //digitalWrite(BUILTIN_LED, 0); |
278 | } |
279 | void handleGetHotWaterEnabled() { |
280 | //digitalWrite(BUILTIN_LED, 1); |
281 | server.send(200, "text/plain" , String(getHotWaterEnabled() ? "on" : "off" )); |
282 | //digitalWrite(BUILTIN_LED, 0); |
283 | } |
284 | void handleGetFlameOn() { |
285 | //digitalWrite(BUILTIN_LED, 1); |
286 | server.send(200, "text/plain" , String(getFlameOn() ? "on" : "off" )); |
287 | //digitalWrite(BUILTIN_LED, 0); |
288 | } |
289 | void handleADC() { |
290 | //digitalWrite(BUILTIN_LED, 1); |
291 | server.send(200, "text/plain" , String(getADC())); |
292 | //digitalWrite(BUILTIN_LED, 0); |
293 | } |
294 | //============================================================== |
295 | // SETUP |
296 | //============================================================== |
297 | void setup_wifi() { |
298 | delay(10); |
299 | //Connect to Wi-Fi Network |
300 | Serial .println(); |
301 | Serial .print( "Connecting to " ); |
302 | Serial .println(ssid); |
303 | WiFi.mode(WIFI_STA); |
304 | WiFi.begin(ssid, password); //Connect to your Wi-Fi router |
305 | // Wait for connection |
306 | while (WiFi.status() != WL_CONNECTED) { |
307 | delay(500); |
308 | Serial .print( "." ); |
309 | } |
310 | //If connection successful show IP address in serial monitor |
311 | Serial .println( "" ); |
312 | Serial .println( "WiFi connected" ); |
313 | Serial .println( "IP address: " ); |
314 | Serial .println(WiFi.localIP()); |
315 | } |
316 | void setup ( void ) { |
317 | pinMode(BUILTIN_LED, OUTPUT); |
318 | digitalWrite(BUILTIN_LED, 0); |
319 | Serial .begin(115200); |
320 | setup_wifi(); |
321 | if (MDNS.begin( "thermostat" )) { |
322 | Serial .println( "MDNS responder started" ); |
323 | } |
324 | //Initialize Webserver |
325 | server.on( "/" , handleRoot); // Ответ сервера на запрос главной страницы |
326 | server.on( "/temp" , handleGetTemp); // Ответ сервера на запрос температуры в помещении |
327 | server.on( "/boilertemp" , handleGetBoilerTemp); // Ответ сервера на запрос температуры подачи котла |
328 | server.on( "/dhwttemp" , handleGetDHWTemp); // Ответ сервера на запрос температуры горячей воды |
329 | server.on( "/outsidetemp" , handleGetOutsideTemp); // Ответ сервера на запрос уличной температуры |
330 | server.on( "/chon" , handleGetCentralHeatingEnabled); // Ответ сервера на запрос состояния СО |
331 | server.on( "/hwon" , handleGetHotWaterEnabled); // Ответ сервера на запрос состояния ГВС |
332 | server.on( "/flameon" , handleGetFlameOn); // Ответ сервера на запрос состояния горелки |
333 | server.on( "/getadc" , handleADC); //Reads ADC function |
334 | server.begin(); |
335 | Serial .println( "HTTP server started" ); |
336 | //============================================================== |
337 | // Init DS18B20 sensor |
338 | //============================================================== |
339 | sensors.begin(); |
340 | sensors.requestTemperatures(); |
341 | sensors.setWaitForConversion( false ); //switch to async mode |
342 | pv, pv_last = sensors.getTempCByIndex(0); |
343 | ts = millis(); |
344 | //============================================================== |
345 | // Init OpenTherm Controller |
346 | //============================================================== |
347 | ot.begin(handleInterrupt); |
348 | //============================================================== |
349 | // Init MQTT Client |
350 | //============================================================== |
351 | client.setServer(mqtt_server, mqtt_port); |
352 | client.setCallback(callback); |
353 | } |
354 | void publish_temperature() { |
355 | Serial .println( "MQTT, Current Room Temperature, °C = " + String(pv)); |
356 | String(pv).toCharArray(buf, 50); |
357 | client.publish( "pv" , buf); |
358 | } |
359 | void publish_boilertemp() { |
360 | Serial .println( "MQTT, CH Temperature, °C = " + String(getBoilerTemp())); |
361 | String(getBoilerTemp()).toCharArray(buf, 50); |
362 | client.publish( "cht" , buf); |
363 | } |
364 | void publish_dhwtemp() { |
365 | Serial .println( "MQTT, DHW Temperature, °C = " + String(getDHWTemp())); |
366 | String(getDHWTemp()).toCharArray(buf, 50); |
367 | client.publish( "dhwt" , buf); |
368 | } |
369 | void publish_outtemp() { |
370 | Serial .println( "MQTT, Outside Temperature, °C = " + String(getOutsideTemp())); |
371 | String(getOutsideTemp()).toCharArray(buf, 50); |
372 | client.publish( "outt" , buf); |
373 | } |
374 | void publish_statusCH() { |
375 | Serial .println( "MQTT, Status Central Heating = " + String(getCentralHeatingEnabled())); |
376 | String(getCentralHeatingEnabled()).toCharArray(buf, 50); |
377 | client.publish( "sch" , buf); |
378 | } |
379 | void publish_ADC() { |
380 | Serial .println( "MQTT, Relative Modulation Level, % = " + String(getADC())); |
381 | String(getADC()).toCharArray(buf, 50); |
382 | client.publish( "adc" , buf); |
383 | } |
384 | void publish_statusDWH() { |
385 | Serial .println( "MQTT, Status Hot Water = " + String(getHotWaterEnabled())); |
386 | String(getHotWaterEnabled()).toCharArray(buf, 50); |
387 | client.publish( "sdwh" , buf); |
388 | } |
389 | void publish_statusFlame() { |
390 | Serial .println( "MQTT, Status Central Heating = " + String(getFlameOn())); |
391 | String(getFlameOn()).toCharArray(buf, 50); |
392 | client.publish( "sfl" , buf); |
393 | } |
394 | void publish_setBoilerTemperature() { |
395 | Serial .println( "MQTT, PID Controller Output, % = Set Point CH Temperature, °C = " + String(op)); |
396 | String(op).toCharArray(buf, 50); |
397 | client.publish( "op" , buf); |
398 | } |
399 | void callback( char * topic, byte * payload, unsigned int length) { |
400 | if (strcmp(topic, "sp" ) != 0) return ; |
401 | String str = String(); |
402 | for ( int i = 0; i < length; i++) { |
403 | str += ( char )payload[i]; |
404 | } |
405 | Serial .println( "MQTT, Setpoint Room Temperature = " + str); |
406 | sp = str.toFloat(); |
407 | } |
408 | void reconnect() { |
409 | while (!client.connected()) { |
410 | Serial .print( "Attempting MQTT connection..." ); |
411 | // Attempt to connect |
412 | if (client.connect( "ESP8266Client" , mqtt_user, mqtt_password)) { |
413 | Serial .println( "connected" ); |
414 | // Once connected, publish an announcement... |
415 | publish_temperature(); |
416 | publish_boilertemp(); |
417 | publish_dhwtemp(); |
418 | publish_outtemp(); |
419 | publish_ADC(); |
420 | publish_statusCH(); |
421 | publish_statusDWH(); |
422 | publish_statusFlame(); |
423 | publish_setBoilerTemperature(); |
424 | // ... and resubscribe |
425 | client.subscribe( "sp" ); |
426 | } else { |
427 | Serial .print( "failed, rc =" ); |
428 | Serial .print(client.state()); |
429 | Serial .println( " try again in 5 seconds" ); |
430 | // Wait 5 seconds before retrying |
431 | delay(5000); |
432 | } |
433 | } |
434 | } |
435 | //============================================================== |
436 | // LOOP |
437 | //============================================================== |
438 | void loop ( void ) { |
439 | new_ts = millis(); |
440 | if (new_ts - ts > 1000) { |
441 | unsigned long response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling); |
442 | OpenThermResponseStatus responseStatus = ot.getLastResponseStatus(); |
443 | if (responseStatus = OpenThermResponseStatus::SUCCESS) { |
444 | //Serial.println("Error: Invalid boiler response " + String(response, HEX)); |
445 | Serial .println( "Текущий статус системы отопления: " + String(ot.isCentralHeatingEnabled(response) ? "on" : "off" )); |
446 | Serial .println( "Текущий статус горячей воды: " + String(ot.isHotWaterEnabled(response) ? "on" : "off" )); |
447 | Serial .println( "Текущий статус горелки: " + String(ot.isFlameOn(response) ? "on" : "off" )); |
448 | Serial .println( "Индикация состояния неисправности: " + String(ot.isFault(response) ? "fault" : "no fault" )); |
449 | Serial .println( "Диагностическая индикация: " + String(ot.isDiagnostic(response) ? "diagnostics" : "no diagnostics" )); |
450 | } |
451 | if (responseStatus == OpenThermResponseStatus::NONE) { |
452 | Serial .println( "Error: OpenTherm is not initialized" ); |
453 | } |
454 | else if (responseStatus == OpenThermResponseStatus::INVALID) { |
455 | Serial .println( "Error: Invalid response " + String(response, HEX)); |
456 | } |
457 | else if (responseStatus == OpenThermResponseStatus::TIMEOUT) { |
458 | Serial .println( "Error: Response timeout" ); |
459 | } |
460 | pv = sensors.getTempCByIndex(0); |
461 | dt = (new_ts - ts) / 1000.0; |
462 | ts = new_ts; |
463 | if (responseStatus == OpenThermResponseStatus::SUCCESS) { |
464 | op = pid(sp, pv, pv_last, ierr, dt); |
465 | //Set CH Temperature |
466 | ot.setBoilerTemperature(op); |
467 | pv_last = pv; |
468 | sensors.requestTemperatures(); //async temperature request |
469 | } |
470 | //Print Temperature |
471 |
472 | Serial .println( "Текущая температура контура СО = " + String(getBoilerTemp()) + " °C" ); |
473 | Serial .println( "Текущая температура контура ГВС = " + String(getDHWTemp()) + " °C" ); |
474 | Serial .println( "Температура на улице = " + String(getOutsideTemp()) + " °C" ); |
475 | Serial .println( "Текущий уровень модуляции горелки = " + String (getADC()) + " %" ); |
476 | Serial .println( "ADC = " + String (analogRead(A0))); |
477 |
478 | publish_temperature(); |
479 | publish_boilertemp(); |
480 | publish_dhwtemp(); |
481 | publish_outtemp(); |
482 | publish_ADC(); |
483 | publish_statusCH(); |
484 | publish_statusDWH(); |
485 | publish_statusFlame(); |
486 | publish_setBoilerTemperature(); |
487 | } |
488 | //MQTT Loop |
489 | if (!client.connected()) { |
490 | reconnect(); |
491 | } |
492 | client. loop (); |
493 | server.handleClient(); //handle http requests |
494 | } |
Посмотрел по диагонали, возможно не прав, у вас получение температуры в самом выводе 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 проблем нет.
1
Здравствуйте
Загрузил ваш скейтч
У меня котел не отключает помпу отопления. У Вас все нормально с этим
И возможно ли дописать скейтч для регулирования температуры горячей воды(можно только через mqtt)
Спасибо
VOVA_iS - не надо этот код брать, он откровенно плохо написан. Не шутите с газом.
VOVA_iS - не надо этот код брать, он откровенно плохо написан. Не шутите с газом.
Тут криво веб сервер написан
По работе котла код скопирован с примера библиотеки OpenTherm.
Mqtt работает хорошо
VOVA_iS, да, помпа молотит постоянно, повесил на термостат своё реле для её управления. Горячую воду то же сделал. Вообще много чего добавил и переделал, и продолжаю делать...
P.S. Да, сервер кривой :-(
VOVA_iS, от веб сервера решил отказаться, оставлю только конфигурационную страничку и всё, а для рулёжки достаточно mqtt.
VOVA_iS, да, помпа молотит постоянно, повесил на термостат своё реле для её управления. Горячую воду то же сделал. Вообще много чего добавил и переделал, и продолжаю делать...
P.S. Да, сервер кривой :-(
про помпу можно подробней:)?
скетч можно с горячей водой?
VOVA_iS, от веб сервера решил отказаться, оставлю только конфигурационную страничку и всё, а для рулёжки достаточно mqtt.
Да я согласен по MQTT управления выше крыши. И умный дом можно добавить полноценно
про помпу можно подробней:)?
скетч можно с горячей водой?
Для помпы использую библиотеку TimingRelay.h:
1
#define PIN_RELAY 12 // D6 На внешнее реле управления насосом СО и внешнюю индикацию работы насоса СО
2
TimingRelay relay(PIN_RELAY, 0, 180000);
// Создаем реле времени (назначенный порт, задержка включения, задержка выключения).
В setup:
1
pinMode(PIN_RELAY, OUTPUT);
2
relay.autoReset =
true
;
// Автоматический запуск таймера задержки выключения.
В loop реле работает по условию:
1
// обработка индикации и реле насоса СО.
2
if
(status_flame == 1 && status_dhw == 0) {
// Если состояния горелки = 1 и статус состояния ГВС = 0 включаем реле.
3
relay.on();
4
}
else
{
5
relay.off();
6
}
7
relay.
loop
();
// Обрабатываем (обязательно).
8
// конец обработки индикации и реле насоса СО.
С ГВС не так всё просто... Можно и по играться уставкой температуры ГВС. :-) При работе от внешнего термостата, а рекомендованный это QAA73, именно он задаёт режим работы ГВС, т.е. когда включить по таймеру (1 раз в неделю) прогрев системы до 65 гр., чтобы убить бацилы. При отсутствии внешнего это делает автоматика котла. Если в термостате этого нет, то ручками и по календарю. У меня пока не получилось автоматом, а может и получилось, полноценно не проверял, здесь, на форуме помогли.
Вот по порядку:
01
#define DAY(x) ((x) * 24 * MIN(60))
02
#define MIN(x) ((x) * 10000ul)
03
#define TIME1 DAY(7)
04
#define TIME2 MIN(60)
05
#define VAL1 50.
06
#define VAL2 60.
07
08
float
spdhw = VAL1;
// точка отсчета температуры горячей воды контура ГВС
09
unsigned
int
hex56 = (spdhw * 256 * 16 / 16);
// точка отсчета температуры горячей воды контура ГВС, но в (HEX)
10
11
// Обработчик входящих тем и полезных нагрузок MQTT
12
void
callback(
char
* topic,
byte
* payload, unsigned
int
length) {
13
char
buffer[length + 1];
14
memcpy(buffer, payload, length);
15
buffer[length] =
'\0'
;
16
float
a = atof((
char
*)buffer);
17
if
(a <= 29) {
18
sp = a;
19
}
else
{
20
spdhw = a;
21
}
22
//Serial.println("MQTT,topic = " + String(a));
23
}
В reconnect добавить строку
1
client.subscribe(
"sp"
);
// эта уже есть
2
client.subscribe(
"spdhw"
);
// добавить
В loop
01
//==============================================================================
02
// Активации функции Антилегионелла ГВС 65°C на 1 час каждые 7 дней по кругу.
03
//==============================================================================
04
05
static
bool
state;
06
static
uint32_t old_millis = millis();
07
if
(state ==
false
) {
08
if
(millis() - old_millis >= TIME1) {
09
old_millis = millis();
10
spdhw = VAL1;
11
state =
true
;
12
}
13
}
else
if
(millis() - old_millis >= TIME2) {
14
old_millis = millis();
15
spdhw = VAL2;
16
state =
false
;
17
}
А как параметр spdhw передаётся в котёл? не вижу
А как параметр spdhw передаётся в котёл? не вижу
Извиняюсь!
1
float
setDHWTemp() {
2
unsigned
long
request56 = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::TdhwSet, hex56);
3
unsigned
long
respons56 = ot.sendRequest(request56);
4
uint16_t dataValue56 = respons56 & 0xFFFF;
5
float
result56 = dataValue56 / 256;
6
return
result56;
7
}
В loop после 466 строки (код в начале) добавить:
1
// Заданная температура ГВС
2
hex56 = (spdhw * 256 * 16 / 16);
3
unsigned
int
setDHWTemp(hex56);
Ещё вырежу всю веб морду:-)
спасибо завтра попробую скомпилировать скетч
VOVA_iS, есть ещё одна задумка. В слимах по ОТ не передаётся уровень модуляции горелки. Можно сделать используя у ESP 8266 А0.
Интересная идея:-) только я на сколько помню на А0 может быть максимум 3.3v
надо сначала замеры сделать у котла:-)
VOVA_iS, вполне достаточно. С котла 9,10 контакты А5 с катушки модуляции идёт ШИМ. Надо ставить операционник, преобразовать ШИМ в линейные 0-5В, короче с него через делитель на А0. Программно потребуется ремапинг АЦП.
про помпу можно подробней:)?
скетч можно с горячей водой?
Для помпы использую библиотеку TimingRelay.h:
1
#define PIN_RELAY 12 // D6 На внешнее реле управления насосом СО и внешнюю индикацию работы насоса СО
2
TimingRelay relay(PIN_RELAY, 0, 180000);
// Создаем реле времени (назначенный порт, задержка включения, задержка выключения).
В setup:
1
pinMode(PIN_RELAY, OUTPUT);
2
relay.autoReset =
true
;
// Автоматический запуск таймера задержки выключения.
В loop реле работает по условию:
1
// обработка индикации и реле насоса СО.
2
if
(status_flame == 1 && status_dhw == 0) {
// Если состояния горелки = 1 и статус состояния ГВС = 0 включаем реле.
3
relay.on();
4
}
else
{
5
relay.off();
6
}
7
relay.
loop
();
// Обрабатываем (обязательно).
8
// конец обработки индикации и реле насоса СО.
Не могу скомпилировать идут ошибки
в коде данные переменные status_flame status_dhw не опредлены
Всё верно, статусы не задекларированы, вставьте это в loop выше индикации и состояния насоса:
01
// обработка индикации состояния ГВС.
02
bool
status_dhw = getHotWaterEnabled();
03
if
(status_dhw == 1) {
04
digitalWrite(EXTERNAL_LED, 1);
05
}
else
{
06
digitalWrite(EXTERNAL_LED, 0);
07
}
08
// обработка индикации состояния горелки.
09
bool
status_flame = getFlameOn();
10
if
(status_flame == 1) {
11
digitalWrite(EXTERNAL_LED_1, 1);
12
}
else
{
13
digitalWrite(EXTERNAL_LED_1, 0);
14
}
Условия для индикации можно отключить, а то опять ругаться будет при компиляции...
я так понимаю нужно это
1
void
callback(
char
* topic,
byte
* payload, unsigned
int
length) {
2
if
(strcmp(topic,
"sp"
) != 0)
return
;
3
String str = String();
4
for
(
int
i = 0; i < length; i++) {
5
str += (
char
)payload[i];
6
}
7
Serial
.println(
"MQTT, Setpoint Room Temperature = "
+ str);
8
sp = str.toFloat();
9
}
Заменить на это
01
// Обработчик входящих тем и полезных нагрузок MQTT
02
void
callback(
char
* topic,
byte
* payload, unsigned
int
length) {
03
char
buffer[length + 1];
04
memcpy(buffer, payload, length);
05
buffer[length] =
'\0'
;
06
float
a = atof((
char
*)buffer);
07
if
(a <= 29) {
08
sp = a;
09
}
else
{
10
spdhw = a;
11
}
Да, верно.
001
#include <ESP8266WiFi.h>
002
#include <PubSubClient.h>
003
#include <WiFiClient.h>
004
#include <TimingRelay.h>
005
#include <ESP8266mDNS.h>
006
#include <OneWire.h>
007
#include <DallasTemperature.h>
008
#include <OpenTherm.h> //<a href="<a href="https://github.com/ihormelnyk/opentherm_library/" rel="nofollow">https://github.com/ihormelnyk/opentherm_library/</a>" rel="nofollow"><a href="https://github.com/ihormelnyk/opentherm_library/" rel="nofollow">https://github.com/ihormelnyk/opentherm_library/</a></a>
009
010
//OpenTherm input and output wires connected to 4 and 5 pins on the OpenTherm Shield
011
const
int
inPin = 4;
//D2
012
const
int
outPin = 5;
//D1
013
014
#define ONE_WIRE_BUS 14 //D5 Data wire is connected to 14 pin on the OpenTherm Shield
015
#define BUILTIN_LED 2 //D4 Встроенный LED
016
#define PIN_RELAY 12 // D6 На внешнее реле управления насосом СО и внешнюю индикацию работы насоса СО
017
TimingRelay relay(PIN_RELAY, 0, 180000);
// Создаем реле времени (назначенный порт, задержка включения, задержка выключения).
018
019
020
const
char
* ssid =
"-------"
;
021
const
char
* password =
"----------"
;
022
const
char
* mqtt_server =
"192.168.10.1"
;
023
const
int
mqtt_port = 1883;
024
const
char
* mqtt_user =
""
;
025
const
char
* mqtt_password =
""
;
026
027
028
//ESP8266WebServer server(80); //Server on port 80
029
OneWire oneWire(ONE_WIRE_BUS);
030
DallasTemperature sensors(&oneWire);
031
OpenTherm ot(inPin, outPin);
032
WiFiClient espClient;
033
PubSubClient client(espClient);
034
char
buf[50];
035
036
float
sp = 20.00,
//set point
037
pv = 0,
//current temperature
038
pv_last = 0,
//prior temperature
039
ierr = 0,
//integral error
040
dt = 0,
//time between measurements
041
op = 0;
//PID controller output
042
unsigned
long
ts = 0, new_ts = 0;
//timestamp
043
bool
enableCentralHeating =
true
;
044
bool
enableHotWater =
true
;
045
bool
enableCooling =
false
;
046
047
int
timer_off = 180;
//Задержка выключения реле насоса СО 3 мин.
048
049
050
051
void
handleInterrupt() {
052
ot.handleInterrupt();
053
}
054
055
float
spdhw = 40;
// точка отсчета температуры горячей воды контура ГВС
056
unsigned
int
hex56 = (spdhw * 256 * 16 / 16);
// точка отсчета температуры горячей воды контура ГВС, но в (HEX)
057
058
float
getTemp() {
059
return
sensors.getTempCByIndex(0);
060
}
061
float
getBoilerTemp() {
062
return
ot.getBoilerTemperature();
063
}
064
float
setDHWTemp() {
065
unsigned
long
request56 = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::TdhwSet, hex56);
066
unsigned
long
respons56 = ot.sendRequest(request56);
067
uint16_t dataValue56 = respons56 & 0xFFFF;
068
float
result56 = dataValue56 / 256;
069
return
result56;
070
}
071
float
getDHWTemp() {
072
unsigned
long
request26 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Tdhw, 0);
073
unsigned
long
respons26 = ot.sendRequest(request26);
074
uint16_t dataValue26 = respons26 & 0xFFFF;
075
float
result26 = dataValue26 / 256;
076
return
result26;
077
}
078
float
getOutsideTemp() {
079
unsigned
long
request27 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Toutside, 0);
080
unsigned
long
respons27 = ot.sendRequest(request27);
081
uint16_t dataValue27 = respons27 & 0xFFFF;
082
if
(dataValue27 > 32768) {
083
//negative
084
float
result27 = -(65536 - dataValue27) / 256;
085
return
result27;
086
}
else
{
087
//positive
088
float
result27 = dataValue27 / 256;
089
return
result27;
090
}
091
}
092
int
getADC() {
093
delay(100);
094
int
ar = analogRead(A0);
095
ar = map(ar, 26, 1023, 0, 100);
096
return
ar;
097
}
098
unsigned
long
getCentralHeatingEnabled() {
099
unsigned
long
response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
100
OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
101
if
(responseStatus = OpenThermResponseStatus::SUCCESS)
102
return
ot.isCentralHeatingActive(response);
103
}
104
unsigned
long
getHotWaterEnabled() {
105
unsigned
long
response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
106
OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
107
if
(responseStatus = OpenThermResponseStatus::SUCCESS)
108
return
ot.isHotWaterActive(response);
109
}
110
unsigned
long
getFlameOn() {
111
unsigned
long
response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
112
OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
113
if
(responseStatus = OpenThermResponseStatus::SUCCESS)
114
return
ot.isFlameOn(response);
115
}
116
float
pid(
float
sp,
float
pv,
float
pv_last,
float
& ierr,
float
dt) {
117
float
Kc = 10.0;
// K / %Heater
118
float
tauI = 50.0;
// sec
119
float
tauD = 1.0;
// sec
120
// PID coefficients
121
float
KP = Kc;
122
float
KI = Kc / tauI;
123
float
KD = Kc * tauD;
124
// upper and lower bounds on heater level
125
float
ophi = 85;
126
float
oplo = 30;
127
// calculate the error
128
float
error = sp - pv;
129
// calculate the integral error
130
ierr = ierr + KI * error * dt;
131
// calculate the measurement derivative
132
float
dpv = (pv - pv_last) / dt;
133
// calculate the PID output
134
float
P = KP * error;
//proportional contribution
135
float
I = ierr;
//integral contribution
136
float
D = -KD * dpv;
//derivative contribution
137
float
op = P + I + D;
138
// implement anti-reset windup
139
if
((op < oplo) || (op > ophi)) {
140
I = I - KI * error * dt;
141
// clip output
142
op = max(oplo, min(ophi, op));
143
}
144
ierr = I;
145
Serial
.println(
"Заданное значение температуры в помещкнии = "
+ String(sp) +
" °C"
);
146
Serial
.println(
"Текущее значение температуры в помещкнии = "
+ String(pv) +
" °C"
);
147
Serial
.println(
"Выхов ПИД регулятора = "
+ String(op));
148
Serial
.println(
"Время между измерениями = "
+ String(dt) +
"; ПИД коэффициенты: П = "
+ String(P) +
"; И = "
+ String(I) +
"; Д = "
+ String(D));
149
return
op;
150
}
151
//===============================================================
152
// Эта процедура выполняется при открытии IP-адреса в браузере
153
//===============================================================
154
155
//==============================================================
156
// SETUP
157
//==============================================================
158
159
void
setup_wifi() {
160
delay(10);
161
//Connect to Wi-Fi Network
162
Serial
.println();
163
Serial
.print(
"Connecting to "
);
164
Serial
.println(ssid);
165
WiFi.mode(WIFI_STA);
166
WiFi.begin(ssid, password);
//Connect to your Wi-Fi router
167
// Wait for connection
168
while
(WiFi.status() != WL_CONNECTED) {
169
delay(500);
170
Serial
.print(
"."
);
171
}
172
//If connection successful show IP address in serial monitor
173
Serial
.println(
""
);
174
Serial
.println(
"WiFi connected"
);
175
Serial
.println(
"IP address: "
);
176
Serial
.println(WiFi.localIP());
177
}
178
void
setup
(
void
) {
179
pinMode(BUILTIN_LED, OUTPUT);
180
digitalWrite(BUILTIN_LED, 0);
181
Serial
.begin(115200);
182
setup_wifi();
183
pinMode (12, OUTPUT);
184
relay.autoReset =
true
;
// Автоматический запуск таймера задержки выключения.
185
if
(MDNS.begin(
"thermostat"
)) {
186
Serial
.println(
"MDNS responder started"
);
187
}
188
//Initialize Webserver
189
190
//==============================================================
191
// Init DS18B20 sensor
192
//==============================================================
193
sensors.begin();
194
sensors.requestTemperatures();
195
sensors.setWaitForConversion(
false
);
//switch to async mode
196
pv, pv_last = sensors.getTempCByIndex(0);
197
ts = millis();
198
//==============================================================
199
// Init OpenTherm Controller
200
//==============================================================
201
ot.begin(handleInterrupt);
202
//==============================================================
203
// Init MQTT Client
204
//==============================================================
205
client.setServer(mqtt_server, mqtt_port);
206
client.setCallback(callback);
207
}
208
void
publish_temperature() {
209
Serial
.println(
"MQTT, Current Room Temperature, °C = "
+ String(pv));
210
String(pv).toCharArray(buf, 50);
211
client.publish(
"pv"
, buf);
212
}
213
void
publish_boilertemp() {
214
Serial
.println(
"MQTT, CH Temperature, °C = "
+ String(getBoilerTemp()));
215
String(getBoilerTemp()).toCharArray(buf, 50);
216
client.publish(
"cht"
, buf);
217
}
218
void
publish_dhwtemp() {
219
Serial
.println(
"MQTT, DHW Temperature, °C = "
+ String(getDHWTemp()));
220
String(getDHWTemp()).toCharArray(buf, 50);
221
client.publish(
"dhwt"
, buf);
222
}
223
void
publish_outtemp() {
224
Serial
.println(
"MQTT, Outside Temperature, °C = "
+ String(getOutsideTemp()));
225
String(getOutsideTemp()).toCharArray(buf, 50);
226
client.publish(
"outt"
, buf);
227
}
228
void
publish_statusCH() {
229
Serial
.println(
"MQTT, Status Central Heating = "
+ String(getCentralHeatingEnabled()));
230
String(getCentralHeatingEnabled()).toCharArray(buf, 50);
231
client.publish(
"sch"
, buf);
232
}
233
void
publish_ADC() {
234
Serial
.println(
"MQTT, Relative Modulation Level, % = "
+ String(getADC()));
235
String(getADC()).toCharArray(buf, 50);
236
client.publish(
"adc"
, buf);
237
}
238
void
publish_statusDWH() {
239
Serial
.println(
"MQTT, Status Hot Water = "
+ String(getHotWaterEnabled()));
240
String(getHotWaterEnabled()).toCharArray(buf, 50);
241
client.publish(
"sdwh"
, buf);
242
}
243
void
publish_statusFlame() {
244
Serial
.println(
"MQTT, Status Central Heating = "
+ String(getFlameOn()));
245
String(getFlameOn()).toCharArray(buf, 50);
246
client.publish(
"sfl"
, buf);
247
}
248
void
publish_setBoilerTemperature() {
249
Serial
.println(
"MQTT, PID Controller Output, % = Set Point CH Temperature, °C = "
+ String(op));
250
String(op).toCharArray(buf, 50);
251
client.publish(
"op"
, buf);
252
}
253
void
callback(
char
* topic,
byte
* payload, unsigned
int
length) {
254
char
buffer[length + 1];
255
memcpy(buffer, payload, length);
256
buffer[length] =
'\0'
;
257
float
a = atof((
char
*)buffer);
258
if
(a <= 29) {
259
sp = a;
260
}
else
{
261
spdhw = a;
262
}
263
// Serial.println("MQTT, Setpoint Room Temperature = " + str);
264
// sp = str.toFloat();
265
}
266
void
reconnect() {
267
while
(!client.connected()) {
268
Serial
.print(
"Attempting MQTT connection..."
);
269
// Attempt to connect
270
if
(client.connect(
"ESP8266Client"
, mqtt_user, mqtt_password)) {
271
Serial
.println(
"connected"
);
272
// Once connected, publish an announcement...
273
publish_temperature();
274
publish_boilertemp();
275
publish_dhwtemp();
276
publish_outtemp();
277
publish_ADC();
278
publish_statusCH();
279
publish_statusDWH();
280
publish_statusFlame();
281
publish_setBoilerTemperature();
282
// ... and resubscribe
283
client.subscribe(
"sp"
);
284
client.subscribe(
"spdhw"
);
285
}
else
{
286
Serial
.print(
"failed, rc ="
);
287
Serial
.print(client.state());
288
Serial
.println(
" try again in 5 seconds"
);
289
// Wait 5 seconds before retrying
290
delay(5000);
291
}
292
}
293
}
294
//==============================================================
295
// LOOP
296
//==============================================================
297
void
loop
(
void
) {
298
new_ts = millis();
299
if
(new_ts - ts > 1000) {
300
unsigned
long
response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
301
OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
302
if
(responseStatus = OpenThermResponseStatus::SUCCESS) {
303
//Serial.println("Error: Invalid boiler response " + String(response, HEX));
304
Serial
.println(
"Текущий статус системы отопления: "
+ String(ot.isCentralHeatingActive(response) ?
"on"
:
"off"
));
305
Serial
.println(
"Текущий статус горячей воды: "
+ String(ot.isHotWaterActive(response) ?
"on"
:
"off"
));
306
Serial
.println(
"Текущий статус горелки: "
+ String(ot.isFlameOn(response) ?
"on"
:
"off"
));
307
Serial
.println(
"Индикация состояния неисправности: "
+ String(ot.isFault(response) ?
"fault"
:
"no fault"
));
308
Serial
.println(
"Диагностическая индикация: "
+ String(ot.isDiagnostic(response) ?
"diagnostics"
:
"no diagnostics"
));
309
}
310
if
(responseStatus == OpenThermResponseStatus::NONE) {
311
Serial
.println(
"Error: OpenTherm is not initialized"
);
312
}
313
else
if
(responseStatus == OpenThermResponseStatus::INVALID) {
314
Serial
.println(
"Error: Invalid response "
+ String(response, HEX));
315
}
316
else
if
(responseStatus == OpenThermResponseStatus::TIMEOUT) {
317
Serial
.println(
"Error: Response timeout"
);
318
}
319
pv = sensors.getTempCByIndex(0);
320
dt = (new_ts - ts) / 1000.0;
321
ts = new_ts;
322
if
(responseStatus == OpenThermResponseStatus::SUCCESS) {
323
op = pid(sp, pv, pv_last, ierr, dt);
324
//Set CH Temperature
325
ot.setBoilerTemperature(op);
326
hex56 = (spdhw * 256 * 16 / 16);
327
unsigned
int
setDHWTemp(hex56);
328
pv_last = pv;
329
sensors.requestTemperatures();
//async temperature request
330
}
331
// обработка индикации состояния ГВС.
332
bool
status_dhw = getHotWaterEnabled();
333
// обработка индикации состояния горелки.
334
bool
status_flame = getFlameOn();
335
//обработка индикации и реле насоса СО.
336
if
(status_flame == 1 && status_dhw == 0) {
// Если состояния горелки = 1 и статус состояния ГВС = 0 включаем реле.
337
relay.on();
338
}
else
{
339
relay.off();
340
}
341
relay.
loop
();
// Обрабатываем (обязательно).
342
//конец обработки индикации и реле насоса СО.
343
344
//Print Temperature
345
346
Serial
.println(
"Текущая температура контура СО = "
+ String(getBoilerTemp()) +
" °C"
);
347
Serial
.println(
"Текущая температура контура ГВС = "
+ String(getDHWTemp()) +
" °C"
);
348
Serial
.println(
"Температура на улице = "
+ String(getOutsideTemp()) +
" °C"
);
349
Serial
.println(
"Текущий уровень модуляции горелки = "
+ String (getADC()) +
" %"
);
350
Serial
.println(
"ADC = "
+ String (analogRead(A0)));
351
352
publish_temperature();
353
publish_boilertemp();
354
publish_dhwtemp();
355
publish_outtemp();
356
publish_ADC();
357
publish_statusCH();
358
publish_statusDWH();
359
publish_statusFlame();
360
publish_setBoilerTemperature();
361
}
362
//MQTT Loop
363
if
(!client.connected()) {
364
reconnect();
365
}
366
client.
loop
();
367
// server.handleClient(); //handle http requests
368
}
Скомпилировал код выше.
Увы температура горяей воды не передается.
Посмотрите где может быть ошибка?
ОК! Посмотрю. У меня сейчас проблема после обновления менеджера плат до версии 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.
001
#include <ESP8266WiFi.h>
002
#include <PubSubClient.h>
003
#include <WiFiClient.h>
004
#include <ESP8266WebServer.h>
005
#include <ESP8266mDNS.h>
006
#include <OneWire.h>
007
#include <DallasTemperature.h>
008
#include <OpenTherm.h>
009
#include <TimingRelay.h>
010
011
//Входные и выходные контакты OpenTherm, подключены к 4 и 5 контактам платы
012
const
int
inPin = 4;
//D2
013
const
int
outPin = 5;
//D1
014
015
#define ONE_WIRE_BUS 14 // D5 Data wire is connected to 14 pin on the OpenTherm Shield
016
#define BUILTIN_LED 2 // D4 Встроенный LED
017
#define PIN_RELAY 12 // D6 На внешнее реле управления насосом СО и внешнюю индикацию работы насоса СО
018
#define EXTERNAL_LED 13 // D7 На внешний LED индикации работы ГВС
019
#define EXTERNAL_LED_1 15 // D8 На внешний LED индикации работы горелки
020
#define EXTERNAL_LED_2 0 // D3 На внешний LED индикации работы авария
021
022
//#define DAY(x) ((x) * 10000ul) // тест
023
#define DAY(x) ((x) * 24 * MIN(60))
024
#define MIN(x) ((x) * 10000ul)
025
#define TIME1 DAY(7)
026
#define TIME2 MIN(60)
027
#define VAL1 50.
028
#define VAL2 60.
029
030
const
char
* ssid =
"********"
;
031
const
char
* password =
"*********"
;
032
const
char
* mqtt_server =
"***********"
;
033
const
int
mqtt_port = 12345;
034
const
char
* mqtt_user =
"***********"
;
035
const
char
* mqtt_password =
"*************"
;
036
037
ESP8266WebServer server(80);
// Сервер на порту 80
038
OneWire oneWire(ONE_WIRE_BUS);
039
DallasTemperature sensors(&oneWire);
040
OpenTherm ot(inPin, outPin);
041
WiFiClient espClient;
042
PubSubClient client(espClient);
043
char
buf[50];
044
045
float
sp = 20,
// точка отсчета комнатной температуры
046
pv = 0,
// текущая температура в комнате
047
pv_last = 0,
// предыдущая температура
048
ierr = 0,
// интегральная погрешность
049
dt = 0,
// время между измерениями
050
op = 0,
// выход ПИД контроллера
051
spdhw = VAL1;
// точка отсчета температуры горячей воды контура ГВС
052
unsigned
long
ts = 0, new_ts = 0;
// отметки времени
053
unsigned
int
hex56 = (spdhw * 256 * 16 / 16);
// точка отсчета температуры горячей воды контура ГВС, но в (HEX)
054
unsigned
int
data126 = 0x013F;
// тип и версия термостата (HEX)
055
bool
enableCentralHeating =
true
;
056
bool
enableHotWater =
true
;
057
bool
enableCooling =
false
;
058
059
TimingRelay relay(PIN_RELAY, 0, 180000);
// Создаем реле времени (назначенный порт, задержка включения, задержка выключения).
060
061
//===============================================================
062
// Обрабатываем внешние прерывания
063
//===============================================================
064
void
handleInterrupt() {
065
ot.handleInterrupt();
066
}
067
068
float
getTemp() {
069
return
sensors.getTempCByIndex(0);
070
}
071
float
getBoilerTemp() {
072
return
ot.getBoilerTemperature();
073
}
074
float
getDHWTemp() {
075
unsigned
long
request26 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Tdhw, 0);
076
unsigned
long
respons26 = ot.sendRequest(request26);
077
uint16_t dataValue26 = respons26 & 0xFFFF;
078
float
result26 = dataValue26 / 256;
079
return
result26;
080
}
081
float
getOutsideTemp() {
082
unsigned
long
request27 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Toutside, 0);
083
unsigned
long
respons27 = ot.sendRequest(request27);
084
uint16_t dataValue27 = respons27 & 0xFFFF;
085
if
(dataValue27 > 32768) {
086
//negative
087
float
result27 = -(65536 - dataValue27) / 256;
088
return
result27;
089
}
else
{
090
//positive
091
float
result27 = dataValue27 / 256;
092
return
result27;
093
}
094
}
095
int
getADC() {
096
delay(100);
097
int
ar = analogRead(A0);
098
ar = map(ar, 0, 1023, 0, 100);
099
return
ar;
100
}
101
unsigned
long
getFault() {
102
unsigned
long
response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
103
OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
104
if
(responseStatus = OpenThermResponseStatus::SUCCESS)
105
return
ot.isFault(response);
106
}
107
unsigned
long
getCentralHeatingEnabled() {
108
unsigned
long
response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
109
OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
110
if
(responseStatus = OpenThermResponseStatus::SUCCESS)
111
return
ot.isCentralHeatingEnabled(response);
112
}
113
unsigned
long
getHotWaterEnabled() {
114
unsigned
long
response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
115
OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
116
if
(responseStatus = OpenThermResponseStatus::SUCCESS)
117
return
ot.isHotWaterEnabled(response);
118
}
119
unsigned
long
getFlameOn() {
120
unsigned
long
response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
121
OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
122
if
(responseStatus = OpenThermResponseStatus::SUCCESS)
123
return
ot.isFlameOn(response);
124
}
125
unsigned
int
getSlaveVersion_type() {
126
unsigned
long
request127 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::SlaveVersion, 0);
127
unsigned
long
respons127 = ot.sendRequest(request127);
128
uint16_t dataValue127 = respons127 & 0xFFFF;
129
unsigned result127 = dataValue127 / 256;
130
return
result127;
131
}
132
unsigned
int
getSlaveVersion_num() {
133
unsigned
long
request127 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::SlaveVersion, 0);
134
unsigned
long
respons127 = ot.sendRequest(request127);
135
uint8_t dataValue127_ = respons127 & 0xFF;
136
unsigned result127_ = dataValue127_;
137
return
result127_;
138
}
139
unsigned
int
setMasterVersion_type() {
140
unsigned
long
request126 = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::MasterVersion, data126);
141
unsigned
long
respons126 = ot.sendRequest(request126);
142
uint16_t dataValue126 = respons126 & 0xFFFF;
143
unsigned result126 = dataValue126 / 256;
144
return
result126;
145
}
146
unsigned
int
setMasterVersion_num() {
147
unsigned
long
request126 = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::MasterVersion, data126);
148
unsigned
long
respons126 = ot.sendRequest(request126);
149
uint8_t dataValue126_ = respons126 & 0xFF;
150
unsigned result126_ = dataValue126_;
151
return
result126_;
152
}
153
float
setDHWTemp() {
154
unsigned
long
request56 = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::TdhwSet, hex56);
155
unsigned
long
respons56 = ot.sendRequest(request56);
156
uint16_t dataValue56 = respons56 & 0xFFFF;
157
float
result56 = dataValue56 / 256;
158
return
result56;
159
}
160
unsigned
int
getFaultCode() {
161
unsigned
long
request5 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::ASFflags, 0);
162
unsigned
long
respons5 = ot.sendRequest(request5);
163
uint8_t dataValue5 = respons5 & 0xFF;
164
unsigned result5 = dataValue5;
165
return
result5;
166
}
167
//===============================================================
168
// Вычисляем коэффициенты ПИД регулятора
169
//===============================================================
170
float
pid(
float
sp,
float
pv,
float
pv_last,
float
& ierr,
float
dt) {
171
float
Kc = 10.0;
// K / %Heater
172
float
tauI = 50.0;
// sec
173
float
tauD = 1.0;
// sec
174
// ПИД коэффициенты
175
float
KP = Kc;
176
float
KI = Kc / tauI;
177
float
KD = Kc * tauD;
178
// верхняя и нижняя границы уровня нагрева
179
float
ophi = 100;
180
float
oplo = 0;
181
// вычислить ошибку
182
float
error = sp - pv;
183
// calculate the integral error
184
ierr = ierr + KI * error * dt;
185
// вычислить производную измерения
186
float
dpv = (pv - pv_last) / dt;
187
// рассчитать выход ПИД регулятора
188
float
P = KP * error;
// пропорциональная составляющая
189
float
I = ierr;
// интегральная составляющая
190
float
D = -KD * dpv;
// дифференциальная составляющая
191
float
op = P + I + D;
192
// защита от сброса
193
if
((op < oplo) || (op > ophi)) {
194
I = I - KI * error * dt;
195
// выход регулятора, он же уставка для ID-1 (температура теплоносителя контура СО котла)
196
op = max(oplo, min(ophi, op));
197
}
198
ierr = I;
199
Serial
.println(
"Заданное значение температуры в помещении = "
+ String(sp) +
" °C"
);
200
Serial
.println(
"Текущее значение температуры в помещении = "
+ String(pv) +
" °C"
);
201
Serial
.println(
"Выхов ПИД регулятора = "
+ String(op));
202
Serial
.println(
"Время между измерениями = "
+ String(dt) +
"; ПИД коэффициенты: П = "
+ String(P) +
"; И = "
+ String(I) +
"; Д = "
+ String(D));
203
return
op;
204
}
205
//===============================================================
206
// Эта процедура выполняется при открытии IP-адреса в браузере
207
//===============================================================
208
void
handleRoot() {
209
digitalWrite(BUILTIN_LED, 1);
210
if
(server.method() == HTTP_POST) {
211
for
(uint8_t i = 0; i < server.args(); i++) {
212
if
(server.argName(i) ==
"sp"
) {
213
sp = server.arg(i).toFloat();
214
}
215
if
(server.argName(i) ==
"spdhw"
) {
216
spdhw = server.arg(i).toFloat();
217
}
218
}
219
}
220
String page = FPSTR(HTTP_HTML);
221
page.replace(
"{0}"
, String(getTemp()));
222
page.replace(
"{1}"
, String((
float
)sp));
223
page.replace(
"{2}"
, String(getBoilerTemp()));
224
page.replace(
"{3}"
, String(getDHWTemp()));
225
page.replace(
"{4}"
, String(getOutsideTemp()));
226
page.replace(
"{5}"
, String(getCentralHeatingEnabled() ?
"on"
:
"off"
));
227
page.replace(
"{6}"
, String(getHotWaterEnabled() ?
"on"
:
"off"
));
228
page.replace(
"{7}"
, String(getFlameOn() ?
"on"
:
"off"
));
229
page.replace(
"{8}"
, String(getADC()));
230
page.replace(
"{9}"
, String((
float
)spdhw));
231
server.send(200,
"text/html"
, page);
232
digitalWrite(BUILTIN_LED, 0);
233
}
234
void
handleGetTemp() {
235
digitalWrite(BUILTIN_LED, 1);
236
server.send(200,
"text/plain"
, String(getTemp()));
237
digitalWrite(BUILTIN_LED, 0);
238
}
239
void
handleGetBoilerTemp() {
240
digitalWrite(BUILTIN_LED, 1);
241
server.send(200,
"text/plain"
, String(getBoilerTemp()));
242
digitalWrite(BUILTIN_LED, 0);
243
}
244
void
handleGetDHWTemp() {
245
digitalWrite(BUILTIN_LED, 1);
246
server.send(200,
"text/plain"
, String(getDHWTemp()));
247
digitalWrite(BUILTIN_LED, 0);
248
}
249
void
handleGetOutsideTemp() {
250
digitalWrite(BUILTIN_LED, 1);
251
server.send(200,
"text/plain"
, String(getOutsideTemp()));
252
digitalWrite(BUILTIN_LED, 0);
253
}
254
void
handleGetCentralHeatingEnabled() {
255
digitalWrite(BUILTIN_LED, 1);
256
server.send(200,
"text/plain"
, String(getCentralHeatingEnabled() ?
"on"
:
"off"
));
257
digitalWrite(BUILTIN_LED, 0);
258
}
259
void
handleGetHotWaterEnabled() {
260
digitalWrite(BUILTIN_LED, 1);
261
server.send(200,
"text/plain"
, String(getHotWaterEnabled() ?
"on"
:
"off"
));
262
digitalWrite(BUILTIN_LED, 0);
263
}
264
void
handleGetFlameOn() {
265
digitalWrite(BUILTIN_LED, 1);
266
server.send(200,
"text/plain"
, String(getFlameOn() ?
"on"
:
"off"
));
267
digitalWrite(BUILTIN_LED, 0);
268
}
269
void
handleADC() {
270
digitalWrite(BUILTIN_LED, 1);
271
server.send(200,
"text/plain"
, String(getADC()));
272
digitalWrite(BUILTIN_LED, 0);
273
}
274
//==============================================================
275
// Подключаемся к сети WiFi
276
//==============================================================
277
void
setup_wifi() {
278
delay(10);
279
// подключение к сети Wi-Fi
280
Serial
.println();
281
Serial
.print(
"Connecting to "
);
282
Serial
.println(ssid);
283
WiFi.mode(WIFI_STA);
284
WiFi.begin(ssid, password);
// Подключение к маршрутизатору Wi-Fi
285
// дождаться соединения
286
while
(WiFi.status() != WL_CONNECTED) {
287
delay(500);
288
Serial
.print(
"."
);
289
}
290
// если соединение успешно, показывает IP-адрес в мониторе
291
Serial
.println(
""
);
292
Serial
.println(
"WiFi connected"
);
293
Serial
.println(
"IP address: "
);
294
Serial
.println(WiFi.localIP());
295
}
296
//==============================================================
297
// Функция SETUP
298
//==============================================================
299
void
setup
() {
300
pinMode(BUILTIN_LED, OUTPUT);
301
pinMode(EXTERNAL_LED, OUTPUT);
302
pinMode(EXTERNAL_LED_1, OUTPUT);
303
pinMode(EXTERNAL_LED_2, OUTPUT);
304
pinMode(PIN_RELAY, OUTPUT);
305
digitalWrite(BUILTIN_LED, 0);
306
digitalWrite(EXTERNAL_LED, 0);
307
digitalWrite(EXTERNAL_LED_1, 0);
308
digitalWrite(EXTERNAL_LED_2, 0);
309
relay.autoReset =
true
;
// Автоматический запуск таймера задержки выключения.
310
Serial
.begin(115200);
311
setup_wifi();
312
if
(MDNS.begin(
"thermostat"
)) {
313
Serial
.println(
"MDNS responder started"
);
314
}
315
// инициализация web сервера
316
server.on(
"/"
, handleRoot);
// Ответ сервера на запрос главной страницы
317
server.on(
"/temp"
, handleGetTemp);
// Ответ сервера на запрос температуры в помещении
318
server.on(
"/boilertemp"
, handleGetBoilerTemp);
// Ответ сервера на запрос температуры подачи котла
319
server.on(
"/dhwttemp"
, handleGetDHWTemp);
// Ответ сервера на запрос температуры горячей воды
320
server.on(
"/outsidetemp"
, handleGetOutsideTemp);
// Ответ сервера на запрос уличной температуры
321
server.on(
"/chon"
, handleGetCentralHeatingEnabled);
// Ответ сервера на запрос состояния СО
322
server.on(
"/hwon"
, handleGetHotWaterEnabled);
// Ответ сервера на запрос состояния ГВС
323
server.on(
"/flameon"
, handleGetFlameOn);
// Ответ сервера на запрос состояния горелки
324
server.on(
"/getadc"
, handleADC);
// Ответ сервера на запрос уровня модуляции горелки
325
server.begin();
326
Serial
.println(
"HTTP server started"
);
327
328
//==============================================================
329
// Инициализация датчика температуры DS18B20
330
//==============================================================
331
sensors.begin();
332
sensors.requestTemperatures();
333
sensors.setWaitForConversion(
false
);
// Переключиться в асинхронный режим
334
pv, pv_last = sensors.getTempCByIndex(0);
335
ts = millis();
336
//==============================================================
337
// Инициализация OpenTherm
338
//==============================================================
339
ot.begin(handleInterrupt);
340
//==============================================================
341
// Инициализация MQTT клиента
342
//==============================================================
343
client.setServer(mqtt_server, mqtt_port);
// подключаемся к MQTT
344
client.setCallback(callback);
// функция получения топиков с брокера
345
}
346
void
publish_temperature() {
347
Serial
.println(
"MQTT, Current Room Temperature, °C = "
+ String(pv));
348
String(pv).toCharArray(buf, 50);
349
client.publish(
"pv"
, buf);
350
}
351
void
publish_boilertemp() {
352
Serial
.println(
"MQTT, CH Temperature, °C = "
+ String(getBoilerTemp()));
353
String(getBoilerTemp()).toCharArray(buf, 50);
354
client.publish(
"cht"
, buf);
355
}
356
void
publish_dhwtemp() {
357
Serial
.println(
"MQTT, DHW Temperature, °C = "
+ String(getDHWTemp()));
358
String(getDHWTemp()).toCharArray(buf, 50);
359
client.publish(
"dhwt"
, buf);
360
}
361
void
publish_outtemp() {
362
Serial
.println(
"MQTT, Outside Temperature, °C = "
+ String(getOutsideTemp()));
363
String(getOutsideTemp()).toCharArray(buf, 50);
364
client.publish(
"outt"
, buf);
365
}
366
void
publish_statusCH() {
367
Serial
.println(
"MQTT, Status Central Heating = "
+ String(getCentralHeatingEnabled()));
368
String(getCentralHeatingEnabled()).toCharArray(buf, 50);
369
client.publish(
"sch"
, buf);
370
}
371
void
publish_ADC() {
372
Serial
.println(
"MQTT, Relative Modulation Level, % = "
+ String(getADC()));
373
String(getADC()).toCharArray(buf, 50);
374
client.publish(
"adc"
, buf);
375
}
376
void
publish_statusDWH() {
377
Serial
.println(
"MQTT, Status Hot Water = "
+ String(getHotWaterEnabled()));
378
String(getHotWaterEnabled()).toCharArray(buf, 50);
379
client.publish(
"sdwh"
, buf);
380
}
381
void
publish_statusFlame() {
382
Serial
.println(
"MQTT, Status Central Heating = "
+ String(getFlameOn()));
383
String(getFlameOn()).toCharArray(buf, 50);
384
client.publish(
"sfl"
, buf);
385
}
386
void
publish_setBoilerTemperature() {
387
Serial
.println(
"MQTT, PID Controller Output, % = Setpoint CH Temperature, °C = "
+ String(op));
388
String(op).toCharArray(buf, 50);
389
client.publish(
"op"
, buf);
390
}
391
// функция обратного вызова, чтение топиков
392
void
callback(
char
* topic,
byte
* payload, unsigned
int
length) {
393
char
buffer[length + 1];
394
memcpy(buffer, payload, length);
395
buffer[length] =
'\0'
;
396
float
a = atof((
char
*)buffer);
397
if
(a <= 29) {
398
sp = a;
399
}
else
{
400
spdhw = a;
401
}
402
Serial
.println(
"MQTT,topic = "
+ String(a));
403
}
404
void
reconnect() {
405
while
(!client.connected()) {
406
Serial
.print(
"Attempting MQTT connection..."
);
407
// Attempt to connect
408
if
(client.connect(
"ESP8266Client"
, mqtt_user, mqtt_password)) {
409
Serial
.println(
"connected"
);
410
// после подключения публикуем объявление...
411
publish_temperature();
412
publish_boilertemp();
413
publish_dhwtemp();
414
publish_outtemp();
415
publish_ADC();
416
publish_statusCH();
417
publish_statusDWH();
418
publish_statusFlame();
419
publish_setBoilerTemperature();
420
// ... и перезаписываем
421
client.subscribe(
"sp"
);
422
client.subscribe(
"spdhw"
);
423
}
else
{
424
Serial
.print(
"failed, rc ="
);
425
Serial
.print(client.state());
426
Serial
.println(
" try again in 5 seconds"
);
427
// Подождать 5 сек. перед повторной попыткой
428
delay(5000);
429
}
430
}
431
}
432
//==============================================================
433
// Функция LOOP
434
//==============================================================
435
void
loop
() {
436
new_ts = millis();
437
if
(new_ts - ts > 1000) {
438
unsigned
long
response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
439
OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
440
if
(responseStatus = OpenThermResponseStatus::SUCCESS) {
441
//Serial.println("Error: Invalid boiler response " + String(response, HEX));
442
}
443
if
(responseStatus == OpenThermResponseStatus::NONE) {
444
Serial
.println(
"Error: OpenTherm is not initialized"
);
445
}
446
else
if
(responseStatus == OpenThermResponseStatus::INVALID) {
447
Serial
.println(
"Error: Invalid response "
+ String(response, HEX));
448
}
449
else
if
(responseStatus == OpenThermResponseStatus::TIMEOUT) {
450
Serial
.println(
"Error: Response timeout"
);
451
}
452
pv = sensors.getTempCByIndex(0);
453
dt = (new_ts - ts) / 1000.0;
454
ts = new_ts;
455
if
(responseStatus == OpenThermResponseStatus::SUCCESS) {
456
op = pid(sp, pv, pv_last, ierr, dt);
457
// Заданная температура СО
458
ot.setBoilerTemperature(op);
459
// Заданная температура ГВС
460
hex56 = (spdhw * 256 * 16 / 16);
461
unsigned
int
setDHWTemp(hex56);
462
pv_last = pv;
463
sensors.requestTemperatures();
//Асинхронный запрос температуры
464
}
465
// Записать ID-2; мастер-код MemberID
466
unsigned
int
data = 0x0004;
467
unsigned
long
request = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::MConfigMMemberIDcode, data);
468
ot.sendRequest(request);
469
// unsigned long request5 = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::ASFflags, 0);
470
// unsigned long respons5 = ot.sendRequest(request5);
471
// uint8_t dataValue5 = respons5 & 0xFF;
472
// unsigned result5 = dataValue5;
473
// Выводим в монитор
474
Serial
.println(
"Текущий статус системы отопления: "
+ String(ot.isCentralHeatingEnabled(response) ?
"on"
:
"off"
));
475
Serial
.println(
"Текущий статус горячей воды: "
+ String(ot.isHotWaterEnabled(response) ?
"on"
:
"off"
));
476
Serial
.println(
"Текущий статус горелки: "
+ String(ot.isFlameOn(response) ?
"on"
:
"off"
));
477
Serial
.println(
"Индикация состояния неисправности: "
+ String(ot.isFault(response) ?
"fault"
:
"no fault"
));
478
Serial
.println(
"Диагностическая индикация: "
+ String(ot.isDiagnostic(response) ?
"diagnostics"
:
"no diagnostics"
));
479
Serial
.println(
"Текущая температура контура СО = "
+ String(getBoilerTemp()) +
" °C"
);
480
Serial
.println(
"Текущая температура контура ГВС = "
+ String(getDHWTemp()) +
" °C"
);
481
Serial
.println(
"Температура на улице = "
+ String(getOutsideTemp()) +
" °C"
);
482
Serial
.println(
"Текуший уровень модуляции горелки = "
+ String (getADC()) +
" %"
);
483
Serial
.println(
"ADC = "
+ String (analogRead(A0)));
484
Serial
.println(
"Уставка температуры ГВС = "
+ String(setDHWTemp()) +
" °C"
);
485
Serial
.println(
"Тип и версия термостата: тип "
+ String(setMasterVersion_type()) +
", версия "
+ String(setMasterVersion_num()));
486
Serial
.println(
"Тип и версия котла: тип "
+ String(getSlaveVersion_type()) +
", версия "
+ String(getSlaveVersion_num()));
487
//=================================================================================================
488
// Индикация и реле насоса СО c откл. с задержкой 3 мин., индикация работы ГВС, горелки
489
//=================================================================================================
490
int
status_error = getFault();
// индикация состояния авария.
491
if
(status_error == 1) {
492
Serial
.println(
"Код неисправности: E "
+ String(getFaultCode()));
493
digitalWrite(EXTERNAL_LED_2, 1);
494
}
else
{
495
digitalWrite(EXTERNAL_LED_2, 0);
496
}
497
int
status_dhw = getHotWaterEnabled();
// индикация состояния ГВС.
498
if
(status_dhw == 1) {
499
digitalWrite(EXTERNAL_LED, 1);
500
}
else
{
501
digitalWrite(EXTERNAL_LED, 0);
502
}
503
int
status_flame = getFlameOn();
// индикация состояния горелки.
504
if
(status_flame == 1) {
505
digitalWrite(EXTERNAL_LED_1, 1);
506
}
else
{
507
digitalWrite(EXTERNAL_LED_1, 0);
508
}
509
if
(status_flame == 1 && status_dhw == 0) {
// Если состояния горелки = 1 и статус состояния ГВС = 0 включаем реле.
510
relay.on();
511
}
else
{
512
relay.off();
513
}
514
relay.
loop
();
// Обрабатываем (обязательно).
515
publish_temperature();
516
publish_boilertemp();
517
publish_dhwtemp();
518
publish_outtemp();
519
publish_ADC();
520
publish_statusCH();
521
publish_statusDWH();
522
publish_statusFlame();
523
publish_setBoilerTemperature();
524
//publish_setDHWTemperature();
525
}
526
// MQTT Loop
527
if
(!client.connected()) {
528
reconnect();
529
}
530
client.
loop
();
531
server.handleClient();
// обработка http-запросов
532
//=================================================================================================
533
// Активации функции Антилегионелла ГВС 65°C на 1 час каждые 7 дней по кругу.
534
//=================================================================================================
535
static
bool
state;
536
static
uint32_t old_millis = millis();
537
if
(state ==
false
) {
538
if
(millis() - old_millis >= TIME1) {
539
old_millis = millis();
540
spdhw = VAL1;
541
state =
true
;
542
}
543
}
else
if
(millis() - old_millis >= TIME2) {
544
old_millis = millis();
545
spdhw = VAL2;
546
state =
false
;
547
}
548
}
P.S.
Так и есть, атрибут ICACHE_RAM_ATTR теперь обязателен в коде, функциях, вызываемых по прерываниям. Проблема работы с 2.5.2 разрешилась.
Проблему с помпой решил немного по другому
в секцию loop добавил
1
if
(pv >= sp) {
2
enableCentralHeating =
false
;
3
}
else
{
4
enableCentralHeating =
true
; }
После добавления этого кода котёл по достижению заданной температуры выключает режим отопления и соответсвенно насос, через заданное время в настройке котла
Проблема с горячей водой так и осталось. Регулирование нету
даже в логах пишется что заданная температура 0
но если изменить код вот так
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;
То заданная температура отображается(температура гвс установлена до подключение платы), но не устанавливается
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 видна?
но если изменить код вот так
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;
Тогда видна
В протоколе ОТ ID Msg 56 имеет | R W | DHW setpoint | f8.8 | 0..127 | Domestic hot water temperature setpoint (°C)
Можно прочитать, что записалось и вывести в монитор, отдельно я этого не делал, просто проверял ранее и всё было ОК. Точно не помню, как переменную обзывал...
1
float
getDHWTemp() {
2
unsigned
long
request56 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::TdhwSet, hex56);
3
unsigned
long
respons56 = ot.sendRequest(request56);
4
uint16_t dataValue56 = respons56 & 0xFFFF;
5
float
result56 = dataValue56 / 256;
6
return
result56;
7
}
VOVA_iS, а как определили, что в котёл не ушла уставка?
1
if
(pv >= sp) {
2
enableCentralHeating =
false
;
3
}
else
{
4
enableCentralHeating =
true
; }
С этим кодом работы помпы ОТ отключается...т.е. интерфейс активируется только при этом условии, а если ОТ не активен, то целевую температуру контур ГВС не примет, стало быть, котёл работает в этом промежутке, в обычном режиме, ON/OFF (перемычка). Хотя, могу и ошибаться, погонять надо...
VOVA_iS, а как определили, что в котёл не ушла уставка?
Банально включаю воду и смотрю на сколько нагрел котел. Было 38 ставлю 45. Все равно греет до 38.
1
if
(pv >= sp) {
2
enableCentralHeating =
false
;
3
}
else
{
4
enableCentralHeating =
true
; }
С этим кодом работы помпы ОТ отключается...т.е. интерфейс активируется только при этом условии, а если ОТ не активен, то целевую температуру контур ГВС не примет, стало быть, котёл работает в этом промежутке, в обычном режиме, ON/OFF (перемычка). Хотя, могу и ошибаться, погонять надо...
Это решение было найдено после проблемы с регулировкой температуры.
С параметром READ и WRITE_ACK установленную температуру читает.
Вообщем добавил свою переменную в MQTT
1
void
publish_setDHWTemp() {
2
Serial
.println(
"MQTT, DHW Temperature Set, °C = "
+ String(setDHWTemp()));
3
String(setDHWTemp()).toCharArray(buf, 50);
4
client.publish(
"dhws"
, buf);
1
Она почему-то в консоли появляется один раз.
Лог с WRITE
VOVA_iS, а как определили, что в котёл не ушла уставка?
Банально включаю воду и смотрю на сколько нагрел котел. Было 38 ставлю 45. Все равно греет до 38.
Гистерезис большой, не зацикливаюсь. Я сейчас откопал свой рабочий код без WEB, внёс в него вашу правку помпы, проверил работает. Только обновите библиотеку ОТ. Там слово Enable заменено на Active (код правленный).
001
/*
002
OpenThermMessageID:
003
004
Status, // flag8 / flag8 Master and Slave Status flags.
005
TSet, // f8.8 Control setpoint ie CH water temperature setpoint (°C)
006
MConfigMMemberIDcode, // flag8 / u8 Master Configuration Flags / Master MemberID Code
007
SConfigSMemberIDcode, // flag8 / u8 Slave Configuration Flags / Slave MemberID Code
008
Command, // u8 / u8 Remote Command
009
ASFflags, // / OEM-fault-code flag8 / u8 Application-specific fault flags and OEM fault code
010
RBPflags, // flag8 / flag8 Remote boiler parameter transfer-enable & read/write flags
011
CoolingControl, // f8.8 Cooling control signal (%)
012
TsetCH2, // f8.8 Control setpoint for 2e CH circuit (°C)
013
TrOverride, // f8.8 Remote override room setpoint
014
TSP, // u8 / u8 Number of Transparent-Slave-Parameters supported by slave
015
TSPindexTSPvalue, // u8 / u8 Index number / Value of referred-to transparent slave parameter.
016
FHBsize, // u8 / u8 Size of Fault-History-Buffer supported by slave
017
FHBindexFHBvalue, // u8 / u8 Index number / Value of referred-to fault-history buffer entry.
018
MaxRelModLevelSetting, // f8.8 Maximum relative modulation level setting (%)
019
MaxCapacityMinModLevel, // u8 / u8 Maximum boiler capacity (kW) / Minimum boiler modulation level(%)
020
TrSet, // f8.8 Room Setpoint (°C)
021
RelModLevel, // f8.8 Relative Modulation Level (%)
022
CHPressure, // f8.8 Water pressure in CH circuit (bar)
023
DHWFlowRate, // f8.8 Water flow rate in DHW circuit. (litres/minute)
024
DayTime, // special / u8 Day of Week and Time of Day
025
Date, // u8 / u8 Calendar date
026
Year, // u16 Calendar year
027
TrSetCH2, // f8.8 Room Setpoint for 2nd CH circuit (°C)
028
Tr, // f8.8 Room temperature (°C)
029
Tboiler, // f8.8 Boiler flow water temperature (°C)
030
Tdhw, // f8.8 DHW temperature (°C)
031
Toutside, // f8.8 Outside temperature (°C)
032
Tret, // f8.8 Return water temperature (°C)
033
Tstorage, // f8.8 Solar storage temperature (°C)
034
Tcollector, // f8.8 Solar collector temperature (°C)
035
TflowCH2, // f8.8 Flow water temperature CH2 circuit (°C)
036
Tdhw2, // f8.8 Domestic hot water temperature 2 (°C)
037
Texhaust, // s16 Boiler exhaust temperature (°C)
038
TdhwSetUBTdhwSetLB = 48, // s8 / s8 DHW setpoint upper & lower bounds for adjustment (°C)
039
MaxTSetUBMaxTSetLB, // s8 / s8 Max CH water setpoint upper & lower bounds for adjustment (°C)
040
HcratioUBHcratioLB, // s8 / s8 OTC heat curve ratio upper & lower bounds for adjustment
041
TdhwSet = 56, // f8.8 DHW setpoint (°C) (Remote parameter 1)
042
MaxTSet, // f8.8 Max CH water setpoint (°C) (Remote parameters 2)
043
Hcratio, // f8.8 OTC heat curve ratio (°C) (Remote parameter 3)
044
RemoteOverrideFunction = 100, // flag8 / - Function of manual and program changes in master and remote room setpoint.
045
OEMDiagnosticCode = 115, // u16 OEM-specific diagnostic/service code
046
BurnerStarts, // u16 Number of starts burner
047
CHPumpStarts, // u16 Number of starts CH pump
048
DHWPumpValveStarts, // u16 Number of starts DHW pump/valve
049
DHWBurnerStarts, // u16 Number of starts burner during DHW mode
050
BurnerOperationHours, // u16 Number of hours that burner is in operation (i.e. flame on)
051
CHPumpOperationHours, // u16 Number of hours that CH pump has been running
052
DHWPumpValveOperationHours, // u16 Number of hours that DHW pump has been running or DHW valve has been opened
053
DHWBurnerOperationHours, // u16 Number of hours that burner is in operation during DHW mode
054
OpenThermVersionMaster, // f8.8 The implemented version of the OpenTherm Protocol Specification in the master.
055
OpenThermVersionSlave, // f8.8 The implemented version of the OpenTherm Protocol Specification in the slave.
056
MasterVersion, // u8 / u8 Master product version number and type
057
SlaveVersion, // u8 / u8 Slave product version number and type
058
*/
059
060
#include <ESP8266WiFi.h>
061
#include <PubSubClient.h>
062
#include <WiFiClient.h>
063
#include <OneWire.h>
064
#include <DallasTemperature.h>
065
#include <OpenTherm.h>
066
067
//Входные и выходные контакты OpenTherm, подключены к 4 и 5 контактам платы
068
const
int
inPin = 4;
//D2
069
const
int
outPin = 5;
//D1
070
071
#define ONE_WIRE_BUS 14 // D5 Data wire is connected to 14 pin on the OpenTherm Shield
072
#define BUILTIN_LED 2 // D4 Встроенный LED
073
#define EXTERNAL_LED_3 12 // D6 На внешний LED индикации работы СО
074
#define EXTERNAL_LED 13 // D7 На внешний LED индикации работы ГВС
075
#define EXTERNAL_LED_1 15 // D8 На внешний LED индикации работы горелки
076
#define EXTERNAL_LED_2 0 // D3 На внешний LED индикации работы авария
077
078
//#define DAY(x) ((x) * 10000ul) // тест
079
#define DAY(x) ((x) * 24 * MIN(60))
080
#define MIN(x) ((x) * 10000ul)
081
#define TIME1 DAY(7)
082
#define TIME2 MIN(60)
083
#define VAL1 50.
084
#define VAL2 60.
085
086
const
char
* ssid =
""
;
087
const
char
* password =
""
;
088
const
char
* mqtt_server =
""
;
089
const
int
mqtt_port = 12345;
090
const
char
* mqtt_user =
""
;
091
const
char
* mqtt_password =
""
;
092
093
OneWire oneWire(ONE_WIRE_BUS);
094
DallasTemperature sensors(&oneWire);
095
OpenTherm ot(inPin, outPin);
096
WiFiClient espClient;
097
PubSubClient client(espClient);
098
char
buf[50];
099
100
float
sp = 20,
// точка отсчета комнатной температуры
101
pv = 0,
// текущая температура в комнате
102
pv_last = 0,
// предыдущая температура
103
ierr = 0,
// интегральная погрешность
104
dt = 0,
// время между измерениями
105
op = 0,
// выход ПИД контроллера
106
spdhw = VAL1;
// точка отсчета температуры горячей воды контура ГВС
107
unsigned
long
ts = 0, new_ts = 0;
// отметки времени
108
unsigned
int
hex56 = (spdhw * 256 * 16 / 16);
// точка отсчета температуры горячей воды контура ГВС, но в (HEX)
109
unsigned
int
data126 = 0x013F;
// тип и версия термостата (HEX)
110
bool
enableCentralHeating =
true
;
111
bool
enableHotWater =
true
;
112
bool
enableCooling =
false
;
113
114
//===============================================================
115
// Обрабатываем внешние прерывания
116
//===============================================================
117
void
handleInterrupt() {
118
ot.handleInterrupt();
119
}
120
121
float
getTemp() {
122
return
sensors.getTempCByIndex(0);
123
}
124
float
getBoilerTemp() {
125
return
ot.getBoilerTemperature();
126
}
127
float
getDHWTemp() {
128
unsigned
long
request26 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Tdhw, 0);
129
unsigned
long
respons26 = ot.sendRequest(request26);
130
uint16_t dataValue26 = respons26 & 0xFFFF;
131
float
result26 = dataValue26 / 256;
132
return
result26;
133
}
134
float
getOutsideTemp() {
135
unsigned
long
request27 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::Toutside, 0);
136
unsigned
long
respons27 = ot.sendRequest(request27);
137
uint16_t dataValue27 = respons27 & 0xFFFF;
138
if
(dataValue27 > 32768) {
139
//negative
140
float
result27 = -(65536 - dataValue27) / 256;
141
return
result27;
142
}
else
{
143
//positive
144
float
result27 = dataValue27 / 256;
145
return
result27;
146
}
147
}
148
int
getADC() {
149
delay(100);
150
int
ar = analogRead(A0);
151
ar = map(ar, 0, 1023, 0, 100);
152
return
ar;
153
}
154
unsigned
long
getFault() {
155
unsigned
long
response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
156
OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
157
if
(responseStatus = OpenThermResponseStatus::SUCCESS)
158
return
ot.isFault(response);
159
}
160
unsigned
long
getCentralHeatingEnabled() {
161
unsigned
long
response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
162
OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
163
if
(responseStatus = OpenThermResponseStatus::SUCCESS)
164
return
ot.isCentralHeatingActive(response);
165
}
166
unsigned
long
getHotWaterEnabled() {
167
unsigned
long
response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
168
OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
169
if
(responseStatus = OpenThermResponseStatus::SUCCESS)
170
return
ot.isHotWaterActive(response);
171
}
172
unsigned
long
getFlameOn() {
173
unsigned
long
response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
174
OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
175
if
(responseStatus = OpenThermResponseStatus::SUCCESS)
176
return
ot.isFlameOn(response);
177
}
178
unsigned
int
getSlaveVersion_type() {
179
unsigned
long
request127 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::SlaveVersion, 0);
180
unsigned
long
respons127 = ot.sendRequest(request127);
181
uint16_t dataValue127 = respons127 & 0xFFFF;
182
unsigned result127 = dataValue127 / 256;
183
return
result127;
184
}
185
unsigned
int
getSlaveVersion_num() {
186
unsigned
long
request127 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::SlaveVersion, 0);
187
unsigned
long
respons127 = ot.sendRequest(request127);
188
uint8_t dataValue127_ = respons127 & 0xFF;
189
unsigned result127_ = dataValue127_;
190
return
result127_;
191
}
192
unsigned
int
setMasterVersion_type() {
193
unsigned
long
request126 = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::MasterVersion, data126);
194
unsigned
long
respons126 = ot.sendRequest(request126);
195
uint16_t dataValue126 = respons126 & 0xFFFF;
196
unsigned result126 = dataValue126 / 256;
197
return
result126;
198
}
199
unsigned
int
setMasterVersion_num() {
200
unsigned
long
request126 = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::MasterVersion, data126);
201
unsigned
long
respons126 = ot.sendRequest(request126);
202
uint8_t dataValue126_ = respons126 & 0xFF;
203
unsigned result126_ = dataValue126_;
204
return
result126_;
205
}
206
float
setDHWTemp() {
207
unsigned
long
request56 = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::TdhwSet, hex56);
208
unsigned
long
respons56 = ot.sendRequest(request56);
209
uint16_t dataValue56 = respons56 & 0xFFFF;
210
float
result56 = dataValue56 / 256;
211
return
result56;
212
}
213
unsigned
int
getFaultCode() {
214
unsigned
long
request5 = ot.buildRequest(OpenThermRequestType::READ, OpenThermMessageID::ASFflags, 0);
215
unsigned
long
respons5 = ot.sendRequest(request5);
216
uint8_t dataValue5 = respons5 & 0xFF;
217
unsigned result5 = dataValue5;
218
return
result5;
219
}
220
//===============================================================
221
// Вычисляем коэффициенты ПИД регулятора
222
//===============================================================
223
float
pid(
float
sp,
float
pv,
float
pv_last,
float
& ierr,
float
dt) {
224
float
Kc = 10.0;
// K / %Heater
225
float
tauI = 50.0;
// sec
226
float
tauD = 1.0;
// sec
227
// ПИД коэффициенты
228
float
KP = Kc;
229
float
KI = Kc / tauI;
230
float
KD = Kc * tauD;
231
// верхняя и нижняя границы уровня нагрева
232
float
ophi = 100;
233
float
oplo = 0;
234
// вычислить ошибку
235
float
error = sp - pv;
236
// calculate the integral error
237
ierr = ierr + KI * error * dt;
238
// вычислить производную измерения
239
float
dpv = (pv - pv_last) / dt;
240
// рассчитать выход ПИД регулятора
241
float
P = KP * error;
// пропорциональная составляющая
242
float
I = ierr;
// интегральная составляющая
243
float
D = -KD * dpv;
// дифференциальная составляющая
244
float
op = P + I + D;
245
// защита от сброса
246
if
((op < oplo) || (op > ophi)) {
247
I = I - KI * error * dt;
248
// выход регулятора, он же уставка для ID-1 (температура теплоносителя контура СО котла)
249
op = max(oplo, min(ophi, op));
250
}
251
ierr = I;
252
Serial
.println(
"Заданное значение температуры в помещении = "
+ String(sp) +
" °C"
);
253
Serial
.println(
"Текущее значение температуры в помещении = "
+ String(pv) +
" °C"
);
254
Serial
.println(
"Выхов ПИД регулятора = "
+ String(op));
255
Serial
.println(
"Время между измерениями = "
+ String(dt) +
"; ПИД коэффициенты: П = "
+ String(P) +
"; И = "
+ String(I) +
"; Д = "
+ String(D));
256
return
op;
257
}
258
//==============================================================
259
// Подключаемся к сети WiFi
260
//==============================================================
261
void
setup_wifi() {
262
delay(10);
263
// подключение к сети Wi-Fi
264
Serial
.println();
265
Serial
.print(
"Connecting to "
);
266
Serial
.println(ssid);
267
WiFi.mode(WIFI_STA);
268
WiFi.begin(ssid, password);
// Подключение к маршрутизатору Wi-Fi
269
// дождаться соединения
270
while
(WiFi.status() != WL_CONNECTED) {
271
delay(500);
272
Serial
.print(
"."
);
273
}
274
// если соединение успешно, показывает IP-адрес в мониторе
275
Serial
.println(
""
);
276
Serial
.println(
"WiFi connected"
);
277
Serial
.println(
"IP address: "
);
278
Serial
.println(WiFi.localIP());
279
}
280
//==============================================================
281
// Функция SETUP
282
//==============================================================
283
284
void
setup
() {
285
pinMode(BUILTIN_LED, OUTPUT);
286
pinMode(EXTERNAL_LED, OUTPUT);
287
pinMode(EXTERNAL_LED_1, OUTPUT);
288
pinMode(EXTERNAL_LED_2, OUTPUT);
289
pinMode(EXTERNAL_LED_3, OUTPUT);
290
digitalWrite(BUILTIN_LED, LOW);
291
digitalWrite(EXTERNAL_LED, LOW);
292
digitalWrite(EXTERNAL_LED_1, LOW);
293
digitalWrite(EXTERNAL_LED_2, LOW);
294
digitalWrite(EXTERNAL_LED_3, LOW);
295
Serial
.begin(115200);
296
setup_wifi();
297
//==============================================================
298
// Инициализация датчика температуры DS18B20
299
//==============================================================
300
sensors.begin();
301
sensors.requestTemperatures();
302
sensors.setWaitForConversion(
false
);
// Переключиться в асинхронный режим
303
pv, pv_last = sensors.getTempCByIndex(0);
304
ts = millis();
305
//==============================================================
306
// Инициализация OpenTherm
307
//==============================================================
308
ot.begin(handleInterrupt);
309
//==============================================================
310
// Инициализация MQTT клиента
311
//==============================================================
312
client.setServer(mqtt_server, mqtt_port);
// подключаемся к MQTT
313
client.setCallback(callback);
// функция получения топиков с брокера
314
}
315
void
publish_temperature() {
316
Serial
.println(
"MQTT, Current Room Temperature, °C = "
+ String(pv));
317
String(pv).toCharArray(buf, 50);
318
client.publish(
"pv"
, buf);
319
}
320
void
publish_boilertemp() {
321
Serial
.println(
"MQTT, CH Temperature, °C = "
+ String(getBoilerTemp()));
322
String(getBoilerTemp()).toCharArray(buf, 50);
323
client.publish(
"cht"
, buf);
324
}
325
void
publish_dhwtemp() {
326
Serial
.println(
"MQTT, DHW Temperature, °C = "
+ String(getDHWTemp()));
327
String(getDHWTemp()).toCharArray(buf, 50);
328
client.publish(
"dhwt"
, buf);
329
}
330
void
publish_outtemp() {
331
Serial
.println(
"MQTT, Outside Temperature, °C = "
+ String(getOutsideTemp()));
332
String(getOutsideTemp()).toCharArray(buf, 50);
333
client.publish(
"outt"
, buf);
334
}
335
void
publish_statusCH() {
336
Serial
.println(
"MQTT, Status Central Heating = "
+ String(getCentralHeatingEnabled()));
337
String(getCentralHeatingEnabled()).toCharArray(buf, 50);
338
client.publish(
"sch"
, buf);
339
}
340
void
publish_ADC() {
341
Serial
.println(
"MQTT, Relative Modulation Level, % = "
+ String(getADC()));
342
String(getADC()).toCharArray(buf, 50);
343
client.publish(
"adc"
, buf);
344
}
345
void
publish_statusDWH() {
346
Serial
.println(
"MQTT, Status Hot Water = "
+ String(getHotWaterEnabled()));
347
String(getHotWaterEnabled()).toCharArray(buf, 50);
348
client.publish(
"sdwh"
, buf);
349
}
350
void
publish_statusFlame() {
351
Serial
.println(
"MQTT, Status Central Heating = "
+ String(getFlameOn()));
352
String(getFlameOn()).toCharArray(buf, 50);
353
client.publish(
"sfl"
, buf);
354
}
355
void
publish_setBoilerTemperature() {
356
Serial
.println(
"MQTT, PID Controller Output, % = Setpoint CH Temperature, °C = "
+ String(op));
357
String(op).toCharArray(buf, 50);
358
client.publish(
"op"
, buf);
359
}
360
// функция обратного вызова, чтение топиков
361
362
void
callback(
char
* topic,
byte
* payload, unsigned
int
length) {
363
char
buffer[length + 1];
364
memcpy(buffer, payload, length);
365
buffer[length] =
'\0'
;
366
float
a = atof((
char
*)buffer);
367
if
(a <= 31) {
368
sp = a;
369
}
else
{
370
spdhw = a;
371
}
372
Serial
.println(
"MQTT,topic = "
+ String(a));
373
}
374
375
void
reconnect() {
376
while
(!client.connected()) {
377
Serial
.print(
"Attempting MQTT connection..."
);
378
// Attempt to connect
379
if
(client.connect(
"ESP8266Client"
, mqtt_user, mqtt_password)) {
380
Serial
.println(
"connected"
);
381
382
// после подключения публикуем объявление...
383
384
publish_temperature();
385
publish_boilertemp();
386
publish_dhwtemp();
387
publish_outtemp();
388
publish_ADC();
389
publish_statusCH();
390
publish_statusDWH();
391
publish_statusFlame();
392
publish_setBoilerTemperature();
393
394
// ... и перезаписываем
395
396
client.subscribe(
"sp"
);
397
client.subscribe(
"spdhw"
);
398
}
else
{
399
Serial
.print(
"failed, rc ="
);
400
Serial
.print(client.state());
401
Serial
.println(
" try again in 5 seconds"
);
402
403
// Подождать 5 сек. перед повторной попыткой
404
405
delay(5000);
406
}
407
}
408
}
409
//==============================================================
410
// Функция LOOP
411
//==============================================================
412
void
loop
() {
413
new_ts = millis();
414
if
(new_ts - ts > 1000) {
415
416
unsigned
long
response = ot.setBoilerStatus(enableCentralHeating, enableHotWater, enableCooling);
417
OpenThermResponseStatus responseStatus = ot.getLastResponseStatus();
418
if
(responseStatus = OpenThermResponseStatus::SUCCESS) {
419
//Serial.println("Error: Invalid boiler response " + String(response, HEX));
420
}
421
if
(responseStatus == OpenThermResponseStatus::NONE) {
422
Serial
.println(
"Error: OpenTherm is not initialized"
);
423
}
424
else
if
(responseStatus == OpenThermResponseStatus::INVALID) {
425
Serial
.println(
"Error: Invalid response "
+ String(response, HEX));
426
}
427
else
if
(responseStatus == OpenThermResponseStatus::TIMEOUT) {
428
Serial
.println(
"Error: Response timeout"
);
429
}
430
431
pv = sensors.getTempCByIndex(0);
432
dt = (new_ts - ts) / 1000.0;
433
ts = new_ts;
434
if
(responseStatus == OpenThermResponseStatus::SUCCESS) {
435
op = pid(sp, pv, pv_last, ierr, dt);
436
437
// Заданная температура СО
438
ot.setBoilerTemperature(op);
439
440
// Заданная температура ГВС
441
hex56 = (spdhw * 256 * 16 / 16);
442
unsigned
int
setDHWTemp(hex56);
443
444
pv_last = pv;
445
sensors.requestTemperatures();
//Асинхронный запрос температуры
446
}
447
448
// Записать ID-2; мастер-код MemberID
449
unsigned
int
data = 0x0004;
450
unsigned
long
request = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::MConfigMMemberIDcode, data);
451
ot.sendRequest(request);
452
453
// unsigned long request5 = ot.buildRequest(OpenThermRequestType::WRITE, OpenThermMessageID::ASFflags, 0);
454
// unsigned long respons5 = ot.sendRequest(request5);
455
// uint8_t dataValue5 = respons5 & 0xFF;
456
// unsigned result5 = dataValue5;
457
458
// Выводим в монитор
459
Serial
.println(
"Текущий статус системы отопления: "
+ String(ot.isCentralHeatingActive(response) ?
"on"
:
"off"
));
460
Serial
.println(
"Текущий статус горячей воды: "
+ String(ot.isHotWaterActive(response) ?
"on"
:
"off"
));
461
Serial
.println(
"Текущий статус горелки: "
+ String(ot.isFlameOn(response) ?
"on"
:
"off"
));
462
Serial
.println(
"Индикация состояния неисправности: "
+ String(ot.isFault(response) ?
"fault"
:
"no fault"
));
463
Serial
.println(
"Диагностическая индикация: "
+ String(ot.isDiagnostic(response) ?
"diagnostics"
:
"no diagnostics"
));
464
Serial
.println(
"Текущая температура контура СО = "
+ String(getBoilerTemp()) +
" °C"
);
465
Serial
.println(
"Текущая температура контура ГВС = "
+ String(getDHWTemp()) +
" °C"
);
466
Serial
.println(
"Температура на улице = "
+ String(getOutsideTemp()) +
" °C"
);
467
Serial
.println(
"Текуший уровень модуляции горелки = "
+ String (getADC()) +
" %"
);
468
Serial
.println(
"ADC = "
+ String (analogRead(A0)));
469
Serial
.println(
"Уставка температуры ГВС = "
+ String(setDHWTemp()) +
" °C"
);
470
Serial
.println(
"Тип и версия термостата: тип "
+ String(setMasterVersion_type()) +
", версия "
+ String(setMasterVersion_num()));
471
Serial
.println(
"Тип и версия котла: тип "
+ String(getSlaveVersion_type()) +
", версия "
+ String(getSlaveVersion_num()));
472
473
//=================================================================================================
474
// Индикация и реле насоса СО c откл. с задержкой 3 мин., индикация работы ГВС, горелки
475
//=================================================================================================
476
int
status_error = getFault();
// индикация состояния авария.
477
if
(status_error == 1) {
478
Serial
.println(
"Код неисправности: E "
+ String(getFaultCode()));
479
digitalWrite(EXTERNAL_LED_2, HIGH);
480
}
else
{
481
digitalWrite(EXTERNAL_LED_2, LOW);
482
}
483
int
status_dhw = getHotWaterEnabled();
// индикация состояния ГВС.
484
if
(status_dhw == 1) {
485
digitalWrite(EXTERNAL_LED, HIGH);
486
}
else
{
487
digitalWrite(EXTERNAL_LED, LOW);
488
}
489
int
status_flame = getFlameOn();
// индикация состояния горелки.
490
if
(status_flame == 1) {
491
digitalWrite(EXTERNAL_LED_1, HIGH);
492
}
else
{
493
digitalWrite(EXTERNAL_LED_1, LOW);
494
}
495
496
if
(pv >= sp) {
// индикация состояния СО.
497
enableCentralHeating =
false
;
498
digitalWrite(EXTERNAL_LED_3, LOW);
499
}
else
{
500
enableCentralHeating =
true
;
501
digitalWrite(EXTERNAL_LED_3, HIGH);
502
}
503
504
publish_temperature();
505
publish_boilertemp();
506
publish_dhwtemp();
507
publish_outtemp();
508
publish_ADC();
509
publish_statusCH();
510
publish_statusDWH();
511
publish_statusFlame();
512
publish_setBoilerTemperature();
513
}
514
// MQTT Loop
515
if
(!client.connected()) {
516
reconnect();
517
}
518
client.
loop
();
519
//=================================================================================================
520
// Активации функции Антилегионелла ГВС 65°C на 1 час каждые 7 дней по кругу.
521
//=================================================================================================
522
523
static
bool
state;
524
static
uint32_t old_millis = millis();
525
526
if
(state ==
false
) {
527
if
(millis() - old_millis >= TIME1) {
528
old_millis = millis();
529
spdhw = VAL1;
530
state =
true
;
531
}
532
}
else
if
(millis() - old_millis >= TIME2) {
533
old_millis = millis();
534
spdhw = VAL2;
535
state =
false
;
536
}
537
}
Ещё, в приложении (не знаю каким пользуетесь) целевую уставку температуры в помещении делайте не более 30 гр., а целевую уставку ГВС не менее 32 гр. Условие описано в этом куске кода, строка 367:
1
if
(a <= 31) {
2
sp = a;
3
}
else
{
4
spdhw = a;
5
}
P.S. Кстати, у вас BAXI SLiM? В интерфейсной плате к LMU33 котла назначение реле какое? Есть предположения? Я интерфейсную плату сам делал, вместо реле повесил светодиод, для индикации, зажигается только при работе контура СО, на ГВС не активируется. При отключении СО, сразу гаснет.
Вообщем такая же фигня... Не проходит установка температуры.
Может проблема с версией термостата.
1
Тип и версия термостата: тип 0, версия 0
2
Тип и версия котла: тип 0, версия 0
У вас что выдает в логах по этим параметрам. Думаю может он представиться нормально не может. Либо представился без регулировки ГВС.
И откуда можно узнать какие варианты есть представления для котла?
Ещё, в приложении (не знаю каким пользуетесь) целевую уставку температуры в помещении делайте не более 30 гр., а целевую уставку ГВС не менее 32 гр. Условие описано в этом куске кода, строка 367:
1
if
(a <= 31) {
2
sp = a;
3
}
else
{
4
spdhw = a;
5
}
P.S. Кстати, у вас BAXI SLiM? В интерфейсной плате к LMU33 котла назначение реле какое? Есть предположения? Я интерфейсную плату сам делал, вместо реле повесил светодиод, для индикации, зажигается только при работе контура СО, на ГВС не активируется. При отключении СО, сразу гаснет.
У меня KOREASTAR Premium. В этом вопросе не помогу.
Загрузите мой последний код, посмотрим, что у вас. У меня котел представляется 1.51. если не ошибаюсь. просто я принты отключил.
Ещё, в приложении (не знаю каким пользуетесь) целевую уставку температуры в помещении делайте не более 30 гр., а целевую уставку ГВС не менее 32 гр. Условие описано в этом куске кода, строка 367:
1
if
(a <= 31) {
2
sp = a;
3
}
else
{
4
spdhw = a;
5
}
P.S. Кстати, у вас BAXI SLiM? В интерфейсной плате к LMU33 котла назначение реле какое? Есть предположения? Я интерфейсную плату сам делал, вместо реле повесил светодиод, для индикации, зажигается только при работе контура СО, на ГВС не активируется. При отключении СО, сразу гаснет.
У меня KOREASTAR Premium. В этом вопросе не помогу.
Понял, тогда и с уровнем модуляции вам разбираться надо. Мой котёл по ОТ этот ID Msg "RelModLevel" не отдаёт, потому сам вычисляю, а не читаю его из котла.