Browse Source

0.4.0 2019-01-04
* updated ESP8266 Arduino Core to v2.6.3
* updated LiquidCrystal_I2C library to v1.1.3 (former version had compatibility issues with core 2.6 as it seems)
https://github.com/johnrickman/LiquidCrystal_I2C/releases
* updated DHT-sensor-library (Adafruit) to v1.3.8
* updated ArduinoJson library from 5.13.0 to 6.13.0, which includes breaking changes, so some code had to be changed as described here: https://arduinojson.org/v6/doc/upgrade/
* updated PersWiFiManager library to v5.0.0
* completely redesigned web interface with new features
* (multi)user and admin mode for web interface
* fixed/improved: HTTP authentication handling
* improved MQTT (re)connection handling and heartbeat feature
* removed Domoticz support completely
* code cleanups

FloKra 4 years ago
parent
commit
73af0917c2
59 changed files with 3915 additions and 2375 deletions
  1. 23 8
      CHANGELOG.md
  2. 87 43
      LIBRARIES.md
  3. 3 1
      TODO.txt
  4. 0 0
      docs/ESP8266-WiFi-Thermostat.txt
  5. 0 0
      docs/ESP8266-WiFi-Thermostat_old.txt
  6. 0 0
      docs/Home Assistant integration NEW.txt
  7. 0 0
      docs/Home Assistant integration.txt
  8. 0 0
      docs/MQTT-commands.txt
  9. 0 0
      docs/Pin-assignment.txt
  10. 0 0
      docs/custom LCD chars.txt
  11. 0 0
      docs/domoticz-thermostat.txt
  12. BIN
      libraries/Bounce2-2.52.zip
  13. BIN
      libraries/Button-2.0.1.zip
  14. BIN
      libraries/DHT-sensor-library-1.3.8.zip
  15. BIN
      libraries/LiquidCrystal_I2C-1.1.3.zip
  16. 0 0
      libraries/LiquidCrystal_I2C-e3701fb-2017-03-09.zip
  17. 0 0
      releases/bin/WiFiThermostat.ino.d1_mini.20180208.bin
  18. 0 0
      releases/bin/WiFiThermostat.ino.d1_mini.20180216_1.bin
  19. 0 0
      releases/bin/WiFiThermostat.ino.d1_mini.20180216_2.bin
  20. 0 0
      releases/bin/WiFiThermostat.ino.d1_mini.20180228.bin
  21. 0 0
      releases/bin/WiFiThermostat.ino.d1_mini.20190530_v0.2.2.bin
  22. 0 0
      releases/bin/WiFiThermostat.ino.d1_mini.20191016_v0.3.1.bin
  23. 0 0
      releases/bin/WiFiThermostat.ino.d1_mini.20191017_v0.3.2.bin
  24. 0 0
      releases/bin/WiFiThermostat.ino.d1_mini.20191031_v0.3.3.bin
  25. BIN
      releases/bin/WiFiThermostat.ino.d1_mini.20191031_v0.3.3_1.bin
  26. BIN
      releases/bin/WiFiThermostat.ino.d1_mini.20200104_v0.4.0.bin
  27. BIN
      releases/bin/WiFiThermostat.ino.d1_mini.20200104_v0.4.0_1.bin
  28. 0 0
      releases/src/WiFiThermostat_0.2.1.7z
  29. 0 0
      releases/src/WiFiThermostat_0.2.2.7z
  30. 0 0
      releases/src/WiFiThermostat_0.3.1.7z
  31. 0 0
      releases/src/WiFiThermostat_0.3.2.7z
  32. BIN
      releases/src/WiFiThermostat_0.3.3.7z
  33. BIN
      releases/src/WiFiThermostat_0.4.0.7z
  34. 0 2
      src/WiFiThermostat/Buttonhandling.ino
  35. 5 5
      src/WiFiThermostat/Display.ino
  36. 315 0
      src/WiFiThermostat/LiquidCrystal_I2C.cpp
  37. 126 0
      src/WiFiThermostat/LiquidCrystal_I2C.h
  38. 653 653
      src/WiFiThermostat/PubSubClient.cpp
  39. 4 4
      src/WiFiThermostat/PubSubClient.h
  40. 232 208
      src/WiFiThermostat/WiFiThermostat.ino
  41. 36 27
      src/WiFiThermostat/commands.ino
  42. 578 370
      src/WiFiThermostat/config.ino
  43. 0 262
      src/WiFiThermostat/domoticz.ino
  44. 50 0
      src/WiFiThermostat/html.ino
  45. 18 0
      src/WiFiThermostat/html_conf.ino
  46. 93 0
      src/WiFiThermostat/html_confadd.ino
  47. 128 0
      src/WiFiThermostat/html_confadv.ino
  48. 115 0
      src/WiFiThermostat/html_confbas.ino
  49. 84 0
      src/WiFiThermostat/html_confdev.ino
  50. 135 0
      src/WiFiThermostat/html_confmqtt.ino
  51. 115 0
      src/WiFiThermostat/html_confweb.ino
  52. 130 0
      src/WiFiThermostat/html_main.ino
  53. 71 0
      src/WiFiThermostat/html_redTemps.ino
  54. 546 504
      src/WiFiThermostat/httpServer.ino
  55. 15 0
      src/WiFiThermostat/miscFunctions.ino
  56. 123 248
      src/WiFiThermostat/mqtt.ino
  57. 134 0
      src/WiFiThermostat/mqtt_out.ino
  58. 11 9
      src/WiFiThermostat/scheduler.ino
  59. 85 31
      src/WiFiThermostat/thermostat.ino

+ 23 - 8
CHANGELOG.md

@@ -1,22 +1,37 @@
 # WiFiThermostat - Changelog
-## 0.3.3
+
+## 0.4.0 2019-01-04
+* updated ESP8266 Arduino Core to v2.6.3
+* updated LiquidCrystal_I2C library to v1.1.3 (former version had compatibility issues with core 2.6 as it seems)
+  https://github.com/johnrickman/LiquidCrystal_I2C/releases
+* updated DHT-sensor-library (Adafruit) to v1.3.8
+* updated ArduinoJson library from 5.13.0 to 6.13.0, which includes breaking changes, so some code had to be changed as described here: https://arduinojson.org/v6/doc/upgrade/
+* updated PersWiFiManager library to v5.0.0
+* completely redesigned web interface with new features
+* (multi)user and admin mode for web interface
+* fixed/improved: HTTP authentication handling
+* improved MQTT (re)connection handling and heartbeat feature
+* removed Domoticz support completely
+* code cleanups
+
+## 0.3.3 2019-11-05
 * DHT sensor reading - retry if failed (does not fix issue)
 * improved temperature sensor timeout handling
 * updated Adafruit DHT22 sensor lib to 1.3.7 - that seems to have finally fixed the sensor reading issue
 * renamed "misc" to "scheduler" as it contains only timing/scheduling functions
 
-## 0.3.2
+## 0.3.2 2019-10-18
 * fixed/improved: HTTP authentication handling
 * added: device hostname (announced via DHCP) can now be configured
 * fixed: restart function on web interface
 * added: show AP SSID + password on display for some seconds when AP mode is entered
 
-## 0.3.1
+## 0.3.1 2019-10-16
 * fixed (issue #1): LCD - outside temp - wrong alignment on 1.1 digit values
 * added: configurable labels for inside and outside temperature ("I" and "O" in EN or "A" in DE)
 * fixed (issue #2): when (new) configuration values are not yet saved, initial values from source code are overwritten with blanks (only for most important values, not for MQTT topics, servers, users etc)
 
-## 0.3.0
+## 0.3.0 2019-10-11
 * split "mode" to "mode" and "preset", changes in nearly all source files
 * add In+Out Temperature display (without humidity) without toggling on LCD line 1
 * add publish topic "presetName" and "presetHA", where presetHA returns "none" for default preset and the name for the rest
@@ -37,7 +52,7 @@
 * added halfscreen (line 2) notifications - used for displaying the full name of the preset when using the MODE button
 * fixed http authentication
 
-## 0.2.2
+## 0.2.2 2019-05-31
 * Home Assistant direct support (setTemp and setMode topics, real name used in setMode, mode names are configurable)
 * added MQTT connection message on LWT topic
 * default MQTT_HEARTBEAT_MAXAGE 180000 (former 12000)
@@ -46,11 +61,11 @@
 * added additional publish topic for PIR sensor (configurable)
 * bugfixes
 
-## 0.2.1
+## 0.2.1 2018-02-28
 * added second reduction mode with different set temperature
 * MQTT reconnect now tries again every 5 min after 10 fails, finally restarts MCU on 20 unsuccessful retries
 
-## 0.2.0
+## 0.2.0 2018-02-16
 * improved MQTT reconnection management with heartbeat and force-reconnect
 * improved MQTT callback function
 * improved overall stability
@@ -63,7 +78,7 @@
 
 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
+## 0.1.3 2018-02-08
 * 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
 * lots of code improvements

+ 87 - 43
LIBRARIES.md

@@ -1,57 +1,101 @@
 ## Libraries
 
-### ESP8266 core for Arduino 2.4.0 beta2 (as 2.4 release has connection bugs)
-* ESP8266WiFi.h
-* WiFiClient.h
-* ESP8266WebServer.h
-* ESP8266mDNS.h
-* ESP8266HTTPUpdateServer.h
-* DNSServer.h
-* FS.h (SPIFFS)
-* Wire.h (I2C library)
-
-https://github.com/esp8266/Arduino
-
-### ArduinoJson 5.13.0
-from library manager
-
-### Adafruit DHT-sensor-library (DHT.h)
-Used unmodified for reading the DHT22 sensor. 
-
-https://github.com/adafruit/DHT-sensor-library
-
-### Adafruit Sensor 1.0.2
-Not really used, but has to be installed when using Adafruit DHT-sensor-library (even if I dont use the DHT_U.h version included in this lib, but gives compile error if not installed).
-
-https://github.com/adafruit/Adafruit_Sensor
-
-### r-downing PersWiFiManager 3.0.1
-AP with captive portal for initial (WiFi-) configuration. 
-Used with modification - added #define WIFI_HTM_PROGMEM to PersWiFiManager.cpp (turns off SPIFFS usage and stores HTML page to PROGMEM)
-
+### ESP8266 core for Arduino
+https://github.com/esp8266/Arduino  
+* includes:  
+    * ESP8266WiFi.h  
+    * WiFiClient.h  
+    * ESP8266WebServer.h  
+    * ESP8266mDNS.h  
+    * ESP8266HTTPUpdateServer.h  
+    * DNSServer.h  
+    * FS.h (SPIFFS)  
+    * Wire.h (I2C library)  
+* used versions:  
+    * from v0.4.0:  
+      Core 2.6.3
+	* pre v0.4.0:  
+	  Core 2.4.0-beta2  
+	  because final 2.4.0 release had connection bugs and older v2.3.0 was stable but had some serious security holes
+
+
+
+### ArduinoJson
+installed from library manager
+* from v0.4.0:  
+  ArduinoJson 6.13.0  
+  which includes breaking changes and needed code updates but is simpler to use and needs less memory  
+* pre v0.4.0:  
+  ArduinoJson 5.13.0  
+
+### Adafruit DHT-sensor-library
+https://github.com/adafruit/DHT-sensor-library  
+Examples include both a "standalone" DHT example and one that works along with the Adafruit Unified Sensor Library. A Unified sensor library is required even if using the standalone version.  
+Used the "standalone" version for reading the DHT22 sensor. 
+
+* from v0.4.0:  
+  DHT-sensor-library-1.3.8  
+* from v0.3.3:  
+  DHT-sensor-library-1.3.7  
+  because v1.3.0 had issues (error reading sensor very often)  
+* pre v0.3.3:  
+  DHT-sensor-library-1.3.0  
+
+### Adafruit Unified Sensor Driver
+dependency of Adafruit DHT-sensor-library, has to be installed to compile sucessfully although not really used for the "standalone" DHT library.  
+https://github.com/adafruit/Adafruit_Sensor  
+* from v0.4.0:  
+  Adafruit Unified Sensor 1.1.1  
+  +dependency Adafruit ADXL343, whatever  
+* pre v0.4.0:  
+  Adafruit Unified Sensor 1.0.2  
+
+### r-downing PersWiFiManager
+AP with captive portal for initial (WiFi-) configuration.  
+Used with modification - added #define WIFI_HTM_PROGMEM to PersWiFiManager.cpp (turns off SPIFFS usage and stores HTML page to PROGMEM to simplify firmware upload)  
 https://github.com/r-downing/PersWiFiManager
 
-### knolleary PubSubClient 2.7
-MQTT client for Arduino. Used with the following modified DEFINEs in PubSubClient.h: 
-* MQTT_MAX_PACKET_SIZE: increased to 512, needed for receiving/parsing domoticz/out topic
+* from v0.4.0:  
+  PersWiFiManager v5.0.0
+* pre v0.4.0:  
+  PersWiFiManager v3.0.1
 
-https://github.com/knolleary/pubsubclient
+### knolleary PubSubClient
+MQTT client for Arduino  
+https://github.com/knolleary/pubsubclient  
+https://pubsubclient.knolleary.net/  
 
-https://pubsubclient.knolleary.net/
+* from v0.4.0:  
+  PubSubClient v2.7  
+  used without modifications due to dropped Domoticz support
+* pre v0.4.0:  
+  PubSubClient v2.7  
+  used with the following modified DEFINEs in PubSubClient.h:  
+  MQTT_MAX_PACKET_SIZE: increased to 512, needed for receiving/parsing domoticz/out topic
 
-### LiquidCrystal I2C (commit e3701fb, Mar 9 2017)
 
-https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library
+### LiquidCrystal I2C 
+* from v0.4.0:  
+  LiquidCrystal_I2C v1.1.3 from here:  
+  https://github.com/johnrickman/LiquidCrystal_I2C/releases
 
-### r89m Button + PushButton
-Button handling library with callbacks on press, release, hold, hold-repeat.
-PushButton.h extends Button.h.
+* pre v0.4.0: commit e3701fb 2017-03-09 from here:  
+  https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library
 
-https://github.com/r89m/PushButton
+### r89m Button + PushButton
+Button handling library with callbacks on press, release, hold, hold-repeat.  
+PushButton.h extends Button.h.  
+https://github.com/r89m/PushButton  
+https://github.com/r89m/Button  
 
-https://github.com/r89m/Button
+- PushButton v1.0.0  
+- Button v2.0.1 (from v0.4.0, v2.0.0 before)
 
 ### Bounce2
-used by r89m Button/PushButton.
+used by r89m Button/PushButton.  
+https://github.com/thomasfredericks/Bounce2  
 
-https://github.com/thomasfredericks/Bounce2
+* from v0.4.0:  
+  Bounce2 2.52
+* pre v0.4.0:  
+  Bounce2 2.41  

+ 3 - 1
TODO.txt

@@ -1,6 +1,5 @@
 # WiFi Thermostat - ToDo
 ## WiFi Manager
-* update PersWiFiManager to 5.0 or switch to different lib as it seems to be somehow abandoned now
 * only start AP mode after reboot when no WiFi credentials are saved or can´t connect
 * stop AP mode automatically after some minutes
 * do not start AP mode when connection is lost, instead only try to reconnect
@@ -8,6 +7,9 @@
 * if reconnect fails for really long time (1h?), reboot
 * fix captive portal function (already done partly, wifi.htm must be on "/" when on AP mode)
 
+DONE:
+* update PersWiFiManager to 5.0 (or switch to different lib as it seems to be somehow abandoned now)
+
 
 ## reset settings via button
 * if MODE button is held during reboot, ask if connection settings should be cleared -> clear if button is pressed again

+ 0 - 0
ESP8266-WiFi-Thermostat.txt → docs/ESP8266-WiFi-Thermostat.txt


+ 0 - 0
ESP8266-WiFi-Thermostat_old.txt → docs/ESP8266-WiFi-Thermostat_old.txt


+ 0 - 0
Home Assistant integration NEW.txt → docs/Home Assistant integration NEW.txt


+ 0 - 0
Home Assistant integration.txt → docs/Home Assistant integration.txt


+ 0 - 0
MQTT-commands.txt → docs/MQTT-commands.txt


+ 0 - 0
Pin-assignment.txt → docs/Pin-assignment.txt


+ 0 - 0
custom LCD chars.txt → docs/custom LCD chars.txt


+ 0 - 0
domoticz-thermostat.txt → docs/domoticz-thermostat.txt


BIN
libraries/Bounce2-2.52.zip


BIN
libraries/Button-2.0.1.zip


BIN
libraries/DHT-sensor-library-1.3.8.zip


BIN
libraries/LiquidCrystal_I2C-1.1.3.zip


+ 0 - 0
libraries/LiquidCrystal_I2C.zip → libraries/LiquidCrystal_I2C-e3701fb-2017-03-09.zip


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


+ 0 - 0
bin/WiFiThermostat.ino.d1_mini.20180216_1.bin → releases/bin/WiFiThermostat.ino.d1_mini.20180216_1.bin


+ 0 - 0
bin/WiFiThermostat.ino.d1_mini.20180216_2.bin → releases/bin/WiFiThermostat.ino.d1_mini.20180216_2.bin


+ 0 - 0
bin/WiFiThermostat.ino.d1_mini.20180228.bin → releases/bin/WiFiThermostat.ino.d1_mini.20180228.bin


+ 0 - 0
bin/WiFiThermostat.ino.d1_mini.20190530_v0.2.2.bin → releases/bin/WiFiThermostat.ino.d1_mini.20190530_v0.2.2.bin


+ 0 - 0
bin/WiFiThermostat.ino.d1_mini.20191016_v0.3.1.bin → releases/bin/WiFiThermostat.ino.d1_mini.20191016_v0.3.1.bin


+ 0 - 0
bin/WiFiThermostat.ino.d1_mini.20191017_v0.3.2.bin → releases/bin/WiFiThermostat.ino.d1_mini.20191017_v0.3.2.bin


+ 0 - 0
bin/WiFiThermostat.ino.d1_mini.20191031_v0.3.3.bin → releases/bin/WiFiThermostat.ino.d1_mini.20191031_v0.3.3.bin


BIN
releases/bin/WiFiThermostat.ino.d1_mini.20191031_v0.3.3_1.bin


BIN
releases/bin/WiFiThermostat.ino.d1_mini.20200104_v0.4.0.bin


BIN
releases/bin/WiFiThermostat.ino.d1_mini.20200104_v0.4.0_1.bin


+ 0 - 0
src/Releases/WiFiThermostat_0.2.1.7z → releases/src/WiFiThermostat_0.2.1.7z


+ 0 - 0
src/Releases/WiFiThermostat_0.2.2.7z → releases/src/WiFiThermostat_0.2.2.7z


+ 0 - 0
src/Releases/WiFiThermostat_0.3.1.7z → releases/src/WiFiThermostat_0.3.1.7z


+ 0 - 0
src/Releases/WiFiThermostat_0.3.2.7z → releases/src/WiFiThermostat_0.3.2.7z


BIN
releases/src/WiFiThermostat_0.3.3.7z


BIN
releases/src/WiFiThermostat_0.4.0.7z


+ 0 - 2
src/WiFiThermostat/Buttonhandling.ino

@@ -138,7 +138,6 @@ void pirSensorOnAction() {
   PIRSensorOn = true;
   Serial.println("PIR sensor ON");
   publishCurrentPIRValue();
-  sendToDomoticz_PIR();
   if (PIR_enablesDisplay) enableDisplay();
 }
 
@@ -146,5 +145,4 @@ void pirSensorOffAction() {
   PIRSensorOn = false;
   Serial.println("PIR sensor OFF");
   publishCurrentPIRValue();
-  sendToDomoticz_PIR();
 }

+ 5 - 5
src/WiFiThermostat/Display.ino

@@ -185,20 +185,20 @@ void updateDisplay() {
 
     if (togglingTempHumAIDisplay) {
 
-      if ( ((now - outTempHumLastUpdate) < maxMeasurementAge) && outTempHumLastUpdate != 0 && whichTempToDisplay == 1 && ((now - lastDisplayToggle) > (displayInterval * 1000)) ) {
+      if ( ((now - outTempHumLastUpdate) < maxMeasurementAgeOut) && outTempHumLastUpdate != 0 && whichTempToDisplay == 1 && ((now - lastDisplayToggle) > (displayInterval * 1000)) ) {
         // outside temp has been updated < 5 min ago, last displayed temp was INSIDE and display interval is overdue
-        whichTempToDisplay = 2; // 1= I, 2= A
+        whichTempToDisplay = 2; // 1= I, 2= O
         lastDisplayToggle = now;
       }
       else if ( whichTempToDisplay != 1 && ((now - lastDisplayToggle) > (displayInterval * 1000)) ) {
-        whichTempToDisplay = 1; // 1= I, 2= A
+        whichTempToDisplay = 1; // 1= I, 2= O
         lastDisplayToggle = now;
       }
 
       lastDisplayUpdate = now;
 
       if (whichTempToDisplay == 2) { //2 = outside temp/hum received via MQTT
-        if ( outTempHumLastUpdate != 0 && (now - outTempHumLastUpdate) < maxMeasurementAge ) {
+        if ( outTempHumLastUpdate != 0 && (now - outTempHumLastUpdate) < maxMeasurementAgeOut ) {
           showTemp = true;
           dtostrf(outTemp, 5, 1, ch_temp);
           sprintf(ch_hum, "%2i", outHum);
@@ -265,7 +265,7 @@ void updateDisplay() {
       lcd.write((uint8_t)3); // °C symbol
       lcd.print(" ");
 
-      if ( outTempHumLastUpdate != 0 && (now - outTempHumLastUpdate) < maxMeasurementAge ) {
+      if ( outTempHumLastUpdate != 0 && (now - outTempHumLastUpdate) < maxMeasurementAgeOut ) {
 
         lcd.print(" ");
         lcd.print(otemplab);

+ 315 - 0
src/WiFiThermostat/LiquidCrystal_I2C.cpp

@@ -0,0 +1,315 @@
+// Based on the work by DFRobot
+
+#include "LiquidCrystal_I2C.h"
+#include <inttypes.h>
+#if defined(ARDUINO) && ARDUINO >= 100
+
+#include "Arduino.h"
+
+#define printIIC(args)	Wire.write(args)
+inline size_t LiquidCrystal_I2C::write(uint8_t value) {
+	send(value, Rs);
+	return 1;
+}
+
+#else
+#include "WProgram.h"
+
+#define printIIC(args)	Wire.send(args)
+inline void LiquidCrystal_I2C::write(uint8_t value) {
+	send(value, Rs);
+}
+
+#endif
+#include "Wire.h"
+
+
+
+// When the display powers up, it is configured as follows:
+//
+// 1. Display clear
+// 2. Function set: 
+//    DL = 1; 8-bit interface data 
+//    N = 0; 1-line display 
+//    F = 0; 5x8 dot character font 
+// 3. Display on/off control: 
+//    D = 0; Display off 
+//    C = 0; Cursor off 
+//    B = 0; Blinking off 
+// 4. Entry mode set: 
+//    I/D = 1; Increment by 1
+//    S = 0; No shift 
+//
+// Note, however, that resetting the Arduino doesn't reset the LCD, so we
+// can't assume that its in that state when a sketch starts (and the
+// LiquidCrystal constructor is called).
+
+LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows)
+{
+  _Addr = lcd_Addr;
+  _cols = lcd_cols;
+  _rows = lcd_rows;
+  _backlightval = LCD_NOBACKLIGHT;
+}
+
+void LiquidCrystal_I2C::init(){
+	init_priv();
+}
+
+void LiquidCrystal_I2C::init_priv()
+{
+	Wire.begin();
+	_displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
+	begin(_cols, _rows);  
+}
+
+void LiquidCrystal_I2C::begin(uint8_t cols, uint8_t lines, uint8_t dotsize) {
+	if (lines > 1) {
+		_displayfunction |= LCD_2LINE;
+	}
+	_numlines = lines;
+
+	// for some 1 line displays you can select a 10 pixel high font
+	if ((dotsize != 0) && (lines == 1)) {
+		_displayfunction |= LCD_5x10DOTS;
+	}
+
+	// SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION!
+	// according to datasheet, we need at least 40ms after power rises above 2.7V
+	// before sending commands. Arduino can turn on way befer 4.5V so we'll wait 50
+	delay(50); 
+  
+	// Now we pull both RS and R/W low to begin commands
+	expanderWrite(_backlightval);	// reset expanderand turn backlight off (Bit 8 =1)
+	delay(1000);
+
+  	//put the LCD into 4 bit mode
+	// this is according to the hitachi HD44780 datasheet
+	// figure 24, pg 46
+	
+	  // we start in 8bit mode, try to set 4 bit mode
+   write4bits(0x03 << 4);
+   delayMicroseconds(4500); // wait min 4.1ms
+   
+   // second try
+   write4bits(0x03 << 4);
+   delayMicroseconds(4500); // wait min 4.1ms
+   
+   // third go!
+   write4bits(0x03 << 4); 
+   delayMicroseconds(150);
+   
+   // finally, set to 4-bit interface
+   write4bits(0x02 << 4); 
+
+
+	// set # lines, font size, etc.
+	command(LCD_FUNCTIONSET | _displayfunction);  
+	
+	// turn the display on with no cursor or blinking default
+	_displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
+	display();
+	
+	// clear it off
+	clear();
+	
+	// Initialize to default text direction (for roman languages)
+	_displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
+	
+	// set the entry mode
+	command(LCD_ENTRYMODESET | _displaymode);
+	
+	home();
+  
+}
+
+/********** high level commands, for the user! */
+void LiquidCrystal_I2C::clear(){
+	command(LCD_CLEARDISPLAY);// clear display, set cursor position to zero
+	delayMicroseconds(2000);  // this command takes a long time!
+}
+
+void LiquidCrystal_I2C::home(){
+	command(LCD_RETURNHOME);  // set cursor position to zero
+	delayMicroseconds(2000);  // this command takes a long time!
+}
+
+void LiquidCrystal_I2C::setCursor(uint8_t col, uint8_t row){
+	int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 };
+	if ( row > _numlines ) {
+		row = _numlines-1;    // we count rows starting w/0
+	}
+	command(LCD_SETDDRAMADDR | (col + row_offsets[row]));
+}
+
+// Turn the display on/off (quickly)
+void LiquidCrystal_I2C::noDisplay() {
+	_displaycontrol &= ~LCD_DISPLAYON;
+	command(LCD_DISPLAYCONTROL | _displaycontrol);
+}
+void LiquidCrystal_I2C::display() {
+	_displaycontrol |= LCD_DISPLAYON;
+	command(LCD_DISPLAYCONTROL | _displaycontrol);
+}
+
+// Turns the underline cursor on/off
+void LiquidCrystal_I2C::noCursor() {
+	_displaycontrol &= ~LCD_CURSORON;
+	command(LCD_DISPLAYCONTROL | _displaycontrol);
+}
+void LiquidCrystal_I2C::cursor() {
+	_displaycontrol |= LCD_CURSORON;
+	command(LCD_DISPLAYCONTROL | _displaycontrol);
+}
+
+// Turn on and off the blinking cursor
+void LiquidCrystal_I2C::noBlink() {
+	_displaycontrol &= ~LCD_BLINKON;
+	command(LCD_DISPLAYCONTROL | _displaycontrol);
+}
+void LiquidCrystal_I2C::blink() {
+	_displaycontrol |= LCD_BLINKON;
+	command(LCD_DISPLAYCONTROL | _displaycontrol);
+}
+
+// These commands scroll the display without changing the RAM
+void LiquidCrystal_I2C::scrollDisplayLeft(void) {
+	command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT);
+}
+void LiquidCrystal_I2C::scrollDisplayRight(void) {
+	command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT);
+}
+
+// This is for text that flows Left to Right
+void LiquidCrystal_I2C::leftToRight(void) {
+	_displaymode |= LCD_ENTRYLEFT;
+	command(LCD_ENTRYMODESET | _displaymode);
+}
+
+// This is for text that flows Right to Left
+void LiquidCrystal_I2C::rightToLeft(void) {
+	_displaymode &= ~LCD_ENTRYLEFT;
+	command(LCD_ENTRYMODESET | _displaymode);
+}
+
+// This will 'right justify' text from the cursor
+void LiquidCrystal_I2C::autoscroll(void) {
+	_displaymode |= LCD_ENTRYSHIFTINCREMENT;
+	command(LCD_ENTRYMODESET | _displaymode);
+}
+
+// This will 'left justify' text from the cursor
+void LiquidCrystal_I2C::noAutoscroll(void) {
+	_displaymode &= ~LCD_ENTRYSHIFTINCREMENT;
+	command(LCD_ENTRYMODESET | _displaymode);
+}
+
+// Allows us to fill the first 8 CGRAM locations
+// with custom characters
+void LiquidCrystal_I2C::createChar(uint8_t location, uint8_t charmap[]) {
+	location &= 0x7; // we only have 8 locations 0-7
+	command(LCD_SETCGRAMADDR | (location << 3));
+	for (int i=0; i<8; i++) {
+		write(charmap[i]);
+	}
+}
+
+// Turn the (optional) backlight off/on
+void LiquidCrystal_I2C::noBacklight(void) {
+	_backlightval=LCD_NOBACKLIGHT;
+	expanderWrite(0);
+}
+
+void LiquidCrystal_I2C::backlight(void) {
+	_backlightval=LCD_BACKLIGHT;
+	expanderWrite(0);
+}
+
+
+
+/*********** mid level commands, for sending data/cmds */
+
+inline void LiquidCrystal_I2C::command(uint8_t value) {
+	send(value, 0);
+}
+
+
+/************ low level data pushing commands **********/
+
+// write either command or data
+void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) {
+	uint8_t highnib=value&0xf0;
+	uint8_t lownib=(value<<4)&0xf0;
+       write4bits((highnib)|mode);
+	write4bits((lownib)|mode); 
+}
+
+void LiquidCrystal_I2C::write4bits(uint8_t value) {
+	expanderWrite(value);
+	pulseEnable(value);
+}
+
+void LiquidCrystal_I2C::expanderWrite(uint8_t _data){                                        
+	Wire.beginTransmission(_Addr);
+	printIIC((int)(_data) | _backlightval);
+	Wire.endTransmission();   
+}
+
+void LiquidCrystal_I2C::pulseEnable(uint8_t _data){
+	expanderWrite(_data | En);	// En high
+	delayMicroseconds(1);		// enable pulse must be >450ns
+	
+	expanderWrite(_data & ~En);	// En low
+	delayMicroseconds(50);		// commands need > 37us to settle
+} 
+
+
+// Alias functions
+
+void LiquidCrystal_I2C::cursor_on(){
+	cursor();
+}
+
+void LiquidCrystal_I2C::cursor_off(){
+	noCursor();
+}
+
+void LiquidCrystal_I2C::blink_on(){
+	blink();
+}
+
+void LiquidCrystal_I2C::blink_off(){
+	noBlink();
+}
+
+void LiquidCrystal_I2C::load_custom_character(uint8_t char_num, uint8_t *rows){
+		createChar(char_num, rows);
+}
+
+void LiquidCrystal_I2C::setBacklight(uint8_t new_val){
+	if(new_val){
+		backlight();		// turn backlight on
+	}else{
+		noBacklight();		// turn backlight off
+	}
+}
+
+void LiquidCrystal_I2C::printstr(const char c[]){
+	//This function is not identical to the function used for "real" I2C displays
+	//it's here so the user sketch doesn't have to be changed 
+	print(c);
+}
+
+
+// unsupported API functions
+void LiquidCrystal_I2C::off(){}
+void LiquidCrystal_I2C::on(){}
+void LiquidCrystal_I2C::setDelay (int cmdDelay,int charDelay) {}
+uint8_t LiquidCrystal_I2C::status(){return 0;}
+uint8_t LiquidCrystal_I2C::keypad (){return 0;}
+uint8_t LiquidCrystal_I2C::init_bargraph(uint8_t graphtype){return 0;}
+void LiquidCrystal_I2C::draw_horizontal_graph(uint8_t row, uint8_t column, uint8_t len,  uint8_t pixel_col_end){}
+void LiquidCrystal_I2C::draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len,  uint8_t pixel_row_end){}
+void LiquidCrystal_I2C::setContrast(uint8_t new_val){}
+
+	

+ 126 - 0
src/WiFiThermostat/LiquidCrystal_I2C.h

@@ -0,0 +1,126 @@
+//YWROBOT
+#ifndef LiquidCrystal_I2C_h
+#define LiquidCrystal_I2C_h
+
+#include <inttypes.h>
+#include "Print.h" 
+#include <Wire.h>
+
+// commands
+#define LCD_CLEARDISPLAY 0x01
+#define LCD_RETURNHOME 0x02
+#define LCD_ENTRYMODESET 0x04
+#define LCD_DISPLAYCONTROL 0x08
+#define LCD_CURSORSHIFT 0x10
+#define LCD_FUNCTIONSET 0x20
+#define LCD_SETCGRAMADDR 0x40
+#define LCD_SETDDRAMADDR 0x80
+
+// flags for display entry mode
+#define LCD_ENTRYRIGHT 0x00
+#define LCD_ENTRYLEFT 0x02
+#define LCD_ENTRYSHIFTINCREMENT 0x01
+#define LCD_ENTRYSHIFTDECREMENT 0x00
+
+// flags for display on/off control
+#define LCD_DISPLAYON 0x04
+#define LCD_DISPLAYOFF 0x00
+#define LCD_CURSORON 0x02
+#define LCD_CURSOROFF 0x00
+#define LCD_BLINKON 0x01
+#define LCD_BLINKOFF 0x00
+
+// flags for display/cursor shift
+#define LCD_DISPLAYMOVE 0x08
+#define LCD_CURSORMOVE 0x00
+#define LCD_MOVERIGHT 0x04
+#define LCD_MOVELEFT 0x00
+
+// flags for function set
+#define LCD_8BITMODE 0x10
+#define LCD_4BITMODE 0x00
+#define LCD_2LINE 0x08
+#define LCD_1LINE 0x00
+#define LCD_5x10DOTS 0x04
+#define LCD_5x8DOTS 0x00
+
+// flags for backlight control
+#define LCD_BACKLIGHT 0x08
+#define LCD_NOBACKLIGHT 0x00
+
+#define En B00000100  // Enable bit
+#define Rw B00000010  // Read/Write bit
+#define Rs B00000001  // Register select bit
+
+class LiquidCrystal_I2C : public Print {
+public:
+  LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows);
+  void begin(uint8_t cols, uint8_t rows, uint8_t charsize = LCD_5x8DOTS );
+  void clear();
+  void home();
+  void noDisplay();
+  void display();
+  void noBlink();
+  void blink();
+  void noCursor();
+  void cursor();
+  void scrollDisplayLeft();
+  void scrollDisplayRight();
+  void printLeft();
+  void printRight();
+  void leftToRight();
+  void rightToLeft();
+  void shiftIncrement();
+  void shiftDecrement();
+  void noBacklight();
+  void backlight();
+  void autoscroll();
+  void noAutoscroll(); 
+  void createChar(uint8_t, uint8_t[]);
+  void setCursor(uint8_t, uint8_t); 
+#if defined(ARDUINO) && ARDUINO >= 100
+  virtual size_t write(uint8_t);
+#else
+  virtual void write(uint8_t);
+#endif
+  void command(uint8_t);
+  void init();
+
+////compatibility API function aliases
+void blink_on();						// alias for blink()
+void blink_off();       					// alias for noBlink()
+void cursor_on();      	 					// alias for cursor()
+void cursor_off();      					// alias for noCursor()
+void setBacklight(uint8_t new_val);				// alias for backlight() and nobacklight()
+void load_custom_character(uint8_t char_num, uint8_t *rows);	// alias for createChar()
+void printstr(const char[]);
+
+////Unsupported API functions (not implemented in this library)
+uint8_t status();
+void setContrast(uint8_t new_val);
+uint8_t keypad();
+void setDelay(int,int);
+void on();
+void off();
+uint8_t init_bargraph(uint8_t graphtype);
+void draw_horizontal_graph(uint8_t row, uint8_t column, uint8_t len,  uint8_t pixel_col_end);
+void draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len,  uint8_t pixel_col_end);
+	 
+
+private:
+  void init_priv();
+  void send(uint8_t, uint8_t);
+  void write4bits(uint8_t);
+  void expanderWrite(uint8_t);
+  void pulseEnable(uint8_t);
+  uint8_t _Addr;
+  uint8_t _displayfunction;
+  uint8_t _displaycontrol;
+  uint8_t _displaymode;
+  uint8_t _numlines;
+  uint8_t _cols;
+  uint8_t _rows;
+  uint8_t _backlightval;
+};
+
+#endif

+ 653 - 653
src/WiFiThermostat/PubSubClient.cpp

@@ -1,653 +1,653 @@
-/*
-  PubSubClient.cpp - A simple client for MQTT.
-  Nick O'Leary
-  http://knolleary.net
-*/
-
-#include "PubSubClient.h"
-#include "Arduino.h"
-
-PubSubClient::PubSubClient() {
-    this->_state = MQTT_DISCONNECTED;
-    this->_client = NULL;
-    this->stream = NULL;
-    setCallback(NULL);
-}
-
-PubSubClient::PubSubClient(Client& client) {
-    this->_state = MQTT_DISCONNECTED;
-    setClient(client);
-    this->stream = NULL;
-}
-
-PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client) {
-    this->_state = MQTT_DISCONNECTED;
-    setServer(addr, port);
-    setClient(client);
-    this->stream = NULL;
-}
-PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client, Stream& stream) {
-    this->_state = MQTT_DISCONNECTED;
-    setServer(addr,port);
-    setClient(client);
-    setStream(stream);
-}
-PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) {
-    this->_state = MQTT_DISCONNECTED;
-    setServer(addr, port);
-    setCallback(callback);
-    setClient(client);
-    this->stream = NULL;
-}
-PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) {
-    this->_state = MQTT_DISCONNECTED;
-    setServer(addr,port);
-    setCallback(callback);
-    setClient(client);
-    setStream(stream);
-}
-
-PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client) {
-    this->_state = MQTT_DISCONNECTED;
-    setServer(ip, port);
-    setClient(client);
-    this->stream = NULL;
-}
-PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client, Stream& stream) {
-    this->_state = MQTT_DISCONNECTED;
-    setServer(ip,port);
-    setClient(client);
-    setStream(stream);
-}
-PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) {
-    this->_state = MQTT_DISCONNECTED;
-    setServer(ip, port);
-    setCallback(callback);
-    setClient(client);
-    this->stream = NULL;
-}
-PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) {
-    this->_state = MQTT_DISCONNECTED;
-    setServer(ip,port);
-    setCallback(callback);
-    setClient(client);
-    setStream(stream);
-}
-
-PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client) {
-    this->_state = MQTT_DISCONNECTED;
-    setServer(domain,port);
-    setClient(client);
-    this->stream = NULL;
-}
-PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client, Stream& stream) {
-    this->_state = MQTT_DISCONNECTED;
-    setServer(domain,port);
-    setClient(client);
-    setStream(stream);
-}
-PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) {
-    this->_state = MQTT_DISCONNECTED;
-    setServer(domain,port);
-    setCallback(callback);
-    setClient(client);
-    this->stream = NULL;
-}
-PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) {
-    this->_state = MQTT_DISCONNECTED;
-    setServer(domain,port);
-    setCallback(callback);
-    setClient(client);
-    setStream(stream);
-}
-
-boolean PubSubClient::connect(const char *id) {
-    return connect(id,NULL,NULL,0,0,0,0,1);
-}
-
-boolean PubSubClient::connect(const char *id, const char *user, const char *pass) {
-    return connect(id,user,pass,0,0,0,0,1);
-}
-
-boolean PubSubClient::connect(const char *id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) {
-    return connect(id,NULL,NULL,willTopic,willQos,willRetain,willMessage,1);
-}
-
-boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) {
-    return connect(id,user,pass,willTopic,willQos,willRetain,willMessage,1);
-}
-
-boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean cleanSession) {
-    if (!connected()) {
-        int result = 0;
-
-        if (domain != NULL) {
-            result = _client->connect(this->domain, this->port);
-        } else {
-            result = _client->connect(this->ip, this->port);
-        }
-        if (result == 1) {
-            nextMsgId = 1;
-            // Leave room in the buffer for header and variable length field
-            uint16_t length = MQTT_MAX_HEADER_SIZE;
-            unsigned int j;
-
-#if MQTT_VERSION == MQTT_VERSION_3_1
-            uint8_t d[9] = {0x00,0x06,'M','Q','I','s','d','p', MQTT_VERSION};
-#define MQTT_HEADER_VERSION_LENGTH 9
-#elif MQTT_VERSION == MQTT_VERSION_3_1_1
-            uint8_t d[7] = {0x00,0x04,'M','Q','T','T',MQTT_VERSION};
-#define MQTT_HEADER_VERSION_LENGTH 7
-#endif
-            for (j = 0;j<MQTT_HEADER_VERSION_LENGTH;j++) {
-                buffer[length++] = d[j];
-            }
-
-            uint8_t v;
-            if (willTopic) {
-                v = 0x04|(willQos<<3)|(willRetain<<5);
-            } else {
-                v = 0x00;
-            }
-            if (cleanSession) {
-                v = v|0x02;
-            }
-
-            if(user != NULL) {
-                v = v|0x80;
-
-                if(pass != NULL) {
-                    v = v|(0x80>>1);
-                }
-            }
-
-            buffer[length++] = v;
-
-            buffer[length++] = ((MQTT_KEEPALIVE) >> 8);
-            buffer[length++] = ((MQTT_KEEPALIVE) & 0xFF);
-
-            CHECK_STRING_LENGTH(length,id)
-            length = writeString(id,buffer,length);
-            if (willTopic) {
-                CHECK_STRING_LENGTH(length,willTopic)
-                length = writeString(willTopic,buffer,length);
-                CHECK_STRING_LENGTH(length,willMessage)
-                length = writeString(willMessage,buffer,length);
-            }
-
-            if(user != NULL) {
-                CHECK_STRING_LENGTH(length,user)
-                length = writeString(user,buffer,length);
-                if(pass != NULL) {
-                    CHECK_STRING_LENGTH(length,pass)
-                    length = writeString(pass,buffer,length);
-                }
-            }
-
-            write(MQTTCONNECT,buffer,length-MQTT_MAX_HEADER_SIZE);
-
-            lastInActivity = lastOutActivity = millis();
-
-            while (!_client->available()) {
-                unsigned long t = millis();
-                if (t-lastInActivity >= ((int32_t) MQTT_SOCKET_TIMEOUT*1000UL)) {
-                    _state = MQTT_CONNECTION_TIMEOUT;
-                    _client->stop();
-                    return false;
-                }
-            }
-            uint8_t llen;
-            uint16_t len = readPacket(&llen);
-
-            if (len == 4) {
-                if (buffer[3] == 0) {
-                    lastInActivity = millis();
-                    pingOutstanding = false;
-                    _state = MQTT_CONNECTED;
-                    return true;
-                } else {
-                    _state = buffer[3];
-                }
-            }
-            _client->stop();
-        } else {
-            _state = MQTT_CONNECT_FAILED;
-        }
-        return false;
-    }
-    return true;
-}
-
-// reads a byte into result
-boolean PubSubClient::readByte(uint8_t * result) {
-   uint32_t previousMillis = millis();
-   while(!_client->available()) {
-     yield();
-     uint32_t currentMillis = millis();
-     if(currentMillis - previousMillis >= ((int32_t) MQTT_SOCKET_TIMEOUT * 1000)){
-       return false;
-     }
-   }
-   *result = _client->read();
-   return true;
-}
-
-// reads a byte into result[*index] and increments index
-boolean PubSubClient::readByte(uint8_t * result, uint16_t * index){
-  uint16_t current_index = *index;
-  uint8_t * write_address = &(result[current_index]);
-  if(readByte(write_address)){
-    *index = current_index + 1;
-    return true;
-  }
-  return false;
-}
-
-uint16_t PubSubClient::readPacket(uint8_t* lengthLength) {
-    uint16_t len = 0;
-    if(!readByte(buffer, &len)) return 0;
-    boolean isPublish = (buffer[0]&0xF0) == MQTTPUBLISH;
-    uint32_t multiplier = 1;
-    uint16_t length = 0;
-    uint8_t digit = 0;
-    uint16_t skip = 0;
-    uint8_t start = 0;
-
-    do {
-        if (len == 5) {
-            // Invalid remaining length encoding - kill the connection
-            _state = MQTT_DISCONNECTED;
-            _client->stop();
-            return 0;
-        }
-        if(!readByte(&digit)) return 0;
-        buffer[len++] = digit;
-        length += (digit & 127) * multiplier;
-        multiplier *= 128;
-    } while ((digit & 128) != 0);
-    *lengthLength = len-1;
-
-    if (isPublish) {
-        // Read in topic length to calculate bytes to skip over for Stream writing
-        if(!readByte(buffer, &len)) return 0;
-        if(!readByte(buffer, &len)) return 0;
-        skip = (buffer[*lengthLength+1]<<8)+buffer[*lengthLength+2];
-        start = 2;
-        if (buffer[0]&MQTTQOS1) {
-            // skip message id
-            skip += 2;
-        }
-    }
-
-    for (uint16_t i = start;i<length;i++) {
-        if(!readByte(&digit)) return 0;
-        if (this->stream) {
-            if (isPublish && len-*lengthLength-2>skip) {
-                this->stream->write(digit);
-            }
-        }
-        if (len < MQTT_MAX_PACKET_SIZE) {
-            buffer[len] = digit;
-        }
-        len++;
-    }
-
-    if (!this->stream && len > MQTT_MAX_PACKET_SIZE) {
-        len = 0; // This will cause the packet to be ignored.
-    }
-
-    return len;
-}
-
-boolean PubSubClient::loop() {
-    if (connected()) {
-        unsigned long t = millis();
-        if ((t - lastInActivity > MQTT_KEEPALIVE*1000UL) || (t - lastOutActivity > MQTT_KEEPALIVE*1000UL)) {
-            if (pingOutstanding) {
-                this->_state = MQTT_CONNECTION_TIMEOUT;
-                _client->stop();
-                return false;
-            } else {
-                buffer[0] = MQTTPINGREQ;
-                buffer[1] = 0;
-                _client->write(buffer,2);
-                lastOutActivity = t;
-                lastInActivity = t;
-                pingOutstanding = true;
-            }
-        }
-        if (_client->available()) {
-            uint8_t llen;
-            uint16_t len = readPacket(&llen);
-            uint16_t msgId = 0;
-            uint8_t *payload;
-            if (len > 0) {
-                lastInActivity = t;
-                uint8_t type = buffer[0]&0xF0;
-                if (type == MQTTPUBLISH) {
-                    if (callback) {
-                        uint16_t tl = (buffer[llen+1]<<8)+buffer[llen+2]; /* topic length in bytes */
-                        memmove(buffer+llen+2,buffer+llen+3,tl); /* move topic inside buffer 1 byte to front */
-                        buffer[llen+2+tl] = 0; /* end the topic as a 'C' string with \x00 */
-                        char *topic = (char*) buffer+llen+2;
-                        // msgId only present for QOS>0
-                        if ((buffer[0]&0x06) == MQTTQOS1) {
-                            msgId = (buffer[llen+3+tl]<<8)+buffer[llen+3+tl+1];
-                            payload = buffer+llen+3+tl+2;
-                            callback(topic,payload,len-llen-3-tl-2);
-
-                            buffer[0] = MQTTPUBACK;
-                            buffer[1] = 2;
-                            buffer[2] = (msgId >> 8);
-                            buffer[3] = (msgId & 0xFF);
-                            _client->write(buffer,4);
-                            lastOutActivity = t;
-
-                        } else {
-                            payload = buffer+llen+3+tl;
-                            callback(topic,payload,len-llen-3-tl);
-                        }
-                    }
-                } else if (type == MQTTPINGREQ) {
-                    buffer[0] = MQTTPINGRESP;
-                    buffer[1] = 0;
-                    _client->write(buffer,2);
-                } else if (type == MQTTPINGRESP) {
-                    pingOutstanding = false;
-                }
-            } else if (!connected()) {
-                // readPacket has closed the connection
-                return false;
-            }
-        }
-        return true;
-    }
-    return false;
-}
-
-boolean PubSubClient::publish(const char* topic, const char* payload) {
-    return publish(topic,(const uint8_t*)payload,strlen(payload),false);
-}
-
-boolean PubSubClient::publish(const char* topic, const char* payload, boolean retained) {
-    return publish(topic,(const uint8_t*)payload,strlen(payload),retained);
-}
-
-boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength) {
-    return publish(topic, payload, plength, false);
-}
-
-boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) {
-    if (connected()) {
-        if (MQTT_MAX_PACKET_SIZE < MQTT_MAX_HEADER_SIZE + 2+strlen(topic) + plength) {
-            // Too long
-            return false;
-        }
-        // Leave room in the buffer for header and variable length field
-        uint16_t length = MQTT_MAX_HEADER_SIZE;
-        length = writeString(topic,buffer,length);
-        uint16_t i;
-        for (i=0;i<plength;i++) {
-            buffer[length++] = payload[i];
-        }
-        uint8_t header = MQTTPUBLISH;
-        if (retained) {
-            header |= 1;
-        }
-        return write(header,buffer,length-MQTT_MAX_HEADER_SIZE);
-    }
-    return false;
-}
-
-boolean PubSubClient::publish_P(const char* topic, const char* payload, boolean retained) {
-    return publish_P(topic, (const uint8_t*)payload, strlen(payload), retained);
-}
-
-boolean PubSubClient::publish_P(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) {
-    uint8_t llen = 0;
-    uint8_t digit;
-    unsigned int rc = 0;
-    uint16_t tlen;
-    unsigned int pos = 0;
-    unsigned int i;
-    uint8_t header;
-    unsigned int len;
-
-    if (!connected()) {
-        return false;
-    }
-
-    tlen = strlen(topic);
-
-    header = MQTTPUBLISH;
-    if (retained) {
-        header |= 1;
-    }
-    buffer[pos++] = header;
-    len = plength + 2 + tlen;
-    do {
-        digit = len % 128;
-        len = len / 128;
-        if (len > 0) {
-            digit |= 0x80;
-        }
-        buffer[pos++] = digit;
-        llen++;
-    } while(len>0);
-
-    pos = writeString(topic,buffer,pos);
-
-    rc += _client->write(buffer,pos);
-
-    for (i=0;i<plength;i++) {
-        rc += _client->write((char)pgm_read_byte_near(payload + i));
-    }
-
-    lastOutActivity = millis();
-
-    return rc == tlen + 4 + plength;
-}
-
-boolean PubSubClient::beginPublish(const char* topic, unsigned int plength, boolean retained) {
-    if (connected()) {
-        // Send the header and variable length field
-        uint16_t length = MQTT_MAX_HEADER_SIZE;
-        length = writeString(topic,buffer,length);
-        uint16_t i;
-        uint8_t header = MQTTPUBLISH;
-        if (retained) {
-            header |= 1;
-        }
-        size_t hlen = buildHeader(header, buffer, plength+length-MQTT_MAX_HEADER_SIZE);
-        uint16_t rc = _client->write(buffer+(MQTT_MAX_HEADER_SIZE-hlen),length-(MQTT_MAX_HEADER_SIZE-hlen));
-        lastOutActivity = millis();
-        return (rc == (length-(MQTT_MAX_HEADER_SIZE-hlen)));
-    }
-    return false;
-}
-
-int PubSubClient::endPublish() {
- return 1;
-}
-
-size_t PubSubClient::write(uint8_t data) {
-    lastOutActivity = millis();
-    return _client->write(data);
-}
-
-size_t PubSubClient::write(const uint8_t *buffer, size_t size) {
-    lastOutActivity = millis();
-    return _client->write(buffer,size);
-}
-
-size_t PubSubClient::buildHeader(uint8_t header, uint8_t* buf, uint16_t length) {
-    uint8_t lenBuf[4];
-    uint8_t llen = 0;
-    uint8_t digit;
-    uint8_t pos = 0;
-    uint16_t len = length;
-    do {
-        digit = len % 128;
-        len = len / 128;
-        if (len > 0) {
-            digit |= 0x80;
-        }
-        lenBuf[pos++] = digit;
-        llen++;
-    } while(len>0);
-
-    buf[4-llen] = header;
-    for (int i=0;i<llen;i++) {
-        buf[MQTT_MAX_HEADER_SIZE-llen+i] = lenBuf[i];
-    }
-    return llen+1; // Full header size is variable length bit plus the 1-byte fixed header
-}
-
-boolean PubSubClient::write(uint8_t header, uint8_t* buf, uint16_t length) {
-    uint16_t rc;
-    uint8_t hlen = buildHeader(header, buf, length);
-
-#ifdef MQTT_MAX_TRANSFER_SIZE
-    uint8_t* writeBuf = buf+(MQTT_MAX_HEADER_SIZE-hlen);
-    uint16_t bytesRemaining = length+hlen;  //Match the length type
-    uint8_t bytesToWrite;
-    boolean result = true;
-    while((bytesRemaining > 0) && result) {
-        bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE)?MQTT_MAX_TRANSFER_SIZE:bytesRemaining;
-        rc = _client->write(writeBuf,bytesToWrite);
-        result = (rc == bytesToWrite);
-        bytesRemaining -= rc;
-        writeBuf += rc;
-    }
-    return result;
-#else
-    rc = _client->write(buf+(MQTT_MAX_HEADER_SIZE-hlen),length+hlen);
-    lastOutActivity = millis();
-    return (rc == hlen+length);
-#endif
-}
-
-boolean PubSubClient::subscribe(const char* topic) {
-    return subscribe(topic, 0);
-}
-
-boolean PubSubClient::subscribe(const char* topic, uint8_t qos) {
-    if (qos > 1) {
-        return false;
-    }
-    if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) {
-        // Too long
-        return false;
-    }
-    if (connected()) {
-        // Leave room in the buffer for header and variable length field
-        uint16_t length = MQTT_MAX_HEADER_SIZE;
-        nextMsgId++;
-        if (nextMsgId == 0) {
-            nextMsgId = 1;
-        }
-        buffer[length++] = (nextMsgId >> 8);
-        buffer[length++] = (nextMsgId & 0xFF);
-        length = writeString((char*)topic, buffer,length);
-        buffer[length++] = qos;
-        return write(MQTTSUBSCRIBE|MQTTQOS1,buffer,length-MQTT_MAX_HEADER_SIZE);
-    }
-    return false;
-}
-
-boolean PubSubClient::unsubscribe(const char* topic) {
-    if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) {
-        // Too long
-        return false;
-    }
-    if (connected()) {
-        uint16_t length = MQTT_MAX_HEADER_SIZE;
-        nextMsgId++;
-        if (nextMsgId == 0) {
-            nextMsgId = 1;
-        }
-        buffer[length++] = (nextMsgId >> 8);
-        buffer[length++] = (nextMsgId & 0xFF);
-        length = writeString(topic, buffer,length);
-        return write(MQTTUNSUBSCRIBE|MQTTQOS1,buffer,length-MQTT_MAX_HEADER_SIZE);
-    }
-    return false;
-}
-
-void PubSubClient::disconnect() {
-    buffer[0] = MQTTDISCONNECT;
-    buffer[1] = 0;
-    _client->write(buffer,2);
-    _state = MQTT_DISCONNECTED;
-    _client->flush();
-    _client->stop();
-    lastInActivity = lastOutActivity = millis();
-}
-
-uint16_t PubSubClient::writeString(const char* string, uint8_t* buf, uint16_t pos) {
-    const char* idp = string;
-    uint16_t i = 0;
-    pos += 2;
-    while (*idp) {
-        buf[pos++] = *idp++;
-        i++;
-    }
-    buf[pos-i-2] = (i >> 8);
-    buf[pos-i-1] = (i & 0xFF);
-    return pos;
-}
-
-
-boolean PubSubClient::connected() {
-    boolean rc;
-    if (_client == NULL ) {
-        rc = false;
-    } else {
-        rc = (int)_client->connected();
-        if (!rc) {
-            if (this->_state == MQTT_CONNECTED) {
-                this->_state = MQTT_CONNECTION_LOST;
-                _client->flush();
-                _client->stop();
-            }
-        }
-    }
-    return rc;
-}
-
-PubSubClient& PubSubClient::setServer(uint8_t * ip, uint16_t port) {
-    IPAddress addr(ip[0],ip[1],ip[2],ip[3]);
-    return setServer(addr,port);
-}
-
-PubSubClient& PubSubClient::setServer(IPAddress ip, uint16_t port) {
-    this->ip = ip;
-    this->port = port;
-    this->domain = NULL;
-    return *this;
-}
-
-PubSubClient& PubSubClient::setServer(const char * domain, uint16_t port) {
-    this->domain = domain;
-    this->port = port;
-    return *this;
-}
-
-PubSubClient& PubSubClient::setCallback(MQTT_CALLBACK_SIGNATURE) {
-    this->callback = callback;
-    return *this;
-}
-
-PubSubClient& PubSubClient::setClient(Client& client){
-    this->_client = &client;
-    return *this;
-}
-
-PubSubClient& PubSubClient::setStream(Stream& stream){
-    this->stream = &stream;
-    return *this;
-}
-
-int PubSubClient::state() {
-    return this->_state;
-}
+/*
+  PubSubClient.cpp - A simple client for MQTT.
+  Nick O'Leary
+  http://knolleary.net
+*/
+
+#include "PubSubClient.h"
+#include "Arduino.h"
+
+PubSubClient::PubSubClient() {
+    this->_state = MQTT_DISCONNECTED;
+    this->_client = NULL;
+    this->stream = NULL;
+    setCallback(NULL);
+}
+
+PubSubClient::PubSubClient(Client& client) {
+    this->_state = MQTT_DISCONNECTED;
+    setClient(client);
+    this->stream = NULL;
+}
+
+PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(addr, port);
+    setClient(client);
+    this->stream = NULL;
+}
+PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client, Stream& stream) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(addr,port);
+    setClient(client);
+    setStream(stream);
+}
+PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(addr, port);
+    setCallback(callback);
+    setClient(client);
+    this->stream = NULL;
+}
+PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(addr,port);
+    setCallback(callback);
+    setClient(client);
+    setStream(stream);
+}
+
+PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(ip, port);
+    setClient(client);
+    this->stream = NULL;
+}
+PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client, Stream& stream) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(ip,port);
+    setClient(client);
+    setStream(stream);
+}
+PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(ip, port);
+    setCallback(callback);
+    setClient(client);
+    this->stream = NULL;
+}
+PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(ip,port);
+    setCallback(callback);
+    setClient(client);
+    setStream(stream);
+}
+
+PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(domain,port);
+    setClient(client);
+    this->stream = NULL;
+}
+PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client, Stream& stream) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(domain,port);
+    setClient(client);
+    setStream(stream);
+}
+PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(domain,port);
+    setCallback(callback);
+    setClient(client);
+    this->stream = NULL;
+}
+PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(domain,port);
+    setCallback(callback);
+    setClient(client);
+    setStream(stream);
+}
+
+boolean PubSubClient::connect(const char *id) {
+    return connect(id,NULL,NULL,0,0,0,0,1);
+}
+
+boolean PubSubClient::connect(const char *id, const char *user, const char *pass) {
+    return connect(id,user,pass,0,0,0,0,1);
+}
+
+boolean PubSubClient::connect(const char *id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) {
+    return connect(id,NULL,NULL,willTopic,willQos,willRetain,willMessage,1);
+}
+
+boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) {
+    return connect(id,user,pass,willTopic,willQos,willRetain,willMessage,1);
+}
+
+boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean cleanSession) {
+    if (!connected()) {
+        int result = 0;
+
+        if (domain != NULL) {
+            result = _client->connect(this->domain, this->port);
+        } else {
+            result = _client->connect(this->ip, this->port);
+        }
+        if (result == 1) {
+            nextMsgId = 1;
+            // Leave room in the buffer for header and variable length field
+            uint16_t length = MQTT_MAX_HEADER_SIZE;
+            unsigned int j;
+
+#if MQTT_VERSION == MQTT_VERSION_3_1
+            uint8_t d[9] = {0x00,0x06,'M','Q','I','s','d','p', MQTT_VERSION};
+#define MQTT_HEADER_VERSION_LENGTH 9
+#elif MQTT_VERSION == MQTT_VERSION_3_1_1
+            uint8_t d[7] = {0x00,0x04,'M','Q','T','T',MQTT_VERSION};
+#define MQTT_HEADER_VERSION_LENGTH 7
+#endif
+            for (j = 0;j<MQTT_HEADER_VERSION_LENGTH;j++) {
+                buffer[length++] = d[j];
+            }
+
+            uint8_t v;
+            if (willTopic) {
+                v = 0x04|(willQos<<3)|(willRetain<<5);
+            } else {
+                v = 0x00;
+            }
+            if (cleanSession) {
+                v = v|0x02;
+            }
+
+            if(user != NULL) {
+                v = v|0x80;
+
+                if(pass != NULL) {
+                    v = v|(0x80>>1);
+                }
+            }
+
+            buffer[length++] = v;
+
+            buffer[length++] = ((MQTT_KEEPALIVE) >> 8);
+            buffer[length++] = ((MQTT_KEEPALIVE) & 0xFF);
+
+            CHECK_STRING_LENGTH(length,id)
+            length = writeString(id,buffer,length);
+            if (willTopic) {
+                CHECK_STRING_LENGTH(length,willTopic)
+                length = writeString(willTopic,buffer,length);
+                CHECK_STRING_LENGTH(length,willMessage)
+                length = writeString(willMessage,buffer,length);
+            }
+
+            if(user != NULL) {
+                CHECK_STRING_LENGTH(length,user)
+                length = writeString(user,buffer,length);
+                if(pass != NULL) {
+                    CHECK_STRING_LENGTH(length,pass)
+                    length = writeString(pass,buffer,length);
+                }
+            }
+
+            write(MQTTCONNECT,buffer,length-MQTT_MAX_HEADER_SIZE);
+
+            lastInActivity = lastOutActivity = millis();
+
+            while (!_client->available()) {
+                unsigned long t = millis();
+                if (t-lastInActivity >= ((int32_t) MQTT_SOCKET_TIMEOUT*1000UL)) {
+                    _state = MQTT_CONNECTION_TIMEOUT;
+                    _client->stop();
+                    return false;
+                }
+            }
+            uint8_t llen;
+            uint16_t len = readPacket(&llen);
+
+            if (len == 4) {
+                if (buffer[3] == 0) {
+                    lastInActivity = millis();
+                    pingOutstanding = false;
+                    _state = MQTT_CONNECTED;
+                    return true;
+                } else {
+                    _state = buffer[3];
+                }
+            }
+            _client->stop();
+        } else {
+            _state = MQTT_CONNECT_FAILED;
+        }
+        return false;
+    }
+    return true;
+}
+
+// reads a byte into result
+boolean PubSubClient::readByte(uint8_t * result) {
+   uint32_t previousMillis = millis();
+   while(!_client->available()) {
+     yield();
+     uint32_t currentMillis = millis();
+     if(currentMillis - previousMillis >= ((int32_t) MQTT_SOCKET_TIMEOUT * 1000)){
+       return false;
+     }
+   }
+   *result = _client->read();
+   return true;
+}
+
+// reads a byte into result[*index] and increments index
+boolean PubSubClient::readByte(uint8_t * result, uint16_t * index){
+  uint16_t current_index = *index;
+  uint8_t * write_address = &(result[current_index]);
+  if(readByte(write_address)){
+    *index = current_index + 1;
+    return true;
+  }
+  return false;
+}
+
+uint16_t PubSubClient::readPacket(uint8_t* lengthLength) {
+    uint16_t len = 0;
+    if(!readByte(buffer, &len)) return 0;
+    bool isPublish = (buffer[0]&0xF0) == MQTTPUBLISH;
+    uint32_t multiplier = 1;
+    uint16_t length = 0;
+    uint8_t digit = 0;
+    uint16_t skip = 0;
+    uint8_t start = 0;
+
+    do {
+        if (len == 5) {
+            // Invalid remaining length encoding - kill the connection
+            _state = MQTT_DISCONNECTED;
+            _client->stop();
+            return 0;
+        }
+        if(!readByte(&digit)) return 0;
+        buffer[len++] = digit;
+        length += (digit & 127) * multiplier;
+        multiplier *= 128;
+    } while ((digit & 128) != 0);
+    *lengthLength = len-1;
+
+    if (isPublish) {
+        // Read in topic length to calculate bytes to skip over for Stream writing
+        if(!readByte(buffer, &len)) return 0;
+        if(!readByte(buffer, &len)) return 0;
+        skip = (buffer[*lengthLength+1]<<8)+buffer[*lengthLength+2];
+        start = 2;
+        if (buffer[0]&MQTTQOS1) {
+            // skip message id
+            skip += 2;
+        }
+    }
+
+    for (uint16_t i = start;i<length;i++) {
+        if(!readByte(&digit)) return 0;
+        if (this->stream) {
+            if (isPublish && len-*lengthLength-2>skip) {
+                this->stream->write(digit);
+            }
+        }
+        if (len < MQTT_MAX_PACKET_SIZE) {
+            buffer[len] = digit;
+        }
+        len++;
+    }
+
+    if (!this->stream && len > MQTT_MAX_PACKET_SIZE) {
+        len = 0; // This will cause the packet to be ignored.
+    }
+
+    return len;
+}
+
+boolean PubSubClient::loop() {
+    if (connected()) {
+        unsigned long t = millis();
+        if ((t - lastInActivity > MQTT_KEEPALIVE*1000UL) || (t - lastOutActivity > MQTT_KEEPALIVE*1000UL)) {
+            if (pingOutstanding) {
+                this->_state = MQTT_CONNECTION_TIMEOUT;
+                _client->stop();
+                return false;
+            } else {
+                buffer[0] = MQTTPINGREQ;
+                buffer[1] = 0;
+                _client->write(buffer,2);
+                lastOutActivity = t;
+                lastInActivity = t;
+                pingOutstanding = true;
+            }
+        }
+        if (_client->available()) {
+            uint8_t llen;
+            uint16_t len = readPacket(&llen);
+            uint16_t msgId = 0;
+            uint8_t *payload;
+            if (len > 0) {
+                lastInActivity = t;
+                uint8_t type = buffer[0]&0xF0;
+                if (type == MQTTPUBLISH) {
+                    if (callback) {
+                        uint16_t tl = (buffer[llen+1]<<8)+buffer[llen+2]; /* topic length in bytes */
+                        memmove(buffer+llen+2,buffer+llen+3,tl); /* move topic inside buffer 1 byte to front */
+                        buffer[llen+2+tl] = 0; /* end the topic as a 'C' string with \x00 */
+                        char *topic = (char*) buffer+llen+2;
+                        // msgId only present for QOS>0
+                        if ((buffer[0]&0x06) == MQTTQOS1) {
+                            msgId = (buffer[llen+3+tl]<<8)+buffer[llen+3+tl+1];
+                            payload = buffer+llen+3+tl+2;
+                            callback(topic,payload,len-llen-3-tl-2);
+
+                            buffer[0] = MQTTPUBACK;
+                            buffer[1] = 2;
+                            buffer[2] = (msgId >> 8);
+                            buffer[3] = (msgId & 0xFF);
+                            _client->write(buffer,4);
+                            lastOutActivity = t;
+
+                        } else {
+                            payload = buffer+llen+3+tl;
+                            callback(topic,payload,len-llen-3-tl);
+                        }
+                    }
+                } else if (type == MQTTPINGREQ) {
+                    buffer[0] = MQTTPINGRESP;
+                    buffer[1] = 0;
+                    _client->write(buffer,2);
+                } else if (type == MQTTPINGRESP) {
+                    pingOutstanding = false;
+                }
+            } else if (!connected()) {
+                // readPacket has closed the connection
+                return false;
+            }
+        }
+        return true;
+    }
+    return false;
+}
+
+boolean PubSubClient::publish(const char* topic, const char* payload) {
+    return publish(topic,(const uint8_t*)payload,strlen(payload),false);
+}
+
+boolean PubSubClient::publish(const char* topic, const char* payload, boolean retained) {
+    return publish(topic,(const uint8_t*)payload,strlen(payload),retained);
+}
+
+boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength) {
+    return publish(topic, payload, plength, false);
+}
+
+boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) {
+    if (connected()) {
+        if (MQTT_MAX_PACKET_SIZE < MQTT_MAX_HEADER_SIZE + 2+strlen(topic) + plength) {
+            // Too long
+            return false;
+        }
+        // Leave room in the buffer for header and variable length field
+        uint16_t length = MQTT_MAX_HEADER_SIZE;
+        length = writeString(topic,buffer,length);
+        uint16_t i;
+        for (i=0;i<plength;i++) {
+            buffer[length++] = payload[i];
+        }
+        uint8_t header = MQTTPUBLISH;
+        if (retained) {
+            header |= 1;
+        }
+        return write(header,buffer,length-MQTT_MAX_HEADER_SIZE);
+    }
+    return false;
+}
+
+boolean PubSubClient::publish_P(const char* topic, const char* payload, boolean retained) {
+    return publish_P(topic, (const uint8_t*)payload, strlen(payload), retained);
+}
+
+boolean PubSubClient::publish_P(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) {
+    uint8_t llen = 0;
+    uint8_t digit;
+    unsigned int rc = 0;
+    uint16_t tlen;
+    unsigned int pos = 0;
+    unsigned int i;
+    uint8_t header;
+    unsigned int len;
+
+    if (!connected()) {
+        return false;
+    }
+
+    tlen = strlen(topic);
+
+    header = MQTTPUBLISH;
+    if (retained) {
+        header |= 1;
+    }
+    buffer[pos++] = header;
+    len = plength + 2 + tlen;
+    do {
+        digit = len % 128;
+        len = len / 128;
+        if (len > 0) {
+            digit |= 0x80;
+        }
+        buffer[pos++] = digit;
+        llen++;
+    } while(len>0);
+
+    pos = writeString(topic,buffer,pos);
+
+    rc += _client->write(buffer,pos);
+
+    for (i=0;i<plength;i++) {
+        rc += _client->write((char)pgm_read_byte_near(payload + i));
+    }
+
+    lastOutActivity = millis();
+
+    return rc == tlen + 4 + plength;
+}
+
+boolean PubSubClient::beginPublish(const char* topic, unsigned int plength, boolean retained) {
+    if (connected()) {
+        // Send the header and variable length field
+        uint16_t length = MQTT_MAX_HEADER_SIZE;
+        length = writeString(topic,buffer,length);
+        uint16_t i;
+        uint8_t header = MQTTPUBLISH;
+        if (retained) {
+            header |= 1;
+        }
+        size_t hlen = buildHeader(header, buffer, plength+length-MQTT_MAX_HEADER_SIZE);
+        uint16_t rc = _client->write(buffer+(MQTT_MAX_HEADER_SIZE-hlen),length-(MQTT_MAX_HEADER_SIZE-hlen));
+        lastOutActivity = millis();
+        return (rc == (length-(MQTT_MAX_HEADER_SIZE-hlen)));
+    }
+    return false;
+}
+
+int PubSubClient::endPublish() {
+ return 1;
+}
+
+size_t PubSubClient::write(uint8_t data) {
+    lastOutActivity = millis();
+    return _client->write(data);
+}
+
+size_t PubSubClient::write(const uint8_t *buffer, size_t size) {
+    lastOutActivity = millis();
+    return _client->write(buffer,size);
+}
+
+size_t PubSubClient::buildHeader(uint8_t header, uint8_t* buf, uint16_t length) {
+    uint8_t lenBuf[4];
+    uint8_t llen = 0;
+    uint8_t digit;
+    uint8_t pos = 0;
+    uint16_t len = length;
+    do {
+        digit = len % 128;
+        len = len / 128;
+        if (len > 0) {
+            digit |= 0x80;
+        }
+        lenBuf[pos++] = digit;
+        llen++;
+    } while(len>0);
+
+    buf[4-llen] = header;
+    for (int i=0;i<llen;i++) {
+        buf[MQTT_MAX_HEADER_SIZE-llen+i] = lenBuf[i];
+    }
+    return llen+1; // Full header size is variable length bit plus the 1-byte fixed header
+}
+
+boolean PubSubClient::write(uint8_t header, uint8_t* buf, uint16_t length) {
+    uint16_t rc;
+    uint8_t hlen = buildHeader(header, buf, length);
+
+#ifdef MQTT_MAX_TRANSFER_SIZE
+    uint8_t* writeBuf = buf+(MQTT_MAX_HEADER_SIZE-hlen);
+    uint16_t bytesRemaining = length+hlen;  //Match the length type
+    uint8_t bytesToWrite;
+    boolean result = true;
+    while((bytesRemaining > 0) && result) {
+        bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE)?MQTT_MAX_TRANSFER_SIZE:bytesRemaining;
+        rc = _client->write(writeBuf,bytesToWrite);
+        result = (rc == bytesToWrite);
+        bytesRemaining -= rc;
+        writeBuf += rc;
+    }
+    return result;
+#else
+    rc = _client->write(buf+(MQTT_MAX_HEADER_SIZE-hlen),length+hlen);
+    lastOutActivity = millis();
+    return (rc == hlen+length);
+#endif
+}
+
+boolean PubSubClient::subscribe(const char* topic) {
+    return subscribe(topic, 0);
+}
+
+boolean PubSubClient::subscribe(const char* topic, uint8_t qos) {
+    if (qos > 1) {
+        return false;
+    }
+    if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) {
+        // Too long
+        return false;
+    }
+    if (connected()) {
+        // Leave room in the buffer for header and variable length field
+        uint16_t length = MQTT_MAX_HEADER_SIZE;
+        nextMsgId++;
+        if (nextMsgId == 0) {
+            nextMsgId = 1;
+        }
+        buffer[length++] = (nextMsgId >> 8);
+        buffer[length++] = (nextMsgId & 0xFF);
+        length = writeString((char*)topic, buffer,length);
+        buffer[length++] = qos;
+        return write(MQTTSUBSCRIBE|MQTTQOS1,buffer,length-MQTT_MAX_HEADER_SIZE);
+    }
+    return false;
+}
+
+boolean PubSubClient::unsubscribe(const char* topic) {
+    if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) {
+        // Too long
+        return false;
+    }
+    if (connected()) {
+        uint16_t length = MQTT_MAX_HEADER_SIZE;
+        nextMsgId++;
+        if (nextMsgId == 0) {
+            nextMsgId = 1;
+        }
+        buffer[length++] = (nextMsgId >> 8);
+        buffer[length++] = (nextMsgId & 0xFF);
+        length = writeString(topic, buffer,length);
+        return write(MQTTUNSUBSCRIBE|MQTTQOS1,buffer,length-MQTT_MAX_HEADER_SIZE);
+    }
+    return false;
+}
+
+void PubSubClient::disconnect() {
+    buffer[0] = MQTTDISCONNECT;
+    buffer[1] = 0;
+    _client->write(buffer,2);
+    _state = MQTT_DISCONNECTED;
+    _client->flush();
+    _client->stop();
+    lastInActivity = lastOutActivity = millis();
+}
+
+uint16_t PubSubClient::writeString(const char* string, uint8_t* buf, uint16_t pos) {
+    const char* idp = string;
+    uint16_t i = 0;
+    pos += 2;
+    while (*idp) {
+        buf[pos++] = *idp++;
+        i++;
+    }
+    buf[pos-i-2] = (i >> 8);
+    buf[pos-i-1] = (i & 0xFF);
+    return pos;
+}
+
+
+boolean PubSubClient::connected() {
+    boolean rc;
+    if (_client == NULL ) {
+        rc = false;
+    } else {
+        rc = (int)_client->connected();
+        if (!rc) {
+            if (this->_state == MQTT_CONNECTED) {
+                this->_state = MQTT_CONNECTION_LOST;
+                _client->flush();
+                _client->stop();
+            }
+        }
+    }
+    return rc;
+}
+
+PubSubClient& PubSubClient::setServer(uint8_t * ip, uint16_t port) {
+    IPAddress addr(ip[0],ip[1],ip[2],ip[3]);
+    return setServer(addr,port);
+}
+
+PubSubClient& PubSubClient::setServer(IPAddress ip, uint16_t port) {
+    this->ip = ip;
+    this->port = port;
+    this->domain = NULL;
+    return *this;
+}
+
+PubSubClient& PubSubClient::setServer(const char * domain, uint16_t port) {
+    this->domain = domain;
+    this->port = port;
+    return *this;
+}
+
+PubSubClient& PubSubClient::setCallback(MQTT_CALLBACK_SIGNATURE) {
+    this->callback = callback;
+    return *this;
+}
+
+PubSubClient& PubSubClient::setClient(Client& client){
+    this->_client = &client;
+    return *this;
+}
+
+PubSubClient& PubSubClient::setStream(Stream& stream){
+    this->stream = &stream;
+    return *this;
+}
+
+int PubSubClient::state() {
+    return this->_state;
+}

+ 4 - 4
src/WiFiThermostat/PubSubClient.h

@@ -23,17 +23,17 @@
 
 // MQTT_MAX_PACKET_SIZE : Maximum packet size
 #ifndef MQTT_MAX_PACKET_SIZE
-#define MQTT_MAX_PACKET_SIZE 512
+#define MQTT_MAX_PACKET_SIZE 128
 #endif
 
 // MQTT_KEEPALIVE : keepAlive interval in Seconds
 #ifndef MQTT_KEEPALIVE
-#define MQTT_KEEPALIVE 10
+#define MQTT_KEEPALIVE 15
 #endif
 
 // MQTT_SOCKET_TIMEOUT: socket timeout interval in Seconds
 #ifndef MQTT_SOCKET_TIMEOUT
-#define MQTT_SOCKET_TIMEOUT 20
+#define MQTT_SOCKET_TIMEOUT 15
 #endif
 
 // MQTT_MAX_TRANSFER_SIZE : limit how much data is passed to the network client
@@ -92,7 +92,7 @@ private:
    uint16_t nextMsgId;
    unsigned long lastOutActivity;
    unsigned long lastInActivity;
-   boolean pingOutstanding;
+   bool pingOutstanding;
    MQTT_CALLBACK_SIGNATURE;
    uint16_t readPacket(uint8_t*);
    boolean readByte(uint8_t * result);

+ 232 - 208
src/WiFiThermostat/WiFiThermostat.ino

@@ -1,20 +1,38 @@
 
-// pre compiletime config
+
+// PRE-COMPILE CONFIGURATION
+
+//#define FORCE_SPIFFS_FORMAT
 
 //#define DEBUG_VERBOSE
+#define DEBUG_SERIAL true
+#define DEBUG_MQTT true
 
 #define SPIFFS_DBG
 #define SPIFFS_USE_MAGIC
 
 #define FIRMWARE_NAME "WiFiThermostat"
-#define VERSION "0.3.3"
+#define FIRMWARE_VERSION "0.4.0"
+#define FIRMWARE_URL "https://git.flokra.at/flo/WiFiThermostat"
+#define FIRMWARE_COPYRIGHT "FloKra"
+#define FIRMWARE_COPYRIGHT_URL "https://www.flokra.at/"
 
-// default values, can later be overridden via configuration (conf)
+// default values, can later be overridden via configuration
+#define MAX_MEASUREMENT_AGE 30000 // in ms, for measured inside Temp/Hum
+#define MAX_MEASUREMENT_AGE_OUT 300000 // in ms, for received outside Temp/Hum
+
+//confDev
 #define DEVICE_NAME "WiFiThermo-1"
+#define HOST_NAME ""
 #define WIFI_APMODE_PASSWORD "LassMiRein"
+
+//confWeb
+#define HTTP_SET_TOKEN "grzbrz"
 #define DEFAULT_HTTP_USER ""
 #define DEFAULT_HTTP_PASS ""
-#define HTTP_SET_TOKEN "grzbrz"
+
+//confMqtt
+#define MQTT_ENABLE true
 #define MQTT_SERVER "mqtt.lan"
 #define MQTT_PORT 1883
 #define MQTT_USER ""
@@ -27,59 +45,59 @@
 #define MQTT_WILLRETAIN false
 #define MQTT_WILLMSG "offline"
 #define MQTT_CONNMSG "online"
-#define DOMOTICZ_OUT_TOPIC "domoticz/out"
-
-// 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 MQTT_TOPIC_PIR ""                // extra publish topic for PIR sensor
+#define MQTT_ENABLE_HEARTBEAT true
+#define MQTT_HEARTBEAT_MAXAGE 180000 // max interval for MQTT heartbeat message. only applicable if MQTT IN-topic is defined. after this timeout MQTT reconnect is forced
+#define MQTT_HEARTBEAT_MAXAGE_REBOOT 1800000 // max interval for MQTT heartbeat message. only applicable if MQTT IN-topic is defined. after this timeout the ESP will reboot
+
+//confBas
+#define DEFAULT_SETTEMP_MIN 16.0         // minimal temperature that can be set
+#define DEFAULT_SETTEMP_MAX 26.0         // maximal temperature that can be set
 #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_SETTEMP_LOW2 20.0        // set temperature in night/low mode
-#define DEFAULT_SETTEMP_HEATOFF 3.0      // set temperature in OFF mode (freezing guard > 0)
-#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
+#define DEFAULT_TOGGLING_I_O_TEMPHUM false
 
+//confAdv
+#define DEFAULT_HYSTERESIS 0.2           // hysteresis, normally 0.1 - 0.5
+#define DEFAULT_HEATING_MIN_OFFTIME 120  // minimal time the heating keeps turned off in s
+#define TEMPSENSOR_CORRECTION_VALUE 0.0  // correction value for temperature sensor reading
+#define HUMSENSOR_CORRECTION_VALUE 0     // correction value for humidity sensor reading
+#define SETTEMP_DECREASE_VALUE 0.0       // decreases the set temp to overcome further temperature rise when the heating is already switched off
+#define OFF_MESSAGE "HEATING OFF"
 #define INSIDE_TEMP_LABEL "I"
 #define OUTSIDE_TEMP_LABEL "O"
-
 #define MODE_NAME_0 "off"
 #define MODE_NAME_1 "heat"
-
 #define PRESET_NAME_0 "none"
 #define PRESET_NAME_1 "reduction1"
 #define PRESET_NAME_2 "reduction2"
 
+//confAdd
+#define OUTTEMP_TOPIC_IN ""
+#define OUTHUM_TOPIC_IN ""
+#define MQTT_TOPIC_PIR ""                // extra publish topic for PIR sensor
+#define MQTT_TOPIC_PIR_ON "ON"
+#define MQTT_TOPIC_PIR_OFF "OFF"
+
+// other (not configurable via WebIF)
+#define DEFAULT_SETTEMP_HEATOFF 5.0      // set temperature in OFF mode (freezing guard > 0)
+
 // default initial values
 #define DEFAULT_SETTEMP 21.5
 #define DEFAULT_HEATINGMODE 1
 #define DEFAULT_PRESET 1
+#define DEFAULT_SETTEMP_LOW 20.0         // set temperature in night/low mode
+#define DEFAULT_SETTEMP_LOW2 18.0        // set temperature in night/low mode
+
 
 // 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 180000 // interval for MQTT heartbeat message. only applicable if MQTT IN-topic is defined. after this timeout MQTT reconnect is forced
+#define SETTEMP_LOW_MAX 21.5 // maximal configurable temperature for reduction mode
 
 // pin assignments and I2C addresses
 #define PIN_DHTSENSOR 13
@@ -97,6 +115,10 @@
 #define RELAISONSTATE HIGH
 #define BUTTONONSTATE LOW
 
+// END PRE-COMPILE CONFIGURATION
+
+//---------------------------------------------------------------------------------------------------------------------------------------------
+
 #include <Button.h>
 #include <ButtonEventCallback.h>
 #include <PushButton.h>
@@ -118,112 +140,124 @@ PushButton pirSensor = PushButton(PIN_PIRSENSOR, PRESSED_WHEN_HIGH);
 #include <DNSServer.h>
 #include <FS.h>
 #include <Wire.h>
-#include <LiquidCrystal_I2C.h>
+#include "LiquidCrystal_I2C.h"
 #include <DHT.h>
 #include <string.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
-
-
-boolean serialdebug = true;
-boolean mqttdebug = true;
+boolean serialdebug = DEBUG_SERIAL;
+boolean mqttdebug = DEBUG_MQTT;
 
 boolean WifiInApMode = false;
 unsigned long WifiApModeStartedAt;
 
 // config variables - do not change here!
-//conf
+
+// confDev
 char deviceName[31];  // device name - for web interface and AP-Mode SSID
 char hostName[31]; // announced hostname on WiFi connection
 char wifiAPModePassword[31];
+
+// confWeb
+char http_token[31];
 char http_user[31];
 char http_pass[31];
-char http_token[31];
+boolean http_user_auth = false;
+char http_user1[31];
+char http_pass1[31];
+char http_user2[31];
+char http_pass2[31];
+
+// confMqtt
+boolean mqtt_enable = MQTT_ENABLE;
 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 = MQTT_OUT_RETAIN; // send MQTT out with retain flag
 char mqtt_willTopic[51];  // MQTT Last Will topic
 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 mqtt_connMsg[31];
-char domoticz_out_topic[55];  // domoticz out topic to subscribe to (only applicable if domoticzIdx_Thermostat and/or domoticzIdx_ThermostatMode is set to >0)
-
-char mqtt_topic_in_cmd[61];
-char mqtt_topic_in_setTemp[61];
-char mqtt_topic_in_setMode[61];
-char mqtt_topic_in_setPreset[61];
+boolean mqtt_outRetain = MQTT_OUT_RETAIN; // send MQTT out with retain flag
+boolean mqtt_enable_heartbeat = MQTT_ENABLE_HEARTBEAT;
+unsigned long mqtt_heartbeat_maxage_reconnect = MQTT_HEARTBEAT_MAXAGE;
+unsigned long mqtt_heartbeat_maxage_reboot = MQTT_HEARTBEAT_MAXAGE_REBOOT;
 
-//conf2
-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];
-char mqtt_topic_pir[51];
-boolean autoSaveSetTemp = AUTOSAVE_SETTEMP;
-boolean autoSaveHeatingMode = AUTOSAVE_SETMODE;
-int heatingMinOffTime = DEFAULT_HEATING_MIN_OFFTIME; // minimal time the heating keeps turned off in s
+// confBas
 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 setTempLow2 = DEFAULT_SETTEMP_LOW2;           // 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
+boolean autoSaveSetTemp = AUTOSAVE_SETTEMP;
+boolean autoSaveHeatingMode = AUTOSAVE_SETMODE;
 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 displayInterval_saved = DEFAULT_DISPLAY_INTERVAL;
 int displayTimeout = DEFAULT_DISPLAY_TIMEOUT;     // display timeout after keypress (illumination)
 boolean PIR_enablesDisplay = DEFAULT_PIR_ENABLES_DISPLAY; // PIR sensor enables display illumination
+boolean togglingTempHumAIDisplay = DEFAULT_TOGGLING_I_O_TEMPHUM;
+
+// confAdv
+float hysteresis = DEFAULT_HYSTERESIS;            // hysteresis, normally 0.1 - 0.5
+int heatingMinOffTime = DEFAULT_HEATING_MIN_OFFTIME; // minimal time the heating keeps turned off in s
+float tempCorrVal = TEMPSENSOR_CORRECTION_VALUE;  // correction value for temperature sensor reading
+int humCorrVal = HUMSENSOR_CORRECTION_VALUE;      // correction value for humidity sensor reading
+float setTempDecreaseVal = SETTEMP_DECREASE_VALUE;    // decreases the set temp to overcome further temperature rise when the heating is already switched off
+char offMessage[15];
+char itemplab[2];
+char otemplab[2];
 char modename0[15];
 char modename1[15];
 char psetname0[15];
 char psetname1[15];
 char psetname2[15];
-char offMessage[15];
-char itemplab[2];
-char otemplab[2];
 
-int maxMeasurementAge = 300000;
+// confAdd
+char mqtt_topic_pir[51];
+char mqtt_payload_pir_on[10];
+char mqtt_payload_pir_off[10];
+char outTemp_topic_in[51];
+char outHum_topic_in[51];
+
+
 
-//set values
+boolean mqtt_tempDisabled_credentialError = false;
+
+char mqtt_topic_in_cmd[61];
+char mqtt_topic_in_setTemp[61];
+char mqtt_topic_in_setMode[61];
+char mqtt_topic_in_setPreset[61];
+
+
+
+// set values
 float setTemp = DEFAULT_SETTEMP;
+float setTempLow = DEFAULT_SETTEMP_LOW;           // set temperature in night/low mode
+float setTempLow2 = DEFAULT_SETTEMP_LOW2;           // set temperature in night/low mode
 float currSetTemp = DEFAULT_SETTEMP;
 byte heatingMode = DEFAULT_HEATINGMODE; // 0 = off, 1 = heat
 byte preset = DEFAULT_PRESET; // 0 = normal/day, 1 = night/reduction 1, 2 = reduction 2 (long term leave)
 
+// saved set values (same as above, held in memory additionally to prevent saving unchanged values)
 float setTempSaved;
-byte heatingModeSaved; // 0 = off, 1 = heat/on
-byte presetSaved; // 0 = normal/day, 1 = night/reduction 1, 2 = reduction 2
+float setTempLowSaved;
+float setTempLow2Saved;
+byte heatingModeSaved;
+byte presetSaved;
 
-// not changeable via configuration
+// global pre set conf variables - not changeable via configuration
 float setTempLowMin = SETTEMP_LOW_MIN;
 float setTempLowMax = SETTEMP_LOW_MAX;
-boolean debug = true;
 int debounceTime = BUTTON_DEBOUNCE_TIME;
 int buttonHoldTime = BUTTON_HOLD_TIME;
-
-// global variables
-float currTemp; // last reading from DHT sensor
-float currTemp_raw; // last reading from DHT sensor
-int currHum; // last reading from DHT sensor
-int currHum_raw; // last reading from DHT sensor
+int maxMeasurementAge = MAX_MEASUREMENT_AGE;
+int maxMeasurementAgeOut = MAX_MEASUREMENT_AGE_OUT;
+
+// global variables for program flow
+float currTemp; // last reading from DHT sensor with smoothing
+float currTemp_raw; // last reading from DHT sensor (raw)
+int currHum; // last reading from DHT sensor with smoothing
+int currHum_raw; // last reading from DHT sensor (raw)
 boolean 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
@@ -236,7 +270,6 @@ char outHum_newValue[4];
 boolean outHum_parseNewValue;
 char currentModeName[15];
 char currentPresetName[15];
-
 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
@@ -248,17 +281,11 @@ boolean displayActive = false; // gets true when button is pressed. display ligh
 boolean PIRSensorOn = false;
 unsigned long heatingOnTime, heatingOffTime;
 
-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 = 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
-unsigned long lastUpdate_preset = 0; // set to millis() every time preset value is changed. next update from domoticz/out will be rejected for dismissUpdateFromDomoticzTimeout in this case
-boolean lastUpdateFromDomoticz_setTemp = false;
-boolean lastUpdateFromDomoticz_heatingMode = false;
-int domoticzUpdateInterval = DOMOTICZ_FORCEUPDATE_INTERVAL; // interval in min to force update of domoticz devices
+unsigned long lastUpdate_setTemp = 0; // set to millis() every time setTemp value is changed
+unsigned long lastUpdate_setTempLow = 0; // set to millis() every time setTemp value is changed
+unsigned long lastUpdate_setTempLow2 = 0; // set to millis() every time setTemp value is changed
+unsigned long lastUpdate_heatingMode = 0; // set to millis() every time heatingMode value is changed
+unsigned long lastUpdate_preset = 0; // set to millis() every time preset value is changed
 
 char cmdPayload[101]; // buffer for commands
 boolean cmdInQueue = false; // command is queued and will be processed next loop() run
@@ -268,6 +295,8 @@ boolean 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
 boolean setTempAlreadySaved = true; // only save if not yet done
+boolean setTempLowAlreadySaved = true; // only save if not yet done
+boolean setTempLow2AlreadySaved = true; // only save if not yet done
 boolean heatingModeAlreadySaved = true; // only save if not yet done
 boolean presetAlreadySaved = true; // only save if not yet done
 
@@ -277,9 +306,7 @@ int countSeconds = 0;
 int countMeasureInterval = 0;
 int countDisplayInterval = 0;
 int displayOverlayMsgTimeout = 2;
-boolean togglingTempHumAIDisplay = false;
 
-byte mqttMode = 0;
 unsigned long mqttLastReconnectAttempt = 0;
 int mqttReconnectAttempts = 0;
 int mqttReconnects = 0;
@@ -303,8 +330,14 @@ char pendingPresetName[15];
 
 boolean displayShowLine2OverlayMsg = false;
 
+// build Uptime String (unnecessary, i know ;) ) 
+unsigned int sysUptime_days = 0;
+unsigned int sysUptime_hours = 0;
+unsigned int sysUptime_mins = 0;
+char uptimeStr[15];
+
 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
+LiquidCrystal_I2C lcd(LCDADDR, LCDCOLS, LCDLINES);
 
 WiFiClient espClient;
 
@@ -323,8 +356,8 @@ void setup() {
   Serial.println();
   Serial.print(FIRMWARE_NAME);
   Serial.print(" v");
-  Serial.print(VERSION);
-  Serial.println("starting...");
+  Serial.print(FIRMWARE_VERSION);
+  Serial.println(" starting...");
 
   pinMode(PIN_RELAIS, OUTPUT);
   digitalWrite(PIN_RELAIS, !RELAISONSTATE);
@@ -351,96 +384,96 @@ void setup() {
   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(wifiAPModePassword, WIFI_APMODE_PASSWORD, 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(mqtt_connMsg, MQTT_CONNMSG, 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);
-  strlcpy(mqtt_topic_pir, MQTT_TOPIC_PIR, 51);
-
-  strlcpy(modename0, MODE_NAME_0, 15);
-  strlcpy(modename1, MODE_NAME_1, 15);
-  strlcpy(psetname0, PRESET_NAME_0, 15);
-  strlcpy(psetname1, PRESET_NAME_1, 15);
-  strlcpy(psetname2, PRESET_NAME_2, 15);
-  strlcpy(itemplab, INSIDE_TEMP_LABEL, 2);
-  strlcpy(otemplab, OUTSIDE_TEMP_LABEL, 2);
-
-  Serial.println("default config values loaded..");
-
-  Serial.println("Mounting FS...");
+  
+  // confDev
+  strlcpy(deviceName, DEVICE_NAME, sizeof(deviceName));
+  strlcpy(hostName, HOST_NAME, sizeof(hostName));
+  strlcpy(wifiAPModePassword, WIFI_APMODE_PASSWORD, sizeof(wifiAPModePassword));
+  
+  //confWeb
+  strlcpy(http_token, HTTP_SET_TOKEN, sizeof(http_token));
+  strlcpy(http_user, DEFAULT_HTTP_USER, sizeof(http_user));
+  strlcpy(http_pass, DEFAULT_HTTP_PASS, sizeof(http_pass));
+  
+  //confMqtt
+  strlcpy(mqtt_server, MQTT_SERVER, sizeof(mqtt_server));
+  strlcpy(mqtt_user, MQTT_USER, sizeof(mqtt_user));
+  strlcpy(mqtt_pass, MQTT_PASS, sizeof(mqtt_pass));
+  strlcpy(mqtt_topic_in, MQTT_TOPIC_IN, sizeof(mqtt_topic_in));
+  strlcpy(mqtt_topic_out, MQTT_TOPIC_OUT, sizeof(mqtt_topic_out));
+  strlcpy(mqtt_willTopic, MQTT_WILLTOPIC, sizeof(mqtt_willTopic));
+  strlcpy(mqtt_willMsg, MQTT_WILLMSG, sizeof(mqtt_willMsg));
+  strlcpy(mqtt_connMsg, MQTT_CONNMSG, sizeof(mqtt_connMsg));
+  //confBas
+  // all values are int, float, boolean - set at var declaration
+  
+  //confAdv
+  strlcpy(offMessage, OFF_MESSAGE, sizeof(offMessage));
+  strlcpy(itemplab, INSIDE_TEMP_LABEL, sizeof(itemplab));
+  strlcpy(otemplab, OUTSIDE_TEMP_LABEL, sizeof(otemplab));
+  strlcpy(modename0, MODE_NAME_0, sizeof(modename0));
+  strlcpy(modename1, MODE_NAME_1, sizeof(modename1));
+  strlcpy(psetname0, PRESET_NAME_0, sizeof(psetname0));
+  strlcpy(psetname1, PRESET_NAME_1, sizeof(psetname1));
+  strlcpy(psetname2, PRESET_NAME_2, sizeof(psetname2));
+  
+  //confAdd
+  strlcpy(mqtt_topic_pir, MQTT_TOPIC_PIR, sizeof(mqtt_topic_pir));
+  strlcpy(mqtt_payload_pir_on, MQTT_TOPIC_PIR_ON, sizeof(mqtt_payload_pir_on));
+  strlcpy(mqtt_payload_pir_off, MQTT_TOPIC_PIR_OFF, sizeof(mqtt_payload_pir_off));
+  strlcpy(outTemp_topic_in, OUTTEMP_TOPIC_IN, sizeof(outTemp_topic_in));
+  strlcpy(outHum_topic_in, OUTHUM_TOPIC_IN, sizeof(outHum_topic_in));
+  
+
+  if(serialdebug) Serial.println("default config values loaded..");
+
+  initDisplay();
+
+  Serial.println(F("Mounting SPIFFS..."));
   if (!SPIFFS.begin()) {
-    Serial.println("Failed to mount file system");
+    Serial.println(F("Failed to mount SPIFFS"));
     return;
   }
 
-  //uncomment for initial SPIFFS format
-  //SPIFFS.format();
-  //Serial.print("Format SPIFFS complete.");
+#ifdef FORCE_SPIFFS_FORMAT
+  SPIFFS.format();
+  Serial.print(F("Format SPIFFS complete."));
+#endif
+
 
-  if (!SPIFFS.exists("/formatComplete.txt")) {
-    Serial.println("Please wait 30 secs for SPIFFS to be formatted");
+  if (!SPIFFS.exists("/formatted")) {
+    Serial.println(F("formatting SPIFFS, please wait 30 secs"));
     SPIFFS.format();
-    Serial.println("Spiffs formatted");
+    Serial.println(F("SPIFFS formatted"));
 
-    File f = SPIFFS.open("/formatComplete.txt", "w");
+    File f = SPIFFS.open("/formatted", "w");
     if (!f) {
-      Serial.println("file open failed");
+      Serial.println(F("creating file '/formatted' failed"));
     } else {
-      f.println("Format Complete");
+      f.println(F("Format Complete"));
     }
     f.close();
   } else {
-    Serial.println("SPIFFS is formatted. Moving along...");
-  }
-
-
-  //  // 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");
-  }
-
-  if (!loadHeatingMode()) {
-    Serial.println("Failed to load file 'heatingMode'");
-  } else {
-    Serial.println("file 'heatingMode' loaded");
+    if(serialdebug) Serial.println(F("SPIFFS is formatted. Moving along..."));
   }
 
-  if (!loadPreset()) {
-    Serial.println("Failed to load file 'preset'");
-  } else {
-    Serial.println("file 'preset' loaded");
+  Serial.println(F("files in SPIFFS:"));
+  Dir dir = SPIFFS.openDir("/");
+  while (dir.next()) {
+    Serial.print(dir.fileName());
+    File f = dir.openFile("r");
+    Serial.print("  ");
+    Serial.println(f.size());
+    f.close();
   }
-
-  //if configuration returns empty strings - use the default from source
+  Serial.println(F("----"));
+  
+  loadConfigs();
+  loadSavedValues();
+  
+  //if configuration returned empty strings - use the defaults where a values is necessary
   if (strlen(deviceName) < 4) strlcpy(deviceName, DEVICE_NAME, 31);
   if (modename0[0] == '\0') strlcpy(modename0, MODE_NAME_0, 15);
   if (modename1[0] == '\0') strlcpy(modename1, MODE_NAME_1, 15);
@@ -450,10 +483,6 @@ void setup() {
   if (itemplab[0] == '\0') strlcpy(itemplab, INSIDE_TEMP_LABEL, 2);
   if (otemplab[0] == '\0') strlcpy(otemplab, OUTSIDE_TEMP_LABEL, 2);
 
-  setTempSaved = setTemp;
-  heatingModeSaved = heatingMode;
-  presetSaved = preset;
-
   updateCurrentHeatingModeName();
   updateCurrentPresetName();
 
@@ -464,33 +493,34 @@ void setup() {
     WiFi.hostname(hostName);
   }
 
-  mqttPrepareSubscribeTopics();
-
-  checkUseDomoticz();
-
   //optional code handlers to run everytime wifi is connected...
   persWM.onConnect([]() {
-    Serial.println("wifi connected");
-    Serial.println(WiFi.SSID());
+    Serial.print("WiFi connected to '");
+    Serial.print(WiFi.SSID());
+    Serial.print("' with IP: ");
     Serial.println(WiFi.localIP());
     WifiInApMode = false;
     displayShowWifiConnected();
   });
   //...or AP mode is started
   persWM.onAp([]() {
-    Serial.println("AP MODE");
-    Serial.println(persWM.getApSsid());
+    Serial.print("WiFi in AP mode with SSID '");
+    Serial.print(persWM.getApSsid());
+    if (strlen(wifiAPModePassword) >= 4) {
+      Serial.print("' and password '");
+      Serial.print(wifiAPModePassword);
+    }
+    Serial.println("'");
     WifiInApMode = true;
     WifiApModeStartedAt = millis();
     displayShowWifiConnectionError();
   });
 
   // sets network name and password for AP mode
-  if (strlen(wifiAPModePassword) < 8) {
-    strlcpy(wifiAPModePassword, WIFI_APMODE_PASSWORD, 31);
-  }
-  persWM.setApCredentials(deviceName, wifiAPModePassword);
-  //  persWM.setApCredentials(DEVICE_NAME);
+  char wifiAPModeSSID[45];
+  sprintf(wifiAPModeSSID, "%s-%s", FIRMWARE_NAME, deviceName);
+  if (strlen(wifiAPModePassword) < 4) persWM.setApCredentials(wifiAPModeSSID);
+  else persWM.setApCredentials(wifiAPModeSSID, wifiAPModePassword);
 
   //make connecting/disconnecting non-blocking
   persWM.setConnectNonBlock(true);
@@ -501,13 +531,12 @@ void setup() {
 
   httpServerInit();
 
-  mqttPrepareConnection();
-  mqttClientInit();
-
-  initDisplay();
+  if (mqtt_enable) {
+    mqttPrepareSubscribeTopics();
+    mqttClientInit();
+  }
 
   Serial.println("setup complete.");
-  //delay(1000);
 } //void setup
 
 void loop() {
@@ -535,12 +564,7 @@ void loop() {
 
   evalCmd();
   yield();
-
-  if ( domoticzOutParseData ) {
-    parseDomoticzOut();
-    yield();
-  }
-
+  
   if (Serial.available()) {
     serialEvent();
     yield();

+ 36 - 27
src/WiFiThermostat/commands.ino

@@ -1,14 +1,14 @@
 
 #define SER_INPUT_SIZE 70
-char serBuffer[SER_INPUT_SIZE + 1];    // Serial Input-Buffer
-int serBufferCount;    // Anzahl der eingelesenen Zeichen
+char serBuffer[SER_INPUT_SIZE + 1];
+int serBufferCount;
 
 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
+    serBuffer[serBufferCount - 1] = '\0';
 #ifdef DEBUG_VERBOSE
     Serial.print("serial cmd: '");
     Serial.print(serBuffer);
@@ -21,24 +21,13 @@ void serialEvent() {
   }
 }
 
-
-
 void evalCmd() {
   if (cmdInQueue) {
-
     //Serial.print("cmdPayload: ");
     //Serial.println(cmdPayload);
 
-    if (strncmp(cmdPayload, "HEARTBEAT", 9) == 0) {
-      Serial.println("resetting HEARTBEAT");
-      mqttLastHeartbeat = millis();
-      mqttConnected = true;
-    }
-
-    else if (strncmp(cmdPayload, "loadconf", 8) == 0) {
-      loadConfig();
-      loadConfig2();
-      mqttPrepareConnection();
+    if (strncmp(cmdPayload, "loadconf", 8) == 0) {
+      loadConfigs();
     }
 
     else if (strncmp(cmdPayload, "set ", 4) == 0) {
@@ -152,25 +141,45 @@ void evalCmd() {
       ESP.restart();
     }
     else if (strncmp(cmdPayload, "save", 4) == 0) {
-      saveConfig();
+      //saveConfig();
+      
+      //saveConfig2();
+      saveConfigDev();
+      yield();
+      saveConfigWeb();
+      yield();
+      saveConfigMqtt();
+      yield();
+      saveConfigBas();
       yield();
-      saveConfig2();
+      saveConfigAdv();
       yield();
+      saveConfigAdd();
+      yield();
+      
 
       saveSetTemp();
-      saveHeatingMode();
-      
-      //Serial.println("saved config to SPIFFS");
-      sendStatus("saved config to SPIFFS");
-      Serial.println("reloading config to check...");
-      loadConfig();
       yield();
-      loadConfig2();
+      saveSetTempLow();
       yield();
+      saveSetTempLow2();
+      yield();
+      saveHeatingMode();
+      yield();
+            
+      //Serial.println("saved config to SPIFFS");
+      mqttSendLog("saved config to SPIFFS");
+      //Serial.println("reloading config to check...");
+      //loadConfig();
+      //yield();
+      //loadConfig2();
+      //yield();
     }
     else if (strncmp(cmdPayload, "getconf", 7) == 0) {
-      printConfig();
-      printConfig2();
+      //printConfig();
+      //printConfig2();
+      printConfigWeb();
+      printConfigMqtt();
     }
     else if (strncmp(cmdPayload, "delconf", 7) == 0) {
       deleteConfig();

File diff suppressed because it is too large
+ 578 - 370
src/WiFiThermostat/config.ino


+ 0 - 262
src/WiFiThermostat/domoticz.ino

@@ -1,262 +0,0 @@
-
-//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();
-        }
-        else if (strcmp(svalue, "30") == 0) {
-          setHeatingmodeTo(3);
-          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(currTemp, 1, 1, buftemp );
-      sprintf(buf, "{\"idx\":%d,\"nvalue\":0,\"svalue\":\"%s;%d;0\"}", domoticzIdx_TempHumSensor, buftemp, currHum);
-      mqttclient.publish(DOMOTICZ_IN_TOPIC, buf);
-    }
-  }
-}
-
-void sendToDomoticz_Heating() {
-  if (domoticzIdx_Heating > 0) {
-    //{"command":"switchlight","idx":238,"switchcmd":"On"}
-    //{"command":"switchlight","idx":238,"switchcmd":"Off"}
-
-    char heatingStatus[4];
-    if (turnHeatingOn) {
-      strcpy(heatingStatus, "On");
-    }
-    else {
-      strcpy(heatingStatus, "Off");
-    }
-
-    char domoticzJson[90];
-    sprintf(domoticzJson, "{\"command\":\"switchlight\",\"idx\":%d,\"switchcmd\":\"%s\"}", domoticzIdx_Heating, heatingStatus);
-    mqttclient.publish(DOMOTICZ_IN_TOPIC, domoticzJson);
-  }
-}
-
-void sendToDomoticz_PIR() {
-  if (domoticzIdx_PIR > 0) {
-    //{"command":"switchlight","idx":277,"switchcmd":"On"}
-    //{"command":"switchlight","idx":277,"switchcmd":"Off"}
-
-    char PIRStatus[4];
-    if (PIRSensorOn) {
-      strcpy(PIRStatus, "On");
-    }
-    else {
-      strcpy(PIRStatus, "Off");
-    }
-
-    char domoticzJson[90];
-    sprintf(domoticzJson, "{\"command\":\"switchlight\",\"idx\":%d,\"switchcmd\":\"%s\"}", domoticzIdx_PIR, PIRStatus);
-    mqttclient.publish(DOMOTICZ_IN_TOPIC, domoticzJson);
-  }
-}
-
-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++;
-
-
-}
-

+ 50 - 0
src/WiFiThermostat/html.ino

@@ -0,0 +1,50 @@
+static const char html_head_part1[] PROGMEM = R"=====(<html><head>
+<meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1,user-scalable=no'/>
+<link rel='stylesheet' href='style.css'>
+<title>)=====";
+
+static const char html_head_part2[] PROGMEM = R"=====(</title>)=====";
+
+static const char html_stylesheet[] PROGMEM = R"=====(
+body {  text-align:center;  font-family:verdana,sans-serif;  background:#ffffff;  }div,fieldset,input,select {  padding:3px;  font-size:1em;  }div#main {  text-align:left;  display:inline-block;  min-width:340px;}div#head {  text-align:left;  font-weight:bold;  font-size:1em;  }div.config {  font-weight:bold;  font-size:0.9em;  }hr {  width: 100%;   border: 1px solid black;  }fieldset {  background:#ffffff;  }p {  margin:0.5em 0;  }input {   width:100%;  box-sizing:border-box;  -webkit-box-sizing:border-box;  -moz-box-sizing:border-box;  background:#ffffff;  color:#000000;  }input[type=checkbox],input[type=radio] {   width:1em;  margin-right:6px;  vertical-align:-1px;  }input[type=range] {  width:99%;  }select {  width:100%;  background:#ffffff;  color:#000000;  }textarea {  resize:none;  width:98%;  height:318px;  padding:5px;  overflow:auto;  background:#ffffff;  color:#000000;}td {  padding:2px;  }button {  border:0;  border-radius:0.3rem;  color:#ffffff;  line-height:2.4rem;  font-size:1.2rem;  width:100%;  -webkit-transition-duration:0.4s;  transition-duration:0.4s;  cursor:pointer;  background:#1fa3ec;  }button:hover {  background:#0e70a4;  }.bred {  background:#d43535;  }.bred:hover {  background:#931f1f;  }.bgrn {  background:#47c266;  }.bgrn:hover {  background:#5aaf6f;}.bgrey {  background:#909090;  }.bgrey:hover {  background:#606060;}a {  color:#1fa3ec;  text-decoration:none;  }.p {  float:left;  text-align:left;  font-weight:normal;  }.q {   float:right;  text-align:right;  }.r {  border-radius:0.3em;  padding:2px;  margin:6px 2px;  }
+)=====";
+
+static const char html_head_end[] PROGMEM = R"=====(</head>)====="; // html_head_part3
+
+static const char html_bodytag_jsinit[] PROGMEM = R"=====(
+<body onload='init()'>)=====";
+
+static const char html_bodytag[] PROGMEM = R"=====(
+<body>)=====";
+
+static const char html_body_pagehead_part1[] PROGMEM = R"=====(
+<div id='main'>
+<div id='head'>)====="; // html_body_pagehead_part1
+
+static const char html_body_pagehead_part2[] PROGMEM = R"=====(
+</div><hr>
+<div></div>)====="; // html_body_pagehead_part2
+
+static const char html_confsaved_body[] PROGMEM = R"=====(
+<script>setTimeout(function(){window.location.href = '.';}, 7000);</script>
+<div>Configuration saved. Restarting...</div>
+<p><form action='.' method='get'><button class='bgrey'>Main Menu</button></form></p>
+)====="; // html_confsaved_body
+
+static const char html_restarting_body[] PROGMEM = R"=====(
+<script>setTimeout(function(){window.location.href = '.';}, 7000);</script>
+<div>Restarting...</div>
+<p><form action='.' method='get'><button class='bgrey'>Main Menu</button></form></p>
+)====="; // html_restarting_body
+
+static const char html_clearconf_body[] PROGMEM = R"=====(
+<script>setTimeout(function(){window.location.href = '.';}, 7000);</script>
+<div>Configuration cleared. Restarting...</div>
+<p><form action='.' method='get'><button class='bgrey'>Main Menu</button></form></p>
+)====="; // html_clearconf_body
+
+static const char html_footer1[] PROGMEM = R"=====(
+<div style='text-align:right;font-size:0.7em;color:#AAA;'><hr/>)=====";
+static const char html_footer2[] PROGMEM = R"=====(</div>
+</div></body></html>
+)=====";

+ 18 - 0
src/WiFiThermostat/html_conf.ino

@@ -0,0 +1,18 @@
+static const char html_conf_body[] PROGMEM = R"=====(
+<p><b>Configuration</b></p>
+<table style='width:100%'>
+<tr><td><form action='wifi.htm' method='get'><button>WiFi</button></form></td></tr>
+<tr><td><form action='confdev' method='get'><button>Device</button></form></td></tr>
+<tr><td><form action='confbas' method='get'><button>Thermostat Basic</button></form></td></tr>
+<tr><td><form action='confadv' method='get'><button>Thermostat Advanced</button></form></td></tr>
+<tr><td><form action='confadd' method='get'><button>Additional Functions</button></form></td></tr>
+<tr><td><form action='confweb' method='get'><button>Web</button></form></td></tr>
+<tr><td><form action='confmqtt' method='get'><button>MQTT</button></form></td></tr>
+<tr><td><form action='update' method='get'><button>OTA Firmware Update</button></form></td></tr>
+</table>
+<div></div>
+<table style='width:100%'><tr>
+<td style='width:50%'><form action='.' method='get'><button class='bgrey'>Main Menu</button></form></td>
+<td style='width:50%'><form action='.' method='get' onsubmit='return confirm("Confirm Restart");'><button name='rst' class='bred'>Restart</button></form></td>
+</tr></table>
+)====="; // html_conf_body

+ 93 - 0
src/WiFiThermostat/html_confadd.ino

@@ -0,0 +1,93 @@
+static const char html_confadd_script[] PROGMEM = R"=====(
+<script>
+  function g(i) { return document.getElementById(i) };
+  function sp(i){g(i).type=(g(i).type==='text'?'password':'text');}
+  var xhttp, reqTime, reqFin;
+  function setCbx(el, da) {
+    if(da == '1') {
+      el.checked = true;
+      el.style.visibility = 'visible';
+    }
+    else {
+      el.checked = false;
+      el.style.visibility = 'visible';
+    }
+  }
+  function updCbxVal(el) {
+    if (el.checked) el.value = '1';
+    else {
+      el.checked = true;
+    el.value = '0';
+      el.style.visibility = 'hidden';
+    }
+  }
+  
+  function transmit(f) {
+    if (!xhttp) {   
+    reqTime = 0;
+    reqFin = false;
+    xhttp = new XMLHttpRequest();
+    xhttp.timeout = 1000;
+    xhttp.overrideMimeType('application/json');
+    xhttp.open('POST', 'confdadd');
+    xhttp.send(f ? (new FormData(f)) : '');
+    xhttp.onreadystatechange = function () {
+      if (xhttp.readyState === XMLHttpRequest.DONE && xhttp.status === 200) {
+        var data = JSON.parse(xhttp.responseText);
+        g('PIRTop').value = data.PIRTop;
+        g('PIROnPld').value = data.PIROnPld;
+        g('PIROffPld').value = data.PIROffPld;
+        g('outTempTop').value = data.outTempTop;
+        g('outHumTop').value = data.outHumTop;
+        xhttp = null;
+        reqFin = true;
+       }
+       else {
+         if(!reqFin && reqTime > 10) {
+           xhttp = null;
+           reqFin = true;
+         }
+       }
+     }
+   }
+    return false;
+  }
+  //transmit();
+  function saveConf() {
+    g('frmConf').submit();
+  }
+  function init() {
+    transmit();
+  }
+  setInterval(function () { ++reqTime; }, 1000);
+</script>
+)====="; // html_confadd_script
+
+static const char html_confadd_body[] PROGMEM = R"=====(
+<b>Configuration - Additional Functions</b>
+<div class='config'>
+<form id='frmConf' action='setConfAdd' method='POST'>
+<br>
+<fieldset>
+<legend>PIR Sensor</legend>
+<p><b>MQTT Publish Topic</b><br><input type='text' name='PIRTop' id='PIRTop'/></p>
+<p><b>MQTT ON Payload</b><br><input type='text' name='PIROnPld' id='PIROnPld'/></p>
+<p><b>MQTT OFF Payload</b><br><input type='text' name='PIROffPld' id='PIROffPld'/></p>
+</fieldset>
+<div></div><br>
+<fieldset>
+<legend>Outside Temp/Hum via MQTT</legend>
+<p style='font-weight:normal;font-size:0.8em;'>Only used if not empty, can be subscribed to and valid data is sent. </p>
+<p><b>O-Temp In-Topic</b><br><input type='text' name='outTempTop' id='outTempTop'/></p>
+<p><b>O-Hum In-Topic</b><br><input type='text' name='outHumTop' id='outHumTop'/><br>
+</fieldset>
+
+<br>
+</form>
+<div></div>
+<table style='width:100%'>
+<td style='width:50%'><button onclick='location="conf";' class='bgrey'>Cancel</button></td>
+<td style='width:50%'><button onclick='return saveConf()' class='bred'>Save</button></td>
+</tr></table>
+</div>
+)====="; // html_confadd_body

+ 128 - 0
src/WiFiThermostat/html_confadv.ino

@@ -0,0 +1,128 @@
+static const char html_confadv_script[] PROGMEM = R"=====(
+<script>
+  function g(i) { return document.getElementById(i) };
+  function sp(i){g(i).type=(g(i).type==='text'?'password':'text');}
+  var xhttp, reqTime, reqFin;
+  function setCbx(el, da) {
+    if(da == '1') {
+      el.checked = true;
+      el.style.visibility = 'visible';
+    }
+    else {
+      el.checked = false;
+      el.style.visibility = 'visible';
+    }
+  }
+  function updCbxVal(el) {
+    if (el.checked) el.value = '1';
+    else {
+      el.checked = true;
+    el.value = '0';
+      el.style.visibility = 'hidden';
+    }
+  }
+  
+  function transmit(f) {
+    if (!xhttp) {   
+    reqTime = 0;
+    reqFin = false;
+    xhttp = new XMLHttpRequest();
+    xhttp.timeout = 1000;
+    xhttp.overrideMimeType('application/json');
+    xhttp.open('POST', 'confdadv');
+    xhttp.send(f ? (new FormData(f)) : '');
+    xhttp.onreadystatechange = function () {
+      if (xhttp.readyState === XMLHttpRequest.DONE && xhttp.status === 200) {
+        var data = JSON.parse(xhttp.responseText);
+        g('tempDec').value = data.tempDec;
+        g('hyst').value = data.hyst;
+        g('minOffTime').value = data.minOffTime;
+        g('tempCorr').value = data.tempCorr;
+        g('humCorr').value = data.humCorr;
+        g('offMsg').value = data.offMsg;
+        g('itemplab').value = data.itemplab;
+        g('otemplab').value = data.otemplab;
+        g('modename1').value = data.modename1;
+        g('modename0').value = data.modename0;
+        g('psetname0').value = data.psetname0;
+        g('psetname1').value = data.psetname1;
+        g('psetname2').value = data.psetname2;
+        xhttp = null;
+        reqFin = true;
+       }
+       else {
+         if(!reqFin && reqTime > 10) {
+           xhttp = null;
+           reqFin = true;
+         }
+       }
+     }
+   }
+    return false;
+  }
+  //transmit();
+  function saveConf() {
+    g('frmConf').submit();
+  }
+  function init() {
+    transmit();
+  }
+  setInterval(function () { ++reqTime; }, 1000);
+</script>
+)====="; // html_confadv_script
+
+static const char html_confadv_body[] PROGMEM = R"=====(
+<b>Configuration - Thermostat Advanced</b>
+<div class='config'>
+<form id='frmConf' action='setConfAdv' method='POST'>
+<br>
+<fieldset>
+<legend>Thermostat - Advanced</legend>
+<p><b>Hysteresis [°C]</b><br><input type='text' name='hyst' id='hyst'></p><br>
+
+<p><b>Min. Heating Off-Time [s]</b><br><input type='number' name='minOffTime' id='minOffTime'></p>
+<p style='font-weight:normal;font-size:0.8em;'>Heating stays off for at least [x] seconds before it <br>can start again.</p><br>
+
+<p><b>Measured Temp Correction [°C] *</b><br><input type='text' name='tempCorr' id='tempCorr'></p>
+<p><b>Measured Hum Correction [%] *</b><br><input type='text' name='humCorr' id='humCorr'></p>
+<p style='font-weight:normal;font-size:0.8em;'>* added to the measured values. <br>Can be negative and/or fractions. <br>Use if measurements deviate from a calibrated sensor.</p><br>
+
+<p><b>Set Temp. Decrease Value **</b><br><input type='text' name='tempDec' id='tempDec'></p>
+<p style='font-weight:normal;font-size:0.8em;'>** the actual set temperatur is decreased by that value.<br>Can be a fraction. Can be used to prevent over-heating <br>when the thermostat is badly placed.<br></p>
+</fieldset>
+<div></div><br>
+<fieldset>
+<legend>Names and Labels</legend>
+<p style='font-weight:normal;font-size:0.8em;'>Used in some MQTT status/commands, <br>and also for the LCD and Web interface.<br></p><br>
+<p><b>Off-Message</b><br><input type='text' name='offMsg' id='offMsg'></p>
+<p style='font-weight:normal;font-size:0.8em;'>Shown on LCD in OFF mode. <br><b>max. 13 chars!</b></p>
+<br>
+<p>Labels</p>
+<p style='font-weight:normal;font-size:0.8em;'>Used on LCD to identify the data displayed. <br><b>MUST be 1 char</b> or empty for default</p>
+<p><b>Inside</b><br><input type='text' name='itemplab' id='itemplab'/></p>
+<p><b>Outside</b><br><input type='text' name='otemplab' id='otemplab'/></p>
+<br>
+<p>Mode</p>
+<p style='font-weight:normal;font-size:0.8em;'>Used in web interface and for Home Assistant <br>MQTT Climate component support. <br>Should be 'heat' and 'off' normally. <br><b>Case sensitive! Lower case recommended.</b><br>Web interface shows all in capitals.</p>
+<p><b>On</b><br><input type='text' name='modename1' id='modename1'></p>
+<p><b>Off</b><br><input type='text' name='modename0' id='modename0'></p>
+<br>
+<p>Preset Names</p>
+<p style='font-weight:normal;font-size:0.8em;'>Used in web interface and for Home Assistant <br>MQTT Climate component support.<br>
+Also shown on LCD at preset change, <br>therefore may not exceed <b>13 chars!</b><br><b>Case sensitive!</b> Type exactly as in HA!</p>
+<p><b>Normal *</b><br><input type='text' name='psetname0' id='psetname0'></p>
+<p style='font-weight:normal;font-size:0.8em;'>* always sends/receives "<i>none</i>" as this is hardcoded in <br>Home Assistant MQTT Climate component</p>
+<p><b>Reduction 1 **</b><br><input type='text' name='psetname1' id='psetname1'></p>
+<p><b>Reduction 2 **</b><br><input type='text' name='psetname2' id='psetname2'></p>
+<p style='font-weight:normal;font-size:0.8em;'>** sent/received as defined</p>
+</fieldset>
+
+<br>
+</form>
+<div></div>
+<table style='width:100%'>
+<td style='width:50%'><button onclick='location="conf";' class='bgrey'>Cancel</button></td>
+<td style='width:50%'><button onclick='return saveConf()' class='bred'>Save</button></td>
+</tr></table>
+</div>
+)====="; // html_confadv_body

+ 115 - 0
src/WiFiThermostat/html_confbas.ino

@@ -0,0 +1,115 @@
+static const char html_confbas_script[] PROGMEM = R"=====(
+<script>
+  function g(i) { return document.getElementById(i) };
+  function sp(i){g(i).type=(g(i).type==='text'?'password':'text');}
+  var xhttp, reqTime, reqFin;
+  function setCbx(el, da) {
+    if(da == '1') {
+      el.checked = true;
+      el.style.visibility = 'visible';
+    }
+    else {
+      el.checked = false;
+      el.style.visibility = 'visible';
+    }
+  }
+  function updCbxVal(el) {
+    if (el.checked) el.value = '1';
+    else {
+      el.checked = true;
+    el.value = '0';
+      el.style.visibility = 'hidden';
+    }
+  }
+  
+  function transmit(f) {
+    if (!xhttp) {   
+    reqTime = 0;
+    reqFin = false;
+  updCbxVal(g('autoSaveTemp'));
+    updCbxVal(g('autoSaveMode'));
+    updCbxVal(g('PIRenDisp'));
+    updCbxVal(g('togTHdisp'));
+    xhttp = new XMLHttpRequest();
+    xhttp.timeout = 1000;
+    xhttp.overrideMimeType('application/json');
+    xhttp.open('POST', 'confdbas');
+    xhttp.send(f ? (new FormData(f)) : '');
+    xhttp.onreadystatechange = function () {
+      if (xhttp.readyState === XMLHttpRequest.DONE && xhttp.status === 200) {
+        var data = JSON.parse(xhttp.responseText);
+        g('tempMin').value = data.tempMin;
+        g('tempMax').value = data.tempMax;
+        g('measInt').value = data.measInt;
+        g('dispInt').value = data.dispInt;
+        g('dispTout').value = data.dispTout;
+    setCbx(g('autoSaveTemp'), data.autoSaveTemp);
+    setCbx(g('autoSaveMode'), data.autoSaveMode);
+    setCbx(g('PIRenDisp'), data.PIRenDisp);
+    setCbx(g('togTHdisp'), data.togTHdisp);
+        xhttp = null;
+        reqFin = true;
+       }
+       else {
+         if(!reqFin && reqTime > 10) {
+           xhttp = null;
+           reqFin = true;
+         }
+       }
+     }
+   }
+    return false;
+  }
+  //transmit();
+  function saveConf() {
+  updCbxVal(g('autoSaveTemp'));
+    updCbxVal(g('autoSaveMode'));
+    updCbxVal(g('PIRenDisp'));
+    updCbxVal(g('togTHdisp'));
+    g('frmConf').submit();
+  }
+  function init() {
+    transmit();
+  }
+  setInterval(function () { ++reqTime; }, 1000);
+</script>
+)====="; // html_confbas_script
+
+static const char html_confbas_body[] PROGMEM = R"=====(
+<b>Configuration - Thermostat Basic</b>
+<div class='config'>
+<form id='frmConf' action='setConfBas' method='POST'>
+<br>
+<fieldset>
+<legend>Thermostat</legend>
+<p><b>Minimum Set Temperature [°C]</b><br><input type='text' name='tempMin' id='tempMin'></p>
+<p><b>Maximum Set Temperature [°C]</b><br><input type='text' name='tempMax' id='tempMax'></p>
+</fieldset>
+<div></div><br>
+<fieldset>
+<legend>Auto Save</legend>
+<p><b>Temperature *</b>&nbsp;<input type='checkbox' name='autoSaveTemp' id='autoSaveTemp'/></p>
+<p><b>Mode/Preset *</b>&nbsp;<input type='checkbox' name='autoSaveMode' id='autoSaveMode'/></p>
+</fieldset>
+<div></div><br>
+<fieldset>
+<legend>Intervals / Timeouts</legend>
+<p><b>Measurement Interval [s]</b><br><input type='number' name='measInt' id='measInt'></p>
+<p><b>Display Update Interval [s]</b><br><input type='number' name='dispInt' id='dispInt'></p>
+<p><b>Display Timeout [s]</b><br><input type='number' name='dispTout' id='dispTout'></p>
+</fieldset>
+<div></div><br>
+<fieldset>
+<legend>Misc</legend>
+<p><b>PIR enables LCD backlight</b>&nbsp;<input type='checkbox' name='PIRenDisp' id='PIRenDisp'/></p>
+<p><b>Toggling I/O-Temp/Hum on LCD</b>&nbsp;<input type='checkbox' name='togTHdisp' id='togTHdisp'/></p>
+</fieldset>
+<br>
+</form>
+<div></div>
+<table style='width:100%'>
+<td style='width:50%'><button onclick='location="conf";' class='bgrey'>Cancel</button></td>
+<td style='width:50%'><button onclick='return saveConf()' class='bred'>Save</button></td>
+</tr></table>
+</div>
+)====="; // html_confbas_body

+ 84 - 0
src/WiFiThermostat/html_confdev.ino

@@ -0,0 +1,84 @@
+static const char html_confdev_script[] PROGMEM = R"=====(
+<script>
+  function g(i) { return document.getElementById(i) };
+  function sp(i){g(i).type=(g(i).type==='text'?'password':'text');}
+  var xhttp, reqTime, reqFin;
+  function setCbx(el, da) {
+    if(da == '1') {
+      el.checked = true;
+      el.style.visibility = 'visible';
+    }
+    else {
+      el.checked = false;
+      el.style.visibility = 'visible';
+    }
+  }
+  function updCbxVal(el) {
+    if (el.checked) el.value = '1';
+    else {
+      el.checked = true;
+    el.value = '0';
+      el.style.visibility = 'hidden';
+    }
+  }
+  
+  function transmit(f) {
+    if (!xhttp) {   
+    reqTime = 0;
+    reqFin = false;
+    xhttp = new XMLHttpRequest();
+    xhttp.timeout = 1000;
+    xhttp.overrideMimeType('application/json');
+    xhttp.open('POST', 'confddev');
+    xhttp.send(f ? (new FormData(f)) : '');
+    xhttp.onreadystatechange = function () {
+      if (xhttp.readyState === XMLHttpRequest.DONE && xhttp.status === 200) {
+        var data = JSON.parse(xhttp.responseText);
+        g('devName').value = data.devName;
+        g('hostName').value = data.hostName;
+        g('wifiappw').value = data.wifiappw;
+        xhttp = null;
+        reqFin = true;
+       }
+       else {
+         if(!reqFin && reqTime > 10) {
+           xhttp = null;
+           reqFin = true;
+         }
+       }
+     }
+   }
+    return false;
+  }
+  //transmit();
+  function saveConf() {
+    g('frmConf').submit();
+  }
+  function init() {
+    transmit();
+    setCbx(g('wifiappwSet'), 0);
+  }
+  setInterval(function () { ++reqTime; }, 1000);
+</script>
+)====="; // html_confdev_script
+
+static const char html_confdev_body[] PROGMEM = R"=====(
+<b>Configuration - Device</b>
+<div class='config'>
+<form id='frmConf' action='setConfDev' method='POST'>
+<br>
+<fieldset>
+<legend>Device</legend>
+<p><b>Device Name *</b><br><input type='text' name='devName' id='devName'></p>
+<p><b>Hostname *</b><br><input type='text' name='hostName' id='hostName'></p>
+<p><b>WiFi AP Password *</b><input type='checkbox' id='wifiappwSet' name='wifiappwSet' onclick='sp("wifiappw")'><br><input type='password' name='wifiappw' id='wifiappw'></p>
+</fieldset>
+<br>
+</form>
+<div></div>
+<table style='width:100%'>
+<td style='width:50%'><button onclick='location="conf";' class='bgrey'>Cancel</button></td>
+<td style='width:50%'><button onclick='return saveConf()' class='bred'>Save</button></td>
+</tr></table>
+</div>
+)====="; // html_confdev_body

+ 135 - 0
src/WiFiThermostat/html_confmqtt.ino

@@ -0,0 +1,135 @@
+static const char html_confmqtt_script[] PROGMEM = R"=====(
+<script>
+  function g(i) { return document.getElementById(i) };
+  function sp(i){g(i).type=(g(i).type==='text'?'password':'text');}
+  var xhttp, reqTime, reqFin;
+  function setCbx(el, da) {
+    if(da == '1') {
+      el.checked = true;
+      el.style.visibility = 'visible';
+    }
+    else {
+      el.checked = false;
+      el.style.visibility = 'visible';
+    }
+  }
+  function updCbxVal(el) {
+    if (el.checked) el.value = '1';
+    else {
+      el.checked = true;
+    el.value = '0';
+      el.style.visibility = 'hidden';
+    }
+  }
+  
+  function transmit(f) {
+    if (!xhttp) {   
+    reqTime = 0;
+    reqFin = false;
+    updCbxVal(g('mqttEnable'));
+    updCbxVal(g('outRet'));
+    updCbxVal(g('willRet'));
+    updCbxVal(g('hbEnable'));
+    xhttp = new XMLHttpRequest();
+    xhttp.timeout = 1000;
+    xhttp.overrideMimeType('application/json');
+    xhttp.open('POST', 'confdmqtt');
+    xhttp.send(f ? (new FormData(f)) : '');
+    xhttp.onreadystatechange = function () {
+      if (xhttp.readyState === XMLHttpRequest.DONE && xhttp.status === 200) {
+        var data = JSON.parse(xhttp.responseText);
+        setCbx(g('mqttEnable'), data.mqttEnable);
+        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;
+        setCbx(g('outRet'), data.outRet);
+        setCbx(g('willRet'), data.willRet);
+        g('willMsg').value = data.willMsg;
+        g('connMsg').value = data.connMsg;
+        setCbx(g('hbEnable'), data.hbEnable);
+        g('hbReconn').value = data.hbReconn;
+        g('hbReboot').value = data.hbReboot;
+        xhttp = null;
+        reqFin = true;
+       }
+       else {
+         if(!reqFin && reqTime > 10) {
+           xhttp = null;
+           reqFin = true;
+         }
+       }
+     }
+   }
+    return false;
+  }
+  //transmit();
+  function saveConf() {
+    updCbxVal(g('mqttEnable'));
+    updCbxVal(g('outRet'));
+    updCbxVal(g('willRet'));
+    updCbxVal(g('hbEnable'));
+    g('frmConf').submit();
+  }
+  function init() {
+    transmit();
+    setCbx(g('mqttPassSet'), 0);
+  }
+  setInterval(function () { ++reqTime; }, 1000);
+</script>
+)====="; // html_confmqtt_script
+
+static const char html_confmqtt_body[] PROGMEM = R"=====(
+<b>Configuration - MQTT</b>
+<div class='config'>
+<form id='frmConf' action='setConfMqtt' method='POST'>
+<br>
+<p><b>Enable MQTT *</b>&nbsp;<input type='checkbox' name='mqttEnable' id='mqttEnable'></p>
+<fieldset>
+<legend>Broker Connection</legend>
+<p><b>Hostname *</b><br><input type='text' name='mqttHost' id='mqttHost'></p>
+<p><b>Port *</b><br><input type='number' name='mqttPort' id='mqttPort'></p>
+<p><b>User *</b><br><input type='text' name='mqttUser' id='mqttUser'></p>
+<p><b>Password *</b><input type='checkbox' id='mqttPassSet' name='mqttPassSet' onclick='sp("mqttPass")'><br><input type='password' name='mqttPass' id='mqttPass'></p>
+</fieldset>
+<br>
+<fieldset>
+<legend>Last Will</legend>
+<p><b>Topic *</b><br><input type='text' name='willTop' id='willTop'></p>
+<p><b>Qos *</b><br><select name='willQos' id='willQos'><option>0</option><option>1</option><option selected='selected'>2</option></select></p>
+<p><b>Retain-Flag *</b>&nbsp;<input type='checkbox' name='willRet' id='willRet'></p>
+<p><b>Last Will Message *</b><br><input type='text' name='willMsg' id='willMsg'></p>
+<p><b>Connect Message *</b><br><input type='text' name='connMsg' id='connMsg'></p>
+</fieldset>
+<br>
+<fieldset>
+<legend>Base Topics</legend>
+<p><b>Base In-Topic *</b><br><input type='text' name='inTop' id='inTop'></p>
+<p style='font-weight:normal;font-size:0.8em;'>* Sub-Topics like cmd... are added automatically.</p>
+<div></div>
+<p><b>Out/State-Topic **</b><br><input type='text' name='outTop' id='outTop'></p>
+<p style='font-weight:normal;font-size:0.8em;'>** default output topic. Sub-Topics are added automatically when needed.</p>
+<p><b>Retain-Flag *</b>&nbsp;<input type='checkbox' name='outRet' id='outRet'></p>
+</fieldset>
+<br>
+<fieldset>
+<legend>Heartbeat</legend>
+<p><b>Enable Heartbeat *</b>&nbsp;<input type='checkbox' name='hbEnable' id='hbEnable'></p>
+<div></div><div></div>
+<p style='font-weight:normal;font-size:0.8em;'>on missing Heartbeat:</p>
+<p><b>Reconnect after [min]</b><br><input type='text' name='hbReconn' id='hbReconn'></p>
+<p><b>Reboot after [min]</b><br><input type='text' name='hbReboot' id='hbReboot'></p>
+</fieldset>
+<div></div>
+</form>
+<div></div>
+<table style='width:100%'>
+<td style='width:50%'><button onclick='location="conf";' class='bgrey'>Cancel</button></td>
+<td style='width:50%'><button onclick='return saveConf()' class='bred'>Save</button></td>
+</tr></table>
+</div>
+)====="; // html_confmqtt_body

+ 115 - 0
src/WiFiThermostat/html_confweb.ino

@@ -0,0 +1,115 @@
+static const char html_confweb_script[] PROGMEM = R"=====(
+<script>
+  function g(i) { return document.getElementById(i) };
+  function sp(i){g(i).type=(g(i).type==='text'?'password':'text');}
+  var xhttp, reqTime, reqFin;
+  function setCbx(el, da) {
+    if(da == '1') {
+      el.checked = true;
+      el.style.visibility = 'visible';
+    }
+    else {
+      el.checked = false;
+      el.style.visibility = 'visible';
+    }
+  }
+  function updCbxVal(el) {
+    if (el.checked) el.value = '1';
+    else {
+      el.checked = true;
+    el.value = '0';
+      el.style.visibility = 'hidden';
+    }
+  }
+  
+  function transmit(f) {
+    if (!xhttp) {   
+    reqTime = 0;
+    reqFin = false;
+    updCbxVal(g('httpAuth'));
+    xhttp = new XMLHttpRequest();
+    xhttp.timeout = 1000;
+    xhttp.overrideMimeType('application/json');
+    xhttp.open('POST', 'confdweb');
+    xhttp.send(f ? (new FormData(f)) : '');
+    xhttp.onreadystatechange = function () {
+      if (xhttp.readyState === XMLHttpRequest.DONE && xhttp.status === 200) {
+        var data = JSON.parse(xhttp.responseText);
+        g('apiToken').value = data.apiToken;
+        g('httpUA').value = data.httpUA;
+        setCbx(g('httpAuth'), data.httpAuth);
+        g('httpU1').value = data.httpU1;
+        g('httpP1').value = data.httpP1;
+        g('httpU2').value = data.httpU2;
+        g('httpP2').value = data.httpP2;
+        xhttp = null;
+        reqFin = true;
+       }
+       else {
+         if(!reqFin && reqTime > 10) {
+           xhttp = null;
+           reqFin = true;
+         }
+       }
+     }
+   }
+    return false;
+  }
+  //transmit();
+  function saveConf() {
+    updCbxVal(g('httpAuth'));
+    if(g('httpPA').value != g('httpPAC').value) {
+      alert("Admin password verification failed!");
+    }
+    else g('frmConf').submit();
+  }
+  function init() {
+    transmit();
+    setCbx(g('httpPASet'), 0);
+    setCbx(g('httpP1Set'), 0);
+    setCbx(g('httpP2Set'), 0);
+  }
+  setInterval(function () { ++reqTime; }, 1000);
+</script>
+)====="; // html_confweb_script
+
+static const char html_confweb_body[] PROGMEM = R"=====(
+<p><b>Configuration - Web</b></p>
+<div class='config'>
+<form id='frmConf' action='setConfWeb' method='POST'>
+<fieldset>
+<legend>HTTP-API</legend>
+<p><b>API Token</b><br><input type='text' name='apiToken' id='apiToken'></p>
+</fieldset>
+<br>
+<fieldset>
+<legend>Admin</legend>
+<p><b>Username *</b><br><input type='text' name='httpUA' id='httpUA'></p>
+<p><b>Password *</b><input type='checkbox' id='httpPASet' name='httpPASet' onclick='sp("httpPA")'><br><input type='password' name='httpPA' id='httpPA'></p>
+<p><b>Confirm Password *</b><br><input type='password' name='httpPAC' id='httpPAC'></p>
+<p style='font-weight:normal;font-size:0.8em;'>Admin Password can only be set but is not displayed for security reasons.</p>
+</fieldset>
+<br>
+<fieldset>
+<legend>Users</legend>
+<p><b>Enable User-Authentication *</b>&nbsp;<input type='checkbox' name='httpAuth' id='httpAuth'></p>
+<p style='font-weight:normal;font-size:0.8em;'>If User-Auth is off and Admin-PW is set, <br>
+Web-Interface can only be accessed by Admin.<br>
+Enable User-Auth and set User 1 with emtpy username and password to <br>
+make usaccessible without Auth.</p>
+<div></div>
+<p><b>User 1 *</b><br><input type='text' name='httpU1' id='httpU1'></p>
+<p><b>User 1 Password *</b><input type='checkbox' id='httpP1Set' name='httpP1Set' onclick='sp("httpP1")'><br><input type='password' name='httpP1' id='httpP1'></p>
+<div></div>
+<p><b>User 2 *</b><br><input type='text' name='httpU2' id='httpU2'></p>
+<p><b>User 2 Password *</b><input type='checkbox' id='httpP2Set' name='httpP2Set' onclick='sp("httpP2")'><br><input type='password' name='httpP2' id='httpP2'></p>
+</fieldset>
+</form>
+<div></div>
+<table style='width:100%'>
+<td style='width:50%'><button onclick='location="conf";' class='bgrey'>Cancel</button></td>
+<td style='width:50%'><button onclick='return saveConf()' class='bred'>Save</button></td>
+</tr></table>
+<div></div>
+</div>
+)====="; // html_confweb_body

+ 130 - 0
src/WiFiThermostat/html_main.ino

@@ -0,0 +1,130 @@
+static const char html_main_script[] PROGMEM = R"=====(
+<script>
+  function g(i) { return document.getElementById(i) };
+  var xhttp, updateTime, reqTime, reqFin;
+  function sendBtn(btn, conf) {
+  var frmn='BtnFrm'+btn;
+  var form = g(frmn);
+  if(conf !== undefined) {
+  if(confirm(conf)) return transmit(form);
+    else return false;
+    }
+    else return transmit(form);
+  }
+  function transmit(f) {
+    if (!xhttp) { 
+      reqTime = 0;
+      reqFin = false;
+      xhttp = new XMLHttpRequest();
+      xhttp.timeout = 1000;
+      xhttp.overrideMimeType("application/json");
+      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.setTemp !== undefined) g('setTemp').innerHTML = data.setTemp.toFixed(1) + '&deg;';
+          if(data.currSetTemp !== undefined) g('currSetTemp').innerHTML = data.currSetTemp.toFixed(1) + '&deg;';
+          if(data.psetName !== undefined) g('pset').innerHTML = data.psetName.toUpperCase();
+          if(data.psetName0 !== undefined) g('btnPs0').innerHTML = data.psetName0;
+          if(data.psetName1 !== undefined) g('btnPs1').innerHTML = data.psetName1;
+          if(data.psetName2 !== undefined) g('btnPs2').innerHTML = data.psetName2;
+          
+      //if(data.temp !== undefined) g('temp').innerHTML = data.temp.toFixed(1);
+          //if(data.hum !== undefined) g('hum').innerHTML = data.hum;
+      
+      if(data.temp !== undefined && data.hum !== undefined) g('currTempHum').innerHTML = data.temp.toFixed(1) + '&deg;&nbsp;&nbsp;&nbsp;' + data.hum.toFixed(0) + '%';
+      else if(data.temp !== undefined) g('currTempHum').innerHTML = data.temp.toFixed(1) + '&deg;';
+      
+          if(data.modeName !== undefined) g('mode').innerHTML = data.modeName.toUpperCase();
+          if(data.mode == '1') { g('btnModeOn').setAttribute('class', ''); g('btnModeOff').setAttribute('class', 'bgrey');}
+          else { g('btnModeOn').setAttribute('class', 'bgrey'); g('btnModeOff').setAttribute('class', '');}
+          if(data.pset == '0') { g('btnPs0').setAttribute('class', ''); g('btnPs1').setAttribute('class', 'bgrey'); g('btnPs2').setAttribute('class', 'bgrey');}
+          else if(data.pset == '1') {g('btnPs0').setAttribute('class', 'bgrey'); g('btnPs1').setAttribute('class', ''); g('btnPs2').setAttribute('class', 'bgrey');}
+          else if(data.pset == '2') {g('btnPs0').setAttribute('class', 'bgrey'); g('btnPs1').setAttribute('class', 'bgrey'); g('btnPs2').setAttribute('class', ''); }
+          
+          if(data.heating == '1' && data.mode == '1') { g('heating').innerHTML = 'HEATING'; g('heating').style = 'color:red;'; }
+          else if(data.heating == '0' && data.mode == '1') { g('heating').innerHTML = 'IDLE'; g('heating').style = 'color:#1fa3ec;'; }
+          else if(data.mode == '0') { g('heating').innerHTML = 'OFF'; g('heating').style = 'color:black;'; }
+          
+          if(data.ssid !== undefined) g('ssid').innerHTML = data.ssid;
+          if(data.mqttstate !== undefined) { 
+            if(data.mqttstate == "CONNECTED") { g('mqttstate').innerHTML = data.mqttstate + ' to <i>' + data.mqtthost + '</i>'; }
+            else g('mqttstate').innerHTML = data.mqttstate;
+          }
+          if(data.uptime !== undefined) g('uptime').innerHTML = data.uptime;
+          if(data.mqttreconn !== undefined) g('mqttreconn').innerHTML = data.mqttreconn;
+          xhttp = null;
+          updateTime = 0;
+          reqFin = true;
+          }
+        else {
+          if(!reqFin && reqTime > 10) {
+            xhttp = null;
+            reqFin = true;
+          }
+        }
+      }
+    }
+    return false;
+  }
+  function init() {
+    transmit();
+  }
+  setInterval(transmit, 2500);
+</script>
+)====="; // html_main_script
+
+static const char html_main_body[] PROGMEM = R"=====(
+<p><b>Temperature</b></p>
+<div id='setTemp' style='text-align:center;font-weight:bold;font-size:40px'></div>
+<div></div>
+<table style='width:100%'><tr>
+<td style='width:50%'><form id='BtnFrmMinus'><input type='hidden' name='BtnMinus' value='1'><button onclick='return sendBtn("Minus")'>-</button></form></td>
+<td style='width:50%'><form id='BtnFrmPlus'><input type='hidden' name='BtnPlus' value='1'><button onclick='return sendBtn("Plus")'>+</button></form></td>
+</tr></table>
+<table style='width:100%'><tr>
+<td style='width:50%'>Current Temp/Hum </td><td><b><span id='currTempHum'></span></td>
+</tr></tr>
+<td>Actual set Temp </td><td><b><span id='currSetTemp'></span></b></td>
+</tr></tr>
+<td>Heating State </td><td><b><span id='heating'></span></b></td>
+</tr></table>
+<div></div>
+<div></div>
+<p><b>Preset</b></p>
+<div id='pset' style='text-align:center;font-weight:bold;font-size:20px'></div>
+<div></div>
+<form id='BtnFrmPset0'><input type='hidden' name='BtnPset0' value='1'><button id='btnPs0' onclick='return sendBtn("Pset0")'>NORMAL</button></form>
+<form id='BtnFrmPset1'><input type='hidden' name='BtnPset1' value='1'><button id='btnPs1' onclick='return sendBtn("Pset1")'>REDUCTION 1</button></form>
+<form id='BtnFrmPset2'><input type='hidden' name='BtnPset2' value='1'><button id='btnPs2' onclick='return sendBtn("Pset2")'>REDUCTION 2</button></form>
+<div></div>
+<div></div>
+<div></div>
+<form action='redTemps' method='GET'><button>Set Reduction Temps</button></form>
+<div></div>
+<p><b>Mode</b></p>
+<div id='mode' style='text-align:center;font-weight:bold;font-size:20px'></div>
+<div></div>
+<table style='width:100%'><tr>
+<td style='width:50%'><form id='BtnFrmOn'><input type='hidden' name='BtnOn' value='1'><button id='btnModeOn' onclick='return sendBtn("On")'>HEAT</button></form></td>
+<td style='width:50%'><form id='BtnFrmOff'><input type='hidden' name='BtnOff' value='1'><button id='btnModeOff' onclick='return sendBtn("Off")'>OFF</button></form></td>
+</tr></table>
+<hr>
+<div></div>
+<p><b>System</b></p>
+<p>WiFi connected to <i><span id='ssid'></span></i></p>
+<p>MQTT <span id='mqttstate'></span></p>
+<p>MQTT reconnects: <span id='mqttreconn'></span></p>
+<p>Uptime: <span id='uptime'></span></p>
+<table style='width:100%'><tr>
+<td style='width:50%'>)====="; // html_main_body2
+
+static const char html_main_body_adminonly[] PROGMEM = R"=====(
+<form action='conf' method='get'><button class='bgrey'>Config</button></form>
+)====="; // html_main_body_adminonly
+
+static const char html_main_body2[] PROGMEM = R"=====(</td>
+<td style='width:50%'><form action='/' method='get' onsubmit='return confirm("Confirm Restart");'><button name='restart' class='bred'>Restart</button></form></td>
+</tr></table>
+)====="; // html_main_body2

+ 71 - 0
src/WiFiThermostat/html_redTemps.ino

@@ -0,0 +1,71 @@
+static const char html_redTemps_script[] PROGMEM = R"=====(
+<script>
+  function g(i) { return document.getElementById(i) };
+  var xhttp, updateTime, reqTime, reqFin;
+  function sendBtn(btn, conf) {
+  var frmn='BtnFrm'+btn;
+  var form = g(frmn);
+  if(conf !== undefined) {
+  if(confirm(conf)) return transmit(form);
+    else return false;
+    }
+    else return transmit(form);
+  }
+  function transmit(f) {
+    if (!xhttp) { 
+      reqTime = 0;
+      reqFin = false;
+      xhttp = new XMLHttpRequest();
+      xhttp.timeout = 1000;
+      xhttp.overrideMimeType("application/json");
+      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.tempLow !== undefined) g('tempLow').innerHTML = data.tempLow.toFixed(1) + '&deg;';
+          if(data.tempLow2 !== undefined) g('tempLow2').innerHTML = data.tempLow2.toFixed(1) + '&deg;';
+          xhttp = null;
+          updateTime = 0;
+          reqFin = true;
+          }
+        else {
+          if(!reqFin && reqTime > 10) {
+            xhttp = null;
+            reqFin = true;
+          }
+        }
+      }
+    }
+    return false;
+  }
+  function init() {
+    transmit();
+  }
+  setInterval(transmit, 2500);
+</script>
+)====="; // html_redTemps_script
+
+static const char html_redTemps_body[] PROGMEM = R"=====(
+<p><b>Reduction 1 - Temperature</b></p>
+<div id='tempLow' style='text-align:center;font-weight:bold;font-size:40px'></div>
+<div></div>
+<table style='width:100%'><tr>
+<td style='width:50%'><form id='BtnFrmL1Minus'><input type='hidden' name='BtnL1Minus' value='1'><button onclick='return sendBtn("L1Minus")'>-</button></form></td>
+<td style='width:50%'><form id='BtnFrmL1Plus'><input type='hidden' name='BtnL1Plus' value='1'><button onclick='return sendBtn("L1Plus")'>+</button></form></td>
+</tr></table>
+
+<p><b>Reduction 2 - Temperature</b></p>
+<div id='tempLow2' style='text-align:center;font-weight:bold;font-size:40px'></div>
+<div></div>
+<table style='width:100%'><tr>
+<td style='width:50%'><form id='BtnFrmL2Minus'><input type='hidden' name='BtnL2Minus' value='1'><button onclick='return sendBtn("L2Minus")'>-</button></form></td>
+<td style='width:50%'><form id='BtnFrmL2Plus'><input type='hidden' name='BtnL2Plus' value='1'><button onclick='return sendBtn("L2Plus")'>+</button></form></td>
+</tr></table>
+
+<div></div>
+<table style='width:100%'><tr>
+<td style='width:100%'><form action='.' method='get'><button class='bgrey'>Main Menu</button></form></td>
+</tr></table>
+
+)====="; // html_redTemps_body

File diff suppressed because it is too large
+ 546 - 504
src/WiFiThermostat/httpServer.ino


+ 15 - 0
src/WiFiThermostat/miscFunctions.ino

@@ -0,0 +1,15 @@
+void updateUptime() {
+  sysUptime_mins++;
+  if(sysUptime_mins == 60) {
+    sysUptime_mins=0;
+    sysUptime_hours++;
+  }
+  if(sysUptime_hours == 24) {
+    sysUptime_hours=0;
+    sysUptime_days++;
+  }
+}
+void buildUptimeString() {
+  if(sysUptime_days > 0) sprintf(uptimeStr, "%dd %02d:%02d", sysUptime_days, sysUptime_hours, sysUptime_mins);
+  else sprintf(uptimeStr, "%02d:%02d", sysUptime_hours, sysUptime_mins);
+}

+ 123 - 248
src/WiFiThermostat/mqtt.ino

@@ -1,4 +1,3 @@
-
 #include <string.h>
 #include <ctype.h>
 char *strlwr(char *str) {
@@ -10,7 +9,6 @@ char *strlwr(char *str) {
   return str;
 }
 
-
 // MQTT callback
 void mqttCallback(char* topic, byte* payload, unsigned int length) {
   //  Serial.print("MQTT payload arrived [");
@@ -32,7 +30,7 @@ void mqttCallback(char* topic, byte* payload, unsigned int length) {
     for (int i = 0; i < len; i++) {
       cmdPayload[i] = (char)payload[i];
     }
-    //cmdPayload[len + 1] = '\0';
+
     cmdPayload[len] = '\0';
     //    Serial.print("cmdPayload:");
     //    Serial.println(cmdPayload);
@@ -50,7 +48,14 @@ void mqttCallback(char* topic, byte* payload, unsigned int length) {
       mqttclient.publish(tmp_topic_pub, tmp_payload_pub);
     }
 
-    cmdInQueue = true; // payload is processed in "commands"
+    if (strncmp(cmdPayload, "HEARTBEAT", 9) == 0) {
+      Serial.println("resetting HEARTBEAT");
+      mqttLastHeartbeat = millis();
+      mqttConnected = true;
+    }
+    else {
+      cmdInQueue = true; // payload is processed in "commands"
+    }
   }//if topic = mqtt_topic_in_cmd
 
   if (strcmp(topic, mqtt_topic_in_setTemp) == 0) { //if topic = mqtt_topic_in_setTemp
@@ -237,49 +242,9 @@ void mqttCallback(char* topic, byte* payload, unsigned int length) {
     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 ) {
-      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[len + 1] = '\0';
-      domoticzOutPayload[len] = '\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;
-  }
-}
-
 void mqttPrepareSubscribeTopics() {
   //char tmp_topic_out[50];
   sprintf(mqtt_topic_in_cmd, "%s/%s", mqtt_topic_in, "cmd");
@@ -299,50 +264,45 @@ boolean mqttReconnect() {
   if (serialdebug) Serial.print("connecting to MQTT broker with ");
 
   boolean connRes;
+  mqttReconnectAttempts++;
 
-  switch (mqttMode) {
-    case 1:
-      if (serialdebug) Serial.println("no user, password and no Last Will");
-      connRes = mqttclient.connect(mqttClientId.c_str());
-      break;
-    case 2:
-      if (serialdebug) Serial.println("user and password, no Last Will");
-      connRes = mqttclient.connect(mqttClientId.c_str(), mqtt_user, mqtt_pass);
-      break;
-    case 3:
-      if (serialdebug) 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:
-      if (serialdebug) Serial.println("Last Will, no user and password");
-      connRes = mqttclient.connect(mqttClientId.c_str(), mqtt_willTopic, mqtt_willQos, mqtt_willRetain, mqtt_willMsg);
-      break;
+  if (strlen(mqtt_user) > 0 && strlen(mqtt_willTopic) == 0) {
+    // user and password, no Last Will
+    if (serialdebug) Serial.println("user and password, no Last Will");
+    connRes = mqttclient.connect(mqttClientId.c_str(), mqtt_user, mqtt_pass);
   }
-
+  else if (strlen(mqtt_user) > 0 && strlen(mqtt_willTopic) > 0) {
+    // user, password and Last Will
+    if (serialdebug) 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);
+  }
+  else if (strlen(mqtt_user) == 0 && strlen(mqtt_willTopic) > 0) {
+    // Last Will but no user and password
+    if (serialdebug) Serial.println("Last Will, no user and password");
+    connRes = mqttclient.connect(mqttClientId.c_str(), mqtt_willTopic, mqtt_willQos, mqtt_willRetain, mqtt_willMsg);
+  }
+  else {
+    // no user, password and no Last Will
+    if (serialdebug) Serial.println("no user, password and no Last Will");
+    connRes = mqttclient.connect(mqttClientId.c_str());
+  }
+  
   if (serialdebug) {
     Serial.print("... attempt: ");
     Serial.print(mqttReconnectAttempts);
     Serial.println();
   }
 
-  if (connRes) {
+  if (connRes) { // if connection was successful
     if (serialdebug) {
       Serial.print("MQTT connected. Reconnects: ");
       Serial.println(mqttReconnects);
     }
-    displayShowMQTTConnected();
+
     mqttConnected = true;
     mqttReconnects++;
+    mqttOnConnect();
 
-    // Once connected, publish an announcement...
-    //    char outMsg[30];
-    //    sprintf(outMsg, "connected, %d reconnects", mqttReconnects);
-    //    mqttclient.publish(mqtt_topic_out, outMsg, mqtt_outRetain);
-
-    mqttclient.publish(mqtt_willTopic, mqtt_connMsg, mqtt_willRetain);
-    publishStatus();
-    //mqttclient.publish(mqtt_topic_out, "connected");
-    // ... and resubscribe
     if (serialdebug) Serial.println("Subscribed to:");
     if (strlen(mqtt_topic_in_cmd) > 0) {
       char mqtt_topic_in_subscribe[52];
@@ -352,21 +312,6 @@ boolean mqttReconnect() {
         mqttInTopicSubscribed = true;
       }
     }
-    //    if (strlen(mqtt_topic_in_setTemp) > 0) {
-    //      if (mqttclient.subscribe(mqtt_topic_in_setTemp)) {
-    //        if (serialdebug) Serial.println(mqtt_topic_in_setTemp);
-    //      }
-    //    }
-    //    if (strlen(mqtt_topic_in_setMode) > 0) {
-    //      if (mqttclient.subscribe(mqtt_topic_in_setMode)) {
-    //        if (serialdebug) Serial.println(mqtt_topic_in_setMode);
-    //      }
-    //    }
-    //    if (strlen(mqtt_topic_in_setPreset) > 0) {
-    //      if (mqttclient.subscribe(mqtt_topic_in_setPreset)) {
-    //        if (serialdebug) Serial.println(mqtt_topic_in_setPreset);
-    //      }
-    //    }
     if (strlen(outTemp_topic_in) > 0) {
       if (mqttclient.subscribe(outTemp_topic_in)) {
         if (serialdebug) Serial.println(outTemp_topic_in);
@@ -377,29 +322,73 @@ boolean mqttReconnect() {
         if (serialdebug) Serial.println(outHum_topic_in);
       }
     }
-    if (useDomoticz && strlen(domoticz_out_topic) > 0) {
-      if (mqttclient.subscribe(domoticz_out_topic)) {
-        if (serialdebug) Serial.println(domoticz_out_topic);
-      }
-    }
+    mqttReconnectAttempts = 0;
     return mqttclient.connected();
   }
   else {
+    int mqttConnRes = mqttclient.state();
     if (serialdebug) {
       Serial.print("MQTT connect FAILED, rc=");
-      Serial.println(mqttclient.state());
+      Serial.print(mqttConnRes);
+      Serial.print(" (");
+      switch(mqttConnRes) {
+        case -4:
+          Serial.print(F("MQTT_CONNECTION_TIMEOUT"));
+          break;
+        case -3:
+          Serial.print(F("MQTT_CONNECTION_LOST"));
+          break;
+        case -2:
+          Serial.print(F("MQTT_CONNECT_FAILED"));
+          break;
+        case -1:
+          Serial.print(F("MQTT_DISCONNECTED"));
+          break;
+        case 0:
+          Serial.print(F("MQTT_CONNECTED"));
+          break;
+        case 1:
+          Serial.print(F("MQTT_CONNECT_BAD_PROTOCOL"));
+          break;
+        case 2:
+          Serial.print(F("MQTT_CONNECT_BAD_CLIENT_ID"));
+          break;
+        case 3:
+          Serial.print(F("MQTT_CONNECT_UNAVAILABLE"));
+          break;
+        case 4:
+          Serial.print(F("MQTT_CONNECT_BAD_CREDENTIALS"));
+          break;
+        case 5:
+          Serial.print(F("MQTT_CONNECT_UNAUTHORIZED"));
+          break;
+      }
+      Serial.println(")");
+      
+    }
+
+    if (mqttReconnectAttempts >= 3 && (mqttConnRes == 4 || mqttConnRes == 5)) {
+      // MQTT credentials are invalid/rejected from the server - stop trying to reconnect until reboot
+      mqtt_tempDisabled_credentialError = true;
+      if(serialdebug) {
+        Serial.println(F("MQTT disabled until reboot due to credential error"));
+      }
     }
-    displayShowMQTTConnectionError();
+    mqttConnected = false;
+    mqttOnDisconnect();
   }
 } //mqttReconnect
 
 
 
 void mqttClientInit() {
-  mqttclient.setServer(mqtt_server, mqtt_port);
-  mqttclient.setCallback(mqttCallback);
-  mqttLastReconnectAttempt = 0;
-  mqttReconnectAttempts = 0;
+  if (mqtt_enable) {
+    mqttclient.setServer(mqtt_server, mqtt_port);
+    mqttclient.setCallback(mqttCallback);
+    mqttLastReconnectAttempt = 0;
+    mqttReconnectAttempts = 0;
+    mqtt_tempDisabled_credentialError = false;
+  }
 }
 
 int lastWifiStatus;
@@ -412,13 +401,13 @@ void mqttHandleConnection() {
     Serial.println(currWifiStatus);
   }
 
-  if ( currWifiStatus == WL_CONNECTED ) {
+  if ( mqtt_enable && !mqtt_tempDisabled_credentialError && currWifiStatus == WL_CONNECTED ) {
     // MQTT reconnect if not connected (nonblocking)
     boolean 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
+    if (mqttInTopicSubscribed && mqtt_heartbeat_maxage_reconnect > 0) { //  if mqtt_topic_in is subscribed
+      if ( (millis() - mqttLastHeartbeat) > mqtt_heartbeat_maxage_reconnect ) { // also reconnect if no HEARTBEAT was received for some time
         doReconnect = true;
         mqttConnected = false;
         mqttLastHeartbeat = millis();
@@ -430,20 +419,17 @@ void mqttHandleConnection() {
       unsigned int mqttReconnectAttemptDelay;
       if (mqttReconnectAttempts < 3) mqttReconnectAttemptDelay = 15000; // if this is the 1-3rd attempt, try again in 15s
       else if (mqttReconnectAttempts < 10) mqttReconnectAttemptDelay = 60000; // if more than 3 attempts failed, try again every min
-      else if (mqttReconnectAttempts < 20) mqttReconnectAttemptDelay = 300000; // if more than 10 attempts failed, try again every 5 min
-      else if (mqttReconnectAttempts >= 6) {
-        // if more than 6 attempts (= > 30 min) failed, restart the ESP
+      else mqttReconnectAttemptDelay = 300000; // if more than 10 attempts failed, try again every 5 min
+
+      if ( mqtt_heartbeat_maxage_reboot > 0 && (millis() - mqttLastHeartbeat) > mqtt_heartbeat_maxage_reboot) {
+        // if no heartbeat was received for set time, reboot the ESP
         delay(100);
         ESP.restart();
       }
 
       if ((millis() - mqttLastReconnectAttempt) > mqttReconnectAttemptDelay) {
         mqttLastReconnectAttempt = millis();
-        mqttReconnectAttempts++;
-        if (mqttReconnect()) { // Attempt to reconnect
-          // attempt successful - reset mqttReconnectAttempts
-          mqttReconnectAttempts = 0;
-        }
+        mqttReconnect();
       }
       mqttclient.loop();
     }
@@ -453,155 +439,44 @@ void mqttHandleConnection() {
   }
 }
 
-void publishStatus() {
+void mqttPublishStatus() {
   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);
-
-  mqttclient.publish(mqtt_willTopic, mqtt_connMsg, mqtt_willRetain);
-  yield();
-}
-
-void sendStatus(char* payload) {
-  char buf[101];
-  strlcpy(buf, payload, 101);
-  Serial.println(buf);
-  mqttclient.publish(mqtt_topic_out, buf, mqtt_outRetain);
-  yield();
-}
-
-void publishCurrentThermostatValues() {
-  char tmp_topic_out[50];
-
-  updateCurrentHeatingModeName();
-  updateCurrentPresetName();
-
-  char ch_setTemp[6];
-  char ch_currSetTemp[6];
-  dtostrf(setTemp, 1, 1, ch_setTemp );
-  dtostrf(currSetTemp, 1, 1, ch_currSetTemp );
-
-  Serial.print("heatingMode: '");
-  Serial.print(heatingMode);
-  Serial.println("'");
-  Serial.print("set temp: '");
-  Serial.print(ch_setTemp);
-  Serial.println("'");
-  Serial.print("current set temp: '");
-  Serial.print(ch_currSetTemp);
-  Serial.println("'");
-
-  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "setTemp");
-  mqttclient.publish(tmp_topic_out, ch_setTemp);
-  yield();
-
-  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "currSetTemp");
-  mqttclient.publish(tmp_topic_out, ch_currSetTemp);
-  yield();
-
-  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "mode");
-  char ch_heatingMode[3];
-  sprintf(ch_heatingMode, "%d", heatingMode);
-  mqttclient.publish(tmp_topic_out, ch_heatingMode);
-
-  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "modeName");
-  mqttclient.publish(tmp_topic_out, currentModeName);
-
-  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "preset");
-  char ch_preset[3];
-  sprintf(ch_preset, "%d", preset);
-  mqttclient.publish(tmp_topic_out, ch_preset);
-
-  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "presetName");
-  mqttclient.publish(tmp_topic_out, currentPresetName);
-
-  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "presetHA");
-  if(preset == 0) mqttclient.publish(tmp_topic_out, "none");
-  else mqttclient.publish(tmp_topic_out, currentPresetName);
-
-  yield();
-
-  char ch_turnHeatingOn[5];
-  if (turnHeatingOn) strcpy(ch_turnHeatingOn, "on");
-  else strcpy(ch_turnHeatingOn, "off");
-  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "heating");
-  mqttclient.publish(tmp_topic_out, ch_turnHeatingOn);
-  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();
+  if (mqtt_enable && mqttclient.state() == 0) {
+    sprintf(outMsg, "MQTT connected, reconnects: %d, uptime: %s, free_heap: %d", mqttReconnects - 1, uptimeStr, ESP.getFreeHeap());
+    mqttclient.publish(mqtt_topic_out, outMsg, mqtt_outRetain);
+  }
 }
 
-void publishCurrentSensorValues() {
-  if ( lastTempUpdate != 0 && (millis() - lastTempUpdate) < 120000 ) {
-    char tmp_topic_out[50];
-
-    char temp_chararr[6];
-    char hum_chararr[4];
-    dtostrf(currTemp, 1, 1, temp_chararr );
-    sprintf(hum_chararr, "%2i", currHum);
-
-    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);
-    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);
-
-    Serial.print("temp_raw: '");
-    Serial.print(temp_chararr);
-    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);
+void mqttSendLog(char* payload) {
+  if (mqtt_enable && mqttclient.state() == 0) {
+    char buf[101];
+    strlcpy(buf, payload, 101);
+    Serial.println(buf);
+    mqttclient.publish(mqtt_topic_out, buf, mqtt_outRetain);
     yield();
   }
 }
 
-void publishCurrentPIRValue() {
-  char tmp_topic_out[50];
-  char PIRStatus[4];
-
-  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "PIR");
+void mqttOnConnect() {
+  mqttPublishConnectMsg();
+  mqttPublishStatus();
+  displayShowMQTTConnected();
+}
 
-  if (PIRSensorOn) {
-    strcpy(PIRStatus, "on");
-  }
-  else {
-    strcpy(PIRStatus, "off");
-  }
-  mqttclient.publish(tmp_topic_out, PIRStatus);
+void mqttOnDisconnect() {
+  displayShowMQTTConnectionError();
+}
 
-  if (strlen(mqtt_topic_pir) > 0) {
-    mqttclient.publish(mqtt_topic_pir, PIRStatus);
+void mqttPublishConnectMsg() {
+  if (strlen(mqtt_willTopic) > 4) {
+    if (mqtt_enable && mqttclient.state() == 0) {
+      mqttclient.publish(mqtt_willTopic, mqtt_connMsg, mqtt_willRetain);
+    }
   }
 }
 
 void mqttPublishHeartbeat() {
-  mqttclient.publish(mqtt_topic_in_cmd, "HEARTBEAT"); // publishes to IN-topic. MQTT will force-reconnect if it does not get a HEARTBEAT for 2 min
+  if (mqtt_enable && mqttclient.state() == 0) {
+    mqttclient.publish(mqtt_topic_in_cmd, "HEARTBEAT"); // publishes to IN-topic. MQTT will force-reconnect if it does not get a HEARTBEAT for 2 min
+  }
 }

+ 134 - 0
src/WiFiThermostat/mqtt_out.ino

@@ -0,0 +1,134 @@
+void publishCurrentThermostatValues() {
+  char tmp_topic_out[50];
+
+  updateCurrentHeatingModeName();
+  updateCurrentPresetName();
+
+  char ch_setTemp[6];
+  char ch_currSetTemp[6];
+  dtostrf(setTemp, 1, 1, ch_setTemp );
+  dtostrf(currSetTemp, 1, 1, ch_currSetTemp );
+
+  Serial.print("heatingMode: '");
+  Serial.print(heatingMode);
+  Serial.println("'");
+  Serial.print("set temp: '");
+  Serial.print(ch_setTemp);
+  Serial.println("'");
+  Serial.print("current set temp: '");
+  Serial.print(ch_currSetTemp);
+  Serial.println("'");
+
+  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "setTemp");
+  mqttclient.publish(tmp_topic_out, ch_setTemp);
+  yield();
+
+  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "currSetTemp");
+  mqttclient.publish(tmp_topic_out, ch_currSetTemp);
+  yield();
+
+  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "mode");
+  char ch_heatingMode[3];
+  sprintf(ch_heatingMode, "%d", heatingMode);
+  mqttclient.publish(tmp_topic_out, ch_heatingMode);
+
+  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "modeName");
+  mqttclient.publish(tmp_topic_out, currentModeName);
+
+  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "preset");
+  char ch_preset[3];
+  sprintf(ch_preset, "%d", preset);
+  mqttclient.publish(tmp_topic_out, ch_preset);
+
+  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "presetName");
+  mqttclient.publish(tmp_topic_out, currentPresetName);
+
+  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "presetHA");
+  if (preset == 0) mqttclient.publish(tmp_topic_out, "none");
+  else mqttclient.publish(tmp_topic_out, currentPresetName);
+
+  yield();
+
+  char ch_turnHeatingOn[5];
+  if (turnHeatingOn) strcpy(ch_turnHeatingOn, "on");
+  else strcpy(ch_turnHeatingOn, "off");
+  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "heating");
+  mqttclient.publish(tmp_topic_out, ch_turnHeatingOn);
+  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() {
+  if ( lastTempUpdate != 0 && (millis() - lastTempUpdate) < 120000 ) {
+    char tmp_topic_out[50];
+
+    char temp_chararr[6];
+    char hum_chararr[4];
+    dtostrf(currTemp, 1, 1, temp_chararr );
+    sprintf(hum_chararr, "%2i", currHum);
+
+    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);
+    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);
+
+    Serial.print("temp_raw: '");
+    Serial.print(temp_chararr);
+    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];
+  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "PIR");
+
+  // PIR internal topic
+  if (PIRSensorOn) {
+    mqttclient.publish(tmp_topic_out, mqtt_payload_pir_on);
+  }
+  else {
+    mqttclient.publish(tmp_topic_out, mqtt_payload_pir_off);
+  }
+
+  // PIR additional topic
+  if (strlen(mqtt_topic_pir) >= 4) {
+    if (PIRSensorOn) {
+      mqttclient.publish(mqtt_topic_pir, mqtt_payload_pir_on);
+    }
+    else {
+      mqttclient.publish(mqtt_topic_pir, mqtt_payload_pir_off);
+    }
+  }
+}

+ 11 - 9
src/WiFiThermostat/scheduler.ino

@@ -12,7 +12,7 @@ void every100ms() {
     count100ms = 0;
     everySecond();
   }
-  checkSaveConfigTriggered();
+  //checkSaveConfigTriggered();
 }
 
 void everySecond() {
@@ -21,8 +21,7 @@ void everySecond() {
     countSeconds = 0;
     everyMinute();
   }
-
-  checkUseDomoticz();
+  
   handleDisplayTimeout();
   checkValuesChanged();
 
@@ -51,18 +50,21 @@ void everySecond() {
 }
 
 void everyMinute() {
-  mqttPublishHeartbeat();
-  publishStatus();
-  publishCurrentSensorValues();
-  publishCurrentThermostatValues();
-  updateDomoticzDevices();
+  updateUptime();
+  buildUptimeString();
+  if(mqtt_enable) {
+    mqttPublishConnectMsg();
+    mqttPublishStatus();
+    if(mqtt_enable_heartbeat) mqttPublishHeartbeat();
+    publishCurrentSensorValues();
+    publishCurrentThermostatValues();
+  }
 
 //  if(WifiInApMode) {
 //    if( (millis() - WifiApModeStartedAt) > WIFI_AP_MODE_TIMEOUT ) {
 //      
 //    }
 //  }
-
   //  Serial.print("WiFi Status: ");
   //  Serial.println(WiFi.status());
 }

+ 85 - 31
src/WiFiThermostat/thermostat.ino

@@ -12,33 +12,33 @@ void readDHTsensorHum() {
 }
 
 void measureTempHum() {
-  
-  for(int i=0; i < DHTreadRetries; i++) {
+
+  for (int i = 0; i < DHTreadRetries; i++) {
     readDHTsensorTemp();
-    if(!isnan(tmpTemp)) {
-      sendStatus("DHT reading Temp OK");
-      i=DHTreadRetries;
+    if (!isnan(tmpTemp)) {
+      mqttSendLog("DHT reading Temp OK");
+      i = DHTreadRetries;
     }
     //else {
-    //  sendStatus("DHT reading Temp failed - retrying..");
+    //  mqttSendLog("DHT reading Temp failed - retrying..");
     //}
   }
 
-  for(int i=0; i < DHTreadRetries; i++) {
+  for (int i = 0; i < DHTreadRetries; i++) {
     readDHTsensorHum();
-    if(!isnan(tmpHum)) {
-      sendStatus("DHT reading Hum OK");
-      i=DHTreadRetries;
+    if (!isnan(tmpHum)) {
+      mqttSendLog("DHT reading Hum OK");
+      i = DHTreadRetries;
     }
     //else {
-    //  sendStatus("DHT reading Hum failed - retrying..");
+    //  mqttSendLog("DHT reading Hum failed - retrying..");
     //}
   }
 
   // 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!");
+    mqttSendLog("Error: Failed to read from DHT sensor!");
   }
   else {
     tmpTemp = tmpTemp + tempCorrVal;
@@ -102,14 +102,14 @@ void thermostat() {
 
     char buf[101];
     sprintf(buf, "heating on since %d s", heatingOnTime);
-    sendStatus(buf);
+    mqttSendLog(buf);
   }
   else if (heatingMode > 0 && !turnHeatingOn) {
     heatingOffTime = (millis() - heatingLastOffMillis) / 1000;
 
     char buf[101];
     sprintf(buf, "heating off since %d s", heatingOffTime);
-    sendStatus(buf);
+    mqttSendLog(buf);
   }
 
 
@@ -136,12 +136,11 @@ void thermostat() {
 
       char buf[101];
       sprintf(buf, "switch heating OFF, on since %d s", heatingOnTime);
-      sendStatus(buf);
+      mqttSendLog(buf);
 
       //Serial.println("heating off");
       //mqttclient.publish(tmp_topic_out, "off");
       publishCurrentThermostatValues();
-      sendToDomoticz_Heating();
     }
     else if ( !turnHeatingOn && heatingMode > 0 && ( currTemp < (currSetTemp - setTempDecreaseVal - hysteresis) ) && ( heatingOffTime > heatingMinOffTime ) ) {
       turnHeatingOn = true;
@@ -151,12 +150,11 @@ void thermostat() {
 
       char buf[101];
       sprintf(buf, "switch heating ON, off since %d s", heatingOffTime);
-      sendStatus(buf);
+      mqttSendLog(buf);
 
       //Serial.println("heating on");
       //mqttclient.publish(tmp_topic_out, "on");
       publishCurrentThermostatValues();
-      sendToDomoticz_Heating();
     }
   }
   else {
@@ -165,13 +163,12 @@ void thermostat() {
       turnHeatingOn = false;
       heatingLastOffMillis = millis();
     }
-    
-    if ( lastTempUpdate != 0 ) sendStatus("switch heating OFF, temp reading not yet available");
-    else if ( (millis() - lastTempUpdate) > maxMeasurementAge ) sendStatus("switch heating OFF, last temp reading too old");
-    
+
+    if ( lastTempUpdate != 0 ) mqttSendLog("switch heating OFF, temp reading not yet available");
+    else if ( (millis() - lastTempUpdate) > maxMeasurementAge ) mqttSendLog("switch heating OFF, last temp reading too old");
+
     //mqttclient.publish(tmp_topic_out, "off");
     publishCurrentThermostatValues();
-    sendToDomoticz_Heating();
   }
 
 }
@@ -292,6 +289,26 @@ void setTempTo(float setTo) {
   }
 }
 
+void setTempLowStepUp() {
+  Serial.println("setTempLow +0.5");
+  if ( setTempLow <= (setTempLowMax - 0.5)) {
+    setTempLow += 0.5;
+    lastValueChange = millis();
+    setTempLowAlreadySaved = false;
+  }
+  updateDisplay();
+}
+
+void setTempLowStepDown() {
+  Serial.println("setTempLow -0.5");
+  if ( setTempLow >= (setTempLowMin + 0.5)) {
+    setTempLow -= 0.5;
+    lastValueChange = millis();
+    setTempLowAlreadySaved = false;
+  }
+  updateDisplay();
+}
+
 void setTempLowTo(float setTo) {
   boolean changes = false;
   if (setTo >= setTempLowMin && setTo <= setTempLowMax) {
@@ -307,11 +324,33 @@ void setTempLowTo(float setTo) {
     changes = true;
   }
   if (changes) {
+    lastValueChange = millis();
+    setTempLowAlreadySaved = false;
     updateDisplay();
     publishCurrentThermostatValues();
   }
 }
 
+void setTempLow2StepUp() {
+  Serial.println("setTempLow2 +0.5");
+  if ( setTempLow2 <= (setTempLowMax - 0.5)) {
+    setTempLow2 += 0.5;
+    lastValueChange = millis();
+    setTempLow2AlreadySaved = false;
+  }
+  updateDisplay();
+}
+
+void setTempLow2StepDown() {
+  Serial.println("setTempLow2 -0.5");
+  if ( setTempLow2 >= (setTempLowMin + 0.5)) {
+    setTempLow2 -= 0.5;
+    lastValueChange = millis();
+    setTempLow2AlreadySaved = false;
+  }
+  updateDisplay();
+}
+
 void setTempLow2To(float setTo) {
   boolean changes = false;
   if (setTo >= setTempLowMin && setTo <= setTempLowMax) {
@@ -327,6 +366,8 @@ void setTempLow2To(float setTo) {
     changes = true;
   }
   if (changes) {
+    lastValueChange = millis();
+    setTempLow2AlreadySaved = false;
     updateDisplay();
     publishCurrentThermostatValues();
   }
@@ -383,34 +424,47 @@ void setPresetTo(byte setTo) {
   }
 }
 
-void checkValuesChanged() { // called every second by everySecond() / misc.ino
-  if ( !setTempAlreadySaved || !heatingModeAlreadySaved || !presetAlreadySaved) {
+void checkValuesChanged() { // called every second by everySecond() / scheduler.ino
+  if ( !setTempAlreadySaved || !heatingModeAlreadySaved || !presetAlreadySaved || !setTempLowAlreadySaved || !setTempLow2AlreadySaved ) {
     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");
+          mqttSendLog("setTemp autosave done");
         }
         setTempAlreadySaved = true;
       }
+      if (!setTempLowAlreadySaved) {
+        lastUpdate_setTempLow = millis();
+        if (autoSaveSetTemp && setTempLow != setTempLowSaved) {
+          saveSetTempLow();
+          mqttSendLog("setTempLow autosave done");
+        }
+        setTempLowAlreadySaved = true;
+      }
+      if (!setTempLow2AlreadySaved) {
+        lastUpdate_setTempLow2 = millis();
+        if (autoSaveSetTemp && setTempLow2 != setTempLow2Saved) {
+          saveSetTempLow2();
+          mqttSendLog("setTempLow2 autosave done");
+        }
+        setTempLow2AlreadySaved = true;
+      }
       if (!heatingModeAlreadySaved) {
         lastUpdate_heatingMode = millis();
-        sendToDomoticz_heatingMode();
         if (autoSaveHeatingMode && heatingMode != heatingModeSaved) {
           saveHeatingMode();
-          sendStatus("heatingMode autosave done");
+          mqttSendLog("heatingMode autosave done");
         }
         heatingModeAlreadySaved = true;
       }
       if (!presetAlreadySaved) {
         lastUpdate_preset = millis();
-        //sendToDomoticz_heatingMode();
         preset = pendingPreset;
         if (autoSaveHeatingMode && preset != presetSaved) {
           savePreset();
-          sendStatus("preset autosave done");
+          mqttSendLog("preset autosave done");
         }
         presetAlreadySaved = true;
       }

Some files were not shown because too many files changed in this diff