diff --git a/Wetterstation.HMI b/Wetterstation.HMI new file mode 100644 index 0000000000000000000000000000000000000000..a60d230890cfb6a381f23448640624b82e6ac854 Binary files /dev/null and b/Wetterstation.HMI differ diff --git a/wetterstation/wetterstation.ino b/wetterstation/wetterstation.ino new file mode 100644 index 0000000000000000000000000000000000000000..369831e8e15dc775d47622d2fef0c980001b2657 --- /dev/null +++ b/wetterstation/wetterstation.ino @@ -0,0 +1,640 @@ +/********************************************************************* + Weather station with a Nextion display, showing date, time, tempe- + rature, humidity, airpressure and weather forecast using + openweathermap.org + + Setup used: + - NodeMCU (ESP8266) + - BME280 sensor + - Nextion 7" 800x480 display + +********************************************************************** + Last updated 20200128 by McUles + + This code is free for personal use, not for commercial purposes. + Please leave this header intact. + + contact: dev@itstall.de +**********************************************************************/ + +//#define DEBUG +#define DEBUG_NO_PAGE_FADE +#define DEBUG_NO_TOUCH +#define nexSerial Serial1 + +// +// include libraries +// +#include <Wire.h> +#include <SPI.h> // I2C library +#include <ArduinoJson.h> // https://github.com/bblanchon/ArduinoJson +#include <ESP8266WiFi.h> // WiFi library +#include <WiFiUdp.h> // Udp library +#include <TimeLib.h> // Time library + +extern "C" { + #include "user_interface.h" +} + +// +// settings for WiFi connection +// +char * ssid = "Sensors"; // WiFi SSID +char * password = "mySensorNetz"; // WiFi password + +unsigned int localPort = 2390; // local port to listen for UDP packets +IPAddress timeServerIP; // IP address of random server +const char* ntpServerName = "de.pool.ntp.org"; // server pool +byte packetBuffer[48]; // buffer to hold incoming and outgoing packets +int timeZoneoffsetGMT = 3600; // offset from Greenwich Meridan Time +boolean DST = false; // daylight saving time +WiFiUDP clockUDP; // initialize a UDP instance + +// +// settings for the openweathermap connection +// sign up, get your api key and insert it below +// +char * servername = "api.openweathermap.org"; // remote server with weather info +String APIKEY = "45f6ce019087f08b45d73872319c0573"; // personal api key for retrieving the weather data + +const int httpPort = 80; +String result; +int cityIDLoop = 0; + +// a list of cities you want to display the forecast for +// get the ID at https://openweathermap.org/ +// type the city, click search and click on the town +// then check the link, like this: https://openweathermap.org/city/5128581 +// 5128581 is the ID for New York +String cityIDs[] = { + "2836455", // Schonungen + "" // end the list with an empty string +}; + +// +// settings +// +int startupDelay = 1000; // startup delay +int loopDelay = 3000; // main loop delay between sensor updates + +int timeServerDelay = 1000; // delay for the time server to reply +int timeServerPasses = 4; // number of tries to connect to the time server before timing out +int timeServerResyncNumOfLoops = 3000; // number of loops before refreshing the time. one loop takes approx. 28 seconds +int timeServerResyncNumOfLoopsCounter = 0; +boolean timeServerConnected = false; // is set to true when the time is read from the server + +int maxForecastLoop = 10; // number of main loops before the forecast is refreshed, looping through all cities +int weatherForecastLoop = 0; +int weatherForecastLoopInc = 1; + +int displayStartupDimValue = 30; // startup display backlight level +int displayDimValue = 150; // main display backlight level +int displayDimStep = 1; // dim step +int dimStartupDelay = 50; // delay for fading in +int dimPageDelay = 0; // delay for fading between pages + +// +// initialize variables +// +int page = 0; + +float bmeAltitude = 0; +float bmeHumidity = 0; +float bmePressure = 0; +float bmeTemperature = 0; + +String command; +String doubleQuote = "\"\""; + +// +// initialize timer +// +os_timer_t secTimer; + +void timerDisplayTime(void *pArg) { + displayTime(); +} + +// +// setup +// +void setup() { +#ifdef DEBUG + Serial.begin(9600); +#endif + + nexSerial.begin(9600); + + printNextionCommand("dims=" + String(0)); // set initial startup backlight value of the Nextion display to 0 + printNextionCommand("dim=" + String(0)); // also set the current backlight value to 0 + printNextionCommand("page 0"); + + delay(startupDelay); + + displayFadeIn(0, displayStartupDimValue, dimStartupDelay); + + connectToWifi(); + clockUDP.begin(localPort); + getTimeFromServer(); + + if (timeServerConnected) { + displayTime(); + } + + displayDate(); + + os_timer_setfn(&secTimer, timerDisplayTime, NULL); + if (timeServerConnected) { + os_timer_arm(&secTimer, 1000, true); + } + + displayFadeIn(displayStartupDimValue, displayDimValue, dimStartupDelay / 2); + +#ifdef DEBUG + Serial.println("Starting main loop"); +#endif +} + +// +// main loop +// +void loop() { + if (!timeServerConnected) { + getTimeFromServer(); + if (timeServerConnected) + os_timer_arm(&secTimer, 1000, true); + } + + if (page == 0) { + + if (weatherForecastLoop == maxForecastLoop) { + timeServerResyncNumOfLoopsCounter += 1; + if (timeServerResyncNumOfLoopsCounter == timeServerResyncNumOfLoops) { + getTimeFromServer(); + timeServerResyncNumOfLoopsCounter = 0; + } + + page = 1; + getWeatherData(); + weatherForecastLoopInc = -weatherForecastLoopInc; + displayDate(); + } + + delayCheckTouch(loopDelay); + } + + else if (page == 1) { + if (weatherForecastLoop == 0) { + page = 0; + +#if !defined (DEBUG_NO_PAGE_FADE) + displayFadeOut(displayDimValue, dimPageDelay); +#endif + + printNextionCommand("page 0"); + +#if !defined (DEBUG_NO_PAGE_FADE) + displayFadeIn(0, displayDimValue, dimPageDelay); +#endif + + weatherForecastLoopInc = -weatherForecastLoopInc; + + displayTime(); + displayDate(); + } + else + delayCheckTouch(loopDelay); + } + + weatherForecastLoop += weatherForecastLoopInc; + +#ifdef DEBUG + Serial.print(page); + Serial.print(" "); + Serial.print(weatherForecastLoop); + Serial.print(" "); + Serial.print(timeServerResyncNumOfLoopsCounter); + Serial.print(" "); + Serial.print(timeServerResyncNumOfLoops); + Serial.println(""); +#endif +} + +void displayDate() { + time_t t = now(); + + command = "date.txt=\"" + String(day(t)) + " " + monthAsString(month(t)) + "\""; + printNextionCommand(command); + command = "year.txt=\"" + String(year(t)) + "\""; + printNextionCommand(command); +} + +void displayTime() { + time_t t = now(); + char timeString[9]; + + snprintf(timeString, sizeof(timeString), "%02d:%02d:%02d", hour(t), minute(t), second(t)); + command = "time.txt=\"" + String(timeString) + "\""; + printNextionCommand(command); +} + +// +// Nextion commands +// +void printNextionCommand (String command) { + nexSerial.print(command); + endNextionCommand(); +} + +void sendToLCD(uint8_t type, String index, String cmd) { + if (type == 1 ) { + nexSerial.print(index); + nexSerial.print(".txt="); + nexSerial.print("\""); + nexSerial.print(cmd); + nexSerial.print("\""); + } + else if (type == 2) { + nexSerial.print(index); + nexSerial.print(".val="); + nexSerial.print(cmd); + } + else if (type == 3) { + nexSerial.print(index); + nexSerial.print(".pic="); + nexSerial.print(cmd); + } + else if (type == 4 ) { + nexSerial.print("page "); + nexSerial.print(cmd); + } + + endNextionCommand(); +} + +void endNextionCommand() { + nexSerial.write(0xff); + nexSerial.write(0xff); + nexSerial.write(0xff); +} + +void displayFadeIn(int fromValue, int toValue, int fadeDelay) { + for (int i = fromValue; i <= toValue; i += displayDimStep) { + if (i > toValue) { + i = toValue; + } + printNextionCommand("dim=" + String(i)); + delay(fadeDelay); + } +} + +void displayFadeOut(int fromValue, int fadeDelay) { + for (int i = fromValue; i >= 0; i -= displayDimStep) { + if (i < 0) { + i = 0; + } + printNextionCommand("dim=" + String(i)); + delay(fadeDelay); + } +} + +// +// network functions +// +void connectToWifi() { + int wifiBlink = 0; + + // WiFi.enableSTA(true); + WiFi.mode(WIFI_STA); + WiFi.disconnect(); + + WiFi.begin(ssid, password); + + while (WiFi.status() != WL_CONNECTED) { + if (wifiBlink == 0) { + printNextionCommand("wifi_connect.txt=\"connect...\""); + wifiBlink = 1; + } + else { + printNextionCommand("wifi_connect.txt=" + doubleQuote); + wifiBlink = 0; + } + delay(500); + } + + printNextionCommand("wifi_connect.txt=" + doubleQuote); +} + +void getTimeFromServer() { +#ifdef DEBUG + Serial.print("Getting time from server..."); +#endif + + int connectStatus = 0, i = 0; + unsigned long unixTime; + + while (i < timeServerPasses && !connectStatus) { +#ifdef DEBUG + Serial.print(i); + Serial.print("..."); +#endif + + printNextionCommand("time.txt=\"get time..\""); + WiFi.hostByName(ntpServerName, timeServerIP); + sendNTPpacket(timeServerIP); + delay(timeServerDelay / 2); + connectStatus = clockUDP.parsePacket(); + printNextionCommand("time.txt=" + doubleQuote); + delay(timeServerDelay / 2); + i++; + } + + if (connectStatus) { +#ifdef DEBUG + Serial.print(i); + Serial.println("...connected"); +#endif + + timeServerConnected = true; + clockUDP.read(packetBuffer, 48); + + // the timestamp starts at byte 40 of the received packet and is four bytes, or two words, long. + unsigned long highWord = word(packetBuffer[40], packetBuffer[41]); + unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]); + // the timestamp is in seconds from 1900, add 70 years to get Unixtime + unixTime = (highWord << 16 | lowWord) - 2208988800 + timeZoneoffsetGMT; + + if (DST) { + unixTime = unixTime + 3600; + } + + setTime(unixTime); + } + else { +#ifdef DEBUG + Serial.print(i); + Serial.println("...failed..."); +#endif + + printNextionCommand("time.txt=\"failed....\""); + delay(timeServerDelay); + printNextionCommand("time.txt=" + doubleQuote); + } +} + +unsigned long sendNTPpacket(IPAddress& address) { + memset(packetBuffer, 0, 48); + packetBuffer[0] = 0b11100011; // LI, Version, Mode + packetBuffer[1] = 0; // Stratum, or type of clock + packetBuffer[2] = 6; // Polling Interval + packetBuffer[3] = 0xEC; // Peer Clock Precision + // 8 bytes of zero for Root Delay & Root Dispersion + packetBuffer[12] = 49; + packetBuffer[13] = 0x4E; + packetBuffer[14] = 49; + packetBuffer[15] = 52; + + clockUDP.beginPacket(address, 123); //NTP requests are to port 123 + clockUDP.write(packetBuffer, 48); + clockUDP.endPacket(); +} + +// +// get and display weather data +// +void getWeatherData() { //client function to send/receive GET request data. + WiFiClient client; + if (!client.connect(servername, httpPort)) { + return; + } + + String cityID = cityIDs[cityIDLoop]; + cityIDLoop++; + + if (cityIDs[cityIDLoop] == "") { + cityIDLoop = 0; + } + + String url = "/data/2.5/forecast?id=" + cityID + "&units=metric&cnt=1&APPID=" + APIKEY + "&lang=de"; + //String url = "/data/2.5/weather?id=" + cityID + "&units=metric&cnt=1&APPID=" + APIKEY; + //check weather properties at https://openweathermap.org/current + + // This will send the request to the server + client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: " + servername + "\r\n" + "Connection: close\r\n\r\n"); + unsigned long timeout = millis(); + while (client.available() == 0) { + if (millis() - timeout > 5000) { + client.stop(); + return; + } + } + + result = ""; + // Read all the lines of the reply from server + while (client.available()) { + result = client.readStringUntil('\r'); + } + + result.replace('[', ' '); + result.replace(']', ' '); + + char jsonArray [result.length() + 1]; + result.toCharArray(jsonArray, sizeof(jsonArray)); + jsonArray[result.length() + 1] = '\0'; + + StaticJsonBuffer<1024> json_buf; + JsonObject& root = json_buf.parseObject(jsonArray); + if (!root.success()) { + nexSerial.println("parseObject() failed"); + } + + //check properties forecasts at https://openweathermap.org/forecast5 + + int weatherID = root["list"]["weather"]["id"]; + + String tmp0 = root["city"]["name"]; + String tmp1 = root["list"]["weather"]["main"]; + String tmp2 = root["list"]["weather"]["description"]; + float tmp3 = root["list"]["main"]["temp_min"]; + float tmp4 = root["list"]["main"]["temp_max"]; + float tmp5 = root["list"]["main"]["humidity"]; + float tmp6 = root["list"]["clouds"]["all"]; + float tmp7 = root["list"]["rain"]["3h"]; + float tmp8 = root["list"]["snow"]["3h"]; + float tmp9 = root["list"]["wind"]["speed"]; + int tmp10 = root["list"]["wind"]["deg"]; + float tmp11 = root["list"]["main"]["pressure"]; + timeZoneoffsetGMT = root["city"]["timezone"]; + //String tmp12 = root["list"]["dt_text"]; command = command + tmp12; + +#if !defined (DEBUG_NO_PAGE_FADE) + displayFadeOut(displayDimValue, dimPageDelay); +#endif + + printNextionCommand("page 1"); + +#if !defined (DEBUG_NO_PAGE_FADE) + displayFadeIn(0, displayDimValue, dimPageDelay ); +#endif + + setWeatherPicture(weatherID); + sendToLCD(1, "city", tmp0); + sendToLCD(1, "description", tmp2); + sendToLCD(1, "humidity", String(tmp5, 0)); + sendToLCD(1, "rain", String(tmp7, 1)); + sendToLCD(1, "wind_dir", getShortWindDirection(tmp10)); + sendToLCD(1, "wind_speed", String(tmp9, 1)); + sendToLCD(1, "pressure", String(tmp11, 0)); + sendToLCD(1, "clouds", String(tmp6, 0)); + sendToLCD(1, "temp_min", String(tmp3, 1)); + sendToLCD(1, "temp_max", String(tmp4, 1)); +} + +String getWindDirection (int degrees) { + int sector = ((degrees + 11) / 22.5 - 1); + switch (sector) { + case 0: return "Nord"; + case 1: return "Nord-Nordost"; + case 2: return "Nordost"; + case 3: return "Ost-Nordost"; + case 4: return "Ost"; + case 5: return "Ost-Südost"; + case 6: return "Südost"; + case 7: return "Süd-Südost"; + case 8: return "Süd"; + case 9: return "Süd-Südwest"; + case 10: return "Südwest"; + case 11: return "West-Nordwest"; + case 12: return "West"; + case 13: return "West-Nordwest"; + case 14: return "Nordwest"; + case 15: return "Nord-Nordwest"; + } +} + +String getShortWindDirection (int degrees) { + int sector = ((degrees + 11) / 22.5 - 1); + switch (sector) { + case 0: return "N"; + case 1: return "NNO"; + case 2: return "NO"; + case 3: return "ONO"; + case 4: return "O"; + case 5: return "OSO"; + case 6: return "SO"; + case 7: return "SSO"; + case 8: return "S"; + case 9: return "SSW"; + case 10: return "SW"; + case 11: return "WSW"; + case 12: return "W"; + case 13: return "WNW"; + case 14: return "NW"; + case 15: return "NNW"; + } +} + +void setWeatherPicture(int weatherID) { + switch (weatherID) { + case 200: + case 201: + case 202: + case 210: sendToLCD(3, "weatherpic", "26"); break; // tstorm1 + case 211: sendToLCD(3, "weatherpic", "27"); break; // tstorm2 + case 212: sendToLCD(3, "weatherpic", "28"); break; // tstorm3 + case 221: + case 230: + case 231: + case 232: sendToLCD(3, "weatherpic", "27"); break; // tstorm2 + + case 300: + case 301: + case 302: + case 310: + case 311: + case 312: + case 313: + case 314: + case 321: sendToLCD(3, "weatherpic", "15"); break; // rain1 + + case 500: + case 501: sendToLCD(3, "weatherpic", "15"); break; // rain1 + case 502: + case 503: + case 504: sendToLCD(3, "weatherpic", "16"); break; // rain2 + case 511: + case 520: + case 521: sendToLCD(3, "weatherpic", "17"); break; // shower1 + case 522: + case 531: sendToLCD(3, "weatherpic", "18"); break; // shower2 + + case 600: sendToLCD(3, "weatherpic", "20"); break; // snow1 + case 601: sendToLCD(3, "weatherpic", "22"); break; // snow3 + case 602: sendToLCD(3, "weatherpic", "24"); break; // snow5 + case 611: + case 612: sendToLCD(3, "weatherpic", "14"); break; // sleet + case 615: sendToLCD(3, "weatherpic", "20"); break; // snow1 + case 616: sendToLCD(3, "weatherpic", "22"); break; // snow3 + case 620: sendToLCD(3, "weatherpic", "20"); break; // snow1 + case 621: sendToLCD(3, "weatherpic", "22"); break; // snow3 + case 622: sendToLCD(3, "weatherpic", "24"); break; // snow5 + + case 701: + case 711: + case 721: sendToLCD(3, "weatherpic", "13"); break; // mist + case 731: sendToLCD(3, "weatherpic", "10"); break; // dunno + case 741: sendToLCD(3, "weatherpic", "11"); break; // fog + case 751: + case 761: + case 762: + case 771: + case 781: sendToLCD(3, "weatherpic", "10"); break; // dunno + + case 800: sendToLCD(3, "weatherpic", "25"); break; // sunny + case 801: sendToLCD(3, "weatherpic", "5"); break; // cloud1 + case 802: sendToLCD(3, "weatherpic", "7"); break; // cloud3 + case 803: sendToLCD(3, "weatherpic", "8"); break; // cloud4 + case 804: sendToLCD(3, "weatherpic", "14"); break; // overcast + + default: sendToLCD(3, "weatherpic", "10"); break; // dunno + } +} + +void delayCheckTouch (int delayTime) { + unsigned long startMillis = millis(); + + while (millis() - startMillis < delayTime) { + delay(1000); + } +} + +String dayAsString(int day) { + switch (day) { + case 1: return "Sonntag"; + case 2: return "Montag"; + case 3: return "Dienstag"; + case 4: return "Mittwoch"; + case 5: return "Donnerstag"; + case 6: return "Freitag"; + case 7: return "Samstag"; + } + return "" ; +} + +String monthAsString(int month) { + switch (month) { + case 1: return "Januar"; + case 2: return "Februar"; + case 3: return "März"; + case 4: return "April"; + case 5: return "Mai"; + case 6: return "Juni"; + case 7: return "Juli"; + case 8: return "August"; + case 9: return "September"; + case 10: return "Oktober"; + case 11: return "November"; + case 12: return "Dezember"; + } + return "" ; +}