WiFiThermostat.ino 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. // pre compiletime config
  2. //#define DEBUG_VERBOSE
  3. #define SPIFFS_DBG
  4. #define SPIFFS_USE_MAGIC
  5. #define FIRMWARE_NAME "WiFiThermostat"
  6. #define VERSION "0.1.3"
  7. // default values, can later be overridden via configuration
  8. #define DEVICE_NAME "WiFi-Thermostat-1"
  9. #define MQTT_SERVER "10.1.1.11"
  10. #define MQTT_PORT 1883
  11. #define MQTT_TOPIC_IN "Test/Thermostat/cmd"
  12. #define MQTT_TOPIC_OUT "Test/Thermostat/status"
  13. #define BUTTON_DEBOUNCE_TIME 120
  14. #define BUTTON_HOLD_TIME 750
  15. #define DOMOTICZ_IN_TOPIC "domoticz/in"
  16. #define DOMOTICZ_OUT_TOPIC "domoticz/out"
  17. #define OUTTEMP_TOPIC_IN "wetter/atemp"
  18. #define OUTHUM_TOPIC_IN "wetter/ahum"
  19. #define CLEARCONF_TOKEN "TUES"
  20. // pin assignments and I2C addresses
  21. #define PIN_DHTSENSOR 13
  22. #define PIN_RELAIS 15 //16
  23. #define PIN_BUTTON_PLUS 2
  24. #define PIN_BUTTON_MINUS 0
  25. #define PIN_BUTTON_MODE 14
  26. #define PIN_PIRSENSOR 12
  27. #define DHTTYPE DHT22 // DHT sensor type
  28. #define LCDADDR 0x27 // I2C address LCD
  29. #define LCDCOLS 16
  30. #define LCDLINES 2
  31. // default logic levels
  32. #define RELAISONSTATE HIGH
  33. #define BUTTONONSTATE LOW
  34. #include <Button.h>
  35. #include <ButtonEventCallback.h>
  36. #include <PushButton.h>
  37. #include <Bounce2.h>
  38. PushButton buttonPlus = PushButton(PIN_BUTTON_PLUS, ENABLE_INTERNAL_PULLUP);
  39. PushButton buttonMinus = PushButton(PIN_BUTTON_MINUS, ENABLE_INTERNAL_PULLUP);
  40. PushButton buttonMode = PushButton(PIN_BUTTON_MODE, ENABLE_INTERNAL_PULLUP);
  41. PushButton pirSensor = PushButton(PIN_PIRSENSOR, PRESSED_WHEN_HIGH);
  42. #include <PersWiFiManager.h>
  43. #include <ArduinoJson.h>
  44. #include <ESP8266WiFi.h>
  45. #include <WiFiClient.h>
  46. #include <ESP8266WebServer.h>
  47. #include <ESP8266mDNS.h>
  48. #include <ESP8266HTTPUpdateServer.h>
  49. #include <PubSubClient.h>
  50. #include <DNSServer.h>
  51. #include <FS.h>
  52. #include <Wire.h>
  53. #include <LiquidCrystal_I2C.h>
  54. #include <DHT.h>
  55. #ifndef MESSZ
  56. #define MESSZ 405 // Max number of characters in JSON message string (4 x DS18x20 sensors)
  57. #endif
  58. // Max message size calculated by PubSubClient is (MQTT_MAX_PACKET_SIZE < 5 + 2 + strlen(topic) + plength)
  59. #if (MQTT_MAX_PACKET_SIZE -TOPSZ -7) < MESSZ // If the max message size is too small, throw an error at compile time
  60. // See pubsubclient.c line 359
  61. #error "MQTT_MAX_PACKET_SIZE is too small in libraries/PubSubClient/src/PubSubClient.h, increase it to at least 512"
  62. #endif
  63. // config variables - do not change here!
  64. //conf
  65. char deviceName[31]; // device name - just for web interface
  66. char http_user[31];
  67. char http_pass[31];
  68. char mqtt_server[41];
  69. int mqtt_port = MQTT_PORT;
  70. char mqtt_user[31];
  71. char mqtt_pass[31];
  72. char mqtt_topic_in[51]; // MQTT in topic for commands
  73. char mqtt_topic_out[51]; // MQTT out base topic, will be extended by various value names
  74. boolean mqtt_outRetain = false; // send MQTT out with retain flag
  75. char mqtt_willTopic[51]; // MQTT Last Will topic
  76. int mqtt_willQos = 2; // MQTT Last Will topic QOS
  77. boolean mqtt_willRetain = false; // MQTT Last Will retain
  78. char mqtt_willMsg[31]; // MQTT Last Will payload
  79. char domoticz_out_topic[55]; // domoticz out topic to subscribe to (only applicable if domoticzIdx_Thermostat and/or domoticzIdx_ThermostatMode is set to >0)
  80. //conf2
  81. int domoticzIdx_Thermostat = 0;
  82. int domoticzIdx_ThermostatMode = 0;
  83. int domoticzIdx_TempHumSensor = 0;
  84. int domoticzIdx_Heating = 0;
  85. int domoticzIdx_PIR = 0;
  86. char outTemp_topic_in[51];
  87. char outHum_topic_in[51];
  88. boolean autoSaveSetTemp = true;
  89. boolean autoSaveHeatingMode = true;
  90. int heatingMinOffTime = 10; // minimal time the heating keeps turned off in s
  91. float setTempMin = 14.0; // minimal temperature that can be set
  92. float setTempMax = 29.0; // maximal temperature that can be set
  93. float setTempLow = 18.0; // set temperature in night/low mode
  94. float hysteresis = 0.5;
  95. float tempCorrVal = 0.0; // correction value for temperature sensor reading
  96. int humCorrVal = 0; // correction value for humidity sensor reading
  97. int measureInterval = 15; // interval for temp/hum measurement
  98. int displayInterval = 5; //
  99. int displayTimeout = 120;
  100. boolean PIR_enablesDisplay = false;
  101. //set values
  102. float setTemp = 21.5;
  103. byte heatingMode = 1; // 0 = off, 1 = normal/day, 2 = night/reduction
  104. float setTempSaved;
  105. byte heatingModeSaved; // 0 = off, 1 = normal/day, 2 = night/reduction
  106. // not changeable via configuration
  107. float setTempLowMin = 14.0;
  108. float setTempLowMax = 19.0;
  109. boolean debug = true;
  110. int debounceTime = BUTTON_DEBOUNCE_TIME;
  111. int buttonHoldTime = BUTTON_HOLD_TIME;
  112. // global variables
  113. float currTemp; // last reading from DHT sensor
  114. float currTemp_raw; // last reading from DHT sensor
  115. int currHum; // last reading from DHT sensor
  116. int currHum_raw; // last reading from DHT sensor
  117. bool turnHeatingOn = false; // true if heating is active (relais switched on)
  118. unsigned long heatingLastOnMillis; // last time heating was switched on
  119. unsigned long heatingLastOffMillis; // last time heating was switched off
  120. float outTemp; // outside temp (via MQTT if enabled and in-topic configured)
  121. int outHum; // outside temp (via MQTT if enabled and in-topic configured)
  122. long outTempHumLastUpdate; // last reading from out temp/hum source
  123. 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
  124. unsigned long lastMeasure = 0; // millis of last temp/hum measurement
  125. unsigned long lastDisplayUpdate = 0; // millis of last display update
  126. unsigned long lastDisplayToggle = 0; // millis of last display toggle
  127. unsigned long lastTempUpdate = 0; // last update time of DHT reading
  128. char msg[50]; // buffer MQTT in payload
  129. char topic[50]; // buffer MQTT in topic
  130. bool displayActive = false; // gets true when button is pressed. display light gets switched on until timeout. button actions are only performed while display is active
  131. bool PIRSensorOn = false;
  132. unsigned long heatingOnTime, heatingOffTime;
  133. boolean useDomoticz = false; // will be set to true in setup() if idx-values other than 0 are configured
  134. boolean domoticzOutParseData = false; // indicates that domoticz/out json data is buffered, will then be parsed in next loop() run
  135. boolean domoticzOutParserBusy = false; // indicates that domoticz/out json data is currently processed - no futher data will be accepted until finished
  136. char domoticzOutPayload[450]; // buffer for domoticz/out data
  137. int dismissUpdateFromDomoticzTimeout = 2500; // after a value was changed by data from domoticz/out, domoticz/out parsing for this device will be turned off for this time to prevent infinite loops
  138. 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
  139. 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
  140. bool lastUpdateFromDomoticz_setTemp = false;
  141. bool lastUpdateFromDomoticz_heatingMode = false;
  142. int domoticzUpdateInterval = 30; // interval in min to force update of domoticz devices
  143. char cmdPayload[101]; // buffer for commands
  144. boolean cmdInQueue = false; // command is queued and will be processed next loop() run
  145. bool saveConfigToFlash = false; // conf is saved in next loop() run
  146. bool saveConfig2ToFlash = false; // conf2 is saved in next loop() run
  147. unsigned int saveValuesTimeout = 5000;
  148. unsigned long lastValueChange; // is set to millis() whenever setTemp value and/or heatingMode value is changed. used for autoSave function with hardcoded 5s timeout
  149. bool setTempAlreadySaved = true; // only save if not yet done
  150. bool heatingModeAlreadySaved = true; // only save if not yet done
  151. byte mqttMode = 0;
  152. unsigned long mqttLastReconnectAttempt = 0;
  153. int mqttReconnectAttempts = 0;
  154. int mqttReconnects = 0;
  155. DHT dht(PIN_DHTSENSOR, DHTTYPE);
  156. LiquidCrystal_I2C lcd(LCDADDR, LCDCOLS, LCDLINES); // set the LCD address to 0x27 for a 16 chars and 2 line display
  157. WiFiClient espClient;
  158. void mqttCallback(char* topic, byte* payload, unsigned int length);
  159. PubSubClient mqttclient(espClient);
  160. ESP8266WebServer httpServer(80);
  161. DNSServer dnsServer;
  162. PersWiFiManager persWM(httpServer, dnsServer);
  163. ESP8266HTTPUpdateServer httpUpdater;
  164. void setup() {
  165. Serial.begin(115200);
  166. delay(500);
  167. Serial.println();
  168. Serial.print(FIRMWARE_NAME);
  169. Serial.print(" v");
  170. Serial.print(VERSION);
  171. Serial.println("starting...");
  172. pinMode(PIN_RELAIS, OUTPUT);
  173. digitalWrite(PIN_RELAIS, !RELAISONSTATE);
  174. pinMode(PIN_PIRSENSOR, INPUT);
  175. buttonPlus.configureButton(configurePushButton);
  176. //buttonPlus.onPress(onButtonPressed); // When the button is first pressed, call the function onButtonPressed (further down the page)
  177. buttonPlus.onHoldRepeat(1000, 350, onButtonHeld); // Once the button has been held for 1 second (1000ms) call onButtonHeld. Call it again every 350ms until it is let go
  178. buttonPlus.onRelease(50, 500, onButtonReleased); // When the button is held >50ms and released after <500ms, call onButtonReleased
  179. buttonMinus.configureButton(configurePushButton);
  180. //buttonMinus.onPress(onButtonPressed); // When the button is first pressed, call the function onButtonPressed (further down the page)
  181. buttonMinus.onHoldRepeat(1000, 350, onButtonHeld); // Once the button has been held for 1 second (1000ms) call onButtonHeld. Call it again every 350ms until it is let go
  182. buttonMinus.onRelease(50, 500, onButtonReleased); // When the button is held >50ms and released after <500ms, call onButtonReleased
  183. buttonMode.configureButton(configurePushButton);
  184. //buttonMode.onPress(onButtonPressed); // When the button is first pressed, call the function onButtonPressed (further down the page)
  185. buttonMode.onHold(1000, onButtonHeldNoRepeat); // Once the button has been held for 1 second (1000ms) call onButtonHeld
  186. buttonMode.onRelease(50, 500, onButtonReleased); // When the button is held >50ms and released after <500ms, call onButtonReleased
  187. pirSensor.configureButton(configurePushButton);
  188. //pirSensor.onPress(onButtonPressed); // When the button is first pressed, call the function onButtonPressed (further down the page)
  189. pirSensor.onHold(500, onButtonHeldNoRepeat); // Once the button has been held for 1 second (1000ms) call onButtonHeld
  190. pirSensor.onRelease(500, onButtonReleased); // When the button is held >50ms and released after <500ms, call onButtonReleased
  191. strlcpy(deviceName, DEVICE_NAME, 31);
  192. strlcpy(mqtt_server, MQTT_SERVER, 41);
  193. strlcpy(mqtt_topic_in, MQTT_TOPIC_IN, 51);
  194. strlcpy(mqtt_topic_out, MQTT_TOPIC_OUT, 51);
  195. strlcpy(domoticz_out_topic, DOMOTICZ_OUT_TOPIC, 51); // changeable subscription topic, as domoticz supports different flat/hierarchical out-topics
  196. strlcpy(outTemp_topic_in, OUTTEMP_TOPIC_IN, 51);
  197. strlcpy(outHum_topic_in, OUTHUM_TOPIC_IN, 51);
  198. Serial.println("default config values loaded..");
  199. Serial.println("Mounting FS...");
  200. if (!SPIFFS.begin()) {
  201. Serial.println("Failed to mount file system");
  202. return;
  203. }
  204. //uncomment for initial SPIFFS format
  205. //SPIFFS.format();
  206. //Serial.print("Format SPIFFS complete.");
  207. if (!SPIFFS.exists("/formatComplete.txt")) {
  208. Serial.println("Please wait 30 secs for SPIFFS to be formatted");
  209. SPIFFS.format();
  210. Serial.println("Spiffs formatted");
  211. File f = SPIFFS.open("/formatComplete.txt", "w");
  212. if (!f) {
  213. Serial.println("file open failed");
  214. } else {
  215. f.println("Format Complete");
  216. }
  217. f.close();
  218. } else {
  219. Serial.println("SPIFFS is formatted. Moving along...");
  220. }
  221. // // load config from SPIFFS if files exist
  222. if (!loadConfig()) {
  223. Serial.println("Failed to load conf.json");
  224. } else {
  225. Serial.println("conf.json loaded");
  226. }
  227. if (!loadConfig2()) {
  228. Serial.println("Failed to load conf2.json");
  229. } else {
  230. Serial.println("conf2.json loaded");
  231. }
  232. if (!loadSetTemp()) {
  233. Serial.println("Failed to load file 'setTemp'");
  234. } else {
  235. Serial.println("file 'setTemp' loaded");
  236. }
  237. if (!loadHeatingMode()) {
  238. Serial.println("Failed to load file 'heatingMode'");
  239. } else {
  240. Serial.println("file 'heatingMode' loaded");
  241. }
  242. setTempSaved = setTemp;
  243. heatingModeSaved = heatingMode;
  244. // initialize DHT11/22 temp/hum sensor
  245. dht.begin();
  246. checkUseDomoticz();
  247. delay(500);
  248. //optional code handlers to run everytime wifi is connected...
  249. persWM.onConnect([]() {
  250. Serial.println("wifi connected");
  251. Serial.println(WiFi.SSID());
  252. Serial.println(WiFi.localIP());
  253. });
  254. //...or AP mode is started
  255. persWM.onAp([]() {
  256. Serial.println("AP MODE");
  257. Serial.println(persWM.getApSsid());
  258. });
  259. //sets network name for AP mode
  260. persWM.setApCredentials(DEVICE_NAME);
  261. //persWM.setApCredentials(DEVICE_NAME, "password"); optional password
  262. //make connecting/disconnecting non-blocking
  263. persWM.setConnectNonBlock(true);
  264. //in non-blocking mode, program will continue past this point without waiting
  265. persWM.begin();
  266. delay(500);
  267. httpServerInit();
  268. mqttPrepareConnection();
  269. mqttClientInit();
  270. initDisplay();
  271. Serial.println("setup complete.");
  272. delay(1000);
  273. } //void setup
  274. void loop() {
  275. checkMillis();
  276. persWM.handleWiFi(); //in non-blocking mode, handleWiFi must be called in the main loop
  277. yield();
  278. dnsServer.processNextRequest();
  279. httpServer.handleClient();
  280. mqttHandleConnection();
  281. buttonPlus.update();
  282. buttonMinus.update();
  283. buttonMode.update();
  284. pirSensor.update();
  285. yield();
  286. evalCmd();
  287. if ( domoticzOutParseData ) {
  288. parseDomoticzOut();
  289. yield();
  290. }
  291. if (Serial.available()) {
  292. serialEvent();
  293. yield();
  294. }
  295. } //void loop