// pre compiletime config #define VERSION "0.1.2" #define DEVICE_NAME "WIFI-THERMOSTAT" #define PIN_DHTSENSOR 13 #define PIN_RELAIS 16 #define PIN_BUTTON_PLUS 0 #define PIN_BUTTON_MINUS 2 #define PIN_BUTTON_MODE 14 #define PIN_PIRSENSOR 12 #define RELAISONSTATE LOW #define BUTTONONSTATE LOW #define DHTTYPE DHT22 // DHT sensor type #define LCDADDR 0x27 // I2C address LCD #define DEBUG_SERIAL //uncomment for Serial debugging statements #ifdef DEBUG_SERIAL #define DEBUG_BEGIN Serial.begin(115200) #define DEBUG_PRINT(x) Serial.println(x) #else #define DEBUG_PRINT(x) #define DEBUG_BEGIN #endif #include "PersWiFiManager.h" #include #include #include #include #include #include #include #include #include "LiquidCrystal_I2C.h" #include #include //#include // BH1750 lux sensor #include "DHT.h" // global variables float humidity, temperature; bool turnHeatingOn = false; bool heatingEnabled = true; byte heatingMode = 1; // 0 = off, 1 = default/day, 2 = night/reduction float setTemp; long heatingLastOnMillis; long heatingLastOffMillis; float aTemp, aHum; long aTempHumLastUpdate; byte whichTempToDisplay; // 1=temp inside (from DHT sensor), 2= temp outside (via MQTT) long lastMeasure = 0; long lastDisplayUpdate = 0; long lastDisplayToggle = 0; long lastTempUpdate = 0; char msg[50]; // buffer MQTT in payload char topic[50]; // buffer MQTT in topic // global variables from other modules //extern bool displayActive; bool displayActive = false; // functions from other modules/files //extern void httpServerHandleNotFound(); //extern void httpServerHandleRoot(); //extern void enableDisplay(); //extern void disableDisplay(); //extern void extendDisplayTimeout(); //extern void handleDisplayTimeout(); //extern void updateDisplay(); //extern void initDisplay(); //extern void plusButtonAction(); //extern void minusButtonAction(); //extern void modeButtonAction(); //extern void modeButtonHoldAction(); //extern void checkButtonStates(); // config variables const char* mqtt_server = "10.1.1.11"; const int mqtt_port = 1883; long minOffTime = 10; // s float setTempDay = 21.5; float setTempNight = 18.0; float setTempMin = 14.0; float setTempMax = 29.0; float setTempMinLow = 10.0; float setTempMaxLow = 19.0; float hysteresis = 0.5; int measureinterval = 60000; // ms int displayinterval = 5000; int displayTimeout = 60000; //const char* mqtt_topic_prefix = "Test/Thermostat/"; const char* mqtt_topic_temp = "Test/Thermostat/Temp"; const char* mqtt_topic_settemp = "Test/Thermostat/SetTemp"; //const char* mqtt_topic_setmode = "Test/Thermostat/SetMode"; const char* mqtt_topic_hum = "Test/Thermostat/Hum"; const char* mqtt_topic_lux = "Test/Thermostat/Lux"; const char* mqtt_topic_heating = "Test/Thermostat/Heating"; const char* mqtt_topic_heating_lastofftime = "Test/Thermostat/HeatingLastOffTime"; const char* mqtt_topic_heating_lastontime = "Test/Thermostat/HeatingLastOnTime"; const char* mqtt_topic_in = "Test/Thermostat/in"; // subscribe topic DHT dht(PIN_DHTSENSOR, DHTTYPE); LiquidCrystal_I2C lcd(0x27, 16, 2); // set the LCD address to 0x27 for a 16 chars and 2 line display WiFiClient espClient; PubSubClient mqttclient(espClient); ESP8266WebServer httpServer(80); DNSServer dnsServer; PersWiFiManager persWM(httpServer, dnsServer); ESP8266HTTPUpdateServer httpUpdater; // MQTT callback void mqttCallback(char* topic, byte* payload, unsigned int length) { Serial.print("Message arrived ["); Serial.print(topic); Serial.print("] "); for (int i = 0; i < length; i++) { Serial.print((char)payload[i]); } Serial.println(); if (strcmp(topic, mqtt_topic_in) == 0) { //if topic = mqtt_topic_in Serial.println("TOPIC!!"); char tmpPayload[length + 1]; for (int i = 0; i < length; i++) { tmpPayload[i] = (char)payload[i]; } tmpPayload[sizeof(tmpPayload) - 1] = '\0'; Serial.print("tmpPayload:"); Serial.println(tmpPayload); if (strncmp(tmpPayload, "set temp ", 9) == 0) { char inValue[length - 9 + 1]; for (int i = 0; i < length; i++) { inValue[i] = (char)payload[i + 9]; } inValue[sizeof(inValue) - 1] = '\0'; Serial.print(inValue); Serial.println(); float invalueFloat = round(atof(inValue) * 2.0) / 2.0; Serial.print(invalueFloat); if (invalueFloat >= setTempMin && invalueFloat <= setTempMax) { setTempDay = invalueFloat; updateDisplay(); } else if (invalueFloat > setTempMax) { setTempDay = setTempMax; updateDisplay(); } else if (invalueFloat < setTempMin) { setTempDay = setTempMin; updateDisplay(); } }// if payload=="set temp " else if (strncmp(tmpPayload, "set tlow ", 9) == 0) { char inValue[length - 9 + 1]; for (int i = 0; i < length; i++) { inValue[i] = (char)payload[i + 9]; } inValue[sizeof(inValue) - 1] = '\0'; Serial.print(inValue); Serial.println(); float invalueFloat = round(atof(inValue) * 2.0) / 2.0; Serial.print(invalueFloat); if (invalueFloat >= setTempMinLow && invalueFloat <= setTempMaxLow) { setTempNight = invalueFloat; updateDisplay(); } else if (invalueFloat > setTempMaxLow) { setTempNight = setTempMaxLow; updateDisplay(); } else if (invalueFloat < setTempMinLow) { setTempNight = setTempMinLow; updateDisplay(); } } else if (strncmp(tmpPayload, "set mode ", 9) == 0) { if ((char)payload[9] == '1') { // switch to mode default/day heatingMode = 1; updateDisplay(); } else if ((char)payload[9] == '2') { // switch to mode night/reduction heatingMode = 2; updateDisplay(); } } else if (strncmp(tmpPayload, "set enab ", 9) == 0) { if ((char)payload[9] == '0') { // switch heating off heatingEnabled = false; updateDisplay(); } else if ((char)payload[9] == '1') { // switch heating on heatingEnabled = true; updateDisplay(); } } }//if topic = mqtt_topic_in if (strcmp(topic, "wetter/atemp") == 0) { //if topic = "wetter/atemp" char inValue[length + 1]; for (int i = 0; i < length; i++) { inValue[i] = (char)payload[i]; } inValue[sizeof(inValue) - 1] = '\0'; //Serial.print(inValue); //Serial.println(); float invalueFloat = atof(inValue); aTemp = invalueFloat; aTempHumLastUpdate = millis(); //Serial.print("atemp="); //Serial.println(invalueFloat); }//if topic = "wetter/atemp" if (strcmp(topic, "wetter/ahum") == 0) { //if topic = "wetter/atemp" char inValue[length + 1]; for (int i = 0; i < length; i++) { inValue[i] = (char)payload[i]; } inValue[sizeof(inValue) - 1] = '\0'; //Serial.print(inValue); //Serial.println(); float invalueFloat = atof(inValue); aHum = invalueFloat; aTempHumLastUpdate = millis(); //Serial.println("ahum="); //Serial.println(invalueFloat); }//if topic = "wetter/ahum" }//mqttCallback long mqttLastReconnectAttempt = 0; boolean mqttReconnect() { // Create a random MQTT client ID String mqttClientId = "ESP8266Client-"; mqttClientId += String(random(0xffff), HEX); if (mqttclient.connect(mqttClientId.c_str())) { Serial.println("connected"); // Once connected, publish an announcement... //mqttclient.publish("TestoutTopic", "hello world"); // ... and resubscribe mqttclient.subscribe(mqtt_topic_in); mqttclient.subscribe("wetter/atemp"); mqttclient.subscribe("wetter/ahum"); } return mqttclient.connected(); } //mqttReconnect void setup() { DEBUG_BEGIN; //for terminal debugging DEBUG_PRINT(); Serial.print("WiFi Thermostat v"); Serial.print(VERSION); Serial.println(" by Flo Kra"); Serial.println("starting..."); pinMode(PIN_RELAIS, OUTPUT); digitalWrite(PIN_RELAIS, HIGH); pinMode(PIN_BUTTON_PLUS, INPUT_PULLUP); pinMode(PIN_BUTTON_MINUS, INPUT_PULLUP); pinMode(PIN_BUTTON_MODE, INPUT_PULLUP); // initialize DHT11/22 temp/hum sensor dht.begin(); //optional code handlers to run everytime wifi is connected... persWM.onConnect([]() { DEBUG_PRINT("wifi connected"); DEBUG_PRINT(WiFi.SSID()); DEBUG_PRINT(WiFi.localIP()); }); //...or AP mode is started persWM.onAp([]() { DEBUG_PRINT("AP MODE"); DEBUG_PRINT(persWM.getApSsid()); }); //allows serving of files from SPIFFS //SPIFFS.begin(); //SPIFFS.format(); //sets network name for AP mode persWM.setApCredentials(DEVICE_NAME); //persWM.setApCredentials(DEVICE_NAME, "password"); optional password //make connecting/disconnecting non-blocking persWM.setConnectNonBlock(true); //in non-blocking mode, program will continue past this point without waiting persWM.begin(); //handles commands from webpage, sends live data in JSON format httpServer.on("/api", []() { DEBUG_PRINT("httpServer.on /api"); if (httpServer.hasArg("plusBtn")) { plusButtonAction(); DEBUG_PRINT(P("web plusBtn")); } //if if (httpServer.hasArg("minusBtn")) { minusButtonAction(); DEBUG_PRINT(P("web minusBtn")); } //if if (httpServer.hasArg("modeBtn")) { modeButtonAction(); DEBUG_PRINT(P("web modeBtn")); } //if if (httpServer.hasArg("onoffBtn")) { modeButtonHoldAction(); DEBUG_PRINT(P("web onoffBtn")); } //if //build json object of program data StaticJsonBuffer<200> jsonBuffer; JsonObject &json = jsonBuffer.createObject(); json["ssid"] = WiFi.SSID(); json["setTemp"] = setTempDay; json["temp"] = temperature; json["hum"] = int(humidity); json["mode"] = heatingMode; json["heatingEnabled"] = heatingEnabled; json["heating"] = turnHeatingOn; char jsonchar[200]; json.printTo(jsonchar); //print to char array, takes more memory but sends in one piece httpServer.send(200, "application/json", jsonchar); }); //httpServer.on /api //get heap status, analog input value and all GPIO statuses in one json call httpServer.on("/info", HTTP_GET, []() { String json = "{"; json += "\"wifissid\":\"" + WiFi.SSID() + "\""; json += "\"heap\":" + String(ESP.getFreeHeap()); //json += ", \"analog\":" + String(analogRead(A0)); //json += ", \"gpio\":" + String((uint32_t)(((GPI | GPO) & 0xFFFF) | ((GP16I & 0x01) << 16))); json += "}"; httpServer.send(200, "text/json", json); json = String(); }); //httpServer.on /info httpServer.on("/", []() { httpServerHandleRoot(); }); httpServer.onNotFound([]() { httpServerHandleNotFound(); }); //httpServer.onNotFound // HTTP Updater at /update httpUpdater.setup(&httpServer); httpServer.begin(); DEBUG_PRINT("setup complete."); mqttclient.setServer(mqtt_server, mqtt_port); mqttclient.setCallback(mqttCallback); mqttLastReconnectAttempt = 0; initDisplay(); } //void setup void loop() { persWM.handleWiFi(); //in non-blocking mode, handleWiFi must be called in the main loop dnsServer.processNextRequest(); httpServer.handleClient(); // MQTT reconnect if not connected (nonblocking) if (!mqttclient.connected()) { long now = millis(); if (now - mqttLastReconnectAttempt > 5000) { mqttLastReconnectAttempt = now; // Attempt to reconnect if (mqttReconnect()) { mqttLastReconnectAttempt = 0; } } } else { // Client connected mqttclient.loop(); }//else checkButtonStates(); handleDisplayTimeout(); // set target temp for heating mode if (heatingMode == 1) { // heating on - default/day mode setTemp = setTempDay; } else if (heatingMode == 2) { // heating of - night/reduction mode setTemp = setTempNight; } if ( (millis() - lastMeasure) > measureinterval) { lastMeasure = millis(); float tmpHum = round(dht.readHumidity()); float tmpTemp = dht.readTemperature(); // Read temperature as Celsius (the default) // Check if any reads failed if (isnan(tmpHum) || isnan(tmpTemp)) { Serial.println("Failed to read from DHT sensor!"); } else { if (tmpTemp < 50.0 && tmpTemp > -20.0) { temperature = tmpTemp; humidity = tmpHum; lastTempUpdate = millis(); } } unsigned long heatingOnTime, heatingOffTime; if (heatingEnabled && turnHeatingOn) { heatingOnTime = (millis() - heatingLastOffMillis) / 1000; Serial.print("heatingOnTime: "); Serial.print(heatingOnTime); Serial.println(); } else if (heatingEnabled && !turnHeatingOn) { heatingOffTime = (millis() - heatingLastOnMillis) / 1000; Serial.print("heatingOffTime: "); Serial.print(heatingOffTime); Serial.println(); } Serial.print("lastTempUpdate: "); Serial.println(lastTempUpdate); long test1 = millis() - lastTempUpdate; Serial.print("lastTempUpdate test1: "); Serial.println(test1); if ( (millis() - lastTempUpdate) < 120000 ) { // thermostat - only active if measured temperature is < 2 min old // thermostat with hysteresis if ( temperature >= setTemp ) { turnHeatingOn = false; heatingLastOnMillis = millis(); digitalWrite(PIN_RELAIS, !RELAISONSTATE); updateDisplay(); Serial.println("heating off"); Serial.print("last onTime: "); Serial.print(heatingOnTime); Serial.println(); mqttclient.publish(mqtt_topic_heating, "off"); //mqttclient.publish(mqtt_topic_heating_lastontime, heatingOnTime); } else if ( heatingEnabled && ( temperature < (setTemp - hysteresis) ) && ( heatingOffTime > minOffTime ) ) { turnHeatingOn = true; digitalWrite(PIN_RELAIS, RELAISONSTATE); updateDisplay(); Serial.println("heating on"); Serial.print("last offTime: "); Serial.print(heatingOffTime); Serial.println(); mqttclient.publish(mqtt_topic_heating, "on"); //mqttclient.publish(mqtt_topic_heating_lastofftime, heatingOffTime); } } else { turnHeatingOn = false; digitalWrite(PIN_RELAIS, !RELAISONSTATE); Serial.println("heating off"); mqttclient.publish(mqtt_topic_heating, "off"); } } // if (lightMeter_present) { // delay(50); // lux = lightMeter.readLightLevel(); // // String lux_str = String(lux, 1); //converting humidity (the float variable above) to a string with 0 decimals // char lux_chararr[lux_str.length() + 1]; // lux_str.toCharArray(lux_chararr, lux_str.length() + 1); //packaging up the data to publish to mqtt whoa... // // Serial.print("Lux: "); // Serial.println(lux_str); // mqttclient.publish(mqtt_topic_lux, lux_chararr); // } long now = millis(); if ( ((now - aTempHumLastUpdate) < 300000) && whichTempToDisplay == 1 && ((now - lastDisplayToggle) > displayinterval) ) { // A-temp has been updated < 5 min ago, last displayed temp was I and display interval is overdue whichTempToDisplay = 2; // 1= I, 2= A lastDisplayToggle = now; } else if ( whichTempToDisplay != 1 && ((now - lastDisplayToggle) > displayinterval) ) { whichTempToDisplay = 1; // 1= I, 2= A lastDisplayToggle = now; } // update display? if ( (now - lastDisplayUpdate) > displayinterval) { lastDisplayUpdate = now; // char temp_chararr[6]; // char hum_chararr[3]; // if ( (millis() - lastTempUpdate) < 120000) { // String temp_str = String(temperature, 1); //converting Temperature (the float variable above) to a string with 1 decimal // //char temp_chararr[temp_str.length() + 1]; // temp_str.toCharArray(temp_chararr, temp_str.length() + 1); //packaging up the data to publish to mqtt whoa... // // String hum_str = String(humidity, 0); //converting humidity (the float variable above) to a string with 0 decimals // //char hum_chararr[hum_str.length() + 1]; // hum_str.toCharArray(hum_chararr, hum_str.length() + 1); //packaging up the data to publish to mqtt whoa... // // mqttclient.publish(mqtt_topic_temp, temp_chararr); // mqttclient.publish(mqtt_topic_hum, hum_chararr); // } // else { // char temp_chararr[] = {'-', '-', ',', '-', '\0'}; // char hum_chararr[] = {'-', '-', '\0'}; // } String temp_str = String(temperature, 1); //converting Temperature (the float variable above) to a string with 1 decimal char temp_chararr[temp_str.length() + 1]; temp_str.toCharArray(temp_chararr, temp_str.length() + 1); //packaging up the data to publish to mqtt whoa... String hum_str = String(humidity, 0); //converting humidity (the float variable above) to a string with 0 decimals char hum_chararr[hum_str.length() + 1]; hum_str.toCharArray(hum_chararr, hum_str.length() + 1); //packaging up the data to publish to mqtt whoa... if ( (now - lastTempUpdate) < 120000 ) { mqttclient.publish(mqtt_topic_temp, temp_chararr); mqttclient.publish(mqtt_topic_hum, hum_chararr); } else { temp_str = "--.-"; hum_str = "--"; } String atemp_str = String(aTemp, 1); //converting Temperature (the float variable above) to a string with 1 decimal char atemp_chararr[atemp_str.length() + 1]; atemp_str.toCharArray(atemp_chararr, atemp_str.length() + 1); //packaging up the data to publish to mqtt whoa... String ahum_str = String(aHum, 0); //converting humidity (the float variable above) to a string with 0 decimals char ahum_chararr[ahum_str.length() + 1]; ahum_str.toCharArray(ahum_chararr, ahum_str.length() + 1); //packaging up the data to publish to mqtt whoa... char setTemp_chararr[5]; //result string 4 positions + \0 at the end // convert float to fprintf type string format 2 positions with 1 decimal place dtostrf(setTemp, 2, 1, setTemp_chararr ); Serial.print("set temp: "); Serial.println(setTemp_chararr); Serial.print("current temp: "); Serial.println(temp_chararr); mqttclient.publish(mqtt_topic_settemp, setTemp_chararr); if (whichTempToDisplay == 2) { // print inside temperature incl = and ° symbol + humidity to lcd, line 1, first 11 chars lcd.setCursor(0, 0); //lcd.write(0x3D); // = Zeichen lcd.print("A"); if (aTemp <= -10); else if (aTemp < 0) lcd.print(" "); else if (aTemp < 10) lcd.print(" "); else lcd.print(" "); lcd.print(atemp_str); // inside temperature should hopefully always be 2.1 chars, so no special formatting necessary lcd.write(0xDF); // degree symbol lcd.print(" "); //lcd.print(humstr2); lcd.print(ahum_str); // always 2 chars lcd.print("%"); lcd.print(" "); } else { // print outside temperature incl = and ° symbol + humidity to lcd, line 1, first 11 chars lcd.setCursor(0, 0); //lcd.write(0x3D); // = Zeichen lcd.print("I"); //lcd.print(" "); // inside temperature should hopefully always be 2.1 chars, so no special formatting/spaces handling necessary if (temperature <= -10); else if (temperature < 0) lcd.print(" "); else if (temperature < 10) lcd.print(" "); else lcd.print(" "); //if (temperature < -9) lcd.print(" "); //else if (temperature < 0) lcd.print(""); //else if (temperature < 10) lcd.print(" "); //else lcd.print(" "); //lcd.print(tempstr2); //lcd.print(temp_str); lcd.print(temp_chararr); lcd.write(0xDF); // degree symbol lcd.print(" "); //lcd.print(humstr2); //lcd.print(hum_str); // always 2 chars lcd.print(hum_chararr); // always 2 chars lcd.print("%"); lcd.print(" "); } // display current mode on LCD lcd.setCursor(13, 0); // to char 15, line 1 if (heatingEnabled) { if ( heatingMode == 1 ) { // day/normal mode lcd.print(" "); lcd.write((uint8_t)1); // sun symbol if mode is day/normal lcd.print(" "); } else if ( heatingMode == 2 ) { // night/reduction mode lcd.print(" "); lcd.write((uint8_t)0); // moon symbol if mode is night/reduction lcd.print(" "); } } else lcd.print(" "); // mode is heating off // display target temperature to line 2, 8 chars length incl space at the end lcd.setCursor(0, 1); // when the heating mode is OFF, do not display target temp - instead show "Heating off" info in line 2 if ( !heatingEnabled ) { // 1234567890123456 lcd.print(" Heizung aus "); } else { lcd.write((uint8_t)2); // Pfeil rechts lcd.print(" "); lcd.print(setTemp_chararr); lcd.write(0xDF); // degree symbol lcd.print(" "); // display status info to line 2 from char 9 -> 8 chars length lcd.setCursor(8, 1); if (turnHeatingOn) { // 12345678 lcd.print("heizen.."); } else if ( heatingMode == 1 ) { // day/normal mode // 12345678 lcd.print(" "); } else if ( heatingMode == 2 ) { // night/reduction mode // 12345678 lcd.print("N-Absenk"); } else lcd.print(" "); } } } //void loop