Browse Source

Grundfunktionen fertig, Webinterface, Konfiguration, Werte speichern, Domoticz-Anbindung weitgehend fertig, Unterstützung für PIR-Sensor fehlt noch
Erste Tests OK, Probebetrieb auf Zielhardware gestartet

FloKra 6 years ago
parent
commit
e65002677b

+ 0 - 0
changelog.txt


+ 13 - 0
domoticz-thermostat.txt

@@ -5,6 +5,9 @@
 	"svalue": "24.00"
 }
 
+{"idx": 219,"nvalue":0,"svalue":"24.00"}
+
+
 
 domoticz/out:
 {
@@ -21,3 +24,13 @@ domoticz/out:
    "svalue1" : "22.00",
    "unit" : 1
 }
+
+
+
+
+
+temp/hum-sensor
+
+idx=IDX&nvalue=0&svalue=TEMP;HUM;HUM_STAT
+
+{"idx":idx,"nvalue":0,"svalue":"TEMP;HUM;0"}

+ 19 - 38
src/WiFiThermostat/Buttonhandling.ino

@@ -19,49 +19,30 @@ unsigned long modeButtonHoldTime = 0;
 bool modeButtonFired = false;
 bool modeButtonHoldFired = false;
 
-//extern bool displayActive;
-//extern void extendDisplayTimeout();
-//extern void updateDisplay();
 
 void plusButtonAction() {
-  if(heatingMode == 1 && heatingEnabled) {
-    Serial.println("+");
-    if ( setTempDay <= (setTempMax - 0.5)) setTempDay += 0.5;
-  }
+  setTempStepUp();
   extendDisplayTimeout();
-  updateDisplay();
 }
 
 void minusButtonAction() {
-  if(heatingMode == 1 && heatingEnabled) {
-    Serial.println("-");
-    if ( setTempDay >= (setTempMin + 0.5)) setTempDay -= 0.5;
-  }
+  setTempStepDown();
   extendDisplayTimeout();
-  updateDisplay();
 }
 
 void modeButtonAction() {
   extendDisplayTimeout();
-  if (heatingEnabled) {
-    Serial.print("switch mode to ");
-    if (heatingMode == 1) heatingMode = 2;
-    else if (heatingMode == 2) heatingMode = 1;
-    Serial.println(heatingMode);
-    updateDisplay();
-  }
+  toggleHeatingMode();
 }
 
 void modeButtonHoldAction() {
   Serial.println("switch off/on");
   extendDisplayTimeout();
-  if (heatingEnabled) heatingEnabled = false;
-  else heatingEnabled = true;
-  updateDisplay();
+  toggleThermostatOnOff();
 }
 
 void checkButtonStates() {
-  
+
   // Buttons einlesen
 
   // plus button
@@ -74,10 +55,10 @@ void checkButtonStates() {
     plusButtonHoldTime = millis() - plusButtonDownMillis;
     if ( plusButtonHoldTime > 50 ) {
       plusButtonLastState = plusButtonCurrentState;
-            if (!displayActive && !plusButtonFired ) {
-              enableDisplay();
-              plusButtonFired = true;
-            }
+      if (!displayActive && !plusButtonFired ) {
+        enableDisplay();
+        plusButtonFired = true;
+      }
       //      else if (displayActive && !plusButtonFired ) {
       //        plusButtonAction();
       //        plusButtonFired = true;
@@ -93,7 +74,7 @@ void checkButtonStates() {
   }
   else if (plusButtonCurrentState == HIGH && plusButtonLastState == LOW) { // button is released again
     plusButtonLastState = plusButtonCurrentState;
-    
+
     if (displayActive && !plusButtonFired) {
       plusButtonAction();
     }
@@ -113,10 +94,10 @@ void checkButtonStates() {
     minusButtonHoldTime = millis() - minusButtonDownMillis;
     if ( minusButtonHoldTime > 50 ) {
       minusButtonLastState = minusButtonCurrentState;
-            if (!displayActive && !minusButtonFired ) {
-              enableDisplay();
-              minusButtonFired = true;
-            }
+      if (!displayActive && !minusButtonFired ) {
+        enableDisplay();
+        minusButtonFired = true;
+      }
       //      else if (displayActive && !minusButtonFired ) {
       //        minusButtonAction();
       //        minusButtonFired = true;
@@ -132,7 +113,7 @@ void checkButtonStates() {
   }
   else if (minusButtonCurrentState == HIGH && minusButtonLastState == LOW) { // button is released again
     minusButtonLastState = minusButtonCurrentState;
-    
+
     if (displayActive && !minusButtonFired) {
       minusButtonAction();
     }
@@ -152,10 +133,10 @@ void checkButtonStates() {
     modeButtonHoldTime = millis() - modeButtonDownMillis;
     if ( modeButtonHoldTime > 50 ) {
       modeButtonLastState = modeButtonCurrentState;
-            if (!displayActive && !modeButtonFired ) {
-              enableDisplay();
-              modeButtonFired = true;
-            }
+      if (!displayActive && !modeButtonFired ) {
+        enableDisplay();
+        modeButtonFired = true;
+      }
       //      else if (displayActive && !modeButtonFired ) {
       //        modeButtonAction();
       //        modeButtonFired = true;

+ 126 - 7
src/WiFiThermostat/Display.ino

@@ -5,8 +5,6 @@ byte customCharArrowRight[8] = {0b00000, 0b00100, 0b00110, 0b11111, 0b11111, 0b0
 //bool displayActive = false;
 unsigned long displayLastOnMillis = 0;
 
-//extern long lastDisplayUpdate;
-
 void enableDisplay() {
   lcd.backlight();
   displayActive = true;
@@ -23,15 +21,11 @@ void extendDisplayTimeout() {
 }
 
 void handleDisplayTimeout() {
-  if (displayActive && (millis() - displayLastOnMillis) > displayTimeout) {
+  if (displayActive && (millis() - displayLastOnMillis) > (displayTimeout * 1000)) {
     disableDisplay();
   }
 }
 
-void updateDisplay() {
-  lastDisplayUpdate = 0;
-}
-
 void initDisplay() {
   lcd.init();
   lcd.createChar(0, customCharMoon);
@@ -56,4 +50,129 @@ void initDisplay() {
   displayActive = true;
   displayLastOnMillis = millis();
 }
+
+void updateDisplay() {
+
+  long now = millis();
+  if ( ((now - outTempHumLastUpdate) < 300000) && outTempHumLastUpdate != 0 && whichTempToDisplay == 1 && ((now - lastDisplayToggle) > (displayInterval * 1000)) ) {
+    // outside temp has been updated < 5 min ago, last displayed temp was "I" and display interval is overdue
+    whichTempToDisplay = 2; // 1= I, 2= A
+    lastDisplayToggle = now;
+  }
+  else if ( whichTempToDisplay != 1 && ((now - lastDisplayToggle) > (displayInterval * 1000)) ) {
+    whichTempToDisplay = 1; // 1= I, 2= A
+    lastDisplayToggle = now;
+  }
+
+  lastDisplayUpdate = now;
+
+  boolean showTemp = false;
+  char temp_chararr[6];
+  char hum_chararr[4];
+  char tempLabel[2];
+
+  if (whichTempToDisplay == 2) {
+    if ( outTempHumLastUpdate != 0 && (now - outTempHumLastUpdate) < 120000 ) {
+      showTemp = true;
+      dtostrf(outTemp, 5, 1, temp_chararr );
+      sprintf(hum_chararr, "%2i", outHum);
+      strcpy(tempLabel, "A");
+    }
+  }
+  else {
+    if ( lastTempUpdate != 0 && (now - lastTempUpdate) < 120000 ) {
+      showTemp = true;
+      dtostrf(temperature, 5, 1, temp_chararr );
+      sprintf(hum_chararr, "%2i", humidity);
+      strcpy(tempLabel, "I");
+    }
+  }
+
+  // LCD line 1
+  // print temperature incl = and ° symbol + humidity to lcd, line 1, first 11 chars
+  lcd.setCursor(0, 0);
+
+  if (showTemp) {
+    //lcd.write(0x3D); // = Zeichen
+    lcd.print(tempLabel);
+    lcd.print(" ");
+    lcd.print(temp_chararr);
+    lcd.write(0xDF); // degree symbol
+    lcd.print(" ");
+
+    if (humidity == 0) lcd.print("--");
+    else if (humidity < 10) {
+      lcd.print(" ");
+      lcd.print(hum_chararr);
+    }
+    else {
+      lcd.print(hum_chararr);
+    }
+    lcd.print("%");
+    lcd.print("  ");
+  }
+  else {
+    lcd.print("             ");
+  }
+
+  // display current mode on LCD
+  lcd.setCursor(13, 0);
+  if (heatingMode > 0) {
+    if ( heatingMode == 1 ) { // day/normal mode
+      lcd.print(" ");
+      lcd.write((uint8_t)1); // sun symbol if mode is day/normal
+      lcd.print(" ");
+    }
+    else if ( heatingMode == 2 ) { // night/reduction mode
+      lcd.print(" ");
+      lcd.write((uint8_t)0); // moon symbol if mode is night/reduction
+      lcd.print(" ");
+    }
+  }
+  else lcd.print("   "); // mode is heating off
+
+
+
+  float curr_setTemp;
+  if (heatingMode == 1) curr_setTemp = setTemp;
+  else if (heatingMode == 2) curr_setTemp = setTempLow;
+  char currSetTemp_chararr[6];
+  dtostrf(curr_setTemp, 5, 1, currSetTemp_chararr );
+
+
+  // LCD line 2
+  // display target temperature to line 2, 8 chars length incl space at the end
+  lcd.setCursor(0, 1);
+
+  // when the heating mode is OFF, do not display target temp - instead show "Heating off" info in line 2
+  if ( heatingMode == 0 ) {
+    //         1234567890123456
+    lcd.print("  Heizung aus   ");
+  }
+  else {
+    lcd.write((uint8_t)2); // Pfeil rechts
+    lcd.print(" ");
+    lcd.print(currSetTemp_chararr);
+    lcd.write(0xDF); // degree symbol
+    lcd.print(" ");
+
+    // display status info to line 2 from char 9 -> 8 chars length
+    lcd.setCursor(8, 1);
+    lcd.print(" ");
+    if (turnHeatingOn) {
+      //         1234567
+      lcd.print("heizen ");
+    }
+    else if ( heatingMode == 1 ) { // day/normal mode
+      //         1234567
+      lcd.print("       ");
+    }
+    else if ( heatingMode == 2 ) { // night/reduction mode
+      //         1234567
+      lcd.print("Absenk.");
+    }
+    else lcd.print("        ");
+  }
+
+}
 

+ 232 - 624
src/WiFiThermostat/WiFiThermostat.ino

@@ -1,35 +1,54 @@
 
 // pre compiletime config
+
+//#define DEBUG_VERBOSE
+
+#define SPIFFS_DBG
+#define SPIFFS_USE_MAGIC
+
+#define FIRMWARE_NAME "WiFiThermostat"
 #define VERSION "0.1.2"
+#define COPYRIGHT " by Flo Kra"
+
+// default values, can later be overridden via configuration
+#define DEVICE_NAME "WiFi-Thermostat-1"
+#define MQTT_SERVER "10.1.1.11"
+#define MQTT_PORT 1883
+#define MQTT_TOPIC_IN "Test/Thermostat/cmd"
+#define MQTT_TOPIC_OUT "Test/Thermostat/status"
+#define BUTTON_DEBOUNCE_TIME 120
+#define BUTTON_HOLD_TIME 750
+#define DOMOTICZ_IN_TOPIC "domoticz/in"
+#define DOMOTICZ_OUT_TOPIC "domoticz/out"
+#define OUTTEMP_TOPIC_IN "wetter/atemp"
+#define OUTHUM_TOPIC_IN "wetter/ahum"
+
+#define CLEARCONF_TOKEN "TUES"
 
-#define DEVICE_NAME "WIFI-THERMOSTAT"
 
+// pin assignments and I2C addresses
 #define PIN_DHTSENSOR 13
 #define PIN_RELAIS 16
-#define PIN_BUTTON_PLUS 0
-#define PIN_BUTTON_MINUS 2
+#define PIN_BUTTON_PLUS 2
+#define PIN_BUTTON_MINUS 0
 #define PIN_BUTTON_MODE 14
 #define PIN_PIRSENSOR 12
 
-#define RELAISONSTATE LOW
-#define BUTTONONSTATE LOW
-
 #define DHTTYPE DHT22 // DHT sensor type
+
 #define LCDADDR 0x27  // I2C address LCD
+#define LCDCOLS 16
+#define LCDLINES 2
 
 
-#define DEBUG_SERIAL //uncomment for Serial debugging statements
+// default logic levels
+#define RELAISONSTATE LOW
+#define BUTTONONSTATE LOW
 
-#ifdef DEBUG_SERIAL
-#define DEBUG_BEGIN Serial.begin(115200)
-#define DEBUG_PRINT(x) Serial.println(x)
-#else
-#define DEBUG_PRINT(x)
-#define DEBUG_BEGIN
-#endif
 
-#include "PersWiFiManager.h"
 
+
+#include "PersWiFiManager.h"
 #include <ArduinoJson.h>
 #include <ESP8266WiFi.h>
 #include <WiFiClient.h>
@@ -37,96 +56,129 @@
 #include <ESP8266mDNS.h>
 #include <ESP8266HTTPUpdateServer.h>
 #include "PubSubClient.h"
-
+#include <DNSServer.h>
+#include <FS.h>
 #include <Wire.h>
 #include "LiquidCrystal_I2C.h"
+#include "DHT.h"
 
-#include <DNSServer.h>
-#include <FS.h>
 
-//#include <AS_BH1750.h> // BH1750 lux sensor
-#include "DHT.h"
+#ifndef MESSZ
+#define MESSZ                405          // Max number of characters in JSON message string (4 x DS18x20 sensors)
+#endif
+// Max message size calculated by PubSubClient is (MQTT_MAX_PACKET_SIZE < 5 + 2 + strlen(topic) + plength)
+#if (MQTT_MAX_PACKET_SIZE -TOPSZ -7) < MESSZ  // If the max message size is too small, throw an error at compile time
+// See pubsubclient.c line 359
+#error "MQTT_MAX_PACKET_SIZE is too small in libraries/PubSubClient/src/PubSubClient.h, increase it to at least 512"
+#endif
+
+
+
+// config variables - do not change here!
+//conf
+char deviceName[31];  // device name - just for web interface
+char http_user[31];
+char http_pass[31];
+char mqtt_server[41];
+int mqtt_port = MQTT_PORT;
+char mqtt_user[31];
+char mqtt_pass[31];
+char mqtt_topic_in[51];   // MQTT in topic for commands
+char mqtt_topic_out[51];  // MQTT out base topic, will be extended by various value names
+boolean mqtt_outRetain = false; // send MQTT out with retain flag
+char mqtt_willTopic[51];  // MQTT Last Will topic
+int mqtt_willQos = 2;     // MQTT Last Will topic QOS
+boolean mqtt_willRetain = false;  // MQTT Last Will retain
+char mqtt_willMsg[31];    // MQTT Last Will payload
+char domoticz_out_topic[55];  // domoticz out topic to subscribe to (only applicable if domoticzIdx_Thermostat and/or domoticzIdx_ThermostatMode is set to >0)
+
+//conf2
+int domoticzIdx_Thermostat = 0;
+int domoticzIdx_ThermostatMode = 0;
+int domoticzIdx_TempHumSensor = 0;
+int domoticzIdx_PIR = 0;
+char outTemp_topic_in[51];
+char outHum_topic_in[51];
+bool autoSaveSetTemp = true;
+bool autoSaveHeatingMode = true;
+int heatingMinOffTime = 10;       // minimal time the heating keeps turned off in s
+float setTempMin = 14.0;        // minimal temperature that can be set
+float setTempMax = 29.0;        // maximal temperature that can be set
+float setTempLow = 18.0;        // set temperature in night/low mode
+float hysteresis = 0.5;
+float tempCorrVal = 0.0;        // correction value for temperature sensor reading
+int humCorrVal = 0;         // correction value for humidity sensor reading
+int measureInterval = 15;       // interval for temp/hum measurement
+int displayInterval = 5;        //
+int displayTimeout = 120;
+
 
+//set values
+float setTemp = 21.5;
+byte heatingMode = 1; // 0 = off, 1 = normal/day, 2 = night/reduction
 
+float setTempSaved;
+byte heatingModeSaved; // 0 = off, 1 = normal/day, 2 = night/reduction
+
+// not changeable via configuration
+float setTempLowMin = 14.0;
+float setTempLowMax = 19.0;
+boolean debug = true;
+int debounceTime = BUTTON_DEBOUNCE_TIME;
+int buttonHoldTime = BUTTON_HOLD_TIME;
 
 // global variables
-float humidity, temperature;
-bool turnHeatingOn = false;
-bool heatingEnabled = true;
-byte heatingMode = 1; // 0 = off, 1 = default/day, 2 = night/reduction
-
-float setTemp;
-long heatingLastOnMillis;
-long heatingLastOffMillis;
-
-float aTemp, aHum;
-long aTempHumLastUpdate;
-byte whichTempToDisplay; // 1=temp inside (from DHT sensor), 2= temp outside (via MQTT)
-
-long lastMeasure = 0;
-long lastDisplayUpdate = 0;
-long lastDisplayToggle = 0;
-long lastTempUpdate = 0;
+float temperature; // last reading from DHT sensor
+int humidity; // last reading from DHT sensor
+bool turnHeatingOn = false; // true if heating is active (relais switched on)
+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)
+int outHum; // outside temp (via MQTT if enabled and in-topic configured)
+long outTempHumLastUpdate; // last reading from out temp/hum source
+byte whichTempToDisplay; // 1=temp inside (from DHT sensor), 2= temp outside (via MQTT) - if out temp/hum available this value and the displayed value pair toggles with every displayInterval
+unsigned long lastMeasure = 0; // millis of last temp/hum measurement
+unsigned long lastDisplayUpdate = 0; // millis of last display update
+unsigned long lastDisplayToggle = 0; // millis of last display toggle
+unsigned long lastTempUpdate = 0; // last update time of DHT reading
 char msg[50];   // buffer MQTT in payload
 char topic[50]; // buffer MQTT in topic
+bool displayActive = false; // gets true when button is pressed. display light gets switched on until timeout. button actions are only performed while display is active
 
-// global variables from other modules
-//extern bool displayActive;
-bool displayActive = false;
-
-// functions from other modules/files
-//extern void httpServerHandleNotFound();
-//extern void httpServerHandleRoot();
-//extern void enableDisplay();
-//extern void disableDisplay();
-//extern void extendDisplayTimeout();
-//extern void handleDisplayTimeout();
-//extern void updateDisplay();
-//extern void initDisplay();
-//extern void plusButtonAction();
-//extern void minusButtonAction();
-//extern void modeButtonAction();
-//extern void modeButtonHoldAction();
-//extern void checkButtonStates();
-
-// config variables
-
-const char* mqtt_server = "10.1.1.11";
-const int mqtt_port = 1883;
-long minOffTime = 10; // s
-
-float setTempDay = 21.5;
-float setTempNight = 18.0;
-float setTempMin = 14.0;
-float setTempMax = 29.0;
-float setTempMinLow = 10.0;
-float setTempMaxLow = 19.0;
-float hysteresis = 0.5;
-int measureinterval = 60000; // ms
-int displayinterval = 5000;
-int displayTimeout = 60000;
-
+unsigned long heatingOnTime, heatingOffTime;
 
-//const char* mqtt_topic_prefix = "Test/Thermostat/";
-const char* mqtt_topic_temp = "Test/Thermostat/Temp";
-const char* mqtt_topic_settemp = "Test/Thermostat/SetTemp";
-//const char* mqtt_topic_setmode = "Test/Thermostat/SetMode";
-const char* mqtt_topic_hum = "Test/Thermostat/Hum";
-const char* mqtt_topic_lux = "Test/Thermostat/Lux";
-const char* mqtt_topic_heating = "Test/Thermostat/Heating";
-const char* mqtt_topic_heating_lastofftime = "Test/Thermostat/HeatingLastOffTime";
-const char* mqtt_topic_heating_lastontime = "Test/Thermostat/HeatingLastOnTime";
-
-const char* mqtt_topic_in = "Test/Thermostat/in";   // subscribe topic
+boolean useDomoticz = false; // will be set to true in setup() if idx-values other than 0 are configured
+boolean domoticzOutParseData = false; // indicates that domoticz/out json data is buffered, will then be parsed in next loop() run
+boolean domoticzOutParserBusy = false; // indicates that domoticz/out json data is currently processed - no futher data will be accepted until finished
+char domoticzOutPayload[450]; // buffer for domoticz/out data
+int dismissUpdateFromDomoticzTimeout = 2500; // after a value was changed by data from domoticz/out, domoticz/out parsing for this device will be turned off for this time to prevent infinite loops
+unsigned long lastUpdate_setTemp = 0; // set to millis() every time setTemp value is changed. next update from domoticz/out will be rejected for dismissUpdateFromDomoticzTimeout in this case 
+unsigned long lastUpdate_heatingMode = 0; // set to millis() every time heatingMode value is changed. next update from domoticz/out will be rejected for dismissUpdateFromDomoticzTimeout in this case 
+bool lastUpdateFromDomoticz_setTemp = false;
+bool lastUpdateFromDomoticz_heatingMode = false;
+int domoticzUpdateInterval = 30; // interval in min to force update of domoticz devices
 
+char cmdPayload[101]; // buffer for commands
+boolean cmdInQueue = false; // command is queued and will be processed next loop() run
+bool saveConfigToFlash = false; // conf is saved in next loop() run
+bool saveConfig2ToFlash = false; // conf2 is saved in next loop() run
 
+unsigned int saveValuesTimeout = 5000;
+unsigned long lastValueChange; // is set to millis() whenever setTemp value and/or heatingMode value is changed. used for autoSave function with hardcoded 5s timeout 
+bool setTempAlreadySaved = true; // only save if not yet done
+bool heatingModeAlreadySaved = true; // only save if not yet done
 
+byte mqttMode = 0; 
+unsigned long mqttLastReconnectAttempt = 0;
+int mqttReconnects = 0;
 
 
 DHT dht(PIN_DHTSENSOR, DHTTYPE);
-LiquidCrystal_I2C lcd(0x27, 16, 2); // set the LCD address to 0x27 for a 16 chars and 2 line display
+LiquidCrystal_I2C lcd(LCDADDR, LCDCOLS, LCDLINES); // set the LCD address to 0x27 for a 16 chars and 2 line display
 
 WiFiClient espClient;
+
+void mqttCallback(char* topic, byte* payload, unsigned int length);
 PubSubClient mqttclient(espClient);
 
 ESP8266WebServer httpServer(80);
@@ -136,194 +188,108 @@ PersWiFiManager persWM(httpServer, dnsServer);
 ESP8266HTTPUpdateServer httpUpdater;
 
 
-// MQTT callback
-void mqttCallback(char* topic, byte* payload, unsigned int length) {
-  Serial.print("Message arrived [");
-  Serial.print(topic);
-  Serial.print("] ");
-  for (int i = 0; i < length; i++) {
-    Serial.print((char)payload[i]);
-  }
+void setup() {
+  Serial.begin(115200);
+  delay(500);
   Serial.println();
+  Serial.print(FIRMWARE_NAME);
+  Serial.print(" v");
+  Serial.print(VERSION);
+  Serial.println(COPYRIGHT);
+  Serial.println("starting...");
 
-  if (strcmp(topic, mqtt_topic_in) == 0) { //if topic = mqtt_topic_in
-    Serial.println("TOPIC!!");
 
-    char tmpPayload[length + 1];
-    for (int i = 0; i < length; i++) {
-      tmpPayload[i] = (char)payload[i];
-    }
-    tmpPayload[sizeof(tmpPayload) - 1] = '\0';
-
-    Serial.print("tmpPayload:");
-    Serial.println(tmpPayload);
-
-    if (strncmp(tmpPayload, "set temp ", 9) == 0) {
-      char inValue[length - 9 + 1];
-      for (int i = 0; i < length; i++) {
-        inValue[i] = (char)payload[i + 9];
-      }
-      inValue[sizeof(inValue) - 1] = '\0';
-      Serial.print(inValue);
-      Serial.println();
-      float invalueFloat = round(atof(inValue) * 2.0) / 2.0;
-
-      Serial.print(invalueFloat);
-      if (invalueFloat >= setTempMin && invalueFloat <= setTempMax) {
-        setTempDay = invalueFloat;
-        updateDisplay();
-      }
-      else if (invalueFloat > setTempMax) {
-        setTempDay = setTempMax;
-        updateDisplay();
-      }
-      else if (invalueFloat < setTempMin) {
-        setTempDay = setTempMin;
-        updateDisplay();
-      }
-    }// if payload=="set temp "
-
-    else if (strncmp(tmpPayload, "set tlow ", 9) == 0) {
-      char inValue[length - 9 + 1];
-      for (int i = 0; i < length; i++) {
-        inValue[i] = (char)payload[i + 9];
-      }
-      inValue[sizeof(inValue) - 1] = '\0';
-      Serial.print(inValue);
-      Serial.println();
-      float invalueFloat = round(atof(inValue) * 2.0) / 2.0;
-
-      Serial.print(invalueFloat);
-      if (invalueFloat >= setTempMinLow && invalueFloat <= setTempMaxLow) {
-        setTempNight = invalueFloat;
-        updateDisplay();
-      }
-      else if (invalueFloat > setTempMaxLow) {
-        setTempNight = setTempMaxLow;
-        updateDisplay();
-      }
-      else if (invalueFloat < setTempMinLow) {
-        setTempNight = setTempMinLow;
-        updateDisplay();
-      }
-    }
+  pinMode(PIN_RELAIS, OUTPUT);
+  digitalWrite(PIN_RELAIS, HIGH);
+  pinMode(PIN_BUTTON_PLUS, INPUT_PULLUP);
+  pinMode(PIN_BUTTON_MINUS, INPUT_PULLUP);
+  pinMode(PIN_BUTTON_MODE, INPUT_PULLUP);
 
-    else if (strncmp(tmpPayload, "set mode ", 9) == 0) {
-      if ((char)payload[9] == '1') {
-        // switch to mode default/day
-        heatingMode = 1;
-        updateDisplay();
-      }
-      else if ((char)payload[9] == '2') {
-        // switch to mode night/reduction
-        heatingMode = 2;
-        updateDisplay();
-      }
-    }
 
-    else if (strncmp(tmpPayload, "set enab ", 9) == 0) {
-      if ((char)payload[9] == '0') {
-        // switch heating off
-        heatingEnabled = false;
-        updateDisplay();
-      }
-      else if ((char)payload[9] == '1') {
-        // switch heating on
-        heatingEnabled = true;
-        updateDisplay();
-      }
-    }
-  }//if topic = mqtt_topic_in
+  strlcpy(deviceName, DEVICE_NAME, 31);
+  strlcpy(mqtt_server, MQTT_SERVER, 41);
+  strlcpy(mqtt_topic_in, MQTT_TOPIC_IN, 51);
+  strlcpy(mqtt_topic_out, MQTT_TOPIC_OUT, 51);
+  strlcpy(domoticz_out_topic, DOMOTICZ_OUT_TOPIC, 51); // changeable subscription topic, as domoticz supports different flat/hierarchical out-topics
+  strlcpy(outTemp_topic_in, OUTTEMP_TOPIC_IN, 51);
+  strlcpy(outHum_topic_in, OUTHUM_TOPIC_IN, 51);
 
-  if (strcmp(topic, "wetter/atemp") == 0) { //if topic = "wetter/atemp"
-    char inValue[length + 1];
-    for (int i = 0; i < length; i++) {
-      inValue[i] = (char)payload[i];
-    }
-    inValue[sizeof(inValue) - 1] = '\0';
-    //Serial.print(inValue);
-    //Serial.println();
-    float invalueFloat = atof(inValue);
-    aTemp = invalueFloat;
-    aTempHumLastUpdate = millis();
-    //Serial.print("atemp=");
-    //Serial.println(invalueFloat);
-  }//if topic = "wetter/atemp"
-
-  if (strcmp(topic, "wetter/ahum") == 0) { //if topic = "wetter/atemp"
-    char inValue[length + 1];
-    for (int i = 0; i < length; i++) {
-      inValue[i] = (char)payload[i];
+  Serial.println("default config values loaded..");
+
+
+  Serial.println("Mounting FS...");
+  if (!SPIFFS.begin()) {
+    Serial.println("Failed to mount file system");
+    return;
+  }
+
+  //SPIFFS.format();
+  //Serial.print("Format SPIFFS complete.");
+
+  if (!SPIFFS.exists("/formatComplete.txt")) {
+    Serial.println("Please wait 30 secs for SPIFFS to be formatted");
+    SPIFFS.format();
+    Serial.println("Spiffs formatted");
+
+    File f = SPIFFS.open("/formatComplete.txt", "w");
+    if (!f) {
+      Serial.println("file open failed");
+    } else {
+      f.println("Format Complete");
     }
-    inValue[sizeof(inValue) - 1] = '\0';
-    //Serial.print(inValue);
-    //Serial.println();
-    float invalueFloat = atof(inValue);
-    aHum = invalueFloat;
-    aTempHumLastUpdate = millis();
-    //Serial.println("ahum=");
-    //Serial.println(invalueFloat);
-  }//if topic = "wetter/ahum"
-
-}//mqttCallback
-
-
-
-long mqttLastReconnectAttempt = 0;
-
-boolean mqttReconnect() {
-  // Create a random MQTT client ID
-  String mqttClientId = "ESP8266Client-";
-  mqttClientId += String(random(0xffff), HEX);
-  if (mqttclient.connect(mqttClientId.c_str())) {
-    Serial.println("connected");
-    // Once connected, publish an announcement...
-    //mqttclient.publish("TestoutTopic", "hello world");
-    // ... and resubscribe
-    mqttclient.subscribe(mqtt_topic_in);
-    mqttclient.subscribe("wetter/atemp");
-    mqttclient.subscribe("wetter/ahum");
+    f.close();
+  } else {
+    Serial.println("SPIFFS is formatted. Moving along...");
   }
-  return mqttclient.connected();
-} //mqttReconnect
 
 
-void setup() {
-  DEBUG_BEGIN; //for terminal debugging
-  DEBUG_PRINT();
+  //  // load config from SPIFFS if files exist
+  if (!loadConfig()) {
+    Serial.println("Failed to load conf.json");
+  } else {
+    Serial.println("conf.json loaded");
+  }
 
+  if (!loadConfig2()) {
+    Serial.println("Failed to load conf2.json");
+  } else {
+    Serial.println("conf2.json loaded");
+  }
 
+  if (!loadSetTemp()) {
+    Serial.println("Failed to load file 'setTemp'");
+  } else {
+    Serial.println("file 'setTemp' loaded");
+  }
 
-  Serial.print("WiFi Thermostat v");
-  Serial.print(VERSION);
-  Serial.println(" by Flo Kra");
-  Serial.println("starting...");
+  if (!loadHeatingMode()) {
+    Serial.println("Failed to load file 'heatingMode'");
+  } else {
+    Serial.println("file 'heatingMode' loaded");
+  }
 
-  pinMode(PIN_RELAIS, OUTPUT);
-  digitalWrite(PIN_RELAIS, HIGH);
-  pinMode(PIN_BUTTON_PLUS, INPUT_PULLUP);
-  pinMode(PIN_BUTTON_MINUS, INPUT_PULLUP);
-  pinMode(PIN_BUTTON_MODE, INPUT_PULLUP);
+  setTempSaved = setTemp;
+  heatingModeSaved = heatingMode;
 
   // initialize DHT11/22 temp/hum sensor
   dht.begin();
 
+  checkUseDomoticz();
+  delay(500);
+
 
   //optional code handlers to run everytime wifi is connected...
   persWM.onConnect([]() {
-    DEBUG_PRINT("wifi connected");
-    DEBUG_PRINT(WiFi.SSID());
-    DEBUG_PRINT(WiFi.localIP());
+    Serial.println("wifi connected");
+    Serial.println(WiFi.SSID());
+    Serial.println(WiFi.localIP());
   });
   //...or AP mode is started
   persWM.onAp([]() {
-    DEBUG_PRINT("AP MODE");
-    DEBUG_PRINT(persWM.getApSsid());
+    Serial.println("AP MODE");
+    Serial.println(persWM.getApSsid());
   });
 
-  //allows serving of files from SPIFFS
-  //SPIFFS.begin();
-  //SPIFFS.format();
   //sets network name for AP mode
   persWM.setApCredentials(DEVICE_NAME);
   //persWM.setApCredentials(DEVICE_NAME, "password"); optional password
@@ -333,402 +299,44 @@ void setup() {
 
   //in non-blocking mode, program will continue past this point without waiting
   persWM.begin();
+  delay(500);
 
-  //handles commands from webpage, sends live data in JSON format
-  httpServer.on("/api", []() {
-    DEBUG_PRINT("httpServer.on /api");
-    if (httpServer.hasArg("plusBtn")) {
-      plusButtonAction();
-      DEBUG_PRINT(P("web plusBtn"));
-    } //if
-    if (httpServer.hasArg("minusBtn")) {
-      minusButtonAction();
-      DEBUG_PRINT(P("web minusBtn"));
-    } //if
-    if (httpServer.hasArg("modeBtn")) {
-      modeButtonAction();
-      DEBUG_PRINT(P("web modeBtn"));
-    } //if
-    if (httpServer.hasArg("onoffBtn")) {
-      modeButtonHoldAction();
-      DEBUG_PRINT(P("web onoffBtn"));
-    } //if
-
-    //build json object of program data
-    StaticJsonBuffer<200> jsonBuffer;
-    JsonObject &json = jsonBuffer.createObject();
-    json["ssid"] = WiFi.SSID();
-    json["setTemp"] = setTempDay;
-    json["temp"] = temperature;
-    json["hum"] = int(humidity);
-    json["mode"] = heatingMode;
-    json["heatingEnabled"] = heatingEnabled;
-    json["heating"] = turnHeatingOn;
-
-    char jsonchar[200];
-    json.printTo(jsonchar); //print to char array, takes more memory but sends in one piece
-    httpServer.send(200, "application/json", jsonchar);
-
-  }); //httpServer.on /api
-
-  //get heap status, analog input value and all GPIO statuses in one json call
-  httpServer.on("/info", HTTP_GET, []() {
-    String json = "{";
-    json += "\"wifissid\":\"" + WiFi.SSID() + "\"";
-    json += "\"heap\":" + String(ESP.getFreeHeap());
-    //json += ", \"analog\":" + String(analogRead(A0));
-    //json += ", \"gpio\":" + String((uint32_t)(((GPI | GPO) & 0xFFFF) | ((GP16I & 0x01) << 16)));
-    json += "}";
-    httpServer.send(200, "text/json", json);
-    json = String();
-  }); //httpServer.on /info
-
-  httpServer.on("/", []() {
-    httpServerHandleRoot();
-  });
-
-  httpServer.on("/config", []() {
-    httpServerHandleConfPage();
-  });
+  httpServerInit();
 
-  httpServer.onNotFound([]() {
-    httpServerHandleNotFound();
-  }); //httpServer.onNotFound
-
-  // HTTP Updater at /update
-  httpUpdater.setup(&httpServer);
-
-
-  httpServer.begin();
-
-  DEBUG_PRINT("setup complete.");
-
-  mqttclient.setServer(mqtt_server, mqtt_port);
-  mqttclient.setCallback(mqttCallback);
-  mqttLastReconnectAttempt = 0;
+  mqttPrepareConnection();
+  mqttClientInit();
 
   initDisplay();
 
+  Serial.println("setup complete.");
+  delay(1000);
 } //void setup
 
 void loop() {
-  persWM.handleWiFi(); //in non-blocking mode, handleWiFi must be called in the main loop
+  checkMillis();
 
+  persWM.handleWiFi(); //in non-blocking mode, handleWiFi must be called in the main loop
+  yield();
   dnsServer.processNextRequest();
   httpServer.handleClient();
 
-  // MQTT reconnect if not connected (nonblocking)
-  if (!mqttclient.connected()) {
-    long now = millis();
-
-    if (now - mqttLastReconnectAttempt > 5000) {
-      mqttLastReconnectAttempt = now;
-      // Attempt to reconnect
-      if (mqttReconnect()) {
-        mqttLastReconnectAttempt = 0;
-      }
-    }
-  } else {
-    // Client connected
-    mqttclient.loop();
-  }//else
+  mqttHandleConnection();
 
   checkButtonStates();
-  handleDisplayTimeout();
-
-
-  // set target temp for heating mode
-  if (heatingMode == 1) { // heating on - default/day mode
-    setTemp = setTempDay;
-  }
-  else if (heatingMode == 2) { // heating of - night/reduction mode
-    setTemp = setTempNight;
-  }
-
-
-  if ( (millis() - lastMeasure) > measureinterval) {
-    lastMeasure = millis();
-
-    float tmpHum = round(dht.readHumidity());
-    float tmpTemp = dht.readTemperature();  // Read temperature as Celsius (the default)
-
-    // Check if any reads failed
-    if (isnan(tmpHum) || isnan(tmpTemp)) {
-      Serial.println("Failed to read from DHT sensor!");
-    }
-    else {
-      if (tmpTemp < 50.0 && tmpTemp > -20.0) {
-        temperature = tmpTemp;
-        humidity = tmpHum;
-        lastTempUpdate = millis();
-      }
-    }
-
-    unsigned long heatingOnTime, heatingOffTime;
-    if (heatingEnabled && turnHeatingOn) {
-      heatingOnTime = (millis() - heatingLastOffMillis) / 1000;
-      Serial.print("heatingOnTime: ");
-      Serial.print(heatingOnTime);
-      Serial.println();
-    }
-    else if (heatingEnabled && !turnHeatingOn) {
-      heatingOffTime = (millis() - heatingLastOnMillis) / 1000;
-      Serial.print("heatingOffTime: ");
-      Serial.print(heatingOffTime);
-      Serial.println();
-    }
-
-
-    Serial.print("lastTempUpdate: ");
-    Serial.println(lastTempUpdate);
-
-    long test1 = millis() - lastTempUpdate;
-    Serial.print("lastTempUpdate test1: ");
-    Serial.println(test1);
-
-    
-    if ( (millis() - lastTempUpdate) < 120000 ) {
-      // thermostat - only active if measured temperature is < 2 min old
-
-      // thermostat with hysteresis
-      if ( temperature >= setTemp ) {
-        turnHeatingOn = false;
-        heatingLastOnMillis = millis();
-        digitalWrite(PIN_RELAIS, !RELAISONSTATE);
-        updateDisplay();
-        Serial.println("heating off");
-
-        Serial.print("last onTime: ");
-        Serial.print(heatingOnTime);
-        Serial.println();
-
-        mqttclient.publish(mqtt_topic_heating, "off");
-
-        //mqttclient.publish(mqtt_topic_heating_lastontime, heatingOnTime);
-      }
-      else if ( heatingEnabled && ( temperature < (setTemp - hysteresis) ) && ( heatingOffTime > minOffTime ) ) {
-        turnHeatingOn = true;
-        digitalWrite(PIN_RELAIS, RELAISONSTATE);
-        updateDisplay();
-        Serial.println("heating on");
-
-        Serial.print("last offTime: ");
-        Serial.print(heatingOffTime);
-        Serial.println();
-
-        mqttclient.publish(mqtt_topic_heating, "on");
-
-        //mqttclient.publish(mqtt_topic_heating_lastofftime, heatingOffTime);
-      }
-    }
-    else {
-      turnHeatingOn = false;
-      digitalWrite(PIN_RELAIS, !RELAISONSTATE);
-      Serial.println("heating off");
-      mqttclient.publish(mqtt_topic_heating, "off");
-    }
-
 
+  yield();
 
-  }
-
-
-
-
-  //    if (lightMeter_present) {
-  //      delay(50);
-  //      lux = lightMeter.readLightLevel();
-  //
-  //      String lux_str = String(lux, 1); //converting humidity (the float variable above) to a string with 0 decimals
-  //      char lux_chararr[lux_str.length() + 1];
-  //      lux_str.toCharArray(lux_chararr, lux_str.length() + 1); //packaging up the data to publish to mqtt whoa...
-  //
-  //      Serial.print("Lux: ");
-  //      Serial.println(lux_str);
-  //      mqttclient.publish(mqtt_topic_lux, lux_chararr);
-  //    }
+  evalCmd();
 
-
-  long now = millis();
-  if ( ((now - aTempHumLastUpdate) < 300000) && whichTempToDisplay == 1 && ((now - lastDisplayToggle) > displayinterval) ) {
-    // A-temp has been updated < 5 min ago, last displayed temp was I and display interval is overdue
-    whichTempToDisplay = 2; // 1= I, 2= A
-    lastDisplayToggle = now;
-  }
-  else if ( whichTempToDisplay != 1 && ((now - lastDisplayToggle) > displayinterval) ) {
-    whichTempToDisplay = 1; // 1= I, 2= A
-    lastDisplayToggle = now;
+  if ( domoticzOutParseData ) {
+    parseDomoticzOut();
+    yield();
   }
 
-  // update display?
-  if ( (now - lastDisplayUpdate) > displayinterval) {
-    lastDisplayUpdate = now;
-
-    //    char temp_chararr[6];
-    //    char hum_chararr[3];
-    //    if ( (millis() - lastTempUpdate) < 120000) {
-    //      String temp_str = String(temperature, 1); //converting Temperature (the float variable above) to a string with 1 decimal
-    //      //char temp_chararr[temp_str.length() + 1];
-    //      temp_str.toCharArray(temp_chararr, temp_str.length() + 1); //packaging up the data to publish to mqtt whoa...
-    //
-    //      String hum_str = String(humidity, 0); //converting humidity (the float variable above) to a string with 0 decimals
-    //      //char hum_chararr[hum_str.length() + 1];
-    //      hum_str.toCharArray(hum_chararr, hum_str.length() + 1); //packaging up the data to publish to mqtt whoa...
-    //
-    //      mqttclient.publish(mqtt_topic_temp, temp_chararr);
-    //      mqttclient.publish(mqtt_topic_hum, hum_chararr);
-    //    }
-    //    else {
-    //      char temp_chararr[] = {'-', '-', ',', '-', '\0'};
-    //      char hum_chararr[] = {'-', '-', '\0'};
-    //    }
-
-
-
-    String temp_str = String(temperature, 1); //converting Temperature (the float variable above) to a string with 1 decimal
-    char temp_chararr[temp_str.length() + 1];
-    temp_str.toCharArray(temp_chararr, temp_str.length() + 1); //packaging up the data to publish to mqtt whoa...
-
-    String hum_str = String(humidity, 0); //converting humidity (the float variable above) to a string with 0 decimals
-    char hum_chararr[hum_str.length() + 1];
-    hum_str.toCharArray(hum_chararr, hum_str.length() + 1); //packaging up the data to publish to mqtt whoa...
-
-    if ( (now - lastTempUpdate) < 120000 ) {
-      mqttclient.publish(mqtt_topic_temp, temp_chararr);
-      mqttclient.publish(mqtt_topic_hum, hum_chararr);
-    }
-    else {
-      temp_str = "--.-";
-      hum_str = "--";
-    }
-
-
-    String atemp_str = String(aTemp, 1); //converting Temperature (the float variable above) to a string with 1 decimal
-    char atemp_chararr[atemp_str.length() + 1];
-    atemp_str.toCharArray(atemp_chararr, atemp_str.length() + 1); //packaging up the data to publish to mqtt whoa...
-
-    String ahum_str = String(aHum, 0); //converting humidity (the float variable above) to a string with 0 decimals
-    char ahum_chararr[ahum_str.length() + 1];
-    ahum_str.toCharArray(ahum_chararr, ahum_str.length() + 1); //packaging up the data to publish to mqtt whoa...
-
-
-
-    char setTemp_chararr[5];     //result string 4 positions + \0 at the end
-    // convert float to fprintf type string format 2 positions with 1 decimal place
-    dtostrf(setTemp, 2, 1, setTemp_chararr );
-
-    Serial.print("set temp: ");
-    Serial.println(setTemp_chararr);
-    Serial.print("current temp: ");
-    Serial.println(temp_chararr);
-
-
-    mqttclient.publish(mqtt_topic_settemp, setTemp_chararr);
-
-
-    if (whichTempToDisplay == 2) {
-      // print inside temperature incl = and ° symbol + humidity to lcd, line 1, first 11 chars
-      lcd.setCursor(0, 0);
-      //lcd.write(0x3D); // = Zeichen
-      lcd.print("A");
-
-      if (aTemp <= -10);
-      else if (aTemp < 0) lcd.print(" ");
-      else if (aTemp < 10) lcd.print("  ");
-      else lcd.print(" ");
-
-      lcd.print(atemp_str); // inside temperature should hopefully always be 2.1 chars, so no special formatting necessary
-      lcd.write(0xDF); // degree symbol
-      lcd.print(" ");
-
-      //lcd.print(humstr2);
-      lcd.print(ahum_str); // always 2 chars
-      lcd.print("%");
-      lcd.print("  ");
-    }
-    else {
-      // print outside temperature incl = and ° symbol + humidity to lcd, line 1, first 11 chars
-      lcd.setCursor(0, 0);
-      //lcd.write(0x3D); // = Zeichen
-      lcd.print("I");
-
-      //lcd.print(" "); // inside temperature should hopefully always be 2.1 chars, so no special formatting/spaces handling necessary
-
-      if (temperature <= -10);
-      else if (temperature < 0) lcd.print(" ");
-      else if (temperature < 10) lcd.print("  ");
-      else lcd.print(" ");
-
-      //if (temperature < -9) lcd.print(" ");
-      //else if (temperature < 0) lcd.print("");
-      //else if (temperature < 10) lcd.print("  ");
-      //else lcd.print(" ");
-      //lcd.print(tempstr2);
-      //lcd.print(temp_str);
-      lcd.print(temp_chararr);
-      lcd.write(0xDF); // degree symbol
-      lcd.print(" ");
-
-      //lcd.print(humstr2);
-      //lcd.print(hum_str); // always 2 chars
-      lcd.print(hum_chararr); // always 2 chars
-      lcd.print("%");
-      lcd.print("  ");
-    }
-
-
-
-    // display current mode on LCD
-    lcd.setCursor(13, 0); // to char 15, line 1
-    if (heatingEnabled) {
-      if ( heatingMode == 1 ) { // day/normal mode
-        lcd.print(" ");
-        lcd.write((uint8_t)1); // sun symbol if mode is day/normal
-        lcd.print(" ");
-      }
-      else if ( heatingMode == 2 ) { // night/reduction mode
-        lcd.print(" ");
-        lcd.write((uint8_t)0); // moon symbol if mode is night/reduction
-        lcd.print(" ");
-      }
-    }
-    else lcd.print("   "); // mode is heating off
-
-
-    // display target temperature to line 2, 8 chars length incl space at the end
-    lcd.setCursor(0, 1);
-
-    // when the heating mode is OFF, do not display target temp - instead show "Heating off" info in line 2
-    if ( !heatingEnabled ) {
-      //         1234567890123456
-      lcd.print("  Heizung aus   ");
-    }
-    else {
-      lcd.write((uint8_t)2); // Pfeil rechts
-      lcd.print(" ");
-      lcd.print(setTemp_chararr);
-      lcd.write(0xDF); // degree symbol
-      lcd.print(" ");
-
-      // display status info to line 2 from char 9 -> 8 chars length
-      lcd.setCursor(8, 1);
-      if (turnHeatingOn) {
-        //         12345678
-        lcd.print("heizen..");
-      }
-      else if ( heatingMode == 1 ) { // day/normal mode
-        //         12345678
-        lcd.print("        ");
-      }
-      else if ( heatingMode == 2 ) { // night/reduction mode
-        //         12345678
-        lcd.print("N-Absenk");
-      }
-      else lcd.print("        ");
-    }
-
-
+  if (Serial.available()) {
+    serialEvent();
+    yield();
   }
-
+  
 } //void loop
 

+ 176 - 3
src/WiFiThermostat/commands.ino

@@ -1,4 +1,177 @@
-/*void evalCmd(char payload[50]) {
- 
+
+#define SER_INPUT_SIZE 70
+char serBuffer[SER_INPUT_SIZE + 1];    // Serial Input-Buffer
+int serBufferCount;    // Anzahl der eingelesenen Zeichen
+
+void serialEvent() {
+  char ch = Serial.read();
+  serBuffer[serBufferCount] = ch;
+  serBufferCount++;
+  if (ch == 13 || ch == 10) { // ASCII code 13 = "CR",  10 = "LF"
+    serBuffer[serBufferCount - 1] = '\0'; // string nullterminieren
+#ifdef DEBUG_VERBOSE
+    Serial.print("serial cmd: '");
+    Serial.print(serBuffer);
+    Serial.println("'");
+#endif
+    strlcpy(cmdPayload, serBuffer, 101);
+    cmdInQueue = true;
+    evalCmd();
+    serBufferCount = 0;
+  }
 }
-*/
+
+
+
+void evalCmd() {
+  if (cmdInQueue) {
+
+    //Serial.print("cmdPayload: ");
+    //Serial.println(cmdPayload);
+
+    if (strncmp(cmdPayload, "loadconf", 8) == 0) {
+      loadConfig();
+      loadConfig2();
+      mqttPrepareConnection();
+    }
+
+    else if (strncmp(cmdPayload, "set ", 4) == 0) {
+      char buf[81];
+      char setconfCmd[16];
+      char setconfPayload[62];
+
+      int len = strlen(cmdPayload) - 4;
+      for (int i = 0; i < len; i++) {
+        if (i < 81) buf[i] = cmdPayload[i + 4];
+      }
+      if (len <= 81) buf[len] = '\0';
+      else buf[81] = '\0';
+
+#ifdef DEBUG_VERBOSE
+      Serial.print("Buf: ");
+      Serial.println(buf);
+#endif
+
+      bool cmdNoPayload = false;
+      int setconfCmdLen = 0;
+      for (int i = 0; i < len; i++) {
+        //if (buf[i] == 32 || buf[i] == '\0') break; // if SPACE command name is finished, if \0 command parameter is missing
+        if (buf[i] == 32 || buf[i] == 61 || buf[i] == 58) break; // if SPACE(32) or =(61) or :(58) command name is finished
+        else if (buf[i] == 0 || buf[i] == 10 || buf[i] == 13) { // if \0, LF (10) or CR (13) command parameter is missing
+          cmdNoPayload = true;
+          break;
+        }
+        setconfCmd[i] = buf[i];
+        setconfCmdLen++;
+      }
+      setconfCmd[setconfCmdLen] = '\0';
+      yield();
+
+      if (setconfCmdLen == len) cmdNoPayload = true;
+
+#ifdef DEBUG_VERBOSE
+      Serial.print("setconfCmd: '");
+      Serial.print(setconfCmd);
+      Serial.print("'");
+#endif
+
+      if ( cmdNoPayload ) {
+#ifdef DEBUG_VERBOSE
+        Serial.println(", no payload, displaying current value");
+#endif
+        getConfig(setconfCmd);
+      }
+      else {
+#ifdef DEBUG_VERBOSE
+        Serial.println();
+#endif
+        int setconfPayloadLen = 0;
+        for (int i = 0; i < len; i++) {
+          char c = buf[i + setconfCmdLen + 1];
+          if (c == 0 || c == 10 || c == 13) break; // if \0, LF (10) or CR (13) command parameter is finished
+          setconfPayload[i] = c;
+          setconfPayloadLen++;
+        }
+        setconfPayload[setconfPayloadLen] = '\0';
+
+#ifdef DEBUG_VERBOSE
+        Serial.print("setconfPayload: '");
+        Serial.print(setconfPayload);
+        Serial.println("'");
+#endif
+        setConfig(setconfCmd, setconfPayload);
+      }
+    }
+    else if (strncmp(cmdPayload, "get ", 4) == 0) {
+      char buf[81];
+      char setconfCmd[16];
+      char setconfPayload[62];
+
+      int len = strlen(cmdPayload) - 4;
+      for (int i = 0; i < len; i++) {
+        if (i < 81) buf[i] = cmdPayload[i + 4];
+      }
+      if (len <= 81) buf[len] = '\0';
+      else buf[81] = '\0';
+
+#ifdef DEBUG_VERBOSE
+      Serial.print("Buf: ");
+      Serial.println(buf);
+#endif
+
+      bool cmdNoPayload = false;
+      int setconfCmdLen = 0;
+      for (int i = 0; i < len; i++) {
+        //if (buf[i] == 32 || buf[i] == '\0') break; // if SPACE command name is finished, if \0 command parameter is missing
+        if (buf[i] == 32 || buf[i] == 61 || buf[i] == 58) break; // if SPACE(32) or =(61) or :(58) command name is finished
+        else if (buf[i] == 0 || buf[i] == 10 || buf[i] == 13) { // if \0, LF (10) or CR (13) command parameter is missing
+          cmdNoPayload = true;
+          break;
+        }
+        setconfCmd[i] = buf[i];
+        setconfCmdLen++;
+      }
+      setconfCmd[setconfCmdLen] = '\0';
+      yield();
+#ifdef DEBUG_VERBOSE
+      Serial.print("setconfCmd: '");
+      Serial.print(setconfCmd);
+      Serial.println("'");
+#endif
+      getConfig(setconfCmd);
+    }
+    else if (strncmp(cmdPayload, "restart", 7) == 0) {
+      Serial.print("restarting...");
+      delay(100);
+      ESP.restart();
+    }
+    else if (strncmp(cmdPayload, "save", 4) == 0) {
+      saveConfig();
+      yield();
+      saveConfig2();
+      yield();
+
+      saveSetTemp();
+      saveHeatingMode();
+      
+      //Serial.println("saved config to SPIFFS");
+      sendStatus("saved config to SPIFFS");
+      Serial.println("reloading config to check...");
+      loadConfig();
+      yield();
+      loadConfig2();
+      yield();
+    }
+    else if (strncmp(cmdPayload, "getconf", 7) == 0) {
+      printConfig();
+      printConfig2();
+    }
+    else if (strncmp(cmdPayload, "delconf", 7) == 0) {
+      deleteConfig();
+    }
+
+
+    cmdInQueue = false;
+  }
+}
+

+ 707 - 0
src/WiFiThermostat/config.ino

@@ -0,0 +1,707 @@
+
+bool setConfig(char* param, char* value) {
+  // sets the corresponding config variable for 'param' to new value
+  // does not trigger saving to flash
+  // does not distinguish between config and config2 as this is only split on flash and web-interface
+
+  if (debug) {
+    Serial.print("setConfig - '");
+    Serial.print(param);
+    Serial.print("' to '");
+    Serial.print(value);
+    Serial.println("'");
+  }
+
+  // values
+  if ( strcmp(param, "temp") == 0 ) {
+    float valueFloat = round(atof(value) * 2.0) / 2.0;
+#ifdef DEBUG_VERBOSE
+    Serial.print(valueFloat);
+#endif
+    setTempTo(valueFloat);
+  }
+  else if ( strcmp(param, "tempLow") == 0 ) {
+    float valueFloat = round(atof(value) * 2.0) / 2.0;
+#ifdef DEBUG_VERBOSE
+    Serial.print(valueFloat);
+#endif
+    setTempLowTo(valueFloat);
+  }
+  else if ( strcmp(param, "mode") == 0 ) {
+    setHeatingmodeTo(atoi(value));
+  }
+
+  //confdata
+  else if ( strcmp(param, "devName") == 0 ) {
+    strlcpy(deviceName, value, 31);
+  }
+  else if ( strcmp(param, "httpUser") == 0 ) {
+    strlcpy(http_user, value, 31);
+  }
+  else if ( strcmp(param, "httpPass") == 0 ) {
+    strlcpy(http_pass, value, 31);
+  }
+  else if ( strcmp(param, "mqttHost") == 0 ) {
+    strlcpy(mqtt_server, value, 41);
+  }
+  else if ( strcmp(param, "mqttPort") == 0 ) {
+    mqtt_port = atoi(value);
+  }
+  else if ( strcmp(param, "mqttUser") == 0 ) {
+    strlcpy(mqtt_user, value, 31);
+  }
+  else if ( strcmp(param, "mqttPass") == 0 ) {
+    strlcpy(mqtt_pass, value, 31);
+  }
+  else if ( strcmp(param, "inTop") == 0 ) {
+    strlcpy(mqtt_topic_in, value, 51);
+  }
+  else if ( strcmp(param, "outTop") == 0 ) {
+    strlcpy(mqtt_topic_out, value, 51);
+  }
+  else if ( strcmp(param, "outRet") == 0 ) {
+    if (atoi(value) == 1) mqtt_outRetain = true;
+    else mqtt_outRetain = false;
+  }
+  else if ( strcmp(param, "willTop") == 0 ) {
+    strlcpy(mqtt_willTopic, value, 51);
+  }
+  else if ( strcmp(param, "willQos") == 0 ) {
+    int tmpval = atoi(value);
+    if (tmpval >= 0 && tmpval <= 2) mqtt_willQos = tmpval;
+  }
+  else if ( strcmp(param, "willRet") == 0 ) {
+    if (atoi(value) == 1) mqtt_willRetain = true;
+    else mqtt_willRetain = false;
+  }
+  else if ( strcmp(param, "willMsg") == 0 ) {
+    strlcpy(mqtt_willMsg, value, 31);
+  }
+  else if ( strcmp(param, "domOutTop") == 0 ) {
+    strlcpy(domoticz_out_topic, value, 51);
+  }
+
+  //confdata2
+  else if ( strcmp(param, "domIdxTherm") == 0 ) {
+    domoticzIdx_Thermostat = atoi(value);
+  }
+  else if ( strcmp(param, "domIdxMode") == 0 ) {
+    domoticzIdx_ThermostatMode = atoi(value);
+  }
+  else if ( strcmp(param, "domIdxTempHum") == 0 ) {
+    domoticzIdx_TempHumSensor = atoi(value);
+  }
+  else if ( strcmp(param, "domIdxPIR") == 0 ) {
+    domoticzIdx_PIR = atoi(value);
+  }
+  else if ( strcmp(param, "outTempTop") == 0 ) {
+    strlcpy(outTemp_topic_in, value, 51);
+  }
+  else if ( strcmp(param, "outHumTop") == 0 ) {
+    strlcpy(outHum_topic_in, value, 51);
+  }
+  else if ( strcmp(param, "autoSaveTemp") == 0 ) {
+    if (atoi(value) == 1) autoSaveSetTemp = true;
+    else autoSaveSetTemp = false;
+  }
+  else if ( strcmp(param, "autoSaveMode") == 0 ) {
+    if (atoi(value) == 1) autoSaveHeatingMode = true;
+    else autoSaveHeatingMode = false;
+  }
+  else if ( strcmp(param, "minOffTime") == 0 ) {
+    heatingMinOffTime = atoi(value);
+  }
+  else if ( strcmp(param, "tempMin") == 0 ) {
+    float valueFloat = round(atof(value) * 2.0) / 2.0;
+#ifdef DEBUG_VERBOSE
+    Serial.print(valueFloat);
+#endif
+    if (valueFloat >= 10 && valueFloat <= 16) {
+      setTempMin = valueFloat;
+    }
+  }
+  else if ( strcmp(param, "tempMax") == 0 ) {
+    float valueFloat = round(atof(value) * 2.0) / 2.0;
+#ifdef DEBUG_VERBOSE
+    Serial.print(valueFloat);
+#endif
+    if (valueFloat >= 18 && valueFloat <= 30) {
+      setTempMax = valueFloat;
+    }
+  }
+  else if ( strcmp(param, "hyst") == 0 ) {
+    float valueFloat = atof(value);
+#ifdef DEBUG_VERBOSE
+    Serial.print(valueFloat);
+#endif
+    if (valueFloat >= 0.1 && valueFloat <= 4.0) {
+      hysteresis = valueFloat;
+    }
+  }
+  else if ( strcmp(param, "tempCorr") == 0 ) {
+    float valueFloat = atof(value);
+    if (valueFloat >= -5.0 && valueFloat <= 5.0) {
+      tempCorrVal = valueFloat;
+    }
+  }
+  else if ( strcmp(param, "humCorr") == 0 ) {
+    int valueInt = atoi(value);
+    if (valueInt >= -40 && valueInt <= 40) {
+      humCorrVal = valueInt;
+    }
+  }
+  else if ( strcmp(param, "measInt") == 0 ) {
+    int valueInt = atoi(value);
+    if (valueInt >= 5 && valueInt <= 120) {
+      measureInterval = valueInt;
+    }
+  }
+  else if ( strcmp(param, "dispInt") == 0 ) {
+    int valueInt = atoi(value);
+    if (valueInt >= 2 && valueInt <= 120) {
+      displayInterval = valueInt;
+    }
+  }
+  else if ( strcmp(param, "dispTout") == 0 ) {
+    int valueInt = atoi(value);
+    if (valueInt >= 2 && valueInt <= 1200) {
+      displayTimeout = valueInt;
+    }
+  }
+}
+
+void getConfig(char* param) {
+  // gets and prints the corresponding config variable for 'param'
+
+  if (debug) {
+    Serial.print("getConfig - '");
+    Serial.print(param);
+    Serial.println("'");
+  }
+
+  char buf[101];
+
+  // values
+  if ( strcmp(param, "temp") == 0 ) {
+    char buf2[11];
+    dtostrf(setTemp, 2, 1, buf2);
+    sprintf(buf, "setTemp: '%s'", buf2);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "tempLow") == 0 ) {
+    char buf2[11];
+    dtostrf(setTempLow, 2, 1, buf2);
+    sprintf(buf, "setTempLow: '%s'", buf2);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "mode") == 0 ) {
+    sprintf(buf, "heatingMode: '%d'", heatingMode);
+    sendStatus(buf);
+  }
+
+  //confdata
+  else if ( strcmp(param, "devName") == 0 ) {
+    sprintf(buf, "devName: '%s'", deviceName);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "httpUser") == 0 ) {
+    sprintf(buf, "httpUser: '%s'", http_user);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "httpPass") == 0 ) {
+    sprintf(buf, "httpPass: '%s'", http_pass);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "mqttHost") == 0 ) {
+    sprintf(buf, "mqttHost: '%s'", mqtt_server);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "mqttPort") == 0 ) {
+    sprintf(buf, "mqttPort: '%s'", mqtt_port);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "mqttUser") == 0 ) {
+    sprintf(buf, "mqttUser: '%s'", mqtt_user);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "mqttPass") == 0 ) {
+    sprintf(buf, "mqttPass: '%s'", mqtt_pass);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "inTop") == 0 ) {
+    sprintf(buf, "inTop: '%s'", mqtt_topic_in);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "outTop") == 0 ) {
+    sprintf(buf, "outTop: '%s'", mqtt_topic_out);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "outRet") == 0 ) {
+    char buf2[11];
+    if (mqtt_outRetain) strcpy(buf2, "1");
+    else strcpy(buf2, "0");
+    sprintf(buf, "outRet: '%s'", buf2);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "willTop") == 0 ) {
+    sprintf(buf, "willTop: '%s'", mqtt_willTopic);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "willQos") == 0 ) {
+    sprintf(buf, "willQos: '%d'", mqtt_willQos);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "willRet") == 0 ) {
+    char buf2[11];
+    if (mqtt_willRetain) strcpy(buf2, "1");
+    else strcpy(buf2, "0");
+    sprintf(buf, "willRet: '%s'", buf2);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "willMsg") == 0 ) {
+    sprintf(buf, "willMsg: '%s'", mqtt_willMsg);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "domOutTop") == 0 ) {
+    sprintf(buf, "domOutTop: '%s'", domoticz_out_topic);
+    sendStatus(buf);
+  }
+
+  //confdata2
+  else if ( strcmp(param, "domIdxTherm") == 0 ) {
+    sprintf(buf, "domIdxTherm: '%d'", domoticzIdx_Thermostat);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "domIdxMode") == 0 ) {
+    sprintf(buf, "domIdxMode: '%d'", domoticzIdx_ThermostatMode);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "domIdxTempHum") == 0 ) {
+    sprintf(buf, "domIdxTempHum: '%d'", domoticzIdx_TempHumSensor);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "domIdxPIR") == 0 ) {
+    sprintf(buf, "domIdxPIR: '%d'", domoticzIdx_PIR);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "outTempTop") == 0 ) {
+    sprintf(buf, "outTempTop: '%s'", outTemp_topic_in);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "outHumTop") == 0 ) {
+    sprintf(buf, "outHumTop: '%s'", outHum_topic_in);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "autoSaveTemp") == 0 ) {
+    char buf2[11];
+    if (autoSaveSetTemp) strcpy(buf2, "1");
+    else strcpy(buf2, "0");
+    sprintf(buf, "autoSaveTemp: '%s'", buf2);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "autoSaveMode") == 0 ) {
+    char buf2[11];
+    if (autoSaveHeatingMode) strcpy(buf2, "1");
+    else strcpy(buf2, "0");
+    sprintf(buf, "autoSaveMode: '%s'", buf2);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "minOffTime") == 0 ) {
+    sprintf(buf, "minOffTime: '%d'", heatingMinOffTime);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "tempMin") == 0 ) {
+    char buf2[11];
+    dtostrf(setTempMin, 2, 1, buf2);
+    sprintf(buf, "tempMin: '%s'", buf2);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "tempMax") == 0 ) {
+    char buf2[11];
+    dtostrf(setTempMax, 2, 1, buf2);
+    sprintf(buf, "tempMax: '%s'", buf2);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "hyst") == 0 ) {
+    char buf2[11];
+    dtostrf(hysteresis, 2, 1, buf2);
+    sprintf(buf, "hyst: '%s'", buf2);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "tempCorr") == 0 ) {
+    char buf2[11];
+    dtostrf(tempCorrVal, 2, 1, buf2);
+    sprintf(buf, "tempCorr: '%s'", buf2);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "humCorr") == 0 ) {
+    sprintf(buf, "humCorr: '%d'", humCorrVal);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "measInt") == 0 ) {
+    sprintf(buf, "measInt: '%d'", measureInterval);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "dispInt") == 0 ) {
+    sprintf(buf, "dispInt: '%d'", displayInterval);
+    sendStatus(buf);
+  }
+  else if ( strcmp(param, "dispTout") == 0 ) {
+    sprintf(buf, "dispTout: '%d'", displayTimeout);
+    sendStatus(buf);
+  }
+}
+
+void printConfig() {
+  // prints current config vars to serial
+  Serial.println("\nconfdata:");
+  Serial.print("devName: ");
+  Serial.println(deviceName);
+  Serial.print("httpUser: ");
+  Serial.println(http_user);
+  Serial.print("httpPass: ");
+  Serial.println(http_pass);
+  Serial.print("mqttHost: ");
+  Serial.println(mqtt_server);
+  Serial.print("mqttPort: ");
+  Serial.println(mqtt_port);
+  Serial.print("mqttUser: ");
+  Serial.println(mqtt_user);
+  Serial.print("mqttPass: ");
+  Serial.println(mqtt_pass);
+  Serial.print("inTop: ");
+  Serial.println(mqtt_topic_in);
+  Serial.print("outTop: ");
+  Serial.println(mqtt_topic_out);
+  Serial.print("outRet: ");
+  Serial.println(mqtt_outRetain);
+  Serial.print("willTop: ");
+  Serial.println(mqtt_willTopic);
+  Serial.print("willQos: ");
+  Serial.println(mqtt_willQos);
+  Serial.print("willRet: ");
+  Serial.println(mqtt_willRetain);
+  Serial.print("willMsg: ");
+  Serial.println(mqtt_willMsg);
+  Serial.print("domOutTop: ");
+  Serial.println(domoticz_out_topic);
+  Serial.println();
+}
+void printConfig2() {
+  Serial.println("\nconfdata2:");
+  Serial.print("domIdxTherm: ");
+  Serial.println(domoticzIdx_Thermostat);
+  Serial.print("domIdxMode: ");
+  Serial.println(domoticzIdx_ThermostatMode);
+  Serial.print("domIdxTempHum: ");
+  Serial.println(domoticzIdx_TempHumSensor);
+  Serial.print("domIdxPIR: ");
+  Serial.println(domoticzIdx_PIR);
+  Serial.print("outTempTop: ");
+  Serial.println(outTemp_topic_in);
+  Serial.print("outHumTop: ");
+  Serial.println(outHum_topic_in);
+  Serial.print("autoSaveTemp: ");
+  Serial.println(autoSaveSetTemp);
+  Serial.print("autoSaveMode: ");
+  Serial.println(autoSaveHeatingMode);
+  Serial.print("minOffTime: ");
+  Serial.println(heatingMinOffTime);
+  Serial.print("tempMin: ");
+  Serial.println(setTempMin);
+  Serial.print("tempMax: ");
+  Serial.println(setTempMax);
+  Serial.print("hyst: ");
+  Serial.println(hysteresis);
+  Serial.print("tempCorr: ");
+  Serial.println(tempCorrVal);
+  Serial.print("humCorr: ");
+  Serial.println(humCorrVal);
+  Serial.print("measInt: ");
+  Serial.println(measureInterval);
+  Serial.print("dispInt: ");
+  Serial.println(displayInterval);
+  Serial.print("dispTout: ");
+  Serial.println(displayTimeout);
+  Serial.println();
+}
+
+
+bool loadConfig() { // loadConfig 1
+  if (SPIFFS.exists("/conf.json")) {
+    File configFile = SPIFFS.open("/conf.json", "r");
+    if (!configFile) {
+      Serial.println("ERR: Failed to open file /conf.json");
+      return false;
+    }
+    else {
+      Serial.println("file /conf.json opened");
+      size_t size = configFile.size();
+
+      Serial.print("file size: ");
+      Serial.println(size);
+
+      //      Serial.println("file content:");
+      //
+      //      while (configFile.available()) {
+      //        //Lets read line by line from the file
+      //        String line = configFile.readStringUntil('\n');
+      //        Serial.println(line);
+      //      }
+      //      Serial.println();
+
+      if (size > 1000) {
+        Serial.println("Config file size is too large");
+        return false;
+      }
+
+      Serial.println("allocate buffer");
+      // Allocate a buffer to store contents of the file.
+      std::unique_ptr<char[]> buf(new char[size]);
+
+      Serial.println("read file bytes to buffer");
+      // We don't use String here because ArduinoJson library requires the input
+      // buffer to be mutable. If you don't use ArduinoJson, you may as well
+      // use configFile.readString instead.
+      configFile.readBytes(buf.get(), size);
+
+      StaticJsonBuffer<1050> jsonBuffer;
+      JsonObject& json = jsonBuffer.parseObject(buf.get());
+
+      if (!json.success()) {
+        Serial.println("Failed to parse config file");
+        return false;
+      }
+
+      strlcpy(deviceName, json["devName"] | "", 31);
+      strlcpy(http_user, json["httpUser"] | "", 31);
+      strlcpy(http_pass, json["httpPass"] | "", 31);
+      strlcpy(mqtt_server, json["mqttHost"] | "", 41);
+      mqtt_port = atoi(json["mqttPort"] | "");
+      strlcpy(mqtt_user, json["mqttUser"] | "", 31);
+      strlcpy(mqtt_pass, json["mqttPass"] | "", 31);
+      strlcpy(mqtt_topic_in, json["inTop"] | "", 51);
+      strlcpy(mqtt_topic_out, json["outTop"] | "", 51);
+
+      if (atoi(json["outRet"] | "") == 1) mqtt_outRetain = true;
+      else mqtt_outRetain = false;
+
+      strlcpy(mqtt_willTopic, json["willTop"] | "", 51);
+      mqtt_willQos = atoi(json["willQos"] | "0");
+
+      if (atoi(json["willRet"] | "") == 1) mqtt_willRetain = true;
+      else mqtt_willRetain = false;
+
+      strlcpy(mqtt_willMsg, json["willMsg"] | "", 31);
+      strlcpy(domoticz_out_topic, json["domOutTop"] | "", 51);
+
+      Serial.println("Loaded config values:");
+      printConfig();
+      return true;
+    }
+    configFile.close();
+  }
+  else {
+    Serial.println("file /config.json file does not exist");
+    return false;
+  }
+
+
+} // loadConfig 1
+
+bool loadConfig2() {
+  if (SPIFFS.exists("/conf2.json")) {
+    File configFile = SPIFFS.open("/conf2.json", "r");
+    if (!configFile) {
+      Serial.println("ERR: Failed to open file /conf2.json");
+      return false;
+    }
+    else {
+      Serial.println("file /conf2.json opened");
+      size_t size = configFile.size();
+      Serial.print("file size: ");
+      Serial.println(size);
+      if (size > 1000) {
+        Serial.println("Config file size is too large");
+        return false;
+      }
+
+      // Allocate a buffer to store contents of the file.
+      std::unique_ptr<char[]> buf(new char[size]);
+
+      // We don't use String here because ArduinoJson library requires the input
+      // buffer to be mutable. If you don't use ArduinoJson, you may as well
+      // use configFile.readString instead.
+      configFile.readBytes(buf.get(), size);
+
+      StaticJsonBuffer<1050> jsonBuffer;
+      JsonObject& json = jsonBuffer.parseObject(buf.get());
+
+      if (!json.success()) {
+        Serial.println("Failed to parse config file");
+        return false;
+      }
+
+      domoticzIdx_Thermostat = atoi(json["domIdxTherm"] | "");
+      domoticzIdx_ThermostatMode = atoi(json["domIdxMode"] | "");
+      domoticzIdx_TempHumSensor = atoi(json["domIdxTempHum"] | "");
+      domoticzIdx_PIR = atoi(json["domIdxPIR"] | "");
+
+      Serial.println("Loaded config values:");
+      printConfig2();
+      return true;
+    }
+  }
+  else {
+    Serial.println("file /conf2.json file does not exist");
+    return false;
+  }
+} //loadConfig2
+
+
+bool loadSetTemp() { // loadSetTemp
+  File configFile = SPIFFS.open("/setTemp", "r");
+  if (!configFile) {
+    Serial.println("ERR: Failed to open file /setTemp");
+    return false;
+  }
+  String s = configFile.readStringUntil('\n');
+  configFile.close();
+  float tmpSetTemp = s.toFloat();
+  if ( tmpSetTemp >= setTempMin && tmpSetTemp <= setTempMax ) {
+    setTemp = tmpSetTemp;
+    return true;
+  }
+  else return false;
+} // loadSetTemp
+
+
+bool loadHeatingMode() { // loadHeatingMode
+  File configFile = SPIFFS.open("/heatingMode", "r");
+  if (!configFile) {
+    Serial.println("ERR: Failed to open file /heatingMode");
+    return false;
+  }
+  String s = configFile.readStringUntil('\n');
+  configFile.close();
+  int tmpHeatingMode = s.toInt();
+  if ( tmpHeatingMode >= 0 && tmpHeatingMode <= 2 ) {
+    heatingMode = tmpHeatingMode;
+    return true;
+  }
+  else return false;
+} // loadHeatingMode
+
+
+bool saveConfig() { // safeConfig
+  StaticJsonBuffer<1050> jsonBuffer;
+  JsonObject& json = jsonBuffer.createObject();
+
+  json["devName"] = deviceName;
+  json["httpUser"] = http_user;
+  json["httpPass"] = http_pass;
+  json["mqttHost"] = mqtt_server;
+  json["mqttPort"] = mqtt_port;
+  json["mqttUser"] = mqtt_user;
+  json["mqttPass"] = mqtt_pass;
+  json["inTop"] = mqtt_topic_in;
+  json["outTop"] = mqtt_topic_out;
+  json["outRet"] = mqtt_outRetain;
+  json["willTop"] = mqtt_willTopic;
+  json["willQos"] = mqtt_willQos;
+  json["willRet"] = mqtt_willRetain;
+  json["willMsg"] = mqtt_willMsg;
+  json["domOutTop"] = domoticz_out_topic;
+
+  yield();
+
+  File configFile = SPIFFS.open("/conf.json", "w");
+  if (!configFile) {
+    Serial.println("Failed to open conf file for writing");
+    return false;
+  }
+
+  json.printTo(configFile);
+  configFile.close();
+  return true;
+} // safeConfig
+
+
+bool saveConfig2() { // safeConfig2
+  StaticJsonBuffer<1050> jsonBuffer;
+  JsonObject& json = jsonBuffer.createObject();
+
+  json["domIdxTherm"] = domoticzIdx_Thermostat;
+  json["domIdxMode"] = domoticzIdx_ThermostatMode;
+  json["domIdxTempHum"] = domoticzIdx_TempHumSensor;
+  json["domIdxPIR"] = domoticzIdx_PIR;
+
+  yield();
+
+  File configFile = SPIFFS.open("/conf2.json", "w");
+  if (!configFile) {
+    Serial.println("Failed to open conf2 file for writing");
+    return false;
+  }
+
+  json.printTo(configFile);
+  configFile.close();
+  return true;
+} // safeConfig2
+
+
+bool saveSetTemp() { // saveSetTemp
+  File configFile = SPIFFS.open("/setTemp", "w");
+  if (!configFile) {
+    Serial.println("Failed to open setTemp file for writing");
+    return false;
+  }
+  configFile.println(setTemp);
+  configFile.close();
+  setTempSaved = setTemp;
+  return true;
+} // saveSetTemp
+
+bool saveHeatingMode() { // saveHeatingMode
+  File configFile = SPIFFS.open("/heatingMode", "w");
+  if (!configFile) {
+    Serial.println("Failed to open heatingMode file for writing");
+    return false;
+  }
+  configFile.println(heatingMode);
+  configFile.close();
+  heatingModeSaved = heatingMode;
+  return true;
+} // saveHeatingMode
+
+
+void checkSaveConfigTriggered() {
+  if (saveConfigToFlash) {
+    saveConfigToFlash = false;
+    saveConfig();
+  }
+  if (saveConfig2ToFlash) {
+    saveConfig2ToFlash = false;
+    saveConfig2();
+  }
+  //  if (saveSetTempToFlash) {
+  //    saveSetTempToFlash = false;
+  //    saveSetTemp();
+  //  }
+  //  if (saveHeatingModeToFlash) {
+  //    saveHeatingModeToFlash = false;
+  //    saveHeatingMode();
+  //  }
+
+}
+
+void deleteConfig() {
+  Serial.println("deleting configuration");
+  if (SPIFFS.remove("/formatComplete.txt")) {
+    Serial.println("config will be deleted after reboot");
+    delay(100);
+    ESP.restart();
+  }
+}
+
+

+ 219 - 0
src/WiFiThermostat/domoticz.ino

@@ -0,0 +1,219 @@
+
+//sample domoticz/out payload switch:
+//{
+//   "Battery" : 255,
+//   "RSSI" : 12,
+//   "description" : "Deckenleuchte Arbeitszimmer\nSonoff-Touch-01",
+//   "dtype" : "Light/Switch",
+//   "id" : "00014121",
+//   "idx" : 209,
+//   "name" : "Arbeitszimmer Licht",
+//   "nvalue" : 1,
+//   "stype" : "Switch",
+//   "switchType" : "On/Off",
+//   "unit" : 1
+//}
+// -> we need idx and nvalue
+
+
+
+//sample domoticz/out payload thermostat:
+//{
+//  "command": "udevice",
+//  "idx": 219,
+//  "nvalue": 0,
+//  "svalue": "24.00"
+//}
+//
+//
+//domoticz/out:
+//{
+//   "Battery" : 255,
+//   "RSSI" : 12,
+//   "description" : "",
+//   "dtype" : "Thermostat",
+//   "id" : "001412B",
+//   "idx" : 219,
+//   "meterType" : "Energy",
+//   "name" : "Raumthermostat Wohnzimmer",
+//   "nvalue" : 0,
+//   "stype" : "SetPoint",
+//   "svalue1" : "22.00",
+//   "unit" : 1
+//} -> we need idx and svalue
+
+//sample domoticz/out payload selector switch:
+//{
+//   "Battery" : 255,
+//   "LevelActions" : "||",
+//   "LevelNames" : "Off|Normal|Nachtabsenkung",
+//   "LevelOffHidden" : "false",
+//   "RSSI" : 12,
+//   "SelectorStyle" : "0",
+//   "description" : "",
+//   "dtype" : "Light/Switch",
+//   "id" : "0001415C",
+//   "idx" : 268,
+//   "name" : "Heizung Betriebsart",
+//   "nvalue" : 2,
+//   "stype" : "Selector Switch",
+//   "svalue1" : "10",
+//   "switchType" : "Selector",
+//   "unit" : 1
+//}
+// -> we need idx and svalue ("0"="Off", "10"="Normal", "20"="Nachtabsenkung"
+
+
+// counter for automatic domoticz update used in function updateDomoticzDevices()
+// initially set to domoticzUpdateInterval, to ensure soon update after boot
+int count_sendToDomoticz_thermostat = domoticzUpdateInterval;
+int count_sendToDomoticz_heatingMode = domoticzUpdateInterval;
+
+
+void parseDomoticzOut() {
+  domoticzOutParseData = false;
+  domoticzOutParserBusy = true;
+  unsigned long idx = 0;
+  int16_t nvalue;
+  char svalue[21];
+  int16_t found = 0;
+
+  StaticJsonBuffer<500> jsonBuf;
+  JsonObject& domoticz = jsonBuf.parseObject(domoticzOutPayload);
+  idx = domoticz["idx"];
+
+  yield();
+
+  if (idx == domoticzIdx_Thermostat || idx == domoticzIdx_ThermostatMode) {
+    nvalue = domoticz["nvalue"];
+    strlcpy(svalue, domoticz["svalue1"] | "", 21);
+
+    if ( idx == domoticzIdx_Thermostat ) {
+      if ((millis() - lastUpdate_setTemp) > dismissUpdateFromDomoticzTimeout) {
+        Serial.print(domoticz_out_topic);
+        Serial.print(" received: ");
+        Serial.print(" idx=");
+        Serial.print(idx);
+        Serial.print(", nvalue=");
+        Serial.print(nvalue);
+        Serial.print(", svalue=");
+        Serial.println(svalue);
+
+        yield();
+
+        float valueFloat = round(atof(svalue) * 2.0) / 2.0;
+        setTempTo(valueFloat);
+        lastValueChange = 0; // force saving of values without delay
+        checkValuesChanged();
+      }
+    }
+    else if ( idx == domoticzIdx_ThermostatMode ) {
+      if ((millis() - lastUpdate_heatingMode) > dismissUpdateFromDomoticzTimeout) {
+        Serial.print(domoticz_out_topic);
+        Serial.print(" received: ");
+        Serial.print(" idx=");
+        Serial.print(idx);
+        Serial.print(", nvalue=");
+        Serial.print(nvalue);
+        Serial.print(", svalue=");
+        Serial.println(svalue);
+
+        yield();
+
+        if (strcmp(svalue, "0") == 0) {
+          setHeatingmodeTo(0);
+          lastValueChange = 0; // force saving of values without delay
+          checkValuesChanged();
+        }
+        else if (strcmp(svalue, "10") == 0) {
+          setHeatingmodeTo(1);
+          lastValueChange = 0; // force saving of values without delay
+          checkValuesChanged();
+        }
+        else if (strcmp(svalue, "20") == 0) {
+          setHeatingmodeTo(2);
+          lastValueChange = 0; // force saving of values without delay
+          checkValuesChanged();
+        }
+      }
+    }
+  }
+  domoticzOutParserBusy = false;
+}
+
+
+void sendToDomoticz_thermostat() {
+  if (domoticzIdx_Thermostat > 0) {
+    //{"idx": 219,"nvalue":0,"svalue":"24.00"}
+    char domSetTempTo[6];
+    dtostrf(setTemp, 1, 1, domSetTempTo);
+
+    char buf[101];
+    sprintf(buf, "{\"idx\":%d,\"nvalue\":0,\"svalue\":\"%s\"}", domoticzIdx_Thermostat, domSetTempTo);
+    mqttclient.publish(DOMOTICZ_IN_TOPIC, buf);
+    count_sendToDomoticz_thermostat = 0;
+  }
+}
+
+
+void sendToDomoticz_heatingMode() {
+  //if (!skipNextDomoticzUpdate_ThermostatMode) {
+  if (domoticzIdx_ThermostatMode > 0) {
+    //skipNextDomoticzUpdate_ThermostatMode = false;
+    //{"command": "switchlight", "idx": 2450, "switchcmd": "Set Level", "level": 100 }
+    if (heatingMode == 0 || heatingMode == 1 || heatingMode == 2) {
+      int domLevel;
+      domLevel = heatingMode * 10;
+      // 0 = Off, 10 = Normal, 20 = Reduction
+      char buf[101];
+      sprintf(buf, "{\"command\":\"switchlight\",\"idx\":%d,\"switchcmd\":\"Set Level\",\"level\":%d}", domoticzIdx_ThermostatMode, domLevel);
+      mqttclient.publish(DOMOTICZ_IN_TOPIC, buf);
+      count_sendToDomoticz_heatingMode = 0;
+    }
+  }
+}
+
+void sendToDomoticz_TempHum() {
+  if (domoticzIdx_TempHumSensor > 0) {
+    if ( lastTempUpdate != 0 && (millis() - lastTempUpdate) < 120000 ) {
+      //{"idx":idx,"nvalue":0,"svalue":"TEMP;HUM;0"}
+      char buf[101];
+      char buftemp[10];
+      dtostrf(temperature, 1, 1, buftemp );
+      sprintf(buf, "{\"idx\":%d,\"nvalue\":0,\"svalue\":\"%s;%d;0\"}", domoticzIdx_TempHumSensor, buftemp, humidity);
+      mqttclient.publish(DOMOTICZ_IN_TOPIC, buf);
+    }
+  }
+}
+
+void checkUseDomoticz() {
+  if (domoticzIdx_Thermostat != 0 || domoticzIdx_ThermostatMode != 0) {
+    useDomoticz = true;
+  }
+  else {
+    useDomoticz = false;
+  }
+}
+
+
+void updateDomoticzDevices() {
+  // sends updates to domoticz, is called every minute by misc/everyMinute()
+
+  // update TempHum-Sensor device every minute
+  sendToDomoticz_TempHum();
+
+  if (count_sendToDomoticz_thermostat >= domoticzUpdateInterval) {
+    sendToDomoticz_thermostat();
+    count_sendToDomoticz_thermostat = 0;
+  }
+  else count_sendToDomoticz_thermostat++;
+
+  if (count_sendToDomoticz_heatingMode >= domoticzUpdateInterval) {
+    sendToDomoticz_heatingMode();
+    count_sendToDomoticz_heatingMode = 0;
+  }
+  else count_sendToDomoticz_heatingMode++;
+
+
+}
+

+ 511 - 51
src/WiFiThermostat/httpServer.ino

@@ -2,7 +2,8 @@
 
 static const char httpRoot[] PROGMEM =
   R"(<html><body>
-  <h1>WiFi Thermostat</h1>
+  <h1><span id='devname'></span></h1>
+  <h3>WiFi Thermostat</h3>
   
   <form id='minusBtnFrm'>
   <input type='hidden' name='minusBtn' value='1'>
@@ -23,26 +24,28 @@ static const char httpRoot[] PROGMEM =
   <input type='button' onclick='return sendModeBtn()' value='MODE'/>
   <br>  
   <br>  
-  <input type='button' onclick='return sendOnOffBtn()' value='EIN/AUS'/><br>
-  <span id='onoff'></span><br>
-  <br>
+  <input id='btn_onoff' type='button' onclick='return sendOnOffBtn()' value=''/><br>
   <br>
   
-  Aktuell: <span id='temp'></span> &#8451;&nbsp;&nbsp;&nbsp;<span id='hum'></span> %<br>
-  Heizung <span id='heating'></span><br>
+  Current: <span id='temp'></span> &#8451;&nbsp;&nbsp;&nbsp;<span id='hum'></span> %<br>
+  Heating <span id='heating'></span><br>
   <br>
-  WiFi verbunden mit <i><span id='ssid'></span></i>.<br>
-  <h6>Letztes Update vor
-  <span id='ut'></span> Sekunden.
+  WiFi connected to <i><span id='ssid'></span></i>.<br>
+  <h6>Last update 
+  <span id='ut'></span> seconds ago. 
   <span id='status'></span>
   </h6>
   <br>
-  <a href='/wifi.htm'>WiFi-Einstellungen</a><br>
-  <a href='/config'>Einstellungen</a><br>
-  <a href='/update'>Firmware Update</a>
+  <a href='/wifi.htm'>WiFi settings</a><br>
+  <a href='/conf'>Base configuration</a><br>
+  <a href='/conf2'>Extended configuration</a><br>
+  <a href='/update'>Firmware update</a><br>
+  <a href='/restart'>Restart</a>
   <script>
   function g(i) { return document.getElementById(i) };
-  var xhttp, updateTime;
+  var xhttp, updateTime, reqTime, reqFin;
+  var textA = 'OFF';
+  var textE = 'ON';
   
   function sendMinusBtn() {
   var form = document.getElementById('minusBtnFrm');
@@ -63,56 +66,288 @@ static const char httpRoot[] PROGMEM =
   
   function transmit(f) {
     if (!xhttp) { 
-      g('status').innerHTML = 'lädt...';
+      g('status').innerHTML = 'loading...';
+   reqTime = 0;
+      reqFin = false;
       xhttp = new XMLHttpRequest();
+    xhttp.timeout = 2000;
       xhttp.open('POST', 'api');
       xhttp.send(f ? (new FormData(f)) : '');
       xhttp.onreadystatechange = function () {
         if (xhttp.readyState === XMLHttpRequest.DONE && xhttp.status === 200) {
           var data = JSON.parse(xhttp.responseText);
+      if(data.devname != undefined) g('devname').innerHTML = data.devname;
           g('temp').innerHTML = data.temp.toFixed(1);
-      g('hum').innerHTML = data.hum;
+          g('hum').innerHTML = data.hum;
           g('setTemp').innerHTML = data.setTemp.toFixed(1);
           g('ssid').innerHTML = data.ssid;
-      
-      if(!data.heatingEnabled) {
-      g('mode').innerHTML = '-';
-      g('onoff').innerHTML = 'AUS';
-      }
-      else {
-      g('onoff').innerHTML = 'EIN';
-      if(data.mode == '1') {
-        g('mode').innerHTML = 'Normalbetrieb';
-      }
-      else if(data.mode == '2') {
-        g('mode').innerHTML = 'Nachtabsenkung';
-      }
-      }
-      
-      if(data.heating == '1') g('heating').innerHTML = 'aktiv';
-      else if(data.heating == '0') g('heating').innerHTML = 'nicht aktiv';
+            
+          if(data.mode == '0') {
+        g('btn_onoff').value = textA;
+            g('mode').innerHTML = 'Heating OFF';
+          }
+          else if(data.mode == '1') {
+        g('btn_onoff').value = textE;
+            g('mode').innerHTML = 'Normal';
+          }
+          else if(data.mode == '2') {
+        g('btn_onoff').value = textE;
+            g('mode').innerHTML = 'Reduction';
+          }
+          
+          if(data.heating == '1') g('heating').innerHTML = 'active';
+          else if(data.heating == '0') g('heating').innerHTML = 'not active';
 
           xhttp = null;
           g('status').innerHTML = '';
           updateTime = 0;
+          reqFin = true;
         }
+    else {
+    if(!reqFin && reqTime > 10) {
+            xhttp = null;
+            reqFin = true;
+          }
+    }
       }
     }
     return false;
   }
   transmit();
-  setInterval(function () { g('ut').innerHTML = ++updateTime; }, 1000);
+  setInterval(function () { g('ut').innerHTML = ++updateTime; ++reqTime; }, 1000);
   setInterval(transmit, 5000);
 </script>
   </body></html>)";
 
 
-static const char httpConfPage[] PROGMEM = 
-R"(<html><body>
-  <h1>WiFi Thermostat</h1>
-  <h2>Configuration</h2>
-  <a href='/'>Home</a>
-  </body></html>)";
+static const char httpConfPage[] PROGMEM =
+  R"(<html><head><body>
+<h3>Base configuration</h3>
+<a href='/'>Home</a><br><br>
+<input type='button' value='reload' onclick='return transmit()'/><br><br>
+<form id='form1' onsubmit='return transmit(this)'>
+Device Name: <input type='text' name='devName' id='devName'/><br><br>
+HTTP User *: <input type='text' name='httpUser' id='httpUser'/><br>
+HTTP Password *: <input type='text' name='httpPass' id='httpPass'/><br><br>
+MQTT Server *: <input type='text' name='mqttHost' id='mqttHost'/><br>
+MQTT Port *: <input type='number' name='mqttPort' id='mqttPort'/><br>
+MQTT User *: <input type='text' name='mqttUser' id='mqttUser'/><br>
+MQTT Password *: <input type='text' name='mqttPass' id='mqttPass'/><br><br>
+In Topic *: <input type='text' name='inTop' id='inTop'/><br>
+Out Topic: <input type='text' name='outTop' id='outTop'/><br>
+Out Retain *: <input type='checkbox' name='outRet' id='outRet'/><br><br>
+LastWill Topic *: <input type='text' name='willTop' id='willTop'/><br>
+LastWill Qos *: <select name='willQos' id='willQos'><option>0</option><option>1</option><option>2</option></select><br>
+LastWill Retain *: <input type='checkbox' name='willRet' id='willRet'/><br>
+LastWill Message *: <input type='text' name='willMsg' id='willMsg'/><br><br>
+Domoticz Out Topic *: <input type='text' name='domOutTop' id='domOutTop'/><br>
+<br>
+<input type='submit' value='Save'/>
+</form>
+<form id='restartForm' onsubmit='return res()'>
+<input type='hidden' name='restart' value='1'>
+<input type='submit' value='Restart'/>
+</form>
+
+<script>
+ function g(i) { return document.getElementById(i) };
+  var xhttp, reqTime, reqFin, rxhttp;
+  
+  function res() {
+    rxhttp = new XMLHttpRequest();
+ rxhttp.timeout = 1000;
+  rxhttp.open('POST', 'restart');
+  rxhttp.send('');
+  rxhttp = null;
+  return false;
+  }
+  function setCheckbox(ele, dat) {
+    if(dat == 1) {
+      ele.checked = true;
+      ele.style.visibility = 'visible';
+    }
+    else {
+      ele.checked = false;
+      ele.style.visibility = 'visible';
+    }
+  }
+  function updateCheckboxValue(ele) {
+    if (ele.checked) ele.value ='1';
+    else {
+      ele.value = '0';
+      ele.checked = true;
+      ele.style.visibility = 'hidden';
+    }
+  }
+  
+  function transmit(f) {
+    if (!xhttp) {   
+    reqTime = 0;
+    reqFin = false;
+    updateCheckboxValue(g('outRet'));
+    updateCheckboxValue(g('willRet'));    
+    xhttp = new XMLHttpRequest();
+    xhttp.timeout = 2000;
+    xhttp.open('POST', 'confdata');
+    xhttp.send(f ? (new FormData(f)) : '');
+    xhttp.onreadystatechange = function () {
+      if (xhttp.readyState === XMLHttpRequest.DONE && xhttp.status === 200) {
+        var data = JSON.parse(xhttp.responseText);
+        g('httpUser').value = data.httpUser;
+        g('httpPass').value = data.httpPass;
+        g('devName').value = data.devName
+        g('mqttHost').value = data.mqttHost;
+        g('mqttPort').value = data.mqttPort;
+        g('mqttUser').value = data.mqttUser;
+        g('mqttPass').value = data.mqttPass;
+        g('inTop').value = data.inTop;
+        g('outTop').value = data.outTop;
+        g('willTop').value = data.willTop;
+        g('willQos').value = data.willQos;
+        setCheckbox(g('outRet'), data.outRet);
+        setCheckbox(g('willRet'), data.willRet);
+        g('willMsg').value = data.willMsg;
+        g('domOutTop').value = data.domOutTop;
+        xhttp = null;
+        reqFin = true;
+       }
+       else {
+         if(!reqFin && reqTime > 10) {
+           xhttp = null;
+           reqFin = true;
+         }
+       }
+     }
+   }
+    return false;
+  }
+  transmit();
+  setInterval(function () { ++reqTime; }, 1000);
+</script>
+</body></html>)";
+
+
+
+static const char httpConf2Page[] PROGMEM =
+  R"(<html><head><body>
+<h3>Extended configuration</h3>
+<a href='/'>Home</a><br><br>
+<input type='button' value='reload' onclick='return transmit()'/><br>
+<form id='form1' onsubmit='return transmit(this)'>
+
+<h4>Domoticz</h4>
+Idx setTemp: <input type='number' name='domIdxTherm' id='domIdxTherm'/><br>
+Idx mode: <input type='number' name='domIdxMode' id='domIdxMode'/><br>
+Idx TempHum Sensor: <input type='number' name='domIdxTempHum' id='domIdxTempHum'/><br>
+Idx PIR: <input type='number' name='domIdxPIR' id='domIdxPIR'/><br>
+
+<h4>Outside Temp/Hum via MQTT</h4>
+Temp In-Topic: <input type='text' name='outTempTop' id='outTempTop'/><br>
+Hum In-Topic: <input type='text' name='outHumTop' id='outHumTop'/><br>
+
+<h4>Auto-Save</h4>
+setTemp: <input type='checkbox' name='autoSaveTemp' id='autoSaveTemp'/><br>
+Mode: <input type='checkbox' name='autoSaveMode' id='autoSaveMode'/><br>
+
+<h4>Thermostat / Heating</h4>
+Min. Off-Time: <input type='number' name='minOffTime' id='minOffTime'/><br>
+Min. Temp: <input type='text' name='tempMin' id='tempMin'/><br>
+Max. Temp: <input type='text' name='tempMax' id='tempMax'/><br>
+Reduction Mode Temp: <input type='text' name='tempLow' id='tempLow'/><br>
+Hysteresis: <input type='text' name='hyst' id='hyst'/><br>
+Temp Correction.: <input type='text' name='tempCorr' id='tempCorr'/><br>
+Hum Correction: <input type='number' name='humCorr' id='humCorr'/><br>
+
+<h4>Intervals / Timeouts</h4>
+Measure Interval: <input type='number' name='measInt' id='measInt'/><br>
+Display Interval: <input type='number' name='dispInt' id='dispInt'/><br>
+Display Timeout: <input type='number' name='dispTout' id='dispTout'/><br>
+
+<br>
+<input type='submit' value='Save'/>
+</form>
+<form id='rebootForm' onsubmit='return res()'>
+<input type='submit' value='Restart'/>
+</form>
+
+<script>
+ function g(i) { return document.getElementById(i) };
+  var xhttp, reqTime, reqFin, rxhttp;
+  
+  function res() {
+    rxhttp = new XMLHttpRequest();
+ rxhttp.timeout = 1000;
+  rxhttp.open('POST', 'restart');
+  rxhttp.send('');
+  rxhttp = null;
+  return false;
+  }
+  function setCheckbox(ele, dat) {
+    if(dat == 1) {
+      ele.checked = true;
+      ele.style.visibility = 'visible';
+    }
+    else {
+      ele.checked = false;
+      ele.style.visibility = 'visible';
+    }
+  }
+  function updateCheckboxValue(ele) {
+    if (ele.checked) ele.value ='1';
+    else {
+      ele.value = '0';
+      ele.checked = true;
+      ele.style.visibility = 'hidden';
+    }
+  }
+
+  function transmit(f) {
+    if (!xhttp) {
+      updateCheckboxValue(g('autoSaveTemp'));
+    updateCheckboxValue(g('autoSaveMode'));
+      xhttp = new XMLHttpRequest();
+      xhttp.timeout = 2000;
+      xhttp.open('POST', 'confdata2');
+      xhttp.send(f ? (new FormData(f)) : '');
+      xhttp.onreadystatechange = function () {
+        if (xhttp.readyState === XMLHttpRequest.DONE && xhttp.status === 200) {
+          var data = JSON.parse(xhttp.responseText);
+          setCheckbox(g('autoSaveTemp'), data.autoSaveTemp);
+          setCheckbox(g('autoSaveMode'), data.autoSaveMode);
+          g('domIdxTherm').value = data.domIdxTherm;
+          g('domIdxMode').value = data.domIdxMode;
+          g('domIdxTempHum').value = data.domIdxTempHum;
+          g('domIdxPIR').value = data.domIdxPIR;
+          g('outTempTop').value = data.outTempTop;
+          g('outHumTop').value = data.outHumTop;
+          g('minOffTime').value = data.minOffTime;
+          g('tempMin').value = data.tempMin;
+          g('tempMax').value = data.tempMax;
+          g('tempLow').value = data.tempLow;
+          g('hyst').value = data.hyst;
+          g('tempCorr').value = data.tempCorr;
+          g('humCorr').value = data.humCorr;
+          g('measInt').value = data.measInt;
+          g('dispInt').value = data.dispInt;
+          g('dispTout').value = data.dispTout;
+          
+          xhttp = null;
+          reqFin = false;
+        }
+      else {
+          if(!reqFin && reqTime > 10) {
+            xhttp = null;
+            reqFin = true;
+          }
+       }
+    }
+    }
+    return false;
+  }
+  transmit();
+  setInterval(function () { ++reqTime; }, 1000);
+</script>
+</body></html>)";
 
 void httpServerHandleRoot() {
   httpServer.send_P(200, "text/html", httpRoot);
@@ -122,20 +357,245 @@ void httpServerHandleConfPage() {
   httpServer.send_P(200, "text/html", httpConfPage);
 }
 
+void httpServerHandleConf2Page() {
+  httpServer.send_P(200, "text/html", httpConf2Page);
+}
+
+//void httpServerHandleNotFound() {
+//  String message = "File Not Found\n\n";
+//  message += "URI: ";
+//  message += httpServer.uri();
+//  message += "\nMethod: ";
+//  message += (httpServer.method() == HTTP_GET) ? "GET" : "POST";
+//  message += "\nArguments: ";
+//  message += httpServer.args();
+//  message += "\n";
+//  for (uint8_t i = 0; i < httpServer.args(); i++) {
+//    message += " " + httpServer.argName(i) + ": " + httpServer.arg(i) + "\n";
+//  }
+//  httpServer.send(404, "text / plain", message);
+//}
+
 void httpServerHandleNotFound() {
-  String message = "File Not Found\n\n";
-  message += "URI: ";
-  message += httpServer.uri();
-  message += "\nMethod: ";
-  message += (httpServer.method() == HTTP_GET) ? "GET" : "POST";
-  message += "\nArguments: ";
-  message += httpServer.args();
-  message += "\n";
-  for (uint8_t i = 0; i < httpServer.args(); i++) {
-    message += " " + httpServer.argName(i) + ": " + httpServer.arg(i) + "\n";
-  }
-  httpServer.send(404, "text/plain", message);
+  //  if (strlen(http_user) > 0 && strlen(http_pass) > 0) {
+  //    if (!httpServer.authenticate(http_user, http_pass))
+  //      return httpServer.requestAuthentication();
+  httpServer.send(404, "text/plain", "");
+  //}
 }
 
+void httpServerInit() {
+  httpServer.on("/delconf", []() {
+    Serial.println("httpServer.on /delconf");
+    if (httpServer.hasArg("token")) {
+      char buf[20];
+      httpServer.arg("token").toCharArray(buf, 20);
+      if (strcmp(buf, CLEARCONF_TOKEN) == 0) {
+        //        httpServer.send(200, "text / plain", "Token OK - deleting config");
+        deleteConfig();
+      }
+    } //if
+    //    else {
+    //      httpServer.send(200, "text / plain", "not allowed");
+    //    }
+  });
+
+  httpServer.on("/api", []() {
+    DEBUG_PRINT("httpServer.on /api");
+    if (httpServer.hasArg("plusBtn")) {
+      plusButtonAction();
+      DEBUG_PRINT(P("web plusBtn"));
+    } //if
+    if (httpServer.hasArg("minusBtn")) {
+      minusButtonAction();
+      DEBUG_PRINT(P("web minusBtn"));
+    } //if
+    if (httpServer.hasArg("modeBtn")) {
+      modeButtonAction();
+      DEBUG_PRINT(P("web modeBtn"));
+    } //if
+    if (httpServer.hasArg("onoffBtn")) {
+      modeButtonHoldAction();
+      DEBUG_PRINT(P("web onoffBtn"));
+    } //if
+
+    //build json object of program data
+    StaticJsonBuffer<200> jsonBuffer;
+    JsonObject &json = jsonBuffer.createObject();
+    json["devname"] = deviceName;
+    json["ssid"] = WiFi.SSID();
+    json["setTemp"] = setTemp;
+    json["temp"] = temperature;
+    json["hum"] = int(humidity);
+    json["mode"] = heatingMode;
+    json["heating"] = turnHeatingOn;
+
+    char jsonchar[200];
+    json.printTo(jsonchar); //print to char array, takes more memory but sends in one piece
+    httpServer.send(200, "application/json", jsonchar);
+
+  }); //httpServer.on /api
+
+  httpServer.on("/restart", []() {
+    Serial.println("web triggered restart");
+    ESP.restart();
+  });
+
+  httpServer.on("/confdata", []() {
+    boolean sendData = false;
+    if (strlen(http_user) > 0 && strlen(http_pass) > 0) {
+      if (!httpServer.authenticate(http_user, http_pass)) return httpServer.requestAuthentication();
+      sendData = true;
+    }
+    else sendData = true;
+    if (sendData) {
+      Serial.println("httpServer.on /confdata");
+
+      for (int i = 0; i < httpServer.args(); i++) {
+        char bufName[20];
+        char bufValue[101];
+        httpServer.argName(i).toCharArray(bufName, 20);
+        httpServer.arg(i).toCharArray(bufValue, 101);
+
+        if (strlen(bufName) > 0) {
+          Serial.print("web update ");
+          Serial.print(bufName);
+          Serial.print(" = ");
+          Serial.println(bufValue);
+          setConfig(bufName, bufValue);
+        }
+        saveConfigToFlash = true; // will be saved in next loop()
+        Serial.println("web triggered saveConfigToFlash");
+      }
+      yield();
+
+      //build json object of program data
+      StaticJsonBuffer<1000> jsonBuffer;
+      JsonObject &json = jsonBuffer.createObject();
+      json["devName"] = deviceName;
+      json["httpUser"] =  http_user;
+      json["httpPass"] =  http_pass;
+      json["mqttHost"] = mqtt_server;
+      json["mqttPort"] = mqtt_port;
+      json["mqttUser"] =  mqtt_user;
+      json["mqttPass"] =  mqtt_pass;
+      json["inTop"] = mqtt_topic_in;
+      json["outTop"] = mqtt_topic_out;
+      json["outRet"] = mqtt_outRetain;
+      json["willTop"] = mqtt_willTopic;
+      json["willQos"] = mqtt_willQos;
+      json["willRet"] = mqtt_willRetain;
+      json["willMsg"] = mqtt_willMsg;
+      json["domOutTop"] = domoticz_out_topic;
+
+      yield();
+
+      char jsonchar[1000];
+      json.printTo(jsonchar); //print to char array, takes more memory but sends in one piece
+      httpServer.send(200, "application/json", jsonchar);
+    }
+  }); //httpServer.on /confdata
 
+  httpServer.on("/confdata2", []() {
+    boolean sendData = false;
+    if (strlen(http_user) > 0 && strlen(http_pass) > 0) {
+      if (!httpServer.authenticate(http_user, http_pass)) return httpServer.requestAuthentication();
+      sendData = true;
+    }
+    else sendData = true;
+    if (sendData) {
+      Serial.println("httpServer.on /confdata2");
+
+      for (int i = 0; i < httpServer.args(); i++) {
+        char bufName[20];
+        char bufValue[101];
+        httpServer.argName(i).toCharArray(bufName, 20);
+        httpServer.arg(i).toCharArray(bufValue, 101);
+
+        if (strlen(bufName) > 0) {
+          Serial.print("web update ");
+          Serial.print(bufName);
+          Serial.print(" = ");
+          Serial.println(bufValue);
+          setConfig(bufName, bufValue);
+        }
+        saveConfig2ToFlash = true;
+        Serial.println("web triggered saveConfig2ToFlash");
+      }
+      yield();
+
+      //build json object of program data
+      StaticJsonBuffer<1000> jsonBuffer;
+      JsonObject &json = jsonBuffer.createObject();
+
+      json["domIdxTherm"] = domoticzIdx_Thermostat;
+      json["domIdxMode"] = domoticzIdx_ThermostatMode;
+      json["domIdxTempHum"] = domoticzIdx_TempHumSensor;
+      json["domIdxPIR"] = domoticzIdx_PIR;
+      json["outTempTop"] = outTemp_topic_in;
+      json["outHumTop"] = outHum_topic_in;
+      json["autoSaveTemp"] = autoSaveSetTemp;
+      json["autoSaveMode"] = autoSaveHeatingMode;
+      json["minOffTime"] = heatingMinOffTime;
+      json["tempMin"] = setTempMin;
+      json["tempMax"] = setTempMax;
+      json["tempLow"] = setTempLow;
+      json["hyst"] = hysteresis;
+      json["tempCorr"] = tempCorrVal;
+      json["humCorr"] = humCorrVal;
+      json["measInt"] = measureInterval;
+      json["dispInt"] = displayInterval;
+      json["dispTout"] = displayTimeout;
+
+
+      yield();
+
+      char jsonchar[1000];
+      json.printTo(jsonchar); //print to char array, takes more memory but sends in one piece
+      httpServer.send(200, "application/json", jsonchar);
+    }
+  }); //httpServer.on /confdata2
+
+
+
+
+  //get heap status, analog input value and all GPIO statuses in one json call
+  httpServer.on("/info", HTTP_GET, []() {
+    boolean sendData = false;
+    if (strlen(http_user) > 0 && strlen(http_pass) > 0) {
+      if (!httpServer.authenticate(http_user, http_pass)) return httpServer.requestAuthentication();
+      sendData = true;
+    }
+    else sendData = true;
+    if (sendData) {
+      String json = " {";
+      json += "\"wifissid\":\"" + WiFi.SSID() + "\"";
+      json += "\"heap\":" + String(ESP.getFreeHeap());
+      json += "}";
+      httpServer.send(200, "text/json", json);
+      json = String();
+    }
+  }); //httpServer.on /info
+
+  httpServer.on("/", []() {
+    httpServerHandleRoot();
+  });
+
+  httpServer.on("/conf", []() {
+    httpServerHandleConfPage();
+  });
+
+  httpServer.on("/conf2", []() {
+    httpServerHandleConf2Page();
+  });
+
+  httpServer.onNotFound([]() {
+    httpServerHandleNotFound();
+  }); //httpServer.onNotFound
+
+  // HTTP Updater at /update
+  httpUpdater.setup(&httpServer);
+
+  httpServer.begin();
+}
 

+ 59 - 0
src/WiFiThermostat/misc.ino

@@ -0,0 +1,59 @@
+unsigned long lastRun = 0;
+int count100ms = 0;
+int countSeconds = 0;
+int countMeasureInterval = 0;
+int countDisplayInterval = 0;
+
+void checkMillis() {
+  if ( (millis() - lastRun) > 100 ) {
+    lastRun = millis();
+    every100ms();
+  }
+}
+
+void every100ms() {
+  if (count100ms < 10) count100ms++;
+  else {
+    count100ms = 0;
+    everySecond();
+  }
+
+  checkSaveConfigTriggered();
+
+}
+
+void everySecond() {
+  if (countSeconds < 60) countSeconds++;
+  else {
+    countSeconds = 0;
+    everyMinute();
+  }
+
+  checkUseDomoticz();
+  handleDisplayTimeout();
+  checkValuesChanged();
+
+  if (countMeasureInterval < measureInterval) countMeasureInterval++;
+  else {
+    countMeasureInterval = 0;
+    measureTempHum();
+    thermostat();
+  }
+
+  if (countDisplayInterval < displayInterval) countDisplayInterval++;
+  else {
+    countDisplayInterval = 0;
+    updateDisplay();
+  }
+
+
+}
+
+void everyMinute() {
+  publishStatus();
+  publishCurrentSensorValues();
+  publishCurrentThermostatValues();
+  updateDomoticzDevices();
+}
+
+

+ 271 - 0
src/WiFiThermostat/mqtt.ino

@@ -0,0 +1,271 @@
+
+// MQTT callback
+void mqttCallback(char* topic, byte* payload, unsigned int length) {
+//  Serial.print("Message arrived [");
+//  Serial.print(topic);
+//  Serial.print("] ");
+//  for (int i = 0; i < length; i++) {
+//    Serial.print((char)payload[i]);
+//  }
+//  Serial.println();
+
+  if (strcmp(topic, mqtt_topic_in) == 0) { //if topic = mqtt_topic_in
+    Serial.print("received MQTT ");
+    Serial.println(mqtt_topic_in);
+
+    for (int i = 0; i < length; i++) {
+      cmdPayload[i] = (char)payload[i];
+    }
+    cmdPayload[length + 1] = '\0';
+
+    Serial.print("cmdPayload:");
+    Serial.println(cmdPayload);
+
+    cmdInQueue = true;
+
+  }//if topic = mqtt_topic_in
+
+  if (strcmp(topic, outTemp_topic_in) == 0) { //if topic = outTemp_topic_in
+    char inValue[length + 1];
+    for (int i = 0; i < length; i++) {
+      inValue[i] = (char)payload[i];
+    }
+    inValue[sizeof(inValue) - 1] = '\0';
+    //Serial.print(inValue);
+    //Serial.println();
+    float invalueFloat = atof(inValue);
+    outTemp = invalueFloat;
+    outTempHumLastUpdate = millis();
+    Serial.print("atemp=");
+    Serial.println(invalueFloat);
+  }//if topic = outTemp_topic_in
+
+  if (strcmp(topic, outHum_topic_in) == 0) { //if topic = outHum_topic_in
+    char inValue[length + 1];
+    for (int i = 0; i < length; i++) {
+      inValue[i] = (char)payload[i];
+    }
+    inValue[sizeof(inValue) - 1] = '\0';
+    //Serial.print(inValue);
+    //Serial.println();
+    outHum = atoi(inValue);
+    outTempHumLastUpdate = millis();
+    Serial.print("ahum=");
+    Serial.println(inValue);
+  }//if topic = outHum_topic_in
+
+  if (strcmp(topic, domoticz_out_topic) == 0) { //if topic = domoticz_out_topic
+    Serial.print("received MQTT ");
+    Serial.println(domoticz_out_topic);
+    if ( !domoticzOutParserBusy ) {
+      for (int i = 0; i < length; i++) {
+        domoticzOutPayload[i] = (char)payload[i];
+      }
+      domoticzOutPayload[length + 1] = '\0';
+      domoticzOutParseData = true; // parse domoticz data in the next loop()
+    }
+  }//if topic = domoticz_out_topic
+}//mqttCallback
+
+void mqttPrepareConnection() {
+  Serial.print("MQTT connection with ");
+  if (strlen(mqtt_user) > 0 && strlen(mqtt_willTopic) == 0) {
+    // user and password, no Last Will
+    Serial.println("user and password, no Last Will");
+    mqttMode = 2;
+  }
+  else if (strlen(mqtt_user) > 0 && strlen(mqtt_willTopic) > 0) {
+    // user, password and Last Will
+    Serial.println("user, password and Last Will");
+    mqttMode = 3;
+  }
+  else if (strlen(mqtt_user) == 0 && strlen(mqtt_willTopic) > 0) {
+    // Last Will but no user and password
+    Serial.println("Last Will but no user and password");
+    mqttMode = 4;
+  }
+  else {
+    // no user, password and no Last Will
+    Serial.println("no user, password and no Last Will");
+    mqttMode = 1;
+  }
+}
+
+boolean mqttReconnect() {
+  // Create a random MQTT client ID
+  String mqttClientId = "ESP8266Client-";
+  mqttClientId += String(random(0xffff), HEX);
+
+  Serial.print("MQTT connection with ");
+
+  boolean connRes;
+
+  switch (mqttMode) {
+    case 1:
+      Serial.println("no user, password and no Last Will");
+      connRes = mqttclient.connect(mqttClientId.c_str());
+      break;
+    case 2:
+      Serial.println("user and password, no Last Will");
+      connRes = mqttclient.connect(mqttClientId.c_str(), mqtt_user, mqtt_pass);
+      break;
+    case 3:
+      Serial.println("user, password and Last Will");
+      connRes = mqttclient.connect(mqttClientId.c_str(), mqtt_user, mqtt_pass, mqtt_willTopic, mqtt_willQos, mqtt_willRetain, mqtt_willMsg);
+      break;
+    case 4:
+      Serial.println("Last Will, no user and password");
+      connRes = mqttclient.connect(mqttClientId.c_str(), mqtt_willTopic, mqtt_willQos, mqtt_willRetain, mqtt_willMsg);
+      break;
+  }
+
+  if (connRes) {
+    Serial.print("MQTT connected. Reconnects: ");
+    Serial.println(mqttReconnects);
+    mqttReconnects++;
+
+    // Once connected, publish an announcement...
+    //    char outMsg[30];
+    //    sprintf(outMsg, "connected, %d reconnects", mqttReconnects);
+    //    mqttclient.publish(mqtt_topic_out, outMsg, mqtt_outRetain);
+    publishStatus();
+    //mqttclient.publish(mqtt_topic_out, "connected");
+    // ... and resubscribe
+    Serial.println("Subscribed to:");
+    if (strlen(mqtt_topic_in) > 0) {
+      if (mqttclient.subscribe(mqtt_topic_in)) {
+        Serial.println(mqtt_topic_in);
+      }
+    }
+    if (strlen(outTemp_topic_in) > 0) {
+      if (mqttclient.subscribe(outTemp_topic_in)) {
+        Serial.println(outTemp_topic_in);
+      }
+    }
+    if (strlen(outHum_topic_in) > 0) {
+      if (mqttclient.subscribe(outHum_topic_in)) {
+        Serial.println(outHum_topic_in);
+      }
+    }
+    if (useDomoticz && strlen(domoticz_out_topic) > 0) {
+      if (mqttclient.subscribe(domoticz_out_topic)) {
+        Serial.println(domoticz_out_topic);
+      }
+    }
+  }
+  return mqttclient.connected();
+} //mqttReconnect
+
+
+
+void mqttClientInit() {
+  mqttclient.setServer(mqtt_server, mqtt_port);
+  mqttclient.setCallback(mqttCallback);
+  mqttLastReconnectAttempt = 0;
+}
+
+void mqttHandleConnection() {
+  if ( WiFi.status() == WL_CONNECTED ) {
+    // MQTT reconnect if not connected (nonblocking)
+    if (!mqttclient.connected()) {
+      long now = millis();
+
+      if (now - mqttLastReconnectAttempt > 5000) {
+        mqttLastReconnectAttempt = now;
+        if (mqttReconnect()) { // Attempt to reconnect
+          mqttLastReconnectAttempt = 0;
+        }
+      }
+    } else { // Client connected
+      mqttclient.loop();
+    }//else
+  }
+}
+
+void publishStatus() {
+  char outMsg[60];
+  long upTime = millis() / 1000;
+  sprintf(outMsg, "connected, reconnects: %d, uptime: %d, free heap: %d", mqttReconnects - 1, upTime, ESP.getFreeHeap());
+  mqttclient.publish(mqtt_topic_out, outMsg, mqtt_outRetain);
+}
+
+void sendStatus(char* payload) {
+  char buf[101];
+  strlcpy(buf, payload, 101);
+  Serial.println(buf);
+  if (mqttclient.connected()) mqttclient.publish(mqtt_topic_out, buf, mqtt_outRetain);
+}
+
+void publishCurrentThermostatValues() {
+  char tmp_topic_out[50];
+
+  float curr_setTemp;
+  if (heatingMode == 1) curr_setTemp = setTemp;
+  else if (heatingMode == 2) curr_setTemp = setTempLow;
+
+  char setTemp_chararr[6];
+  char currSetTemp_chararr[6];
+
+  dtostrf(setTemp, 1, 1, setTemp_chararr );
+  dtostrf(curr_setTemp, 1, 1, currSetTemp_chararr );
+
+  Serial.print("heatingMode: '");
+  Serial.print(heatingMode);
+  Serial.println("'");
+  Serial.print("set temp: '");
+  Serial.print(setTemp_chararr);
+  Serial.println("'");
+  Serial.print("current set temp: '");
+  Serial.print(currSetTemp_chararr);
+  Serial.println("'");
+
+  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "setTemp");
+  mqttclient.publish(tmp_topic_out, setTemp_chararr);
+
+  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "currSetTemp");
+  mqttclient.publish(tmp_topic_out, currSetTemp_chararr);
+
+  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "heatingMode");
+  char heatingMode_chararr[3];
+  sprintf(heatingMode_chararr, "%d", heatingMode);
+  mqttclient.publish(tmp_topic_out, heatingMode_chararr);
+
+  char turnHeatingOn_char[5];
+  if (turnHeatingOn) strcpy(turnHeatingOn_char, "on");
+  else strcpy(turnHeatingOn_char, "off");
+  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "heating");
+  mqttclient.publish(tmp_topic_out, turnHeatingOn_char);
+
+  char buf[101];
+  sprintf(buf, "%d", heatingOnTime);
+  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "heatingOnTime");
+  mqttclient.publish(tmp_topic_out, buf);
+
+  sprintf(buf, "%d", heatingOffTime);
+  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "heatingOffTime");
+  mqttclient.publish(tmp_topic_out, buf);
+}
+
+void publishCurrentSensorValues() {
+  if ( lastTempUpdate != 0 && (millis() - lastTempUpdate) < 120000 ) {
+    char tmp_topic_out[50];
+
+    char temp_chararr[6];
+    char hum_chararr[4];
+    dtostrf(temperature, 1, 1, temp_chararr );
+    sprintf(hum_chararr, "%2i", humidity);
+
+    Serial.print("temp: '");
+    Serial.print(temp_chararr);
+    Serial.println("'");
+    sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "temp");
+    mqttclient.publish(tmp_topic_out, temp_chararr);
+
+    Serial.print("humidity: '");
+    Serial.print(humidity);
+    Serial.println("'");
+    sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "hum");
+    mqttclient.publish(tmp_topic_out, hum_chararr);
+  }
+}
+

+ 276 - 0
src/WiFiThermostat/thermostat.ino

@@ -0,0 +1,276 @@
+void measureTempHum() {
+  float tmpHum = round(dht.readHumidity());
+  float tmpTemp = dht.readTemperature();  // Read temperature as Celsius (the default)
+
+  int tmpHumInt = tmpHum;
+
+  // Check if any reads failed
+  if (isnan(tmpHum) || isnan(tmpTemp)) {
+    //Serial.println("Failed to read from DHT sensor!");
+    sendStatus("Error: Failed to read from DHT sensor!");
+  }
+  else {
+    if (tmpTemp < 50.0 && tmpTemp > -20.0) {
+      // measurement is in range
+      if ( lastTempUpdate > 0 && tmpTemp <= ( temperature + 2.0 ) && tmpTemp >= ( temperature - 2.0 ) ) {
+        // temp has already been measured - only accept new measurement if it does not differ much from the last value
+        //Temp = (Temp * (FilterFaktor -1) + AktuellerMesswert) / FilterFaktor;
+        //temperature = tmpTemp;
+        temperature = ((temperature * 9 + tmpTemp) / 10) + tempCorrVal; // filter
+        humidity = tmpHumInt + humCorrVal;
+        lastTempUpdate = millis();
+      }
+      else if ( lastTempUpdate == 0 || (millis() - lastTempUpdate) > 300000 ) {
+        // this is the first measurement or the last one is older than 5m - then accept this measurement
+        temperature = tmpTemp + tempCorrVal;
+        humidity = tmpHumInt + humCorrVal;
+        lastTempUpdate = millis();
+
+      }
+      // skip in all other cases
+
+      //#ifdef DEBUG_VERBOSE
+      //      Serial.print("lastTempUpdate: ");
+      //      long lastTempUpdateDelta = millis() - lastTempUpdate;
+      //      Serial.print(lastTempUpdateDelta / 1000);
+      //      Serial.println("s ago");
+      //#endif
+    }
+  }
+}
+
+void thermostat() {
+  float curr_setTemp;
+  // set target temp for heating mode
+  if (heatingMode == 1) { // heating on - default/day mode
+    curr_setTemp = setTemp;
+  }
+  else if (heatingMode == 2) { // heating of - night/reduction mode
+    curr_setTemp = setTempLow;
+  }
+
+  char tmp_topic_out[50];
+  if (heatingMode > 0 && turnHeatingOn) {
+    heatingOnTime = (millis() - heatingLastOnMillis) / 1000;
+
+    char buf[101];
+    sprintf(buf, "heating on since %d s", heatingOnTime);
+    sendStatus(buf);
+  }
+  else if (heatingMode > 0 && !turnHeatingOn) {
+    heatingOffTime = (millis() - heatingLastOffMillis) / 1000;
+
+    char buf[101];
+    sprintf(buf, "heating off since %d s", heatingOffTime);
+    sendStatus(buf);
+  }
+
+
+  //char tmp_topic_out[50];
+  //sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "heating");
+
+  if ( lastTempUpdate != 0 && (millis() - lastTempUpdate) <= 120000 ) {
+    // thermostat - only active if measured temperature is < 2 min old
+
+#ifdef DEBUG_VERBOSE
+    Serial.print("thermostat, lastTempUpdate=");
+    Serial.print(lastTempUpdate);
+    Serial.print(", lastTempUpdate_delta=");
+    long lastTempUpdateDelta = millis() - lastTempUpdate;
+    Serial.println(lastTempUpdateDelta);
+#endif
+
+    // thermostat with hysteresis
+    if ( turnHeatingOn && temperature >= curr_setTemp ) {
+      turnHeatingOn = false;
+      heatingLastOffMillis = millis();
+      digitalWrite(PIN_RELAIS, !RELAISONSTATE);
+      updateDisplay();
+
+      char buf[101];
+      sprintf(buf, "switch heating OFF, on since %d s", heatingOnTime);
+      sendStatus(buf);
+
+      //Serial.println("heating off");
+      //mqttclient.publish(tmp_topic_out, "off");
+      publishCurrentThermostatValues();
+    }
+    else if ( !turnHeatingOn && heatingMode > 0 && ( temperature < (curr_setTemp - hysteresis) ) && ( heatingOffTime > heatingMinOffTime ) ) {
+      turnHeatingOn = true;
+      heatingLastOnMillis = millis();
+      digitalWrite(PIN_RELAIS, RELAISONSTATE);
+      updateDisplay();
+
+      char buf[101];
+      sprintf(buf, "switch heating ON, off since %d s", heatingOffTime);
+      sendStatus(buf);
+
+      //Serial.println("heating on");
+      //mqttclient.publish(tmp_topic_out, "on");
+      publishCurrentThermostatValues();
+    }
+  }
+  else {
+    if (turnHeatingOn) {
+      digitalWrite(PIN_RELAIS, !RELAISONSTATE);
+      turnHeatingOn = false;
+      heatingLastOffMillis = millis();
+    }
+
+    if ( lastTempUpdate != 0 ) sendStatus("switch heating OFF, temp reading not yet available");
+    else if ( (millis() - lastTempUpdate) > 120000 ) sendStatus("switch heating OFF, last temp reading too old");
+
+    //mqttclient.publish(tmp_topic_out, "off");
+    publishCurrentThermostatValues();
+  }
+
+}
+
+void toggleHeatingMode() {
+  if (heatingMode > 0) {
+    Serial.print("switch mode to ");
+    if (heatingMode == 1) {
+      heatingMode = 2;
+      lastValueChange = millis();
+      heatingModeAlreadySaved = false;
+    }
+    else if (heatingMode == 2) {
+      heatingMode = 1;
+      lastValueChange = millis();
+      heatingModeAlreadySaved = false;
+    }
+    Serial.println(heatingMode);
+    updateDisplay();
+  }
+}
+
+void toggleThermostatOnOff() {
+  if (heatingMode > 0) {
+    heatingMode = 0;
+    lastValueChange = millis();
+    heatingModeAlreadySaved = false;
+  }
+  else {
+    heatingMode = 1;
+    lastValueChange = millis();
+    heatingModeAlreadySaved = false;
+  }
+  updateDisplay();
+}
+
+void setTempStepUp() {
+  if (heatingMode == 1) {
+    Serial.println("+");
+    if ( setTemp <= (setTempMax - 0.5)) {
+      setTemp += 0.5;
+      lastValueChange = millis();
+      setTempAlreadySaved = false;
+    }
+    updateDisplay();
+  }
+}
+
+void setTempStepDown() {
+  if (heatingMode == 1) {
+    Serial.println("-");
+    if ( setTemp >= (setTempMin + 0.5)) {
+      setTemp -= 0.5;
+      lastValueChange = millis();
+      setTempAlreadySaved = false;
+    }
+    updateDisplay();
+  }
+}
+
+void setTempTo(float setTo) {
+  bool changes = false;
+  if (setTo >= setTempMin && setTo <= setTempMax) {
+    setTemp = setTo;
+    changes = true;
+  }
+  else if (setTo > setTempMax) {
+    setTemp = setTempMax;
+    changes = true;
+  }
+  else if (setTo < setTempMin) {
+    setTemp = setTempMin;
+    changes = true;
+  }
+  if (changes) {
+    lastValueChange = millis();
+    setTempAlreadySaved = false;
+    updateDisplay();
+    publishCurrentThermostatValues();
+  }
+}
+
+void setTempLowTo(float setTo) {
+  bool changes = false;
+  if (setTo >= setTempLowMin && setTo <= setTempLowMax) {
+    setTempLow = setTo;
+    changes = true;
+  }
+  else if (setTo > setTempLowMax) {
+    setTempLow = setTempLowMax;
+    changes = true;
+  }
+  else if (setTo < setTempLowMin) {
+    setTempLow = setTempLowMin;
+    changes = true;
+  }
+  if (changes) {
+    updateDisplay();
+    publishCurrentThermostatValues();
+  }
+}
+
+void setHeatingmodeTo(byte setTo) {
+  bool changes = false;
+  switch (setTo) {
+    case 0:
+      heatingMode = 0;
+      changes = true;
+      break;
+    case 1:
+      heatingMode = 1;
+      changes = true;
+      break;
+    case 2:
+      heatingMode = 2;
+      changes = true;
+      break;
+  }
+  if (changes) {
+    lastValueChange = millis();
+    heatingModeAlreadySaved = false;
+    updateDisplay();
+    publishCurrentThermostatValues();
+  }
+}
+
+void checkValuesChanged() { // called every second by everySecond() / misc.ino
+  if ( !setTempAlreadySaved || !heatingModeAlreadySaved ) {
+    if ( (millis() - lastValueChange) > saveValuesTimeout ) { // value was changed 5s ago. now save if auto-save enabled
+      if (!setTempAlreadySaved) {
+        lastUpdate_setTemp = millis();
+        sendToDomoticz_thermostat();
+        if (autoSaveSetTemp && setTemp != setTempSaved) {
+          saveSetTemp();
+          sendStatus("setTemp autosave done");
+        }
+        setTempAlreadySaved = true;
+      }
+      if (!heatingModeAlreadySaved) {
+        lastUpdate_heatingMode = millis();
+        sendToDomoticz_heatingMode();
+        if (autoSaveHeatingMode && heatingMode != heatingModeSaved) {
+          saveHeatingMode();
+          sendStatus("heatingMode autosave done");
+        }
+        heatingModeAlreadySaved = true;
+      }
+    }
+  }
+}
+
+