Weather and Forecast Display for Swedish FHEM users
-
My need for a indoor and outdoor temperature and weather display was a part of my try-out of Fhem as a MySensors Controller. It turned out surprisingly well. A bit of newbie struggling with German, Perl and regExp I admit, but in the end it turned out to be more simple than that. It was actually harder to make it display the weather forecast conditions in Swedish, which was finally solved by using the weather provider condition code, progmem and custum created lcd characters.
I hope bits and pieces can be useful even if you don't want the exact same application.
The project was based on the MySensors display exemple and BulldogLowell's weather station project.
Where some of my modifications to that are:- Removed scene controller button and internal sensor functionality.
- Changed date and time format
- Fewer slides and less static information per slide.
- Some more info from controller (other sensors).
- Weather conditions in Swedish
Controller (FHEM) runs on a RPiB+ and MySensors gateway is Ethernet(W5100). Perhaps it could be necessary to add timers to the controllers "set" transmission code with a faster one. I haven't seen any issues this far (a week).
The wiring is just the basic for radio and display.
And I used this box and this display and i2c-interface.
Slide 1 presents date and time and update-status if time or weather not has been updated in a while (some hours).
Slide 2 Indoor and outdoor temperature and humidity. (preferably from other MYS-nodes, but for this particular project I used the weather service providers data for outdoor temp only because my mys-network hasn't any "outdoor access")
Slide 3 Today's forecast and High temp and Low temp.
Slide 4 Tomorrow's forecast, high temp and low temp.
fhem.cfg -extract
(Creation of weather service and other MySensor devices should be straight forward. I'll try to help and share more if there are questions.)define MYSENSOR_112 MYSENSORS_DEVICE 112 attr MYSENSOR_112 IODev mysEthGateway1 attr MYSENSOR_112 mapReading_button_off3 3 button_off attr MYSENSOR_112 mapReading_button_on3 3 button_on attr MYSENSOR_112 mapReading_conditions 3 value5 attr MYSENSOR_112 mapReading_forecast 4 value3 attr MYSENSOR_112 mapReading_inHumid 4 value2 attr MYSENSOR_112 mapReading_inTemp 4 value1 attr MYSENSOR_112 mapReading_outHumid 3 value2 attr MYSENSOR_112 mapReading_outTemp 3 value1 attr MYSENSOR_112 mapReading_switch4 4 switch attr MYSENSOR_112 mapReading_todayHigh 3 value4 attr MYSENSOR_112 mapReading_todayLow 3 value3 attr MYSENSOR_112 mapReading_tomorrowHigh 4 value4 attr MYSENSOR_112 mapReading_tomorrowLow 4 value5 attr MYSENSOR_112 mode node attr MYSENSOR_112 room Hall attr MYSENSOR_112 setReading_conditions 1 attr MYSENSOR_112 setReading_forecast 1 attr MYSENSOR_112 setReading_inHumid 1 attr MYSENSOR_112 setReading_inTemp 1 attr MYSENSOR_112 setReading_outHumid 1 attr MYSENSOR_112 setReading_outTemp 1 attr MYSENSOR_112 setReading_switch4 on,off attr MYSENSOR_112 setReading_todayHigh 1 attr MYSENSOR_112 setReading_todayLow 1 attr MYSENSOR_112 setReading_tomorrowHigh 1 attr MYSENSOR_112 setReading_tomorrowLow 1 attr MYSENSOR_112 stateFormat switch4 attr MYSENSOR_112 verbose 3 attr MYSENSOR_112 version 1.4.1 define myWeather2disp_ntfy notify myWeather {\ my $a=ReadingsVal("myWeather","humidity",0);;;;\ fhem("set MYSENSOR_112 outHumid ".$a);;;;\ $a=ReadingsVal("myWeather","temp_c",0);;;;\ fhem("set MYSENSOR_112 outTemp ".$a);;;;\ $a=ReadingsVal("myWeather","fc1_high_c",0);;;;\ fhem("set MYSENSOR_112 todayHigh ".$a);;;;\ $a=ReadingsVal("myWeather","fc1_low_c",0);;;;\ fhem("set MYSENSOR_112 todayLow ".$a);;;;\ $a=ReadingsVal("myWeather","fc2_high_c",0);;;;\ fhem("set MYSENSOR_112 tomorrowHigh ".$a);;;;\ $a=ReadingsVal("myWeather","fc2_low_c",0);;;;\ fhem("set MYSENSOR_112 tomorrowLow ".$a);;;;\ $a=ReadingsVal("myWeather","fc1_code",0);;;;\ fhem("set MYSENSOR_112 conditions ".$a);;;;\ $a=ReadingsVal("myWeather","fc2_code",0);;;;\ fhem("set MYSENSOR_112 forecast ".$a)\ } attr myWeather2disp_ntfy room Macro,Hall define myIndoorTemp2disp_ntfy notify MYSENSOR_105 {\ my $a=ReadingsVal("MYSENSOR_105","temperature1",0);;;;\ $a=int($a+0.5);;;;\ fhem("set MYSENSOR_112 inTemp ".$a);;;;\ $a=ReadingsVal("MYSENSOR_105","humidity",0);;;;\ $a=int($a+0.5);;;;\ fhem("set MYSENSOR_112 inHumid ".$a)\ } attr myIndoorTemp2disp_ntfy room Hall,Macro
Sensor (Display) Node Code
(v2.5 presented below, original 2.3 is here.)/* EgWeatherDisplay2.5 Weather Display for FHEM with Swedish conditions By m26872, 20150826 changes from 2.4 to 2.5 - Automatic DST time for the next 4 years (controller still sync with UTC) - Now use the gw.wait() - Cleaned displays from some static info - Updated child-ids, sensor and variable definitions to to aline with MySensors 1.5 and to avoid unwanted autocreations by fhem. changes from 2.3 to 2.4 - New (correct) translations for some conditions - Removed two unused "scene button" declarations. */ #define STATES 4 #define CLOCK_SYNC_INTERVAL 3600000UL // 1 hour #define DISPLAY_INTERVAL 3500UL // 3,5s #define FIVE_HOURS 18000000UL #define THREE_HOURS 10800000UL #define ONE_MIN 60000UL #define DST_YEARS 5 #define Y2015_DST_ON 1427594400UL // 2015-03-29 02:00 #define Y2015_DST_OFF 1445738400UL // 2015-10-25 02:00 1445742000UL // 2015-10-25 03:00 #define Y2016_DST_ON 1459044000UL // 2016-03-27 02:00 #define Y2016_DST_OFF 1477792800UL // 2016-10-30 02:00 #define Y2017_DST_ON 1490493600UL // 2017-03-26 02:00 #define Y2017_DST_OFF 1509242400UL // 2017-10-29 02:00 #define Y2018_DST_ON 1521943200UL // 2018-03-25 02:00 #define Y2018_DST_OFF 1540692000UL // 2018-10-28 02:00 #define Y2019_DST_ON 1553997600UL // 2019-03-31 02:00 #define Y2019_DST_OFF 1572141600UL // 2019-10-27 02:00 #define DEBUG #ifdef DEBUG #define DEBUG_SERIAL(x) Serial.begin(x) #define DEBUG_PRINT(x) Serial.print(x) #define DEBUG_PRINTLN(x) Serial.println(x) #else #define DEBUG_SERIAL(x) #define DEBUG_PRINT(x) #define DEBUG_PRINTLN(x) #endif #include <avr/pgmspace.h> #include <Wire.h> #include <Time.h> #include <SPI.h> #include <MySensor.h> #include <LiquidCrystal_I2C.h> #define NODE_ID 112 #define CHILD_ID_3 3 #define CHILD_ID_4 4 LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // Set the LCD I2C address (Enl: https://arduino-info.wikispaces.com/LCD-Blue-I2C) void (*lcdDisplay[STATES])(); byte state = STATES-1; byte timeCounter = 0; unsigned long lastTime; unsigned long lastClockSet; unsigned long lastClockReceived = 0UL; unsigned long lastWeatherUpdate = 0UL; int indoorTemp = -99; int indoorHumidity = -99; int outdoorTemp = -99; int outdoorHumidity = -99; int todayHigh = -99; int todayLow = -99; int tomorrowHigh = -99; int tomorrowLow = -99; int todayConditionCode = -99; int tomorrowConditionCode = -99; String todayCondition = "Not yet Reported"; String tomorrowCondition = "Not yet Reported"; unsigned long dstTable[DST_YEARS][2] = {{Y2015_DST_ON,Y2015_DST_OFF},{Y2016_DST_ON,Y2016_DST_OFF},{Y2017_DST_ON,Y2017_DST_OFF},{Y2018_DST_ON,Y2018_DST_OFF},{Y2019_DST_ON,Y2019_DST_OFF}}; // MySensor related MySensor gw; MyMessage msgVAR1(CHILD_ID_3, V_VAR1);// outdoor temperature MyMessage msgVAR2(CHILD_ID_3, V_VAR2);// outdoor humidity MyMessage msgVAR3(CHILD_ID_3, V_VAR3);// today's low MyMessage msgVAR4(CHILD_ID_3, V_VAR4);// today's high MyMessage msgVAR5(CHILD_ID_3, V_VAR5);// conditions MyMessage msgBackLight(CHILD_ID_4, V_STATUS); MyMessage msgLVAR1(CHILD_ID_4, V_VAR1); // indoor temperature MyMessage msgLVAR2(CHILD_ID_4, V_VAR2); // indoor humidity MyMessage msgLVAR3(CHILD_ID_4, V_VAR3); // forecast for tomorrow // Swedish weather condition translations stored to flash. Special characters (å,ä,ö,Å,Ä,Ö) substituted with resp. LCD custom made character (01-06). const char cs0[] PROGMEM = "Tornado"; // tornado const char cs1[] PROGMEM = "Tropisk storm"; // tropical storm const char cs2[] PROGMEM = "Orkan"; // hurricane const char cs3[] PROGMEM = "Kraftig \01ska"; // severe thunderstorms const char cs4[] PROGMEM = "\04ska"; // thunderstorms const char cs5[] PROGMEM = "Sn\03blandat regn"; // mixed rain and snow const char cs6[] PROGMEM = "Regn o bl\03tsn\03"; // mixed rain and sleet const char cs7[] PROGMEM = "Sn\03 och bl\03tsn\03"; // mixed snow and sleet const char cs8[] PROGMEM = "Underkylt dugg"; // freezing drizzle const char cs9[] PROGMEM = "Duggregn"; // drizzle const char cs10[] PROGMEM = "Underkylt regn"; // freezing rain const char cs11[] PROGMEM = "Skurar"; // showers const char cs12[] PROGMEM = "Skurar"; // showers const char cs13[] PROGMEM = "Sn\03 o vindbyar"; // snow flurries const char cs14[] PROGMEM = "L\02tta sn\03byar"; // light snow showers const char cs15[] PROGMEM = "Sn\03 o bl\01st"; // blowing snow const char cs16[] PROGMEM = "Sn\03"; // snow const char cs17[] PROGMEM = "Hagel"; // hail const char cs18[] PROGMEM = "Bl\03tsn\03"; // sleet const char cs19[] PROGMEM = "Damm"; // dust const char cs20[] PROGMEM = "Dimma"; // foggy const char cs21[] PROGMEM = "Disigt"; // haze const char cs22[] PROGMEM = "R\03kigt"; // smoky const char cs23[] PROGMEM = "H\01rd vind"; // blustery const char cs24[] PROGMEM = "Bl\01sigt"; // windy const char cs25[] PROGMEM = "Kallt"; // cold const char cs26[] PROGMEM = "Mulet"; // cloudy const char cs27[] PROGMEM = "\06verv\02g. mulet"; // mostly cloudy (night) const char cs28[] PROGMEM = "\06verv\02g. mulet"; // mostly cloudy (day) const char cs29[] PROGMEM = "V\02xl. molnighet"; // partly cloudy (night) const char cs30[] PROGMEM = "V\02xl. molnighet"; // partly cloudy (day) const char cs31[] PROGMEM = "Klart"; // clear (night) const char cs32[] PROGMEM = "Soligt"; // sunny const char cs33[] PROGMEM = "\06verv\02g. klart"; // fair (night) const char cs34[] PROGMEM = "\06verv\02g. soligt"; // fair (day) const char cs35[] PROGMEM = "Regn och hagel"; // mixed rain and hail const char cs36[] PROGMEM = "Varmt"; // hot const char cs37[] PROGMEM = "Enstaka \01ska"; // isolated thunderstorms const char cs38[] PROGMEM = "Spridd \01ska"; // scattered thunderstorms const char cs39[] PROGMEM = "Spridd \01ska"; // scattered thunderstorms const char cs40[] PROGMEM = "Spridda skurar"; // scattered showers const char cs41[] PROGMEM = "Krafigt sn\03fall"; // heavy snow const char cs42[] PROGMEM = "Spridda sn\03byar"; // scattered snow showers const char cs43[] PROGMEM = "Krafigt sn\03fall"; // heavy snow const char cs44[] PROGMEM = "V\02xl. molnighet"; // partly cloudy const char cs45[] PROGMEM = "\04skskurar"; // thundershowers const char cs46[] PROGMEM = "Sn\03byar"; // snow showers const char cs47[] PROGMEM = "Enst. \01skskurar"; // isolated thundershowers const char cs48[] PROGMEM = "(not available)"; // (3200) not available const char* const condStrTable[] PROGMEM = {cs0,cs1,cs2,cs3,cs4,cs5,cs6,cs7,cs8,cs9,cs10,cs11,cs12,cs13,cs14,cs15,cs16,cs17,cs18,cs19, cs20,cs21,cs22,cs23,cs24,cs25,cs26,cs27,cs28,cs29,cs30,cs31,cs32,cs33,cs34,cs35,cs36,cs37,cs38,cs39,cs40,cs41,cs42,cs43,cs44,cs45,cs46,cs47,cs48}; char condString[17]; void setup() { DEBUG_SERIAL(115200); DEBUG_PRINTLN(F("Serial started")); lcdDisplay[0] = lcdDisplay0; lcdDisplay[1] = lcdDisplay1; lcdDisplay[2] = lcdDisplay2; lcdDisplay[3] = lcdDisplay3; gw.begin(getVariables, NODE_ID); gw.sendSketchInfo("WeatherDisplay", "2.5"); gw.present(CHILD_ID_3, S_CUSTOM); gw.present(CHILD_ID_4, S_LIGHT); lcd.begin(16,2); createSweCaracters(); lcd.setCursor(0, 0); lcd.print(F("Syncing Time")); lcd.setCursor(0, 1); int clockCounter = 0; while(timeStatus() == timeNotSet && clockCounter < 60) { gw.process(); DEBUG_PRINTLN(F("getting Time")); gw.requestTime(receiveTime); gw.wait(1000); lcd.print("."); clockCounter++; if (clockCounter > 16) { lcd.clear(); lcd.print(F("**Failed Clock**")); lcd.setCursor(0,1); lcd.print(F("*Syncronization*")); gw.wait(2000); break; } } lcd.clear(); lastTime = millis(); } void loop() { gw.process(); if (millis() - lastClockSet >= CLOCK_SYNC_INTERVAL) { DEBUG_PRINTLN(F("getting Time, again")); gw.requestTime(receiveTime); lastClockSet = millis(); } if (millis() - lastTime >= DISPLAY_INTERVAL) { state++; if (state > STATES - 1) state = 0; DEBUG_PRINT(F("State:")); DEBUG_PRINTLN(state); lastTime += DISPLAY_INTERVAL; fastClear(); lcdDisplay[state](); } } void fastClear() { lcd.setCursor(0,0); lcd.print(F(" ")); lcd.setCursor(0,1); lcd.print(F(" ")); } void lcdDisplay0() { lcd.setCursor(0,0); lcd.print(year()); lcd.print("-"); DEBUG_PRINT(F("Date: ")); DEBUG_PRINT(year()); DEBUG_PRINT("-"); if (month() < 10) { lcd.print("0"); DEBUG_PRINT("0"); } lcd.print(month()); lcd.print("-"); DEBUG_PRINT(month()); DEBUG_PRINT("-"); if (day() < 10) { lcd.print("0"); DEBUG_PRINT("0"); } lcd.print(day()); DEBUG_PRINT(day()); lcd.print(" "); DEBUG_PRINT(F(" Time: ")); if (hour() < 10) { lcd.print("0"); DEBUG_PRINT("0"); } lcd.print(hour()); lcd.print(":"); DEBUG_PRINT(hour()); DEBUG_PRINT(":"); if (minute() < 10) { lcd.print("0"); DEBUG_PRINT("0"); } lcd.print(minute()); DEBUG_PRINTLN(minute()); lcd.setCursor(0,1); if (millis() - lastClockSet > FIVE_HOURS) { lcd.print(F("Clk ")); lcd.print((millis()-lastClockSet)/ONE_MIN); lcd.print(F(" min ago")); DEBUG_PRINT(F(" (ClockSync > FIVE_HOURS): lastClockSet ")); DEBUG_PRINT((millis()-lastClockSet)/ONE_MIN); DEBUG_PRINTLN(F(" min ago")); } else if (millis() - lastWeatherUpdate > THREE_HOURS) { // ( Controller weather update set to 1/hour ) lcd.print(F("Wtr ")); lcd.print((millis()-lastWeatherUpdate)/ONE_MIN); lcd.print(F(" min ago")); DEBUG_PRINT(F(" (lastWeatherUpdate > THREE_HOURS): lastWeatherUpdate ")); DEBUG_PRINT((millis()-lastWeatherUpdate)/ONE_MIN); DEBUG_PRINTLN(F(" min ago")); } else { lcd.print(F(" ")); } } void lcdDisplay1() { lcd.setCursor(0,0); //lcd.print(F("In T:")); lcd.print(F("In ")); lcd.print(indoorTemp); lcd.print(char(223)); DEBUG_PRINT(F("In T:")); DEBUG_PRINT(indoorTemp); DEBUG_PRINT("C"); //lcd.print(" H:"); lcd.print(" "); lcd.print(indoorHumidity); lcd.print("%"); DEBUG_PRINT(F(" H:")); DEBUG_PRINT(indoorHumidity); DEBUG_PRINTLN("%"); lcd.setCursor(0,1); //lcd.print(F("Ut T:")); lcd.print(F("Ut ")); lcd.print(outdoorTemp); lcd.print(char(223)); DEBUG_PRINT(F("Out T:")); DEBUG_PRINT(outdoorTemp); DEBUG_PRINT("C"); //lcd.print(" H:"); lcd.print(" "); lcd.print(outdoorHumidity); lcd.print("%"); DEBUG_PRINT(F(" H:")); DEBUG_PRINT(outdoorHumidity); DEBUG_PRINTLN("%"); } void lcdDisplay2() { lcd.setCursor(0,0); //lcd.print(F("1 H:")); lcd.print(F("1 ")); lcd.print(todayHigh); lcd.print(char(223)); DEBUG_PRINT(F("Today's High: ")); DEBUG_PRINT(todayHigh); DEBUG_PRINT(F("C ")); //lcd.print(F(" L:")); lcd.print(F(" ")); lcd.print(todayLow); lcd.print(char(223)); DEBUG_PRINT(F("Today's Low: ")); DEBUG_PRINT(todayLow); DEBUG_PRINTLN(F("C ")); lcd.setCursor(0,1); lcd.print(todayCondition); DEBUG_PRINT(F("Today's Weather (code): ")); DEBUG_PRINTLN(todayConditionCode); } void lcdDisplay3() { lcd.setCursor(0,0); //lcd.print(F("2 H:")); lcd.print(F("2 ")); lcd.print(tomorrowHigh); lcd.print(char(223)); DEBUG_PRINT(F("Tomorrow's High: ")); DEBUG_PRINT(tomorrowHigh); DEBUG_PRINT(F("C ")); //lcd.print(F(" L:")); lcd.print(F(" ")); lcd.print(tomorrowLow); lcd.print(char(223)); DEBUG_PRINT(F("Tomorrow's Low: ")); DEBUG_PRINT(tomorrowLow); DEBUG_PRINTLN(F("C ")); lcd.setCursor(0,1); lcd.print(tomorrowCondition); DEBUG_PRINT(F("Tomorrow's Weather (code): ")); DEBUG_PRINTLN(tomorrowConditionCode); } void receiveTime(unsigned long time) { DEBUG_PRINT(F("Time value received: ")); // (UTC) DEBUG_PRINTLN(time); setTime(time); DEBUG_PRINT(F("Hour received: ")); // (UTC) DEBUG_PRINTLN(hour()); setTime(hour()+1,minute(),second(),day(),month(),year()); // Sets CET timezone (GMT +1) DEBUG_PRINT(F("Time zone hour: ")); // (UTC) DEBUG_PRINTLN(hour()); adjustDST(time); // Sets DST (+1) if necessary DEBUG_PRINT(F("Adjusted DST hour: ")); // (UTC) DEBUG_PRINTLN(hour()); } void adjustDST(unsigned long t) { if ((year() >= 2015) && (year() < 2015+DST_YEARS)) { if ((t > dstTable[year()-2015][0]) && (t < dstTable[year()-2015][1])) { setTime(hour()+1,minute(),second(),day(),month(),year()); } } } void getVariables(const MyMessage &message) { if (message.sensor == CHILD_ID_3) { if (message.type == V_VAR1) { outdoorTemp = atoi(message.data); DEBUG_PRINT(F("outdoorTemp recieved:")); DEBUG_PRINTLN(outdoorTemp); } if (message.type == V_VAR2) { outdoorHumidity = atoi(message.data); DEBUG_PRINT(F("outdoorHumidity recieved:")); DEBUG_PRINTLN(outdoorHumidity); } if (message.type == V_VAR3) { todayLow = atoi(message.data); DEBUG_PRINT(F("Received Today's LOW:")); DEBUG_PRINTLN(todayLow); } if (message.type == V_VAR4) { todayHigh = atoi(message.data); DEBUG_PRINT(F("Received Today's HIGH:")); DEBUG_PRINTLN(todayHigh); } if (message.type == V_VAR5) { todayConditionCode = atoi(message.data); DEBUG_PRINT(F("Received today's Conditions: ")); DEBUG_PRINT(todayConditionCode); DEBUG_PRINT(" "); getNewSweCond(todayConditionCode); todayCondition = condString; DEBUG_PRINTLN(todayCondition); lastWeatherUpdate = millis(); DEBUG_PRINTLN(F(" and reset the lastWeatherUpdate-counter.")); } } if (message.sensor == CHILD_ID_4) { if (message.type == V_STATUS) { int ledState = atoi(message.data); if (ledState > 0) { lcd.backlight(); } else { lcd.noBacklight(); } } if (message.type == V_VAR1) { indoorTemp = atoi(message.data); DEBUG_PRINT(F("indoorTemp recieved:")); DEBUG_PRINTLN(indoorTemp); } if (message.type == V_VAR2) { indoorHumidity = atoi(message.data); DEBUG_PRINT(F("indoorHumidity recieved:")); DEBUG_PRINTLN(indoorHumidity); } if (message.type == V_VAR3) { tomorrowConditionCode = atoi(message.data); DEBUG_PRINT(F("Received Tomorrow's Forecast: ")); DEBUG_PRINT(tomorrowConditionCode); DEBUG_PRINT(" "); getNewSweCond(tomorrowConditionCode); tomorrowCondition = condString; DEBUG_PRINTLN(tomorrowCondition); } if (message.type == V_VAR4) { tomorrowHigh = atoi(message.data); DEBUG_PRINT(F("Received Tomorrow's HIGH:")); DEBUG_PRINTLN(tomorrowHigh); } if (message.type == V_VAR5) { tomorrowLow = atoi(message.data); DEBUG_PRINT(F("Received Tomorrow's LOW:")); DEBUG_PRINTLN(tomorrowLow); } } } // Update the translated condition. void getNewSweCond(int code) { if (code > 0 && code < 48) { strcpy_P(condString, (char*)pgm_read_word(&(condStrTable[code]))); } else { strcpy_P(condString, (char*)pgm_read_word(&(condStrTable[48]))); } } void createSweCaracters(){ byte AwithRing[8] = { B00100, B01010, B01110, B00001, B01111, B10001, B01111, }; byte AwithDots[8] = { B01010, B00000, B01110, B00001, B01111, B10001, B01111, }; byte OwithDots[8] = { B01010, B00000, B01110, B10001, B10001, B10001, B01110, }; byte CapitalAwithRing[8] = { B00100, B01010, B01110, B10001, B11111, B10001, B10001, }; byte CapitalAwithDots[8] = { B01010, B00000, B01110, B10001, B11111, B10001, B10001, }; byte CapitalOwithDots[8] = { B01010, B00000, B01110, B10001, B10001, B10001, B01110, }; lcd.createChar(1, AwithRing); lcd.createChar(2, AwithDots); lcd.createChar(3, OwithDots); lcd.createChar(4, CapitalAwithRing); lcd.createChar(5, CapitalAwithDots); lcd.createChar(6, CapitalOwithDots); }
-
This is so great! Thank you!
-
Updated display node code to latest version (v2.5). The recent DST change passed without issues.
-
Yahoo world weather service seems to be have closed down. I got the last update the 24th.
-
So I changed the weather service provider to the FHEM-proven Openweather/wetter.com. I still only send the forecast conditions as codes to spare some MyS net traffic. I'm not as confident of my translations this time.
I only post the node code below. For the Fhem part I just followed this Openweather wiki/guide and changed to new ReadingsVal in my myWeather2disp_ntfy macro posted above.
/* EgWeatherDisplay2.6 Weather and Forecast Display for FHEM with Swedish Conditions By m26872, 20160329 changes from 2.5 to 2.6 - Changed weather condition codes and translations due to changed service provider (from yahoo to wetter.com) changes from 2.4 to 2.5 - Automatic DST time for the next 4 years (controller always sync with UTC) - Now use the gw.wait() - Cleaned displays from some static info - Updated child-ids, sensor and variable definitions to to aline with MySensors 1.5 and to avoid unwanted autocreations by fhem. changes from 2.3 to 2.4 - New (correct) translations for some conditions - Removed two unused "scene button" declarations. */ #define STATES 4 #define CLOCK_SYNC_INTERVAL 3600000UL // 1 hour #define DISPLAY_INTERVAL 3500UL // 3,5s #define FIVE_HOURS 18000000UL #define THREE_HOURS 10800000UL #define ONE_MIN 60000UL #define DST_YEARS 5 #define Y2015_DST_ON 1427594400UL // 2015-03-29 02:00 #define Y2015_DST_OFF 1445738400UL // 2015-10-25 02:00 1445742000UL // 2015-10-25 03:00 #define Y2016_DST_ON 1459044000UL // 2016-03-27 02:00 #define Y2016_DST_OFF 1477792800UL // 2016-10-30 02:00 #define Y2017_DST_ON 1490493600UL // 2017-03-26 02:00 #define Y2017_DST_OFF 1509242400UL // 2017-10-29 02:00 #define Y2018_DST_ON 1521943200UL // 2018-03-25 02:00 #define Y2018_DST_OFF 1540692000UL // 2018-10-28 02:00 #define Y2019_DST_ON 1553997600UL // 2019-03-31 02:00 #define Y2019_DST_OFF 1572141600UL // 2019-10-27 02:00 #define INDEX_LIST_LAST_POS 42 //#define DEBUG #ifdef DEBUG #define DEBUG_SERIAL(x) Serial.begin(x) #define DEBUG_PRINT(x) Serial.print(x) #define DEBUG_PRINTLN(x) Serial.println(x) #else #define DEBUG_SERIAL(x) #define DEBUG_PRINT(x) #define DEBUG_PRINTLN(x) #endif #include <avr/pgmspace.h> #include <Wire.h> #include <Time.h> #include <SPI.h> #include <MySensor.h> #include <LiquidCrystal_I2C.h> #define NODE_ID 112 #define CHILD_ID_3 3 #define CHILD_ID_4 4 LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // Set the LCD I2C address (Enl: https://arduino-info.wikispaces.com/LCD-Blue-I2C) void (*lcdDisplay[STATES])(); byte state = STATES-1; byte timeCounter = 0; unsigned long lastTime; unsigned long lastClockSet; unsigned long lastClockReceived = 0UL; unsigned long lastWeatherUpdate = 0UL; int indoorTemp = -99; int indoorHumidity = -99; int outdoorTemp = -99; int outdoorHumidity = -99; int todayHigh = -99; int todayLow = -99; int tomorrowHigh = -99; int tomorrowLow = -99; int todayConditionCode = -99; int tomorrowConditionCode = -99; String todayCondition = "Not yet Reported"; String tomorrowCondition = "Not yet Reported"; unsigned long dstTable[DST_YEARS][2] = {{Y2015_DST_ON,Y2015_DST_OFF},{Y2016_DST_ON,Y2016_DST_OFF},{Y2017_DST_ON,Y2017_DST_OFF},{Y2018_DST_ON,Y2018_DST_OFF},{Y2019_DST_ON,Y2019_DST_OFF}}; MySensor gw; MyMessage msgVAR1(CHILD_ID_3, V_VAR1);// outdoor temperature MyMessage msgVAR2(CHILD_ID_3, V_VAR2);// outdoor humidity MyMessage msgVAR3(CHILD_ID_3, V_VAR3);// today's low MyMessage msgVAR4(CHILD_ID_3, V_VAR4);// today's high MyMessage msgVAR5(CHILD_ID_3, V_VAR5);// conditions MyMessage msgBackLight(CHILD_ID_4, V_STATUS); MyMessage msgLVAR1(CHILD_ID_4, V_VAR1); // indoor temperature MyMessage msgLVAR2(CHILD_ID_4, V_VAR2); // indoor humidity MyMessage msgLVAR3(CHILD_ID_4, V_VAR3); // forecast for tomorrow // Swedish weather condition translations stored to flash. Special characters (å,ä,ö,Å,Ä,Ö) substituted with resp. LCD custom made character (01-06). const char cs0[] PROGMEM = "Soligt o klart"; // sonnig const char cs1[] PROGMEM = "V\02xl. molnighet"; // leicht bewölkt const char cs2[] PROGMEM = "\06verv\02g. mulet"; // wolkig const char cs3[] PROGMEM = "Mulet"; // bedeckt const char cs4[] PROGMEM = "Disigt"; // Nebel const char cs5[] PROGMEM = "Duggregn"; // Sprühregen const char cs6[] PROGMEM = "Regn"; // Regen const char cs7[] PROGMEM = "Sn\03fall"; // Schnee const char cs8[] PROGMEM = "Skurar"; // Schauer const char cs9[] PROGMEM = "\04ska"; // Gewitter const char cs10[] PROGMEM = "\06verv\02g. soligt"; // leicht bewölkt const char cs20[] PROGMEM = "V\02xl. molnighet"; // wolkig const char cs30[] PROGMEM = "\06verv\02g. mulet"; // bedeckt const char cs40[] PROGMEM = "Dimma"; // Nebel const char cs45[] PROGMEM = "Tjock dimma"; // Nebel const char cs48[] PROGMEM = "Underkylt dis"; // Nebel mit Reifbildung const char cs49[] PROGMEM = "Underkylt dimma"; // Nebel mit Reifbildung const char cs50[] PROGMEM = "Duggregn"; // Sprühregen const char cs51[] PROGMEM = "Enstaka duggregn"; // leichter Sprühregen const char cs53[] PROGMEM = "Duggregn"; // Sprühregen const char cs55[] PROGMEM = "Rikligt duggregn"; // starker Sprühregen const char cs56[] PROGMEM = "Underkylt d.regn"; // leichter Sprühregen, gefrierend const char cs57[] PROGMEM = "Underkylt d.regn" ; // starker Sprühregen, gefrierend const char cs60[] PROGMEM = "L\02tt regn"; // leichter Regen const char cs61[] PROGMEM = "L\02tt regn"; // leichter Regen const char cs63[] PROGMEM = "Regn"; // mäßiger Regen const char cs65[] PROGMEM = "Kraftigt regn"; // starker Regen const char cs66[] PROGMEM = "L\02tt underk.regn"; // leichter Regen, gefrierend const char cs67[] PROGMEM = "Underkylt regn"; // mäßiger od. starker Regen, gefrierend const char cs68[] PROGMEM = "L\02tt sn\03bl.regn"; // leichter Schnee-Regen const char cs69[] PROGMEM = "Sn\03bl.regn"; // starker Schnee-Regen const char cs70[] PROGMEM = "L\02tt sn\03fall"; // leichter Schneefall const char cs71[] PROGMEM = "L\02tt sn\03fall"; // leichter Schneefall const char cs73[] PROGMEM = "Sn\03fall"; // mäßiger Schneefall const char cs75[] PROGMEM = "Krafigt sn\03fall" ; // starker Schneefall const char cs80[] PROGMEM = "Enstaka skurar"; // leichter Regen - Schauer const char cs81[] PROGMEM = "Skurar"; // Regen - Schauer const char cs82[] PROGMEM = "Spridda skurar"; // starker Regen - Schauer const char cs83[] PROGMEM = "Enstaka by/skur"; // leichter Schnee / Regen - Schauer const char cs84[] PROGMEM = "Spridda by/skur"; // starker Schnee / Regen - Schauer const char cs85[] PROGMEM = "Enstaka sn\03byar" ; // leichter Schnee - Schauer const char cs86[] PROGMEM = "Spridda sn\03byar"; // mäßiger od. starker Schnee - Schauer const char cs90[] PROGMEM = "\04ska" ; // Gewitter const char cs95[] PROGMEM = "Enstaka \01ska" ; // leichtes Gewitter const char cs96[] PROGMEM = "Spridd \01ska" ; // starkes Gewitter const char cs999[] PROGMEM = "(Ingen uppgift)"; // Keine Angabe const char cs9999[] PROGMEM = "(not available)"; // (3200) not available const char* const condStrTable[] PROGMEM = {cs0,cs1,cs2,cs3,cs4,cs5,cs6,cs7,cs8,cs9,cs10,cs20,cs30,cs40,cs45,cs48,cs49,cs50,cs51,cs53,cs55,cs57,cs60,cs61,cs63,cs65,cs66,cs67,cs68,cs69,cs70,cs71,cs73,cs75,cs80,cs81,cs82,cs83,cs84,cs85,cs86,cs90,cs95,cs96,cs999,cs9999}; char condString[17]; const int indexList[] PROGMEM = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,20,30,40,45,48,49,50,51,53,55,57,60,61,63,65,66,67,68,69,70,71,73,75,80,81,82,83,84,85,86,90,95,96,999,9999}; void setup() { DEBUG_SERIAL(115200); DEBUG_PRINTLN(F("Serial started")); lcdDisplay[0] = lcdDisplay0; lcdDisplay[1] = lcdDisplay1; lcdDisplay[2] = lcdDisplay2; lcdDisplay[3] = lcdDisplay3; gw.begin(getVariables, NODE_ID); gw.sendSketchInfo("WeatherDisplay", "2.6"); gw.present(CHILD_ID_3, S_CUSTOM); gw.present(CHILD_ID_4, S_LIGHT); lcd.begin(16,2); createSweCaracters(); lcd.setCursor(0, 0); lcd.print(F("Syncing Time")); lcd.setCursor(0, 1); int clockCounter = 0; while(timeStatus() == timeNotSet && clockCounter < 60) { gw.process(); DEBUG_PRINTLN(F("getting Time")); gw.requestTime(receiveTime); gw.wait(1000); lcd.print("."); clockCounter++; if (clockCounter > 16) { lcd.clear(); lcd.print(F("**Failed Clock**")); lcd.setCursor(0,1); lcd.print(F("*Syncronization*")); gw.wait(2000); break; } } lcd.clear(); lastTime = millis(); } void loop() { gw.process(); if (millis() - lastClockSet >= CLOCK_SYNC_INTERVAL) { DEBUG_PRINTLN(F("getting Time, again")); gw.requestTime(receiveTime); lastClockSet = millis(); } if (millis() - lastTime >= DISPLAY_INTERVAL) { state++; if (state > STATES - 1) state = 0; DEBUG_PRINT(F("State:")); DEBUG_PRINTLN(state); lastTime += DISPLAY_INTERVAL; fastClear(); lcdDisplay[state](); } } void fastClear() { lcd.setCursor(0,0); lcd.print(F(" ")); lcd.setCursor(0,1); lcd.print(F(" ")); } void lcdDisplay0() { lcd.setCursor(0,0); lcd.print(year()); lcd.print("-"); DEBUG_PRINT(F("Date: ")); DEBUG_PRINT(year()); DEBUG_PRINT("-"); if (month() < 10) { lcd.print("0"); DEBUG_PRINT("0"); } lcd.print(month()); lcd.print("-"); DEBUG_PRINT(month()); DEBUG_PRINT("-"); if (day() < 10) { lcd.print("0"); DEBUG_PRINT("0"); } lcd.print(day()); DEBUG_PRINT(day()); lcd.print(" "); DEBUG_PRINT(F(" Time: ")); if (hour() < 10) { lcd.print("0"); DEBUG_PRINT("0"); } lcd.print(hour()); lcd.print(":"); DEBUG_PRINT(hour()); DEBUG_PRINT(":"); if (minute() < 10) { lcd.print("0"); DEBUG_PRINT("0"); } lcd.print(minute()); DEBUG_PRINTLN(minute()); lcd.setCursor(0,1); if (millis() - lastClockSet > FIVE_HOURS) { lcd.print(F("Clk ")); lcd.print((millis()-lastClockSet)/ONE_MIN); lcd.print(F(" min ago")); DEBUG_PRINT(F(" (ClockSync > FIVE_HOURS): lastClockSet ")); DEBUG_PRINT((millis()-lastClockSet)/ONE_MIN); DEBUG_PRINTLN(F(" min ago")); } else if (millis() - lastWeatherUpdate > THREE_HOURS) { // ( Controller weather update set to 1/hour ) lcd.print(F("Wtr ")); lcd.print((millis()-lastWeatherUpdate)/ONE_MIN); lcd.print(F(" min ago")); DEBUG_PRINT(F(" (lastWeatherUpdate > THREE_HOURS): lastWeatherUpdate ")); DEBUG_PRINT((millis()-lastWeatherUpdate)/ONE_MIN); DEBUG_PRINTLN(F(" min ago")); } else { lcd.print(F(" ")); } } void lcdDisplay1() { lcd.setCursor(0,0); //lcd.print(F("In T:")); lcd.print(F("In ")); lcd.print(indoorTemp); lcd.print(char(223)); DEBUG_PRINT(F("In T:")); DEBUG_PRINT(indoorTemp); DEBUG_PRINT("C"); //lcd.print(" H:"); lcd.print(" "); lcd.print(indoorHumidity); lcd.print("%"); DEBUG_PRINT(F(" H:")); DEBUG_PRINT(indoorHumidity); DEBUG_PRINTLN("%"); lcd.setCursor(0,1); //lcd.print(F("Ut T:")); lcd.print(F("Ut ")); lcd.print(outdoorTemp); lcd.print(char(223)); DEBUG_PRINT(F("Out T:")); DEBUG_PRINT(outdoorTemp); DEBUG_PRINT("C"); //lcd.print(" H:"); lcd.print(" "); lcd.print(outdoorHumidity); lcd.print("%"); DEBUG_PRINT(F(" H:")); DEBUG_PRINT(outdoorHumidity); DEBUG_PRINTLN("%"); } void lcdDisplay2() { lcd.setCursor(0,0); //lcd.print(F("1 H:")); lcd.print(F("1 ")); lcd.print(todayHigh); lcd.print(char(223)); DEBUG_PRINT(F("Today's High: ")); DEBUG_PRINT(todayHigh); DEBUG_PRINT(F("C ")); //lcd.print(F(" L:")); lcd.print(F(" ")); lcd.print(todayLow); lcd.print(char(223)); DEBUG_PRINT(F("Today's Low: ")); DEBUG_PRINT(todayLow); DEBUG_PRINTLN(F("C ")); lcd.setCursor(0,1); lcd.print(todayCondition); DEBUG_PRINT(F("Today's Weather (code): ")); DEBUG_PRINTLN(todayConditionCode); } void lcdDisplay3() { lcd.setCursor(0,0); //lcd.print(F("2 H:")); lcd.print(F("2 ")); lcd.print(tomorrowHigh); lcd.print(char(223)); DEBUG_PRINT(F("Tomorrow's High: ")); DEBUG_PRINT(tomorrowHigh); DEBUG_PRINT(F("C ")); //lcd.print(F(" L:")); lcd.print(F(" ")); lcd.print(tomorrowLow); lcd.print(char(223)); DEBUG_PRINT(F("Tomorrow's Low: ")); DEBUG_PRINT(tomorrowLow); DEBUG_PRINTLN(F("C ")); lcd.setCursor(0,1); lcd.print(tomorrowCondition); DEBUG_PRINT(F("Tomorrow's Weather (code): ")); DEBUG_PRINTLN(tomorrowConditionCode); } void receiveTime(unsigned long time) { DEBUG_PRINT(F("Time value received: ")); // (UTC) DEBUG_PRINTLN(time); setTime(time); DEBUG_PRINT(F("Hour received: ")); // (UTC) DEBUG_PRINTLN(hour()); setTime(hour()+1,minute(),second(),day(),month(),year()); // Sets CET timezone (GMT +1) DEBUG_PRINT(F("Time zone hour: ")); // (UTC) DEBUG_PRINTLN(hour()); adjustDST(time); // Sets DST (+1) if necessary DEBUG_PRINT(F("Adjusted DST hour: ")); // (UTC) DEBUG_PRINTLN(hour()); } void adjustDST(unsigned long t) { if ((year() >= 2015) && (year() < 2015+DST_YEARS)) { if ((t > dstTable[year()-2015][0]) && (t < dstTable[year()-2015][1])) { setTime(hour()+1,minute(),second(),day(),month(),year()); } } } void getVariables(const MyMessage &message) { if (message.sensor == CHILD_ID_3) { if (message.type == V_VAR1) { outdoorTemp = atoi(message.data); DEBUG_PRINT(F("outdoorTemp recieved:")); DEBUG_PRINTLN(outdoorTemp); } if (message.type == V_VAR2) { outdoorHumidity = atoi(message.data); DEBUG_PRINT(F("outdoorHumidity recieved:")); DEBUG_PRINTLN(outdoorHumidity); } if (message.type == V_VAR3) { todayLow = atoi(message.data); DEBUG_PRINT(F("Received Today's LOW:")); DEBUG_PRINTLN(todayLow); } if (message.type == V_VAR4) { todayHigh = atoi(message.data); DEBUG_PRINT(F("Received Today's HIGH:")); DEBUG_PRINTLN(todayHigh); } if (message.type == V_VAR5) { todayConditionCode = atoi(message.data); DEBUG_PRINT(F("Received today's Conditions: ")); DEBUG_PRINT(todayConditionCode); DEBUG_PRINT(" "); getNewSweCond(todayConditionCode); todayCondition = condString; DEBUG_PRINTLN(todayCondition); lastWeatherUpdate = millis(); DEBUG_PRINTLN(F(" and reset the lastWeatherUpdate-counter.")); } } if (message.sensor == CHILD_ID_4) { if (message.type == V_STATUS) { int ledState = atoi(message.data); if (ledState > 0) { lcd.backlight(); } else { lcd.noBacklight(); } } if (message.type == V_VAR1) { indoorTemp = atoi(message.data); DEBUG_PRINT(F("indoorTemp recieved:")); DEBUG_PRINTLN(indoorTemp); } if (message.type == V_VAR2) { indoorHumidity = atoi(message.data); DEBUG_PRINT(F("indoorHumidity recieved:")); DEBUG_PRINTLN(indoorHumidity); } if (message.type == V_VAR3) { tomorrowConditionCode = atoi(message.data); DEBUG_PRINT(F("Received Tomorrow's Forecast: ")); DEBUG_PRINT(tomorrowConditionCode); DEBUG_PRINT(" "); getNewSweCond(tomorrowConditionCode); tomorrowCondition = condString; DEBUG_PRINTLN(tomorrowCondition); } if (message.type == V_VAR4) { tomorrowHigh = atoi(message.data); DEBUG_PRINT(F("Received Tomorrow's HIGH:")); DEBUG_PRINTLN(tomorrowHigh); } if (message.type == V_VAR5) { tomorrowLow = atoi(message.data); DEBUG_PRINT(F("Received Tomorrow's LOW:")); DEBUG_PRINTLN(tomorrowLow); } } } // Update the translated condition. void getNewSweCond(int code) { int cci = csCodeIndex(code); DEBUG_PRINT("cci: "); DEBUG_PRINTLN(cci); strcpy_P(condString, (char*)pgm_read_word(&(condStrTable[cci]))); } uint8_t csCodeIndex(int csCode) { uint8_t i = 0; while ( (pgm_read_word_near(indexList + i) < csCode) && (i < INDEX_LIST_LAST_POS) ) { i++; } return i; } void createSweCaracters(){ byte AwithRing[8] = { B00100, B01010, B01110, B00001, B01111, B10001, B01111, }; byte AwithDots[8] = { B01010, B00000, B01110, B00001, B01111, B10001, B01111, }; byte OwithDots[8] = { B01010, B00000, B01110, B10001, B10001, B10001, B01110, }; byte CapitalAwithRing[8] = { B00100, B01010, B01110, B10001, B11111, B10001, B10001, }; byte CapitalAwithDots[8] = { B01010, B00000, B01110, B10001, B11111, B10001, B10001, }; byte CapitalOwithDots[8] = { B01010, B00000, B01110, B10001, B10001, B10001, B01110, }; lcd.createChar(1, AwithRing); lcd.createChar(2, AwithDots); lcd.createChar(3, OwithDots); lcd.createChar(4, CapitalAwithRing); lcd.createChar(5, CapitalAwithDots); lcd.createChar(6, CapitalOwithDots); }
-
Yahoo weather in fhem is fixed again...
W
-
@vorowski Thx for the info. It's been down before so I don't mind trying something else anyway. Maybe I can start presenting and logging forecasts from different prividers and then evaluate them