Pārlūkot izejas kodu

## 0.7.0 2022-11-25
* display active heating lock status
* lock symbol on LCD
* "HEATING LOCKED" state text on web interface
* heating locked rest time text on web interface
* rest time on MQTT topic heatingLockTime
* toggle heating mode on/off disables active heating lock
* added heating pause mode
* can be enabled via MQTT and HTTP on /setPause = 1 (0 to disable)
* can be enabled via HTTP on /api?setPause=1 (0 to disable)
* has internal configurable timeout i.E. 30 min
* when active - show pause symbol on LCD
* publish rest time on MQTT topic heatingPause
* toggle heating mode on/off disables pause mode
* replace workaround for minHeatingOffTime active after reboot from v0.6.5 with actual fix
* thermostat logic every second, not depending on interval for measurements
* setTemp - setTempDecreaseVal is now displayed in WebIF as "Actual set Temp"
* IP display on start - removed "IP: " so that also IPs with all 3 digit octets can be displayed
* FIX: Measured Temp Correction and Set Temp. Decrease Value could not be changed/saved
* WebSockets console enabled by default (old console still available)
* moon symbol mirrored

FloKra 1 gadu atpakaļ
vecāks
revīzija
7b8b691e10

+ 22 - 0
CHANGELOG.md

@@ -1,5 +1,27 @@
 # WiFiThermostat - Changelog
 
+## 0.7.0 2022-11-25
+* display active heating lock status
+  *  lock symbol on LCD
+  *  "HEATING LOCKED" state text on web interface
+  *  heating locked rest time text on web interface
+  *  rest time on MQTT topic heatingLockTime
+  * toggle heating mode on/off disables active heating lock
+* added heating pause mode
+  * can be enabled via MQTT and HTTP on /setPause = 1 (0 to disable)
+  * can be enabled via HTTP on /api?setPause=1 (0 to disable)
+  * has internal configurable timeout i.E. 30 min
+  * when active - show pause symbol on LCD
+  * publish rest time on MQTT topic heatingPause
+  * toggle heating mode on/off disables pause mode
+* replace workaround for minHeatingOffTime active after reboot from v0.6.5 with actual fix
+* thermostat logic every second, not depending on interval for measurements
+* setTemp - setTempDecreaseVal is now displayed in WebIF as "Actual set Temp"
+* IP display on start - removed "IP: " so that also IPs with all 3 digit octets can be displayed
+* FIX: Measured Temp Correction and Set Temp. Decrease Value could not be changed/saved
+* WebSockets console enabled by default (old console still available)
+* moon symbol mirrored
+
 ## 0.6.6 2022-11-25
 
 * changed order when switching mode with physical mode button so that less button presses are required to toggle i.E. from Normal to Reduction 1 and back

BIN
releases/bin/WiFiThermostat.ino.d1_mini.20221125_v0.7.0.bin


BIN
releases/src/WiFiThermostat_0.7.0.zip


+ 43 - 7
src/Display.ino

@@ -1,13 +1,16 @@
 
 // LCD
 unsigned char customCharSun[] = {B00100, B10001, B01110, B11111, B11111, B01110, B10001, B00100};
-unsigned char customCharMoon[] = {B11000, B01110, B00110, B00111, B00111, B00110, B01110, B11000};
+//unsigned char customCharMoon[] = {B11000, B01110, B00110, B00111, B00111, B00110, B01110, B11000};
+unsigned char customCharMoon[] = {B00011, B01110, B01100, B11100, B11100, B01100, B01110, B00011};
 unsigned char customCharArrowRight[] = {B01000, B01100, B01110, B01111, B01110, B01100, B01000, B00000};
 unsigned char customCharDegC[] = {B01000, B10100, B01000, B00011, B00100, B00100, B00011, B00000};
 unsigned char customCharConn[] = {B00000, B00000, B11100, B00010, B11001, B00101, B10101, B00000};
 unsigned char customCharDisconn[] = {B10001, B01010, B00100, B01010, B10001, B00000, B10000, B00000};
 unsigned char customCharReduction2[] = {B00000, B11111, B01110, B00100, B00000, B11111, B01110, B00100}; //(2 arrows down)
 unsigned char customCharFlame[] = {B00100, B00100, B01010, B01010, B10101, B10101, B10101, B01110};
+unsigned char customCharLock[] = {B01110, B10001, B10001, B11111, B11011, B11011, B11111, B00000};
+unsigned char customCharPause[] = {B11011, B11011, B11011, B11011, B11011, B11011, B11011, B11011};
 // boolean displayActive = false;
 unsigned long displayLastOnMillis = 0;
 
@@ -36,12 +39,15 @@ void initDisplay()
   lcd.init();
   lcd.createChar(0, customCharMoon);
   lcd.createChar(1, customCharSun);
-  lcd.createChar(2, customCharArrowRight);
+  lcd.createChar(2, customCharArrowRight);  // replaced by > char
   lcd.createChar(3, customCharDegC);
   lcd.createChar(4, customCharConn);
   lcd.createChar(5, customCharDisconn);
   lcd.createChar(6, customCharReduction2);
   lcd.createChar(7, customCharFlame);
+  //lcd.createChar(7, customCharLock);
+  
+  // only 8 custom chars are possible :-/
 
   lcd.setCursor(0, 0);
 
@@ -68,8 +74,8 @@ void displayShowWifiConnected()
 
   lcd.print("WiFi connected  ");
   lcd.setCursor(0, 1);
-  lcd.print("IP:             ");
-  lcd.setCursor(4, 1);
+  lcd.print("                ");
+  lcd.setCursor(0, 1);
   lcd.print(WiFi.localIP());
 
   lcd.backlight();
@@ -116,7 +122,7 @@ void displayShowMQTTConnected()
 {
   lcd.setCursor(0, 0);
 
-  lcd.print("MQTT connected  ");
+  lcd.print("MQTT conn. to:  ");
   lcd.setCursor(0, 1);
   lcd.print("                ");
   lcd.setCursor(0, 1);
@@ -327,6 +333,20 @@ void updateDisplay()
       //}
       displayAddConnectionIcon();
     }
+    //else if (heatingPause > 0)
+    //{ // when heating pause mode is active, do not display target temp - instead show "PAUSE" info in line 2
+    //  //         1234567890123456
+    //  lcd.print("               ");
+    //  lcd.setCursor(0, 1);
+    //  //lcd.print(confAdv.pauseMessage);
+    //  lcd.print("PAUSE 0:");
+    //
+    //  unsigned int _pauseMin = heatingPause / 60;
+    //  if(_pauseMin < 10) lcd.print("0");
+    //  lcd.print(_pauseMin);
+    //  
+    //  displayAddConnectionIcon();
+    //}
     else if (displayShowLine2OverlayMsg)
     {
       displayShowLine2OverlayMsg = false;
@@ -342,6 +362,8 @@ void updateDisplay()
     else
     {
       lcd.write((uint8_t)2); // arrow right symbol
+      //lcd.print(">");
+      
       // lcd.print(" ");
       lcd.print(ch_currSetTemp);
       // lcd.write(0xDF); // degree symbol
@@ -367,13 +389,27 @@ void updateDisplay()
         lcd.write((uint8_t)6); // reduction 2 (2 arrows down) symbol if mode is reduction 2
       }
 
-      lcd.setCursor(12, 1);
-      if (turnHeatingOn)
+      if (heatingPause > 0)
+      {
+        lcd.createChar(7, customCharPause);
+        lcd.setCursor(12, 1);
+        lcd.write((uint8_t)7); // pause symbol if heating is paused
+      }
+      else if (heatingLockTime > 0)
+      {
+        lcd.createChar(7, customCharLock);
+        lcd.setCursor(12, 1);
+        lcd.write((uint8_t)7); // lock symbol if heating should run BUT lockout time is active
+      }
+      else if (turnHeatingOn)
       {
+        lcd.createChar(7, customCharFlame);
+        lcd.setCursor(12, 1);
         lcd.write((uint8_t)7); // flame symbol if heating is active
       }
       else
       {
+        lcd.setCursor(12, 1);
         lcd.print(" ");
       }
       displayAddConnectionIcon();

+ 12 - 3
src/WiFiThermostat.ino

@@ -55,6 +55,7 @@ PROGMEM const char PGMStr_FIRMWARE_SHORTNAME[] = FIRMWARE_SHORTNAME;
 PROGMEM const char PGMStr_changedTo[] = "changed to";
 PROGMEM const char PGMStr_preset[] = "preset";
 PROGMEM const char PGMStr_heatingMode[] = "heatingMode";
+PROGMEM const char PGMStr_heatingPause[] = "heatingPause";
 PROGMEM const char PGMStr_setTemp[] = "setTemp";
 PROGMEM const char PGMStr_currentSetTemp[] = "currSetTemp";
 PROGMEM const char PGMStr_setTempLow[] = "setTempLow";
@@ -71,6 +72,11 @@ PROGMEM const char PGMStr_connectedTo[] = "connected to";
 PROGMEM const char PGMStr_reconnects[] = "reconnects";
 PROGMEM const char PGMStr_connectedFailed[] = "connect FAILED";
 
+PROGMEM const char PGMStr_enabled[] = "enabled";
+PROGMEM const char PGMStr_disabled[] = "disabled";
+
+
+
 PROGMEM const char compile_date[] = __DATE__ " " __TIME__;
 
 const char PGMStr_confDevWiFi[] PROGMEM = "confDevWiFi";
@@ -231,6 +237,7 @@ struct confDataAdv
   char psetName0[15];
   char psetName1[15];
   char psetName2[15];
+  int pauseTout;
   bool wasChanged;
 } confAdv;
 
@@ -285,13 +292,16 @@ char mqtt_topic_in_cmd[61];
 char mqtt_topic_in_setTemp[61];
 char mqtt_topic_in_setMode[61];
 char mqtt_topic_in_setPreset[61];
+char mqtt_topic_in_setPause[61];
 
 // set values
 float setTemp = DEFAULT_SETTEMP;
 float setTempLow = DEFAULT_SETTEMP_LOW;   // set temperature in night/low mode
 float setTempLow2 = DEFAULT_SETTEMP_LOW2; // set temperature in night/low mode
 float currSetTemp = DEFAULT_SETTEMP;
+float currActualSetTemp = DEFAULT_SETTEMP;
 uint8_t heatingMode = DEFAULT_HEATINGMODE; // 0 = off, 1 = heat
+uint16_t heatingPause = 0;
 uint8_t preset = DEFAULT_PRESET;           // 0 = normal/day, 1 = night/reduction 1, 2 =
                                            // reduction 2 (long term leave)
 
@@ -317,6 +327,8 @@ float currTemp_raw;                 // last reading from DHT sensor (raw)
 uint16_t currHum;                   // last reading from DHT sensor with smoothing
 uint16_t currHum_raw;               // last reading from DHT sensor (raw)
 bool turnHeatingOn = false;         // true if heating is active (relais switched on)
+//bool heatingLockTimeActive = false; // true if heating is not active due to active lock time
+uint16_t heatingLockTime = 0;
 unsigned long heatingLastOnMillis;  // last time heating was switched on
 unsigned long heatingLastOffMillis; // last time heating was switched off
 float outTemp;                      // outside temp (via MQTT if enabled and in-topic configured)
@@ -626,9 +638,6 @@ void setup()
     mqttClientInit();
   }
 
-  // set heatingOffTime to confAdv.heatingMinOffTime so that the heating can start immediately after a reboot
-  heatingOffTime = confAdv.heatingMinOffTime;
-
   sendLog(F("DEV:  setup complete."), LOGLEVEL_INFO);
 } // void setup
 

+ 25 - 5
src/config.ino

@@ -1058,6 +1058,18 @@ void setConfig(char *param, char *value)
       }
     }
   }
+  else if (strcmp(param, "pausetout") == 0)
+  {
+    int valueInt = atoi(value);
+    if (valueInt != confAdv.pauseTout)
+    {
+      if (valueInt >= 0 && valueInt <= 3600)
+      {
+        confAdv.pauseTout = valueInt;
+        confAdv.wasChanged = true;
+      }
+    }
+  }
 
   // confAdd
   else if (strcmp(param, "outtemptop") == 0)
@@ -1727,7 +1739,7 @@ void getConfig(char *param)
   }
   if (strcmp(param, "tempdec") == 0 || isConfPage == 5)
   {
-    sprintf_P(buf, "%s.tempDec: %2.1f", PGMStr_confAdv, confAdv.setTempDecreaseVal);
+    sprintf_P(buf, "%s.tempDec: %1.2f", PGMStr_confAdv, confAdv.setTempDecreaseVal);
     sendLog(buf);
   }
   if (strcmp(param, "hyst") == 0 || isConfPage == 5)
@@ -1785,6 +1797,11 @@ void getConfig(char *param)
     sprintf_P(buf, "%s.oTempLab: '%s'", PGMStr_confAdv, confAdv.oTempLabel);
     sendLog(buf);
   }
+  if (strcmp(param, "pausetout") == 0 || isConfPage == 5)
+  {
+    sprintf_P(buf, "%s.pauseTout: %d", PGMStr_confAdv, confAdv.pauseTout);
+    sendLog(buf);
+  }
 
   // confAdd
   if (strcmp(param, "outtemptop") == 0 || isConfPage == 6)
@@ -2406,11 +2423,11 @@ boolean loadConfigAdv()
         return false;
       }
 
-      confAdv.hysteresis = json["hyst"] | 0.1;
-      confAdv.heatingMinOffTime = json["minOffTime"] | 120;
-      confAdv.tempCorrVal = json["tempCorr"] | 0;
+      confAdv.hysteresis = json["hyst"] | 0.2;
+      confAdv.heatingMinOffTime = json["minOffTime"] | 600;
+      confAdv.tempCorrVal = json["tempCorr"] | 0.0;
       confAdv.humCorrVal = json["humCorr"] | 0;
-      confAdv.setTempDecreaseVal = json["tempDec"] | 0;
+      confAdv.setTempDecreaseVal = json["tempDec"] | 0.0;
 
       strlcpy(confAdv.offMessage, json["offMsg"] | "", sizeof(confAdv.offMessage));
 
@@ -2422,6 +2439,7 @@ boolean loadConfigAdv()
       strlcpy(confAdv.psetName0, json["psetName0"] | "", sizeof(confAdv.psetName0));
       strlcpy(confAdv.psetName1, json["psetName1"] | "", sizeof(confAdv.psetName1));
       strlcpy(confAdv.psetName2, json["psetName2"] | "", sizeof(confAdv.psetName2));
+      confAdv.pauseTout = json["pauseTout"] | 1800;
 
       // Serial.println("Loaded config values:");
       //getConfig((char *)"confAdv");
@@ -3069,6 +3087,7 @@ boolean saveConfigAdv()
     json["psetName0"] = confAdv.psetName0;
     json["psetName1"] = confAdv.psetName1;
     json["psetName2"] = confAdv.psetName2;
+    json["pauseTout"] = confAdv.pauseTout;
 
     yield();
 
@@ -3458,6 +3477,7 @@ void loadConf_defaults()
   strlcpy(confAdv.psetName0, PRESET_NAME_0, sizeof(confAdv.psetName0));
   strlcpy(confAdv.psetName1, PRESET_NAME_1, sizeof(confAdv.psetName1));
   strlcpy(confAdv.psetName2, PRESET_NAME_2, sizeof(confAdv.psetName2));
+  confAdv.pauseTout = 1800;
   confAdv.wasChanged = false;
 
   // confAdd

+ 1 - 1
src/fwsettings.h

@@ -14,7 +14,7 @@
 // ENABLE FEATURES
 #define ENABLE_FEATURE_NTP_TIME
 #define ENABLE_FEATURE_WEB_CONSOLE
-//#define ENABLE_FEATURE_WEB_CONSOLE_WEBSOCKETS
+#define ENABLE_FEATURE_WEB_CONSOLE_WEBSOCKETS
 #define ENABLE_FEATURE_PW_ENCRYPTION
 #define ENABLE_FEATURE_HTTP_UPDATER
 

+ 1 - 1
src/fwversion.h

@@ -1,6 +1,6 @@
 // DEFINE NAMES
 #define FIRMWARE_NAME "WiFiThermostat"
-#define FIRMWARE_VERSION "0.6.5"
+#define FIRMWARE_VERSION "0.7.0"
 #define FIRMWARE_URL "https://git.flokra.at/flo/WiFiThermostat"
 #define FIRMWARE_COPYRIGHT "FloKra"
 #define FIRMWARE_COPYRIGHT_URL "https://www.flokra.at/"

+ 5 - 1
src/html_confAdv.h

@@ -49,6 +49,7 @@ static const char html_confadv_script[] PROGMEM = R"=====(
         g('psetName0').value = data.psetName0;
         g('psetName1').value = data.psetName1;
         g('psetName2').value = data.psetName2;
+        g('pauseTout').value = data.pauseTout;
         xhttp = null;
         reqFin = true;
        }
@@ -90,7 +91,10 @@ static const char html_confadv_body[] PROGMEM = R"=====(
 <p class='n'>* added to the measured values. <br>Can be negative and/or fractions. <br>Use if measurements deviate from a calibrated sensor.</p><br>
 
 <p><b>Set Temp. Decrease Value **</b><br><input type='text' name='tempDec' id='tempDec'></p>
-<p class='n'>** the actual set temperatur is decreased by that value.<br>Can be a fraction. Can be used to prevent over-heating <br>when the thermostat is badly placed.<br></p>
+<p class='n'>** the actual set temperatur is decreased by that value.<br>Can be a fraction. Can be used to prevent over-heating <br>when the thermostat is badly placed.<br></p><br>
+
+<p><b>Heating Pause Timeout [s]</b><br><input type='text' name='pauseTout' id='pauseTout'></p>
+<p class='n'>Heating Pause can be enabled via MQTT /cmd/setPause=1. Set Timeout in seconds here.</p><br>
 </fieldset>
 <div></div><br>
 <fieldset>

+ 11 - 1
src/html_confWeb.h

@@ -120,7 +120,17 @@ make usaccessible without Auth.</p>
 <fieldset>
 <legend>Console</legend>
 <p><b>Enable Web Console</b>&nbsp;<input type='checkbox' name='wConsole' id='wConsole'></p>
+)====="; // html_confweb_body
+
+#ifdef ENABLE_FEATURE_WEB_CONSOLE_WEBSOCKETS
+static const char html_confweb_body_wsconsole[] PROGMEM = R"=====(
 <p><b>Enable WebSockets Console (Experimental)</b>&nbsp;<input type='checkbox' name='wsConsole' id='wsConsole'></p>
+)====="; // html_confweb_body_wsconsole
+#endif
+
+
+
+static const char html_confweb_body2[] PROGMEM = R"=====(
 </fieldset>
 </form>
 <div></div>
@@ -130,4 +140,4 @@ make usaccessible without Auth.</p>
 </tr></table>
 <div></div>
 </div>
-)====="; // html_confweb_body
+)====="; // html_confweb_body2

+ 12 - 2
src/html_main.h

@@ -27,6 +27,7 @@ static const char html_main_script[] PROGMEM = R"=====(
           var data = JSON.parse(xhttp.responseText);
           if(data.setTemp !== undefined) g('setTemp').innerHTML = data.setTemp.toFixed(1) + '&deg;';
           if(data.currSetTemp !== undefined) g('currSetTemp').innerHTML = data.currSetTemp.toFixed(1) + '&deg;';
+          if(data.actSetTemp !== undefined) g('actSetTemp').innerHTML = data.actSetTemp.toFixed(1) + '&deg;';
           if(data.psetName !== undefined) g('pset').innerHTML = data.psetName.toUpperCase();
           if(data.psetName0 !== undefined) g('btnPs0').innerHTML = data.psetName0;
           if(data.psetName1 !== undefined) g('btnPs1').innerHTML = data.psetName1;
@@ -47,9 +48,15 @@ static const char html_main_script[] PROGMEM = R"=====(
           else if(data.pset == '1') {g('btnPs0').setAttribute('class', 'bgrey'); g('btnPs1').setAttribute('class', ''); g('btnPs2').setAttribute('class', 'bgrey');}
           else if(data.pset == '2') {g('btnPs0').setAttribute('class', 'bgrey'); g('btnPs1').setAttribute('class', 'bgrey'); g('btnPs2').setAttribute('class', ''); }
           
-          if(data.heating == '1' && data.mode == '1') { g('heating').innerHTML = 'HEATING'; g('heating').style = 'color:red;'; }
+          if(parseInt(data.heatingPause) >= 1 && data.mode == '1') { g('heating').innerHTML = 'PAUSED'; g('heating').style = 'color:red;'; }
+          else if(data.heating == '1' && data.mode == '1') { g('heating').innerHTML = 'ACTIVE'; g('heating').style = 'color:orange;'; }
+          else if(data.heating == '2' && data.mode == '1') { g('heating').innerHTML = 'LOCKED'; g('heating').style = 'color:red;'; }
           else if(data.heating == '0' && data.mode == '1') { g('heating').innerHTML = 'IDLE'; g('heating').style = 'color:#1fa3ec;'; }
           else if(data.mode == '0') { g('heating').innerHTML = 'OFF'; g('heating').style = 'color:black;'; }
+
+          
+          if(data.heatingLockTime !== undefined) g('heatingLockTime').innerHTML = data.heatingLockTime + 's';
+          if(data.heatingPause !== undefined) g('heatingPause').innerHTML = data.heatingPause + 's';
           
           var APname;
           if(data.WiFiNum == 1) APname='Default';
@@ -104,8 +111,11 @@ static const char html_main_body[] PROGMEM = R"=====(
 <table style='width:100%'>
 <tr><td style='width:50%'>Current Temp/Hum </td><td><b><span id='currTempHum'></span></td></tr>
 <tr><td>Outside Temp/Hum </td><td><b><span id='outTempHum'></span></td></tr>
-<tr><td>Actual set Temp </td><td><b><span id='currSetTemp'></span></b></td></tr>
+<tr><td>Current set Temp </td><td><b><span id='currSetTemp'></span></b></td></tr>
+<tr><td>Actual set Temp </td><td><b><span id='actSetTemp'></span></b></td></tr>
 <tr><td>Heating State </td><td><b><span id='heating'></span></b></td></tr>
+<tr><td>Heating Lock Time</td><td><b><span id='heatingLockTime'></span></b></td></tr>
+<tr><td>Heating Pause Time</td><td><b><span id='heatingPause'></span></b></td></tr>
 </table>
 <div></div>
 <div></div>

+ 27 - 4
src/httpServer.ino

@@ -83,6 +83,10 @@ void httpServerHandleConfWebPage()
   httpServer.sendContent_P(html_bodytag_jsinit);
   httpServerSendHtmlBodyPageheadChunked();
   httpServer.sendContent_P(html_confweb_body);
+  #ifdef ENABLE_FEATURE_WEB_CONSOLE_WEBSOCKETS
+  httpServer.sendContent_P(html_confweb_body_wsconsole);
+  #endif
+  httpServer.sendContent_P(html_confweb_body2);
   httpServerSendHtmlFooterChunked();
 }
 
@@ -479,6 +483,15 @@ void httpServerInit()
         setTempTo(valueFloat);
       }
 
+      if (httpServer.hasArg("setPause"))
+      {
+        sendLog(F("WEB: /api?setPause"), LOGLEVEL_INFO);
+        char bufVal[20];
+        httpServer.arg("setPause").toCharArray(bufVal, 20);
+        int valueInt = atoi(bufVal);
+        setHeatingPause(valueInt);
+      }
+
       if (httpServer.hasArg("setMode"))
       {
         sendLog(F("WEB: /api?setMode"), LOGLEVEL_INFO);
@@ -505,7 +518,7 @@ void httpServerInit()
       //dtostrf(setTemp, 1, 1, ch_currSetTemp );
 
       //build json object of program data
-      StaticJsonDocument<700> json;
+      StaticJsonDocument<850> json;
 
       json["devname"] = confDevWiFi.deviceName;
       json["ssid"] = WiFi.SSID();
@@ -541,9 +554,18 @@ void httpServerInit()
 
       json["setTemp"] = setTemp;
       json["currSetTemp"] = currSetTemp;
+      json["actSetTemp"] = currActualSetTemp;
       json["temp"] = round1(currTemp);
       json["hum"] = int(currHum);
-      json["heating"] = turnHeatingOn;
+
+      uint8_t _heatingState;
+      if(turnHeatingOn) _heatingState = 1;
+      else if(heatingLockTime > 0) _heatingState = 2;
+      else _heatingState = 0;
+
+      json["heating"] = _heatingState;
+      json["heatingLockTime"] = heatingLockTime;
+      json["heatingPause"] = heatingPause;
       json["mode"] = heatingMode;
       json["modeName"] = currentModeName;
       json["pset"] = preset;
@@ -556,7 +578,7 @@ void httpServerInit()
       json["outTemp"] = round1(outTemp);
       json["outHum"] = outHum;
 
-      char jsonchar[500];
+      char jsonchar[750];
       serializeJson(json, jsonchar);
       httpServer.send(200, "application/json", jsonchar);
     }
@@ -1243,7 +1265,7 @@ void httpServerInit()
       //yield();
 
       //build json object of program data
-      StaticJsonDocument<600> json;
+      StaticJsonDocument<750> json;
       json["minOffTime"] = confAdv.heatingMinOffTime;
       json["tempDec"] = confAdv.setTempDecreaseVal;
       json["hyst"] = confAdv.hysteresis;
@@ -1257,6 +1279,7 @@ void httpServerInit()
       json["psetName2"] = confAdv.psetName2;
       json["iTempLab"] = confAdv.iTempLabel;
       json["oTempLab"] = confAdv.oTempLabel;
+      json["pauseTout"] = confAdv.pauseTout;
 
       yield();
 

+ 38 - 0
src/mqtt.ino

@@ -187,6 +187,43 @@ void mqttCallback(char *topic, unsigned char *payload, uint16_t length)
     cmdInQueue = false; // payload is processed in "commands"
   }                     //if topic = mqtt_topic_in_setMode
 
+  if (strcmp(topic, mqtt_topic_in_setPause) == 0)
+  { //if topic = mqtt_topic_in_setMode
+    uint8_t len;
+    uint8_t _setTo = 255;
+
+    if (length < sizeof(cmdPayload))
+      len = length; // if input is bigger than dest buffer, cut
+    else
+      len = sizeof(cmdPayload) - 1;
+
+    for (int i = 0; i < len; i++)
+    {
+      cmdPayload[i] = (char)payload[i];
+    }
+    //cmdPayload[len + 1] = '\0';
+    cmdPayload[len] = '\0';
+    //    Serial.print("cmdPayload:");
+    //    Serial.println(cmdPayload);
+
+    char tmpPayload[21];
+    strlcpy(tmpPayload, cmdPayload, sizeof(tmpPayload));
+    strlwr(tmpPayload);
+
+    
+    if (strcmp(tmpPayload, "1") == 0) _setTo = 1;
+    else if (strcmp(tmpPayload, "0") == 0) _setTo = 0;
+
+    if(_setTo == 1 || _setTo == 0) {
+      setHeatingPause(_setTo);
+      char buf[50];
+      sprintf(buf, "MQTT IN: setPause to %u", _setTo);
+      sendLog(buf, LOGLEVEL_INFO);
+    }
+    
+    cmdInQueue = false; // payload is processed in "commands"
+  }                     //if topic = mqtt_topic_in_setPause
+
   if (strcmp(topic, mqtt_topic_in_setPreset) == 0)
   { //if topic = mqtt_topic_in_setPreset
     char tmpPayload[21];
@@ -309,6 +346,7 @@ void mqttPrepareSubscribeTopics()
   sprintf(mqtt_topic_in_setTemp, "%s/%s", mqtt_topic_in_cmd, "setTemp");
   sprintf(mqtt_topic_in_setMode, "%s/%s", mqtt_topic_in_cmd, "setMode");
   sprintf(mqtt_topic_in_setPreset, "%s/%s", mqtt_topic_in_cmd, "setPreset");
+  sprintf(mqtt_topic_in_setPause, "%s/%s", mqtt_topic_in_cmd, "setPause");
 }
 
 bool mqttReconnect(void)

+ 30 - 3
src/mqtt_out.ino

@@ -132,22 +132,49 @@ void publishCurrentThermostatValues(bool force = false)
 
   yield();
 
-  //char buf[101];
-  //sprintf(buf, "%lu", heatingOnTime);
+  char _buf[21];
+
+  sprintf(_buf, "%lu", heatingOnTime);
   sprintf(tmp_topic_out, "%s/%s", confMqtt.mqtt_topic_out, "heatingOnTime");
+  mqttclient.publish(tmp_topic_out, _buf);
+
+  sprintf(tmp_topic_out, "%s/%s", confMqtt.mqtt_topic_out, "heatingOnTime_Hms");
   mqttclient.publish(tmp_topic_out, getTimeStringFromSeconds(heatingOnTime));
   yield();
 
-  //sprintf(buf, "%lu", heatingOffTime);
+  sprintf(_buf, "%lu", heatingOffTime);
   sprintf(tmp_topic_out, "%s/%s", confMqtt.mqtt_topic_out, "heatingOffTime");
+  mqttclient.publish(tmp_topic_out, _buf);
+  sprintf(tmp_topic_out, "%s/%s", confMqtt.mqtt_topic_out, "heatingOffTime_Hms");
   mqttclient.publish(tmp_topic_out, getTimeStringFromSeconds(heatingOffTime));
 
+  publish_heatingLockTime();
+  publish_heatingPauseTime();  
+
   yield();
 }
 
+void publish_heatingLockTime() {
+  char _tmp_topic_out[50];
+  char _buf[21];
+  sprintf(_buf, "%i", heatingLockTime);
+  sprintf(_tmp_topic_out, "%s/%s", confMqtt.mqtt_topic_out, "heatingLockTime");
+  mqttclient.publish(_tmp_topic_out, _buf);
+}
+
+void publish_heatingPauseTime() {
+  char _tmp_topic_out[50];
+  char _buf[21];
+  sprintf(_buf, "%i", heatingPause);
+  sprintf(_tmp_topic_out, "%s/%s", confMqtt.mqtt_topic_out, "heatingPause");
+  mqttclient.publish(_tmp_topic_out, _buf);
+}
+
+
 float currTemp_lastPublished;
 int currHum_lastPublished;
 
+
 void publishCurrentSensorValues(bool force = false)
 {
   if (lastTempUpdate != 0 && (millis() - lastTempUpdate) < 120000)

+ 13 - 1
src/scheduler.ino

@@ -54,9 +54,21 @@ void everySecond()
   {
     countMeasureInterval = 0;
     measureTempHum();
-    thermostat();
+    //thermostat();
   }
 
+  if(heatingPause > 0) {
+    heatingPause--;
+    publish_heatingPauseTime();
+  }
+
+  if(heatingLockTime > 0) {
+    heatingLockTime--;
+    publish_heatingLockTime();
+  }
+
+  thermostat();
+
   if (countDisplayInterval < confBas.displayInterval)
     countDisplayInterval++;
   else

+ 67 - 13
src/thermostat.ino

@@ -82,16 +82,20 @@ void updateCurrSetTemp() {
   if (heatingMode > 0) { // heating on
     if (preset == 0) { // normal/day preset
       currSetTemp = setTemp;
+      currActualSetTemp = setTemp -  confAdv.setTempDecreaseVal;
     }
     else if (preset == 1) { // night/reduction preset
       currSetTemp = setTempLow;
+      currActualSetTemp = setTempLow - confAdv.setTempDecreaseVal;
     }
     else if (preset == 2) { // night/reduction 2 preset
       currSetTemp = setTempLow2;
+      currActualSetTemp = setTempLow2 - confAdv.setTempDecreaseVal;
     }
   }
   else { // if heatingMode == 0
     currSetTemp = DEFAULT_SETTEMP_HEATOFF;
+    currActualSetTemp = currSetTemp;
   }
 }
 
@@ -106,7 +110,10 @@ void thermostat() {
     //sendLog(buf, LOGLEVEL_INFO);
   }
   else if (heatingMode > 0 && !turnHeatingOn) {
-    heatingOffTime = (millis() - heatingLastOffMillis) / 1000;
+    if(heatingLastOnMillis > 0) {
+      // prevent locking the heating after a reboot
+      heatingOffTime = (millis() - heatingLastOffMillis) / 1000;
+    }
 
     //char buf[101];
     //sprintf(buf, "heating off for %lus", heatingOffTime);
@@ -129,9 +136,10 @@ void thermostat() {
 #endif
 
     // thermostat with hysteresis
-    if ( turnHeatingOn && currTemp >= (currSetTemp - confAdv.setTempDecreaseVal) ) {
+    if ( turnHeatingOn && currTemp >= currActualSetTemp ) {
       turnHeatingOn = false;
       heatingLastOffMillis = millis();
+      heatingLockTime = confAdv.heatingMinOffTime;
       digitalWrite(PIN_RELAIS, !RELAISONSTATE);
       updateDisplay();
 
@@ -143,22 +151,39 @@ void thermostat() {
       //mqttclient.publish(tmp_topic_out, "off");
       publishCurrentThermostatValues();
     }
-    else if ( !turnHeatingOn && heatingMode > 0 && ( currTemp < (currSetTemp - confAdv.setTempDecreaseVal - confAdv.hysteresis) ) && ( heatingOffTime > confAdv.heatingMinOffTime ) ) {
-      turnHeatingOn = true;
-      heatingLastOnMillis = millis();
-      digitalWrite(PIN_RELAIS, RELAISONSTATE);
-      updateDisplay();
+    else if ( !turnHeatingOn && heatingMode > 0 && heatingLockTime == 0 && heatingPause == 0 && ( currTemp < (currActualSetTemp - confAdv.hysteresis) ) ) {
+      if ( heatingOffTime > confAdv.heatingMinOffTime || heatingLastOnMillis == 0) {
+        turnHeatingOn = true;
+        //heatingLockTimeActive = false;
+        //heatingLockTime = 0;
+        heatingLastOnMillis = millis();
+        digitalWrite(PIN_RELAIS, RELAISONSTATE);
+        updateDisplay();
 
-      char buf[101];
-      sprintf(buf, "TSTAT: switch heating ON, off for %lus", heatingOffTime);
-      sendLog(buf, LOGLEVEL_INFO);
+        char buf[101];
+        sprintf(buf, "TSTAT: switch heating ON, off for %lus", heatingOffTime);
+        sendLog(buf, LOGLEVEL_INFO);
 
-      //Serial.println("heating on");
-      //mqttclient.publish(tmp_topic_out, "on");
-      publishCurrentThermostatValues();
+        //Serial.println("heating on");
+        //mqttclient.publish(tmp_topic_out, "on");
+        publishCurrentThermostatValues();
+      }
+      //else {
+      //  // heating should run BUT lock time is active
+      //  heatingLockTimeActive = true;
+      //  heatingLockTime = confAdv.heatingMinOffTime - heatingOffTime;
+      //  updateDisplay();
+      //}
+    }
+    else if (heatingPause > 0) {
+      turnHeatingOn = false;
+      heatingLastOffMillis = millis();
+      digitalWrite(PIN_RELAIS, !RELAISONSTATE);
+      updateDisplay();
     }
   }
   else {
+    // turn off as no temp reading is available
     if (turnHeatingOn) {
       digitalWrite(PIN_RELAIS, !RELAISONSTATE);
       turnHeatingOn = false;
@@ -177,11 +202,15 @@ void thermostat() {
 void toggleOnOff() {
   if (heatingMode > 0) {
     heatingMode = 0;
+    heatingPause = 0;
+    heatingLockTime = 0;
     lastValueChange = millis();
     heatingModeAlreadySaved = false;
   }
   else {
     heatingMode = 1;
+    heatingPause = 0;
+    heatingLockTime = 0;
     lastValueChange = millis();
     heatingModeAlreadySaved = false;
   }
@@ -424,6 +453,8 @@ void setTempLow2To(float setTo) {
 void setHeatingmodeTo(unsigned char setTo) {
   if(setTo != heatingMode) {
     heatingMode = setTo;
+    heatingPause = 0;
+    heatingLockTime = 0;
     updateCurrSetTemp();
     lastValueChange = millis();
     heatingModeAlreadySaved = false;
@@ -439,6 +470,29 @@ void setHeatingmodeTo(unsigned char setTo) {
   }
 }
 
+void setHeatingPause(uint8_t _setTo) {
+  bool _change = false;
+  char _buf1[30];
+  if(_setTo == 0) {
+    heatingPause = 0;
+    _change = true;
+    sprintf_P(_buf1, "%s: %s %s", PGMStr_thermostat, PGMStr_heatingPause, PGMStr_disabled);
+  }
+  else if (_setTo == 1 && heatingPause == 0) {
+    heatingPause = confAdv.pauseTout;
+    _change = true;
+    sprintf_P(_buf1, "%s: %s %s", PGMStr_thermostat, PGMStr_heatingPause, PGMStr_enabled);
+  }
+
+  if(_change) {
+    lastValueChange = millis();
+    thermostat();
+    updateDisplay();
+    publishCurrentThermostatValues();
+    sendLog(_buf1);
+  }
+}
+
 void setPresetTo(unsigned char setTo) {
   if(setTo != preset) {
     if(setTo >= 0 && setTo <=2) {