Browse Source

v0.2.0
* improved MQTT reconnection management with heartbeat and force-reconnect
* improved MQTT callback function
* improved overall stability
* added correction value for set point temperature
* added some defines for default values
* added http control api with token
* added mqttReconnect http call
* bugfixes
* some documentation

Important: for now compiled using Arduino 1.8.5 with ESP8266-Arduino core 2.4.0-rc2, as WiFi (re)connection does not work properly in release 2.4.0 (spent many hours on that)!!

FloKra 6 years ago
parent
commit
ad0cd017bd

+ 13 - 0
CHANGELOG.md

@@ -1,4 +1,17 @@
 # WiFiThermostat - Changelog
+## 0.2.0
+* improved MQTT reconnection management with heartbeat and force-reconnect
+* improved MQTT callback function
+* improved overall stability
+* added correction value for set point temperature
+* added some defines for default values
+* added http control api with token
+* added mqttReconnect http call
+* bugfixes
+* some documentation
+
+Important: for now compiled using Arduino 1.8.5 with ESP8266-Arduino core 2.4.0-rc2, as WiFi (re)connection does not work properly in release 2.4.0 (spent many hours on that)!!
+
 ## 0.1.3
 * button handling now uses library r89m/PushButton.h
 * added support for external PIR motion sensor, can be configured to enable display backlight on movement, pushes status to Domoticz and MQTT

+ 80 - 0
HTTP-web-interface-and-API.md

@@ -0,0 +1,80 @@
+# WiFiThermostat - HTTP web interface and API
+
+## Web interface
+The thermostat runs a small web interface for direct control and configuration. 
+The web interface itself runs on HTTP port 80. 
+
+URL: http://IPADDRESS/
+
+It can be user/password protected using the options in ***Base configuration***.
+The Web interface includes the following functions: 
+* WiFi settings
+* Base configuration
+* Extended configuration
+* Firmware update
+* Restart
+
+## HTTP API
+
+Some values can be also directly be changed using this HTTP API. 
+Most options use a TOKEN for "protection", which can be different to the HTTP password and must be configured on the ***Base configuration*** page on the web interface.
+
+Currently the following HTTP API calls are implemented: 
+
+### Set point
+Sets the heating target temperature. 
+
+URL: http://IPADDRESS/setPoint?value=VALUE&token=TOKEN
+
+Where VALUE is float in 0.5 steps >= configured setTempMin & <= setTempMax.
+
+Lower values result in setTemp = setTempMin, higher in setTemp = setTempMax. 
+
+Decimals other than .0 and .5 are rounded to the nearest valid value.
+
+### Set mode
+Sets the heating mode. 
+Can be one of the following values: 
+* 0 - heating off
+* 1 - normal heating mode
+* 2 - reduction mode
+
+URL: http://IPADDRESS/setMode?value=VALUE&token=TOKEN
+
+### MQTT reconnect
+Forces MQTT reconnect. Can be used without token or authentication. 
+
+URL: http://IPADDRESS/mqttReconnect
+
+### Restart
+Restarts the MCU. Can be used without token or authentication. 
+
+URL: http://IPADDRESS/restart
+
+### Info
+Returns some runtime information. Currently not much. 
+
+URL: http://IPADDRESS/info
+
+### API
+Returns a JSON string with some current data. Can currently be used without token or authentication but this will be changed. 
+Also used for the buttons on the web interface. 
+
+http://IPADDRESS/api
+
+Example: {"devname":"Thermostat 1","ssid":"SSID","setTemp":22.5,"temp":22.40049,"hum":37,"mode":1,"heating":false}
+
+Where: 
+* setTemp = set point temperature
+* temp, hum = current readings from the temperature/humidity sensor
+* mode = heating mode
+* heating = false if heating is currently off, true if heating is currently on
+
+Additionally /api can take the following arguments, which are only intended to be used by the web interface buttons: 
+* plusBtn
+* minusBtn
+* modeBtn
+* onoffBtn
+
+### confdata, confdata2
+This is the backend of the configuration pages in the web interface. Only intended to be used by that. 

+ 0 - 0
MQTT-Befehle.txt → MQTT-commands.txt


+ 16 - 0
Pin-assignment.txt

@@ -0,0 +1,16 @@
+Pin	GPIO	Desc							Usage
+----------------------------------------------------------------------
+D3	GPIO0	IO, 10k Pull-up					left Button (+)
+D4	GPIO2	IO, 10k Pull-up, BUILTIN_LED	middle Button (-)
+D5	GPIO14	IO, SCK							right Button (Mode)
+D7	GPIO13	IO, MOSI						DHT22 Temp/Hum sensor
+D0	GPIO16	IO								relay output, control heating
+D2	GPIO4	IO, SDA							I2C SDA
+D1	GPIO5	IO, SCL							I2C SCL
+D6	GPIO12	IO, MISO						PIR sensor
+
+D8	GPIO15	10k Pull-down, SS				-- unused --
+
+
+Hardware via I2C:
+LCD 1602	0x27

+ 0 - 17
Pinzuordnung.txt

@@ -1,17 +0,0 @@
-Pin	GPIO	Anmerkung						Verwendung
-----------------------------------------------------------------------
-D3	GPIO0	IO, 10k Pull-up					Button links (+)
-D4	GPIO2	IO, 10k Pull-up, BUILTIN_LED	Button mitte (-)
-D5	GPIO14	IO, SCK							Button rechts (Mode)
-D7	GPIO13	IO, MOSI						DHT22 Temp/Hum Sensor
-D0	GPIO16	IO								Heizungsansteuerung
-D2	GPIO4	IO, SDA							I2C SDA
-D1	GPIO5	IO, SCL							I2C SCL
-D6	GPIO12	IO, MISO						vorgesehen für PIR-Sensor
-
-D8	GPIO15	10k Pull-down, SS				-- nicht verwendet --
-
-
-Hardware via I2C:
- - Display 1602			Addr: 0x27
- - Lux-Sensor BH1750	Addr: 0x23

+ 0 - 0
bin/WiFiThermostat.ino.d1_mini.bin → bin/WiFiThermostat.ino.d1_mini.20180208.bin


BIN
bin/WiFiThermostat.ino.d1_mini.20180216_1.bin


BIN
bin/WiFiThermostat.ino.d1_mini.20180216_2.bin


+ 11 - 1
src/WiFiThermostat/Display.ino

@@ -47,7 +47,7 @@ void initDisplay() {
   //    delay(200);
   //  }
   lcd.backlight();
-  delay(1000);
+  //delay(1000);
   ///lcd.clear();
   displayActive = true;
   displayLastOnMillis = millis();
@@ -90,9 +90,19 @@ void updateDisplay() {
     }
   }
 
+  // 12345667890123456
+  // =20.0° 35% >22.5°
+  // A -10.2° 95%    N
+  // A 22.2° 95%     N
+  // A 5.2° 95%      N
+  
+  
+
   // 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

+ 108 - 41
src/WiFiThermostat/WiFiThermostat.ino

@@ -7,22 +7,66 @@
 #define SPIFFS_USE_MAGIC
 
 #define FIRMWARE_NAME "WiFiThermostat"
-#define VERSION "0.1.3"
+#define VERSION "0.2.0"
 
-// default values, can later be overridden via configuration
+
+
+// default values, can later be overridden via configuration (conf)
 #define DEVICE_NAME "WiFi-Thermostat-1"
+#define DEFAULT_HTTP_USER ""
+#define DEFAULT_HTTP_PASS ""
+#define HTTP_SET_TOKEN "grzbrz"
 #define MQTT_SERVER "10.1.1.11"
 #define MQTT_PORT 1883
+#define MQTT_USER ""
+#define MQTT_PASS ""
 #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 MQTT_OUT_RETAIN false
+#define MQTT_WILLTOPIC ""
+#define MQTT_WILLQOS 2
+#define MQTT_WILLRETAIN false
+#define MQTT_WILLMSG ""
 #define DOMOTICZ_OUT_TOPIC "domoticz/out"
-#define OUTTEMP_TOPIC_IN "wetter/atemp"
-#define OUTHUM_TOPIC_IN "wetter/ahum"
 
-#define CLEARCONF_TOKEN "TUES"
+// default values, can later be overridden via configuration (conf2)
+#define DOMOTICZ_IDX_THERMOSTAT 0
+#define DOMOTICZ_IDX_THERMOSTATMODE 0
+#define DOMOTICZ_IDX_TEMPHUMSENSOR 0
+#define DOMOTICZ_IDX_HEATING 0
+#define DOMOTICZ_IDX_PIR 0
+#define OUTTEMP_TOPIC_IN ""
+#define OUTHUM_TOPIC_IN ""
+#define AUTOSAVE_SETTEMP true
+#define AUTOSAVE_SETMODE true
+#define DEFAULT_HEATING_MIN_OFFTIME 120  // minimal time the heating keeps turned off in s
+#define DEFAULT_SETTEMP_MIN 16.0         // minimal temperature that can be set
+#define DEFAULT_SETTEMP_MAX 25.0         // maximal temperature that can be set
+#define DEFAULT_SETTEMP_LOW 18.0         // set temperature in night/low mode
+#define DEFAULT_HYSTERESIS 0.1           // hysteresis, normally 0.1 - 0.5
+#define SETTEMP_DECREASE_VALUE 0.0       // decreases the set temp to overcome further temperature rise when the heating is already switched off
+#define TEMPSENSOR_CORRECTION_VALUE 0.0  // correction value for temperature sensor reading
+#define HUMSENSOR_CORRECTION_VALUE 0     // correction value for humidity sensor reading
+#define DEFAULT_MEASURE_INTERVAL 15      // interval for temp/hum measurement
+#define DEFAULT_DISPLAY_INTERVAL 5       // interval for display updates (if out-temp is active, display will toggle in this interval)
+#define DEFAULT_DISPLAY_TIMEOUT 30       // display timeout after keypress (illumination)
+#define DEFAULT_PIR_ENABLES_DISPLAY false
+
+// default initial values
+#define DEFAULT_SETTEMP 21.5
+#define DEFAULT_HEATINGMODE 1
+
+
+// default values that can only be configured at compile time / hardware configuration
+#define CLEARCONF_TOKEN "DOIT!" // Token used to reset configuration via http call on http://<IP>/delconf?token=<TOKEN> (use when password is forgotten)
+#define BUTTON_DEBOUNCE_TIME 120
+#define BUTTON_HOLD_TIME 750
+#define DOMOTICZ_IN_TOPIC "domoticz/in" // if Domoticz IDXes are configured, updates will be sent to this topic
+#define SETTEMP_LOW_MIN 14.0 // minimal configurable temperature for reduction mode
+#define SETTEMP_LOW_MAX 20.0 // maximal configurable temperature for reduction mode
+#define DOMOTICZ_DISMISSUPDATE_TIMEOUT 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
+#define DOMOTICZ_FORCEUPDATE_INTERVAL 15 // interval in min to force update of domoticz devices
+#define MQTT_HEARTBEAT_MAXAGE 120000 // interval for MQTT heartbeat message. only applicable if MQTT IN-topic is defined. after this timeout MQTT reconnect is forced
 
 // pin assignments and I2C addresses
 #define PIN_DHTSENSOR 13
@@ -31,9 +75,7 @@
 #define PIN_BUTTON_MINUS 0
 #define PIN_BUTTON_MODE 14
 #define PIN_PIRSENSOR 12
-
 #define DHTTYPE DHT22 // DHT sensor type
-
 #define LCDADDR 0x27  // I2C address LCD
 #define LCDCOLS 16
 #define LCDLINES 2
@@ -83,51 +125,53 @@ PushButton pirSensor = PushButton(PIN_PIRSENSOR, PRESSED_WHEN_HIGH);
 char deviceName[31];  // device name - just for web interface
 char http_user[31];
 char http_pass[31];
+char http_token[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
+boolean mqtt_outRetain = MQTT_OUT_RETAIN; // 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
+int mqtt_willQos = MQTT_WILLQOS;     // MQTT Last Will topic QOS
+boolean mqtt_willRetain = MQTT_WILLRETAIN;  // 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_Heating = 0;
-int domoticzIdx_PIR = 0;
+int domoticzIdx_Thermostat = DOMOTICZ_IDX_THERMOSTAT;
+int domoticzIdx_ThermostatMode = DOMOTICZ_IDX_THERMOSTATMODE;
+int domoticzIdx_TempHumSensor = DOMOTICZ_IDX_TEMPHUMSENSOR;
+int domoticzIdx_Heating = DOMOTICZ_IDX_HEATING;
+int domoticzIdx_PIR = DOMOTICZ_IDX_PIR;
 char outTemp_topic_in[51];
 char outHum_topic_in[51];
-boolean autoSaveSetTemp = true;
-boolean 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;
-boolean PIR_enablesDisplay = false;
+boolean autoSaveSetTemp = AUTOSAVE_SETTEMP;
+boolean autoSaveHeatingMode = AUTOSAVE_SETMODE;
+int heatingMinOffTime = DEFAULT_HEATING_MIN_OFFTIME; // minimal time the heating keeps turned off in s
+float setTempMin = DEFAULT_SETTEMP_MIN;           // minimal temperature that can be set
+float setTempMax = DEFAULT_SETTEMP_MAX;           // maximal temperature that can be set
+float setTempLow = DEFAULT_SETTEMP_LOW;           // set temperature in night/low mode
+float hysteresis = DEFAULT_HYSTERESIS;            // hysteresis, normally 0.1 - 0.5
+float setTempDecreaseVal = SETTEMP_DECREASE_VALUE;    // decreases the set temp to overcome further temperature rise when the heating is already switched off
+float tempCorrVal = TEMPSENSOR_CORRECTION_VALUE;  // correction value for temperature sensor reading
+int humCorrVal = HUMSENSOR_CORRECTION_VALUE;      // correction value for humidity sensor reading
+int measureInterval = DEFAULT_MEASURE_INTERVAL;   // interval for temp/hum measurement
+int displayInterval = DEFAULT_DISPLAY_INTERVAL;   // interval for display updates (if out-temp is active, display will toggle in this interval)
+int displayTimeout = DEFAULT_DISPLAY_TIMEOUT;     // display timeout after keypress (illumination)
+boolean PIR_enablesDisplay = DEFAULT_PIR_ENABLES_DISPLAY; // PIR sensor enables display illumination
 
 //set values
-float setTemp = 21.5;
-byte heatingMode = 1; // 0 = off, 1 = normal/day, 2 = night/reduction
+float setTemp = DEFAULT_SETTEMP;
+byte heatingMode = DEFAULT_HEATINGMODE; // 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;
+float setTempLowMin = SETTEMP_LOW_MIN;
+float setTempLowMax = SETTEMP_LOW_MAX;
 boolean debug = true;
 int debounceTime = BUTTON_DEBOUNCE_TIME;
 int buttonHoldTime = BUTTON_HOLD_TIME;
@@ -143,6 +187,11 @@ 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
+char outTemp_newValue[6];
+bool outTemp_parseNewValue;
+char outHum_newValue[4];
+bool outHum_parseNewValue;
+
 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
@@ -158,12 +207,12 @@ boolean useDomoticz = false; // will be set to true in setup() if idx-values oth
 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
+int dismissUpdateFromDomoticzTimeout = DOMOTICZ_DISMISSUPDATE_TIMEOUT; // 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
+int domoticzUpdateInterval = DOMOTICZ_FORCEUPDATE_INTERVAL; // 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
@@ -179,6 +228,8 @@ byte mqttMode = 0;
 unsigned long mqttLastReconnectAttempt = 0;
 int mqttReconnectAttempts = 0;
 int mqttReconnects = 0;
+unsigned long mqttLastHeartbeat;
+bool mqttInTopicSubscribed = false;
 
 DHT dht(PIN_DHTSENSOR, DHTTYPE);
 LiquidCrystal_I2C lcd(LCDADDR, LCDCOLS, LCDLINES); // set the LCD address to 0x27 for a 16 chars and 2 line display
@@ -226,13 +277,22 @@ void setup() {
   //pirSensor.onPress(onButtonPressed); // When the button is first pressed, call the function onButtonPressed (further down the page)
   pirSensor.onHold(500, onButtonHeldNoRepeat); // Once the button has been held for 1 second (1000ms) call onButtonHeld
   pirSensor.onRelease(500, onButtonReleased); // When the button is held >50ms and released after <500ms, call onButtonReleased
-  
 
+  //set conf default values (bool, int and float variables are set at declaration)
   strlcpy(deviceName, DEVICE_NAME, 31);
+  strlcpy(http_user, DEFAULT_HTTP_USER, 31);
+  strlcpy(http_pass, DEFAULT_HTTP_PASS, 31);
+  strlcpy(http_token, HTTP_SET_TOKEN, 31);  
   strlcpy(mqtt_server, MQTT_SERVER, 41);
+  strlcpy(mqtt_user, MQTT_USER, 31);
+  strlcpy(mqtt_pass, MQTT_PASS, 31);
   strlcpy(mqtt_topic_in, MQTT_TOPIC_IN, 51);
   strlcpy(mqtt_topic_out, MQTT_TOPIC_OUT, 51);
+  strlcpy(mqtt_willTopic, MQTT_WILLTOPIC, 51);
+  strlcpy(mqtt_willMsg, MQTT_WILLMSG, 31);
   strlcpy(domoticz_out_topic, DOMOTICZ_OUT_TOPIC, 51); // changeable subscription topic, as domoticz supports different flat/hierarchical out-topics
+
+  //set conf2 default values (bool, int and float variables are set at declaration)
   strlcpy(outTemp_topic_in, OUTTEMP_TOPIC_IN, 51);
   strlcpy(outHum_topic_in, OUTHUM_TOPIC_IN, 51);
 
@@ -331,7 +391,7 @@ void setup() {
   initDisplay();
 
   Serial.println("setup complete.");
-  delay(1000);
+  //delay(1000);
 } //void setup
 
 void loop() {
@@ -339,10 +399,16 @@ void loop() {
 
   persWM.handleWiFi(); //in non-blocking mode, handleWiFi must be called in the main loop
   yield();
-  dnsServer.processNextRequest();
-  httpServer.handleClient();
 
   mqttHandleConnection();
+  yield();
+
+  outTempHum_updateOnNewValue();
+
+  yield();
+
+  dnsServer.processNextRequest();
+  httpServer.handleClient();
 
   buttonPlus.update();
   buttonMinus.update();
@@ -352,6 +418,7 @@ void loop() {
   yield();
 
   evalCmd();
+  yield();
 
   if ( domoticzOutParseData ) {
     parseDomoticzOut();

+ 6 - 1
src/WiFiThermostat/commands.ino

@@ -29,7 +29,12 @@ void evalCmd() {
     //Serial.print("cmdPayload: ");
     //Serial.println(cmdPayload);
 
-    if (strncmp(cmdPayload, "loadconf", 8) == 0) {
+    if (strncmp(cmdPayload, "HEARTBEAT", 9) == 0) {
+      Serial.println("resetting HEARTBEAT");
+      mqttLastHeartbeat = millis();
+    }
+
+    else if (strncmp(cmdPayload, "loadconf", 8) == 0) {
       loadConfig();
       loadConfig2();
       mqttPrepareConnection();

+ 30 - 0
src/WiFiThermostat/config.ino

@@ -41,6 +41,9 @@ bool setConfig(char* param, char* value) {
   else if ( strcmp(param, "httpPass") == 0 ) {
     strlcpy(http_pass, value, 31);
   }
+  else if ( strcmp(param, "httpToken") == 0 ) {
+    strlcpy(http_token, value, 31);
+  }
   else if ( strcmp(param, "mqttHost") == 0 ) {
     strlcpy(mqtt_server, value, 41);
   }
@@ -132,6 +135,15 @@ bool setConfig(char* param, char* value) {
       setTempMax = valueFloat;
     }
   }
+  else if ( strcmp(param, "tempDec") == 0 ) {
+    float valueFloat = atof(value);
+#ifdef DEBUG_VERBOSE
+    Serial.print(valueFloat);
+#endif
+    if (valueFloat >= 0.0 && valueFloat <= 1.5) {
+      setTempDecreaseVal = valueFloat;
+    }
+  }
   else if ( strcmp(param, "hyst") == 0 ) {
     float valueFloat = atof(value);
 #ifdef DEBUG_VERBOSE
@@ -220,6 +232,10 @@ void getConfig(char* param) {
     sprintf(buf, "httpPass: '%s'", http_pass);
     sendStatus(buf);
   }
+  else if ( strcmp(param, "httpToken") == 0 ) {
+    sprintf(buf, "httpToken: '%s'", http_token);
+    sendStatus(buf);
+  }
   else if ( strcmp(param, "mqttHost") == 0 ) {
     sprintf(buf, "mqttHost: '%s'", mqtt_server);
     sendStatus(buf);
@@ -334,6 +350,12 @@ void getConfig(char* param) {
     sprintf(buf, "tempMax: '%s'", buf2);
     sendStatus(buf);
   }
+  else if ( strcmp(param, "tempDec") == 0 ) {
+    char buf2[11];
+    dtostrf(setTempDecreaseVal, 2, 1, buf2);
+    sprintf(buf, "tempDec: '%s'", buf2);
+    sendStatus(buf);
+  }
   else if ( strcmp(param, "hyst") == 0 ) {
     char buf2[11];
     dtostrf(hysteresis, 2, 1, buf2);
@@ -380,6 +402,8 @@ void printConfig() {
   Serial.println(http_user);
   Serial.print("httpPass: ");
   Serial.println(http_pass);
+  Serial.print("httpToken: ");
+  Serial.println(http_token);
   Serial.print("mqttHost: ");
   Serial.println(mqtt_server);
   Serial.print("mqttPort: ");
@@ -434,6 +458,8 @@ void printConfig2() {
   Serial.println(setTempMax);
   Serial.print("tempLow: ");
   Serial.print(setTempLow);
+  Serial.print("tempDec: ");
+  Serial.println(setTempDecreaseVal);
   Serial.print("hyst: ");
   Serial.println(hysteresis);
   Serial.print("tempCorr: ");
@@ -501,6 +527,7 @@ bool loadConfig() { // loadConfig 1
       strlcpy(deviceName, json["devName"] | "", 31);
       strlcpy(http_user, json["httpUser"] | "", 31);
       strlcpy(http_pass, json["httpPass"] | "", 31);
+      strlcpy(http_token, json["httpToken"] | "", 31);
       strlcpy(mqtt_server, json["mqttHost"] | "", 41);
       mqtt_port = atoi(json["mqttPort"] | "");
       strlcpy(mqtt_user, json["mqttUser"] | "", 31);
@@ -586,6 +613,7 @@ bool loadConfig2() {
       setTempMin = atof(json["tempMin"] | "");
       setTempMax = atof(json["tempMax"] | "");
       setTempLow = atof(json["tempLow"] | "");
+      setTempDecreaseVal = atof(json["tempDec"] | "");
       hysteresis = atof(json["hyst"] | "");
       tempCorrVal = atof(json["tempCorr"] | "");
       humCorrVal = atoi(json["humCorr"] | "");
@@ -649,6 +677,7 @@ bool saveConfig() { // safeConfig
   json["devName"] = deviceName;
   json["httpUser"] = http_user;
   json["httpPass"] = http_pass;
+  json["httpToken"] = http_token;
   json["mqttHost"] = mqtt_server;
   json["mqttPort"] = mqtt_port;
   json["mqttUser"] = mqtt_user;
@@ -704,6 +733,7 @@ bool saveConfig2() { // safeConfig2
   json["tempMin"] = setTempMin;
   json["tempMax"] = setTempMax;
   json["tempLow"] = setTempLow;
+  json["tempDec"] = setTempDecreaseVal;
   json["hyst"] = hysteresis;
   json["tempCorr"] = tempCorrVal;
   json["humCorr"] = humCorrVal;

+ 53 - 0
src/WiFiThermostat/httpServer.ino

@@ -129,6 +129,7 @@ static const char httpConfPage[] PROGMEM =
 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>
+HTTP set token: <input type='text' name='httpToken' id='httpToken'/><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>
@@ -195,6 +196,7 @@ Domoticz Out Topic *: <input type='text' name='domOutTop' id='domOutTop'/><br>
         var data = JSON.parse(xhttp.responseText);
         g('httpUser').value = data.httpUser;
         g('httpPass').value = data.httpPass;
+        g('httpToken').value = data.httpToken;
         g('devName').value = data.devName
         g('mqttHost').value = data.mqttHost;
         g('mqttPort').value = data.mqttPort;
@@ -255,6 +257,7 @@ 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>
+set temp decrease value: <input type='text' name='tempDec' id='tempDec'/><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>
@@ -331,6 +334,7 @@ PIR enables Display: <input type='checkbox' name='PIRenDisp' id='PIRenDisp'/>
           g('tempMin').value = data.tempMin;
           g('tempMax').value = data.tempMax;
           g('tempLow').value = data.tempLow;
+          g('tempDec').value = data.tempDec;
           g('hyst').value = data.hyst;
           g('tempCorr').value = data.tempCorr;
           g('humCorr').value = data.humCorr;
@@ -448,6 +452,53 @@ void httpServerInit() {
     ESP.restart();
   });
 
+  httpServer.on("/mqttReconnect", []() {
+    Serial.println("web triggered mqttReconnect");
+    mqttReconnect();
+    httpServer.sendHeader("Location", String("/"), true);
+    httpServer.send (302, "text/plain", "OK");
+  });
+
+  httpServer.on("/setPoint", []() {
+    if (httpServer.hasArg("token")) {
+      char buf[20];
+      httpServer.arg("token").toCharArray(buf, 20);
+      if (strcmp(buf, http_token) == 0) {
+        Serial.println("web triggered setPoint");
+        if (httpServer.hasArg("value")) {
+          char bufVal[20];
+          httpServer.arg("value").toCharArray(bufVal, 20);
+          float valueFloat = round(atof(bufVal) * 2.0) / 2.0;
+          setTempTo(valueFloat);
+          httpServer.send (200, "text/plain", "OK");
+        }
+      }
+    } //if
+    else {
+      httpServer.send (403, "text/plain", "FORBIDDEN");
+    }
+  });
+
+  httpServer.on("/setMode", []() {
+    if (httpServer.hasArg("token")) {
+      char buf[20];
+      httpServer.arg("token").toCharArray(buf, 20);
+      if (strcmp(buf, http_token) == 0) {
+        Serial.println("web triggered setMode");
+        if (httpServer.hasArg("value")) {
+          char bufVal[20];
+          httpServer.arg("value").toCharArray(bufVal, 20);
+          int valueInt = atoi(bufVal);
+          if(valueInt >= 0 && valueInt <= 2) setHeatingmodeTo(valueInt);
+          httpServer.send (200, "text/plain", "OK");
+        }
+      }
+    } //if
+    else {
+      httpServer.send (403, "text/plain", "FORBIDDEN");
+    }
+  });
+
   httpServer.on("/confdata", []() {
     boolean sendData = false;
     if (strlen(http_user) > 0 && strlen(http_pass) > 0) {
@@ -482,6 +533,7 @@ void httpServerInit() {
       json["devName"] = deviceName;
       json["httpUser"] =  http_user;
       json["httpPass"] =  http_pass;
+      json["httpToken"] =  http_token;
       json["mqttHost"] = mqtt_server;
       json["mqttPort"] = mqtt_port;
       json["mqttUser"] =  mqtt_user;
@@ -548,6 +600,7 @@ void httpServerInit() {
       json["tempMin"] = setTempMin;
       json["tempMax"] = setTempMax;
       json["tempLow"] = setTempLow;
+      json["tempDec"] = setTempDecreaseVal;
       json["hyst"] = hysteresis;
       json["tempCorr"] = tempCorrVal;
       json["humCorr"] = humCorrVal;

+ 4 - 0
src/WiFiThermostat/misc.ino

@@ -50,10 +50,14 @@ void everySecond() {
 }
 
 void everyMinute() {
+  mqttPublishHeartbeat();
   publishStatus();
   publishCurrentSensorValues();
   publishCurrentThermostatValues();
   updateDomoticzDevices();
+
+//  Serial.print("WiFi Status: ");
+//  Serial.println(WiFi.status());
 }
 
 

+ 109 - 61
src/WiFiThermostat/mqtt.ino

@@ -1,72 +1,71 @@
 
 // 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();
+  //  Serial.print("MQTT payload 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++) {
+    int len;
+    if (length < 101) len = length; // if input is bigger than dest buffer, cut
+    else len = 100;
+
+    for (int i = 0; i < len; i++) {
       cmdPayload[i] = (char)payload[i];
     }
-    cmdPayload[length + 1] = '\0';
-
-    Serial.print("cmdPayload:");
-    Serial.println(cmdPayload);
-
-    cmdInQueue = true;
-
+    cmdPayload[len + 1] = '\0';
+    //    Serial.print("cmdPayload:");
+    //    Serial.println(cmdPayload);
+    cmdInQueue = true; // payload is processed in "commands"
   }//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];
+    int len;
+    if (length < 6) len = length; // if input is bigger than dest buffer, cut
+    else len = 5;
+    for (int i = 0; i < len; i++) {
+      outTemp_newValue[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);
+    outTemp_newValue[len + 1] = '\0';
+    outTemp_parseNewValue = true;
   }//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];
+    int len;
+    if (length < 4) len = length; // if input is bigger than dest buffer, cut
+    else len = 3;
+    for (int i = 0; i < len; i++) {
+      outHum_newValue[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);
+    outHum_newValue[len + 1] = '\0';
+    outHum_parseNewValue = true;
   }//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++) {
+      int len;
+      if (length < 450) len = length; // if input is bigger than dest buffer, cut
+      else len = 449;
+      for (int i = 0; i < len; i++) {
         domoticzOutPayload[i] = (char)payload[i];
       }
-      domoticzOutPayload[length + 1] = '\0';
+      domoticzOutPayload[len + 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) {
@@ -91,26 +90,28 @@ void mqttPrepareConnection() {
   }
 }
 
-boolean mqttReconnect() {
+bool mqttReconnect() {
+  mqttclient.disconnect();
+  delay(10);
   // Create a random MQTT client ID
   String mqttClientId = "ESP8266Client-";
   mqttClientId += String(random(0xffff), HEX);
 
-  Serial.print("MQTT - connecting with ");
+  Serial.print("connecting to MQTT broker with ");
 
-  boolean connRes;
+  bool connRes;
 
   switch (mqttMode) {
     case 1:
-      Serial.println("no user, password and no Last Will");
+      Serial.print("no user, password and no Last Will");
       connRes = mqttclient.connect(mqttClientId.c_str());
       break;
     case 2:
-      Serial.println("user and password, no Last Will");
+      Serial.print("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");
+      Serial.print("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:
@@ -119,6 +120,10 @@ boolean mqttReconnect() {
       break;
   }
 
+  Serial.print("... attempt: ");
+  Serial.print(mqttReconnectAttempts);
+  Serial.println();
+
   if (connRes) {
     Serial.print("MQTT connected. Reconnects: ");
     Serial.println(mqttReconnects);
@@ -135,6 +140,7 @@ boolean mqttReconnect() {
     if (strlen(mqtt_topic_in) > 0) {
       if (mqttclient.subscribe(mqtt_topic_in)) {
         Serial.println(mqtt_topic_in);
+        mqttInTopicSubscribed = true;
       }
     }
     if (strlen(outTemp_topic_in) > 0) {
@@ -153,6 +159,10 @@ boolean mqttReconnect() {
       }
     }
   }
+  else {
+    Serial.print("MQTT connect FAILED, rc=");
+    Serial.println(mqttclient.state());
+  }
   return mqttclient.connected();
 } //mqttReconnect
 
@@ -162,29 +172,50 @@ void mqttClientInit() {
   mqttclient.setServer(mqtt_server, mqtt_port);
   mqttclient.setCallback(mqttCallback);
   mqttLastReconnectAttempt = 0;
+  mqttReconnectAttempts = 0;
 }
 
+int lastWifiStatus;
+
 void mqttHandleConnection() {
-  if ( WiFi.status() == WL_CONNECTED ) {
+  int currWifiStatus = WiFi.status();
+  if ( currWifiStatus != lastWifiStatus ) {
+    lastWifiStatus = currWifiStatus;
+    Serial.print("WiFi status changed to: ");
+    Serial.println(currWifiStatus);
+  }
+
+  if ( currWifiStatus == WL_CONNECTED ) {
     // MQTT reconnect if not connected (nonblocking)
-    if (!mqttclient.connected()) {
-      long now = millis();
+    bool doReconnect = false;
+    if (!mqttclient.connected()) doReconnect = true;
+
+    if (mqttInTopicSubscribed) { //  if mqtt_topic_in is subscribed
+      if ( (millis() - mqttLastHeartbeat) > MQTT_HEARTBEAT_MAXAGE) { // also reconnect if no HEARTBEAT was received for some time
+        doReconnect = true;
+        mqttLastHeartbeat = millis();
+        Serial.println("MQTT HEARTBEAT overdue. Force-Reconnecting MQTT...");
+      }
+    }
 
-      int mqttReconnectAttemptDelay;
-      if(mqttReconnectAttempts <= 3) mqttReconnectAttemptDelay = 15000; // if this is the 1-3rd attempt, try again in 15s
-      else mqttReconnectAttemptDelay = 300000; // if more than 3 attempts failed, try again every 5 min
+    if (doReconnect) {
+      unsigned int mqttReconnectAttemptDelay;
+      if (mqttReconnectAttempts < 3) mqttReconnectAttemptDelay = 15000; // if this is the 1-3rd attempt, try again in 15s
+      else mqttReconnectAttemptDelay = 60000; // if more than 3 attempts failed, try again in 1 min
 
-      if (now - mqttLastReconnectAttempt > mqttReconnectAttemptDelay) {
-        mqttLastReconnectAttempt = now;
+      if ((millis() - mqttLastReconnectAttempt) > mqttReconnectAttemptDelay) {
+        mqttLastReconnectAttempt = millis();
         mqttReconnectAttempts++;
         if (mqttReconnect()) { // Attempt to reconnect
           // attempt successful - reset mqttReconnectAttempts
-          mqttReconnectAttempts=0;
+          mqttReconnectAttempts = 0;
         }
       }
-    } else { // Client connected
       mqttclient.loop();
-    }//else
+    }
+    else {
+      mqttclient.loop();
+    }
   }
 }
 
@@ -193,13 +224,15 @@ void publishStatus() {
   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);
+  yield();
 }
 
 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);
+  mqttclient.publish(mqtt_topic_out, buf, mqtt_outRetain);
+  yield();
 }
 
 void publishCurrentThermostatValues() {
@@ -227,29 +260,35 @@ void publishCurrentThermostatValues() {
 
   sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "setTemp");
   mqttclient.publish(tmp_topic_out, setTemp_chararr);
+  yield();
 
   sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "currSetTemp");
   mqttclient.publish(tmp_topic_out, currSetTemp_chararr);
-
+  yield();
+  
   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);
-
+  yield();
+  
   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);
-
+  yield();
+  
   char buf[101];
   sprintf(buf, "%d", heatingOnTime);
   sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "heatingOnTime");
   mqttclient.publish(tmp_topic_out, buf);
+  yield();
 
   sprintf(buf, "%d", heatingOffTime);
   sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "heatingOffTime");
   mqttclient.publish(tmp_topic_out, buf);
+  yield();
 }
 
 void publishCurrentSensorValues() {
@@ -266,12 +305,14 @@ void publishCurrentSensorValues() {
     Serial.println("'");
     sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "temp");
     mqttclient.publish(tmp_topic_out, temp_chararr);
+    yield();
 
     Serial.print("hum: '");
     Serial.print(currHum);
     Serial.println("'");
     sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "hum");
     mqttclient.publish(tmp_topic_out, hum_chararr);
+    yield();
 
     dtostrf(currTemp_raw, 1, 1, temp_chararr );
     sprintf(hum_chararr, "%2i", currHum_raw);
@@ -281,22 +322,24 @@ void publishCurrentSensorValues() {
     Serial.println("'");
     sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "temp_raw");
     mqttclient.publish(tmp_topic_out, temp_chararr);
+    yield();
 
     Serial.print("hum_raw: '");
     Serial.print(currHum_raw);
     Serial.println("'");
     sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "hum_raw");
     mqttclient.publish(tmp_topic_out, hum_chararr);
+    yield();
   }
 }
 
 void publishCurrentPIRValue() {
   char tmp_topic_out[50];
   char PIRStatus[4];
-  
+
   sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "PIR");
 
-  if(PIRSensorOn) {
+  if (PIRSensorOn) {
     strcpy(PIRStatus, "on");
   }
   else {
@@ -304,4 +347,9 @@ void publishCurrentPIRValue() {
   }
   mqttclient.publish(tmp_topic_out, PIRStatus);
 }
+
+void mqttPublishHeartbeat() {
+  mqttclient.publish(mqtt_topic_in, "HEARTBEAT"); // publishes to IN-topic. MQTT will force-reconnect if it does not get a HEARTBEAT for 2 min
+}
+
 

+ 26 - 0
src/WiFiThermostat/outTempHum.ino

@@ -0,0 +1,26 @@
+// variables - must be declared globally!
+//char outTemp_newValue[6];
+//bool outTemp_parseNewValue;
+//char outHum_newValue[4];
+//bool outHum_parseNewValue;
+
+void outTempHum_updateOnNewValue() {
+  if(outTemp_parseNewValue) outTemp_update();
+  if(outHum_parseNewValue) outHum_update();
+}
+
+void outTemp_update() {
+  outTemp = atof(outTemp_newValue);
+  outTempHumLastUpdate = millis();
+  Serial.print("outTemp=");
+  Serial.println(outTemp);
+  outTemp_parseNewValue = false;
+}
+
+void outHum_update() {
+  outHum = atoi(outHum_newValue);
+  outTempHumLastUpdate = millis();
+  Serial.print("outHum=");
+  Serial.println(outHum);
+  outHum_parseNewValue = false;
+}

+ 2 - 2
src/WiFiThermostat/thermostat.ino

@@ -86,7 +86,7 @@ void thermostat() {
 #endif
 
     // thermostat with hysteresis
-    if ( turnHeatingOn && currTemp >= curr_setTemp ) {
+    if ( turnHeatingOn && currTemp >= (curr_setTemp - setTempDecreaseVal) ) {
       turnHeatingOn = false;
       heatingLastOffMillis = millis();
       digitalWrite(PIN_RELAIS, !RELAISONSTATE);
@@ -101,7 +101,7 @@ void thermostat() {
       publishCurrentThermostatValues();
       sendToDomoticz_Heating();
     }
-    else if ( !turnHeatingOn && heatingMode > 0 && ( currTemp < (curr_setTemp - hysteresis) ) && ( heatingOffTime > heatingMinOffTime ) ) {
+    else if ( !turnHeatingOn && heatingMode > 0 && ( currTemp < (curr_setTemp - setTempDecreaseVal - hysteresis) ) && ( heatingOffTime > heatingMinOffTime ) ) {
       turnHeatingOn = true;
       heatingLastOnMillis = millis();
       digitalWrite(PIN_RELAIS, RELAISONSTATE);