//#include #include "fwversion.h" #include "fwsettings.h" // ESP8266 core libs #include #include #include #include #ifdef USE_MQTT_TLS #include #else #include #endif // WiFiManager #include // DNS server used for captive portal in WiFiManager #include // MQTT client #include // SPIFFS FS support - used for config storage #include // Json lib - used for config storage and web api #include //#include // I2C and LCD display #include #include // DHT22 sensor lib #include // Button handling #include #include #include #include PROGMEM const char PGMStr_FIRMWARE_NAME[] = FIRMWARE_NAME; PROGMEM const char PGMStr_FIRMWARE_VERSION[] = FIRMWARE_VERSION; PROGMEM const char PGMStr_FIRMWARE_URL[] = FIRMWARE_URL; PROGMEM const char PGMStr_FIRMWARE_COPYRIGHT[] = FIRMWARE_COPYRIGHT; PROGMEM const char PGMStr_FIRMWARE_COPYRIGHT_URL[] = FIRMWARE_COPYRIGHT_URL; PROGMEM const char PGMStr_FIRMWARE_SHORTNAME[] = FIRMWARE_SHORTNAME; PROGMEM const char PGMStr_changedTo[] = "changed to"; PROGMEM const char PGMStr_preset[] = "preset"; PROGMEM const char PGMStr_heatingMode[] = "heatingMode"; PROGMEM const char PGMStr_setTemp[] = "setTemp"; PROGMEM const char PGMStr_currentSetTemp[] = "currSetTemp"; PROGMEM const char PGMStr_setTempLow[] = "setTempLow"; PROGMEM const char PGMStr_setTempLow2[] = "setTempLow2"; PROGMEM const char PGMStr_switchHeating[] = "switch heating"; PROGMEM const char PGMStr_mqttRetainedSave[] = "MQTT retained save"; PROGMEM const char PGMStr_heating[] = "heating"; PROGMEM const char PGMStr_thermostat[] = "THST"; PROGMEM const char PGMStr_WiFi[] = "WiFi"; //PROGMEM const char PGMStr_connectedTo[] = "connected to"; //PROGMEM const char PGMStr_withIP[] = "with IP"; PROGMEM const char PGMStr_MQTT[] = "MQTT"; PROGMEM const char PGMStr_connectedTo[] = "connected to"; PROGMEM const char PGMStr_reconnects[] = "reconnects"; PROGMEM const char PGMStr_connectedFailed[] = "connect FAILED"; PROGMEM const char compile_date[] = __DATE__ " " __TIME__; const char PGMStr_confDevWiFi[] PROGMEM = "confDevWiFi"; const char PGMStr_confWeb[] PROGMEM = "confWeb"; const char PGMStr_confMqtt[] PROGMEM = "confMqtt"; const char PGMStr_confBas[] PROGMEM = "confBas"; const char PGMStr_confAdv[] PROGMEM = "confAdv"; const char PGMStr_confAdd[] PROGMEM = "confAdd"; const char PGMStr_confTime[] PROGMEM = "confTime"; const char PGMStr_confLog[] PROGMEM = "confLog"; //const char PGMStr_delimiter[] PROGMEM = "--------"; const char PGMStr_MQTTState_DIS[] PROGMEM = "DISABLED"; const char PGMStr_MQTTState_M4[] PROGMEM = "CONNECTION_TIMEOUT"; const char PGMStr_MQTTState_M3[] PROGMEM = "CONNECTION_LOST"; const char PGMStr_MQTTState_M2[] PROGMEM = "CONNECT_FAILED"; const char PGMStr_MQTTState_M1[] PROGMEM = "DISCONNECTED"; const char PGMStr_MQTTState_0[] PROGMEM = "CONNECTED"; const char PGMStr_MQTTState_1[] PROGMEM = "CONNECT_BAD_PROTOCOL"; const char PGMStr_MQTTState_2[] PROGMEM = "CONNECT_BAD_CLIENT_ID"; const char PGMStr_MQTTState_3[] PROGMEM = "CONNECT_UNAVAILABLE"; const char PGMStr_MQTTState_4[] PROGMEM = "CONNECT_BAD_CREDENTIALS"; const char PGMStr_MQTTState_5[] PROGMEM = "CONNECT_UNAUTHORIZED"; const char *const PGMStr_MQTTStates[] PROGMEM = { PGMStr_MQTTState_M4, PGMStr_MQTTState_M3, PGMStr_MQTTState_M2, PGMStr_MQTTState_M1, PGMStr_MQTTState_0, PGMStr_MQTTState_1, PGMStr_MQTTState_2, PGMStr_MQTTState_3, PGMStr_MQTTState_4, PGMStr_MQTTState_5, }; char mqttCurrentStateName[25]; //--------------------------------------------------------------------------------------------------------------------------------------------- PushButton buttonPlus = PushButton(PIN_BUTTON_PLUS, ENABLE_INTERNAL_PULLUP); PushButton buttonMinus = PushButton(PIN_BUTTON_MINUS, ENABLE_INTERNAL_PULLUP); PushButton buttonMode = PushButton(PIN_BUTTON_MODE, ENABLE_INTERNAL_PULLUP); PushButton pirSensor = PushButton(PIN_PIRSENSOR, PRESSED_WHEN_HIGH); bool WifiInApMode = false; unsigned long WifiApModeStartedAt; #ifdef ENABLE_FEATURE_PW_ENCRYPTION char encKey[] = PW_ENCRYPTION_KEY; #endif bool confEncryptionEnabled = false; #ifdef ENABLE_FEATURE_NTP_TIME struct tm lt; // http://www.cplusplus.com/reference/ctime/tm/ //const char *const PROGMEM dayNames[] = {"Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"}; //const char *const PROGMEM dayShortNames[] = {"So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"}; //const char *const PROGMEM monthNames[] = {"Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"}; //const char *const PROGMEM monthShortNames[] = {"Jan", "Feb", "Mrz", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"}; #endif // config variables - do not change here! // confDevWiFi struct confDataDevWiFi { char deviceName[33]; // device name - for web interface and AP-Mode SSID char hostName[33]; // announced hostname on WiFi connection char WiFiSSID1[33]; char WiFiPW1[64]; char WiFiSSID2[33]; char WiFiPW2[64]; char WiFiAPModeSSID[33]; // generated, not set via config char WiFiAPModePassword[64]; uint16_t WiFiAPModeTimeout; uint16_t WiFiRetryInterval; uint16_t WiFiConnCheckInterval; uint16_t WiFiRebootOnNoConnect; bool wasChanged; } confDevWiFi; // confWeb struct confDataWeb { char http_token[31]; char http_user[31]; char http_pass[31]; bool http_user_auth; char http_user1[31]; char http_pass1[31]; char http_user2[31]; char http_pass2[31]; bool wConsole; bool wsConsole; bool wasChanged; } confWeb; // confMqtt struct confDataMqtt { bool mqtt_enable; char mqtt_server[41]; uint16_t 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 char mqtt_willTopic[51]; // MQTT Last Will topic uint8_t mqtt_willQos; // MQTT Last Will topic QOS bool mqtt_willRetain; // MQTT Last Will retain char mqtt_willMsg[31]; // MQTT Last Will payload char mqtt_connMsg[31]; bool mqtt_outRetain; // send MQTT out with retain flag bool mqtt_outRetain_sensors; // send MQTT out with retain flag uint8_t mqtt_outPubInterval; uint8_t mqtt_outPubInterval_sensors; bool mqtt_enable_heartbeat; unsigned long mqtt_heartbeat_maxage_reconnect; unsigned long mqtt_heartbeat_maxage_reboot; bool wasChanged; } confMqtt; // confBas struct confDataBas { float setTempMin; // minimal temperature that can be set float setTempMax; // maximal temperature that can be set bool autoSaveSetTemp; bool autoSaveHeatingMode; bool saveToMqttRetained; uint16_t measureInterval; // interval for temp/hum measurement uint16_t displayInterval; // interval for display updates (if out-temp is // active, display will toggle in this interval) uint16_t displayTimeout; // display timeout after keypress (illumination) bool PIR_enablesDisplay; // PIR sensor enables display illumination bool PIR_enablesDisplay_preset0only; bool togglingTempHumAIDisplay; bool wasChanged; } confBas; // confAdv struct confDataAdv { float hysteresis; // hysteresis, normally 0.1 - 0.5 uint16_t heatingMinOffTime; // minimal time the heating keeps turned off in // s float tempCorrVal; // correction value for // temperature sensor reading int humCorrVal; // correction value for humidity sensor reading float setTempDecreaseVal; // decreases the set temp to overcome further // temperature rise when the heating is already // switched off char offMessage[15]; char iTempLabel[2]; char oTempLabel[2]; char modeName0[15]; char modeName1[15]; char psetName0[15]; char psetName1[15]; char psetName2[15]; bool wasChanged; } confAdv; // confAdd struct confDataAdd { 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]; bool wasChanged; } confAdd; // confTime struct confDataTime { bool ntpEnable; char timeZoneStr[50]; char ntpServer1[31]; char ntpServer2[31]; unsigned long ntpSyncInterval; bool wasChanged; } confTime; // confLog struct confDataLog { uint8_t logLevelSerial; uint8_t logLevelWeb; uint8_t logLevelMqtt; uint8_t logLevelSyslog; bool logWebRequests; bool wasChanged; } confLog; // Uptime struct sysUptimeStruct { unsigned int day = 0; unsigned int hour = 0; unsigned int min = 0; unsigned int sec = 0; unsigned int highMillis = 0; unsigned int rollover = 0; char uptimeStr[16]; } sysUptime; bool 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; uint8_t heatingMode = DEFAULT_HEATINGMODE; // 0 = off, 1 = heat uint8_t 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; float setTempLowSaved; float setTempLow2Saved; uint8_t heatingModeSaved; uint8_t presetSaved; // global pre set conf variables - not changeable via configuration float setTempLowMin = SETTEMP_LOW_MIN; float setTempLowMax = SETTEMP_LOW_MAX; uint16_t debounceTime = BUTTON_DEBOUNCE_TIME; uint16_t buttonHoldTime = BUTTON_HOLD_TIME; unsigned long maxMeasurementAge = MAX_MEASUREMENT_AGE; unsigned long 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) uint16_t currHum; // last reading from DHT sensor with smoothing uint16_t currHum_raw; // last reading from DHT sensor (raw) bool turnHeatingOn = false; // true if heating is active (relais switched on) unsigned long heatingLastOnMillis; // last time heating was switched on unsigned long heatingLastOffMillis; // last time heating was switched off float outTemp; // outside temp (via MQTT if enabled and in-topic configured) uint16_t outHum; // outside temp (via MQTT if enabled and in-topic configured) unsigned long outTempHumLastUpdate; // last reading from out temp/hum source char outTemp_newValue[6]; bool outTemp_parseNewValue; char outHum_newValue[4]; bool outHum_parseNewValue; char currentModeName[15]; char currentPresetName[15]; uint16_t displayInterval; uint8_t whichTempToDisplay; // 1=temp inside (from DHT sensor), 2= temp outside // (via MQTT) - if out temp/hum available this value // and the displayed value pair toggles with every // displayInterval unsigned long lastMeasure = 0; // millis of last temp/hum measurement unsigned long lastDisplayUpdate = 0; // millis of last display update unsigned long lastDisplayToggle = 0; // millis of last display toggle unsigned long lastTempUpdate = 0; // last update time of DHT reading //char msg[101]; // buffer MQTT in payload //char topic[70]; // buffer MQTT in topic bool displayActive = false; // gets true when button is pressed. display light // gets switched on until timeout. button actions // are only performed while display is active bool PIRSensorOn = false; unsigned long heatingOnTime, heatingOffTime; //bool config_was_changed = false; bool configChangedRestartRequired = false; bool configChangedMqttConnResetRequired = false; 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 bool cmdInQueue = false; // command is queued and will be processed next loop() run // not used any more, now triggered directly //bool saveConfigToFlash = false; // conf is saved in next loop() run //bool saveConfig2ToFlash = false; // conf2 is saved in next loop() run // ---- uint16_t 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 unsigned long lastConfigChange; bool lastConfigChangeNoteAlreadyDone = false; bool setTempAlreadySaved = true; // only save if not yet done bool setTempLowAlreadySaved = true; // only save if not yet done bool setTempLow2AlreadySaved = true; // only save if not yet done bool heatingModeAlreadySaved = true; // only save if not yet done bool presetAlreadySaved = true; // only save if not yet done unsigned long lastRun = 0; uint16_t count100ms = 0; uint16_t countSeconds = 0; uint16_t countMeasureInterval = 0; uint16_t countDisplayInterval = 0; uint16_t displayOverlayMsgTimeout = 2; unsigned long mqttLastReconnectAttempt = 0; uint16_t mqttReconnectAttempts = 0; uint16_t mqttReconnects = 0; bool mqttConnected = false; unsigned long mqttLastHeartbeat; bool mqttInTopicSubscribed = false; bool pendingRestart = false; bool doRestart = false; unsigned long pendingRestart_lastMillis = 0; bool displayShowFullscreenMsg = false; // unsigned long displayShowFullscreenMsg_lastMillis = 0; bool pendingPresetToggle = false; bool updateDisplayImmediately = false; uint8_t pendingPreset; char pendingPresetName[15]; bool displayShowLine2OverlayMsg = false; // if command "debugmode 1" is called... bool sysInfoEverySecond = false; #ifdef ENABLE_FEATURE_WEB_CONSOLE char webLogBuffer[WEB_CONSOLE_BUFFER_SIZE]; unsigned int webLogBuffer_index = 0; #endif // END global variables DHT dht(PIN_DHTSENSOR, DHTTYPE); LiquidCrystal_I2C lcd(LCDADDR, LCDCOLS, LCDLINES); #ifdef USE_MQTT_TLS //WiFiClientSecure wifiClient; BearSSL::WiFiClientSecure wifiClient; #else WiFiClient wifiClient; #endif PubSubClient mqttclient(wifiClient); ESP8266WebServer httpServer(80); // WebSocketsServer #ifdef ENABLE_FEATURE_WEB_CONSOLE_WEBSOCKETS WebSocketsServer webSocket(81); unsigned long int wsValidSessionId = 0; #endif DNSServer dnsServer; PersWiFiManager persWM(httpServer, dnsServer); #ifdef ENABLE_FEATURE_HTTP_UPDATER ESP8266HTTPUpdateServer httpUpdater; #endif // FUNCTION PROTOTYPES #ifdef ENABLE_FEATURE_NTP_TIME void updateTime(); bool setupTime(); void syncClock(bool force = false); #endif char *getUptimeStr(bool includeSeconds = false); void sendLog(const char *msg, uint8_t loglevel = 1); void sendLog(const __FlashStringHelper *msg, uint8_t loglevel = 1); void mqttCallback(char *topic, byte *payload, uint16_t length); char *getTimeStringFromSeconds(unsigned int inSeconds); // END - FUNCTION PROTOTYPES // MAIN FUNCTIONS void setup() { Serial.begin(115200); delay(50); Serial.println(); Serial.print(PGMStr_FIRMWARE_NAME); Serial.print(F(" v")); Serial.print(PGMStr_FIRMWARE_VERSION); Serial.println(F(" starting...")); pinMode(PIN_RELAIS, OUTPUT); digitalWrite(PIN_RELAIS, !RELAISONSTATE); setupInputsButtons(); // set conf default values loadConf_defaults(); //if (serialdebug) // Serial.println("default config values loaded.."); sendLog(F("CONF: default config loaded."), LOGLEVEL_INFO); initDisplay(); sendLog(F("Mounting SPIFFS..."), LOGLEVEL_INFO); if (!SPIFFS.begin()) { sendLog(F("Failed to mount SPIFFS"), LOGLEVEL_ERROR); return; } #ifdef FORCE_SPIFFS_FORMAT SPIFFS_format(); #endif SPIFFS_formatIfIsnt(); //SPIFFS_listFiles(); #ifdef ENABLE_FEATURE_PW_ENCRYPTION confCheckEncrypted(); #endif loadConf_all(); loadSavedValues(); // if configuration returned empty strings - use the defaults where a values // is necessary if (strlen(deviceName) < 4) strlcpy(deviceName, DEVICE_NAME, // 31); loadConf_restoreDefaultWhenMissing(); updateCurrentHeatingModeName(); updateCurrentPresetName(); // initialize DHT11/22 temp/hum sensor dht.begin(); if (strlen(confDevWiFi.hostName) >= 4) { // if no hostname is set WiFi manager will create a unique one automatically // based on MAC address WiFi.hostname(confDevWiFi.hostName); } // optional code handlers to run everytime wifi is connected... persWM.onConnect([]() { //Serial.print(F("WiFi: connected to '")); //Serial.print(WiFi.SSID()); //Serial.print(F("' with IP: ")); //Serial.println(WiFi.localIP()); //char buf[60]; //sprintf(buf, "%s: %s '%s' %s: '%s'", PGMStr_WiFi, PGMStr_connectedTo, WiFi.SSID().c_str(), PGMStr_withIP, WiFi.localIP().toString().c_str()); //sendLog(buf, LOGLEVEL_INFO); printIpcfg(); WifiInApMode = false; #ifdef ENABLE_FEATURE_NTP_TIME if (confTime.ntpEnable) setupTime(); #endif displayShowWifiConnected(); }); //...or AP mode is started persWM.onAp([]() { //Serial.print(F("WiFi: AP mode, SSID: '")); //Serial.print(persWM.getApSsid()); //if (strlen(confDevWiFi.WiFiAPModePassword) >= 4) //{ // Serial.print("', PW: '"); // Serial.print(confDevWiFi.WiFiAPModePassword); //} //Serial.println("'"); char buf[60]; sprintf(buf, "%s: AP-MODE started, SSID '%s', PW: '%s'", PGMStr_WiFi, persWM.getApSsid().c_str(), confDevWiFi.WiFiAPModePassword); sendLog(buf, LOGLEVEL_INFO); WifiInApMode = true; WifiApModeStartedAt = millis(); displayShowWifiConnectionError(); }); persWM.onApOff([]() { //Serial.println(F("WiFi: AP mode stopped")); sendLog(F("WiFi: AP-MODE stopped"), LOGLEVEL_INFO); }); // sets network name and password for AP mode // sprintf(WiFiAPModeSSID, "%s-%s", FIRMWARE_SHORTNAME, deviceName); if (strlen(confDevWiFi.deviceName) >= 8) strlcpy(confDevWiFi.WiFiAPModeSSID, confDevWiFi.deviceName, sizeof(confDevWiFi.WiFiAPModeSSID)); else { byte mac[6]; WiFi.macAddress(mac); sprintf(confDevWiFi.WiFiAPModeSSID, "%s-%02X%02X", FIRMWARE_SHORTNAME, (uint8_t)mac[4], (uint8_t)mac[5]); } //Serial.print(F("WiFi: AP-SSID name: ")); //Serial.println(confDevWiFi.WiFiAPModeSSID); if (strlen(confDevWiFi.WiFiAPModePassword) < 5) { persWM.setApCredentials(confDevWiFi.WiFiAPModeSSID); //sendLog("WIFI-AP: open network"); } else { persWM.setApCredentials(confDevWiFi.WiFiAPModeSSID, confDevWiFi.WiFiAPModePassword); //sendLog("WIFI-AP: with password"); } //Serial.print(F("WiFi: SSID 1: ")); Serial.println(confDevWiFi.WiFiSSID1); //Serial.print("', PW: '"); Serial.print(confDevWiFi.WiFiPW1); Serial.println("'"); //Serial.print(F("WiFi: SSID 2: ")); Serial.println(confDevWiFi.WiFiSSID2); //Serial.print("', PW: '"); Serial.print(confDevWiFi.WiFiPW2); Serial.println("'"); persWM.setWifi1Credentials(confDevWiFi.WiFiSSID1, confDevWiFi.WiFiPW1); persWM.setWifi2Credentials(confDevWiFi.WiFiSSID2, confDevWiFi.WiFiPW2); persWM.setHttpCredentials(confWeb.http_user, confWeb.http_pass); persWM.setApTimeout(confDevWiFi.WiFiAPModeTimeout); persWM.setConnCheckInterval(confDevWiFi.WiFiConnCheckInterval); persWM.setForceRetryWifi1Interval(confDevWiFi.WiFiRetryInterval); persWM.setRebootOnNoConnect(confDevWiFi.WiFiRebootOnNoConnect); // make connecting/disconnecting non-blocking persWM.setConnectNonBlock(true); // in non-blocking mode, program will continue past this point without waiting persWM.begin(); #ifdef USE_MQTT_TLS //randomSeed(micros()); //wifiClient.setFingerprint(MQTT_TLS_fingerprint); wifiClient.setInsecure(); wifiClient.allowSelfSignedCerts(); wifiClient.setCiphersLessSecure(); #endif delay(500); httpServerInit(); #ifdef ENABLE_FEATURE_WEB_CONSOLE_WEBSOCKETS if (confWeb.wsConsole) startWebSocketServer(); #endif if (confMqtt.mqtt_enable) { mqttPrepareSubscribeTopics(); mqttClientInit(); } sendLog(F("DEV: setup complete."), LOGLEVEL_INFO); } // void setup void loop() { checkMillis(); persWM.handleWiFi(); // in non-blocking mode, handleWiFi must be called in the // main loop yield(); mqttHandleConnection(); yield(); outTempHum_updateOnNewValue(); yield(); dnsServer.processNextRequest(); httpServer.handleClient(); buttonPlus.update(); buttonMinus.update(); buttonMode.update(); pirSensor.update(); yield(); evalCmd(); yield(); if (Serial.available()) { serialEvent(); yield(); } if (updateDisplayImmediately) { updateDisplayImmediately = false; updateDisplay(); } #ifdef ENABLE_FEATURE_WEB_CONSOLE_WEBSOCKETS if (confWeb.wsConsole) { if (WiFi.status() == WL_CONNECTED) { webSocket.loop(); } else { webSocket.disconnect(); } } #endif } // void loop //#ifdef USE_MQTT_TLS //void verifytls() { // // Use WiFiClientSecure class to create TLS connection // Serial.print(F("MQTT: trying TLS connection to ")); // Serial.println(confMqtt.mqtt_server); // if (!wifiClient.connect(confMqtt.mqtt_server, confMqtt.mqtt_port)) { // Serial.println("connection failed"); // return; // } // // if (wifiClient.verify(MQTT_TLS_fingerprint, confMqtt.mqtt_server)) { // Serial.println("certificate matches"); // } else { // Serial.println("certificate doesn't match"); // } //} //#endif