OpenWeatherMap daily forecast of weather condition using free API

Hello,

i’ve seen a number of examples calculating daily forecast min/max temp values based on the 3 hour forecast values already but in addition wanted to have one daily weather status as icon/text for today and tomorrow.

I also use item definitions and grouping to parse values and added a logic to rate weather status based on weather condition ID’s. Unfortunately they are not min/max so i needed to group them first from best to worst or in weather terms clear to thunderstorms:

  • Group 800: Clear
  • Group 80x: Clouds
  • Group 3xx: Drizzle
  • Group 5xx: Rain
  • Group 6xx: Snow
  • Group 7xx: Atmosphere
  • Group 2xx: Thunderstorm
    • 200: Thunderstorm|thunderstorm with light rain
    • 201: Thunderstorm|thunderstorm with rain
    • 202: Thunderstorm|thunderstorm with heavy rain
    • 210: Thunderstorm|light thunderstorm
    • 221: Thunderstorm|ragged thunderstorm
    • 230: Thunderstorm|thunderstorm with light drizzle
    • 231: Thunderstorm|thunderstorm with drizzle
    • 232: Thunderstorm|thunderstorm with heavy drizzle
    • 211: Thunderstorm|thunderstorm
    • 212: Thunderstorm|heavy thunderstorm

weather.items

DateTime LocalWeatherAndForecast_Current_LetzteMessung {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:current#time-stamp"}
String LocalWeatherAndForecast_Today_WetterlageID
String LocalWeatherAndForecast_Tomorrow_WetterlageID
		
DateTime WeatherForecast_Hours03 (gWeatherForecastTime) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours03#time-stamp"}
DateTime WeatherForecast_Hours06 (gWeatherForecastTime) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours06#time-stamp"}
DateTime WeatherForecast_Hours09 (gWeatherForecastTime) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours09#time-stamp"}
DateTime WeatherForecast_Hours12 (gWeatherForecastTime) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours12#time-stamp"}
DateTime WeatherForecast_Hours15 (gWeatherForecastTime) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours15#time-stamp"}
DateTime WeatherForecast_Hours18 (gWeatherForecastTime) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours18#time-stamp"}
DateTime WeatherForecast_Hours21 (gWeatherForecastTime) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours21#time-stamp"}
DateTime WeatherForecast_Hours24 (gWeatherForecastTime) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours24#time-stamp"}
DateTime WeatherForecast_Hours27 (gWeatherForecastTime) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours27#time-stamp"}
DateTime WeatherForecast_Hours30 (gWeatherForecastTime) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours30#time-stamp"}
DateTime WeatherForecast_Hours33 (gWeatherForecastTime) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours33#time-stamp"}
DateTime WeatherForecast_Hours36 (gWeatherForecastTime) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours36#time-stamp"}
DateTime WeatherForecast_Hours39 (gWeatherForecastTime) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours39#time-stamp"}
DateTime WeatherForecast_Hours42 (gWeatherForecastTime) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours42#time-stamp"}
DateTime WeatherForecast_Hours45 (gWeatherForecastTime) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours45#time-stamp"}
DateTime WeatherForecast_Hours48 (gWeatherForecastTime) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours48#time-stamp"}
Number:Temperature WeatherForecast_Hours03_Temp (gWeatherForecastTemp) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours03#temperature"}
Number:Temperature WeatherForecast_Hours06_Temp (gWeatherForecastTemp) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours06#temperature"}
Number:Temperature WeatherForecast_Hours09_Temp (gWeatherForecastTemp) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours09#temperature"}
Number:Temperature WeatherForecast_Hours12_Temp (gWeatherForecastTemp) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours12#temperature"}
Number:Temperature WeatherForecast_Hours15_Temp (gWeatherForecastTemp) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours15#temperature"}
Number:Temperature WeatherForecast_Hours18_Temp (gWeatherForecastTemp) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours18#temperature"}
Number:Temperature WeatherForecast_Hours21_Temp (gWeatherForecastTemp) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours21#temperature"}
Number:Temperature WeatherForecast_Hours24_Temp (gWeatherForecastTemp) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours24#temperature"}
Number:Temperature WeatherForecast_Hours27_Temp (gWeatherForecastTemp) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours27#temperature"}
Number:Temperature WeatherForecast_Hours30_Temp (gWeatherForecastTemp) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours30#temperature"}
Number:Temperature WeatherForecast_Hours33_Temp (gWeatherForecastTemp) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours33#temperature"}
Number:Temperature WeatherForecast_Hours36_Temp (gWeatherForecastTemp) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours36#temperature"}
Number:Temperature WeatherForecast_Hours39_Temp (gWeatherForecastTemp) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours39#temperature"}
Number:Temperature WeatherForecast_Hours42_Temp (gWeatherForecastTemp) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours42#temperature"}
Number:Temperature WeatherForecast_Hours45_Temp (gWeatherForecastTemp) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours45#temperature"}
Number:Temperature WeatherForecast_Hours48_Temp (gWeatherForecastTemp) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours48#temperature"}
String WeatherForecast_Hours03_ID (gWeatherForecastID) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours03#condition-id"}
String WeatherForecast_Hours06_ID (gWeatherForecastID) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours06#condition-id"}
String WeatherForecast_Hours09_ID (gWeatherForecastID) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours09#condition-id"}
String WeatherForecast_Hours12_ID (gWeatherForecastID) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours12#condition-id"}
String WeatherForecast_Hours15_ID (gWeatherForecastID) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours15#condition-id"}
String WeatherForecast_Hours18_ID (gWeatherForecastID) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours18#condition-id"}
String WeatherForecast_Hours21_ID (gWeatherForecastID) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours21#condition-id"}
String WeatherForecast_Hours24_ID (gWeatherForecastID) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours24#condition-id"}
String WeatherForecast_Hours27_ID (gWeatherForecastID) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours27#condition-id"}
String WeatherForecast_Hours30_ID (gWeatherForecastID) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours30#condition-id"}
String WeatherForecast_Hours33_ID (gWeatherForecastID) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours33#condition-id"}
String WeatherForecast_Hours36_ID (gWeatherForecastID) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours36#condition-id"}
String WeatherForecast_Hours39_ID (gWeatherForecastID) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours39#condition-id"}
String WeatherForecast_Hours42_ID (gWeatherForecastID) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours42#condition-id"}
String WeatherForecast_Hours45_ID (gWeatherForecastID) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours45#condition-id"}
String WeatherForecast_Hours48_ID (gWeatherForecastID) {channel="openweathermap:weather-and-forecast:<binding-bridge-ID>:local:forecastHours48#condition-id"}

Note: you will have to replace with your weather bridge binding ID, e.g. “12ab34c5”.

weather.rules

rule "Wetter Update"
when
	System started or
	Item LocalWeatherAndForecast_Current_LetzteMessung changed
then
	if ((LocalWeatherAndForecast_Current_LetzteMessung!=NULL)) {

		Thread::sleep(25)  //wait until all weather items are properly updated

		var today_min = 50|°C
		var today_max = -50|°C
		var tomorrow_min = 50|°C
		var tomorrow_max = -50|°C
		val String todaystr = now.toString.substring(0,10)
		val String tomorrowstr = (now.plusHours(24)).toString.substring(0,10)

//Today's min/max temperature
		gWeatherForecastTime.members.filter[f | f.state.toString.substring(0,10)==todaystr].forEach[wtime | {
			var wtemp = gWeatherForecastTemp.members.findFirst[j | j.name == wtime.name.toString + "_Temp"]
			if (wtemp.state != NULL) {
				if ((wtemp.state) < today_min) {
					today_min = wtemp.state
					}
				if ((wtemp.state) > today_max) {
					today_max = wtemp.state
					}
				}
			}
		]

//Tomorrow's min/max temperature
		gWeatherForecastTime.members.filter[f | f.state.toString.substring(0,10)==tomorrowstr].forEach[wtime | {
			var wtemp = gWeatherForecastTemp.members.findFirst[j | j.name == wtime.name.toString + "_Temp"]
			if (wtemp.state != NULL) {
				if ((wtemp.state) < tomorrow_min) {
					tomorrow_min = wtemp.state
					}
				if ((wtemp.state) > tomorrow_max) {
					tomorrow_max = wtemp.state
					}
				}
			}
		]

//Today's "worst" weather condition
		var Number wID_num							// loop variable for today's forecast Weather Condition ID 
		var Number wIDmax_num = 0					// day's worst weather condition ID

		gWeatherForecastTime.members.filter[f | f.state.toString.substring(0,10)==todaystr].forEach[wtime | {
			var wID = gWeatherForecastID.members.findFirst[j | j.name == wtime.name.toString + "_ID"]
			if (wID.state != NULL) {
//				logInfo("wetterMinMax", "ID item " + wID.toString)
				wID_num = Integer::parseInt(wID.state.toString)
// check forecast categories in order of worst priority to assign "worst" weather condition ID
// heavy thunderstorm and thunderstorm
				if (wID_num == 212) {				// no matter what current max value is assign worst ID immediately
					wIDmax_num = wID_num
					}
				else if (wID_num == 211) {			// unless ID not 212 then assign 2nd worst immediately
 					if (wIDmax_num != 212) {
						wIDmax_num = wID_num
						}
					}
// all other thunderstorms
				else if ((wID_num >= 200) && (wID_num < 233)) {
					if ((wIDmax_num != 211) && (wIDmax_num != 212)) {
						if (wIDmax_num <233) {		// if max ID is a Thunderstorm already (but not severe) assign max of Thunderstorm
							wIDmax_num=Math::max(wIDmax_num, wID_num)
							}
						else {						// else assign Thunderstorm ID
							wIDmax_num = wID_num
							}
						}
					}
// all other weather conditions except clear and cloudy
				else if ((wID_num >= 300) && (wID_num < 782)) {
					if ((wIDmax_num >=800) || (wIDmax_num ==0)) {	// if max ID is clear/cloudy or not used before assign condition ID
						wIDmax_num = wID_num
						}
					else {											// else assign max of condition ID
						wIDmax_num=Math::max(wIDmax_num, wID_num)
						}
					}
// clear and cloudy conditions
				else if ((wID_num >= 800) && (wID_num < 805)) {
					if ((wIDmax_num >=800) || (wIDmax_num ==0)) {	// if max ID is clear/cloudy already or not used before assign max of condition ID
						wIDmax_num=Math::max(wIDmax_num, wID_num)
						}
					}
				}
			}
		]
		if (wIDmax_num == 0) {						// late in a day WeatherForecast_Hours03 could be pointing to the following day already therefore do not update
			logInfo("wetter Update", "TodayMaxWetterlageID=0, not updating")
			}
		else {
			LocalWeatherAndForecast_Today_WetterlageID.postUpdate(wIDmax_num.toString)
			}

//Tomorrow's "worst" weather condition
		wIDmax_num = 0
		gWeatherForecastTime.members.filter[f | f.state.toString.substring(0,10)==tomorrowstr].forEach[wtime | {
			var wID = gWeatherForecastID.members.findFirst[j | j.name == wtime.name.toString + "_ID"]
			if (wID.state != NULL) {
				wID_num = Integer::parseInt(wID.state.toString)
// check forecast categories in order of worst priority to assign "worst" weather condition ID
// heavy thunderstorm and thunderstorm
				if (wID_num == 212) {					// no matter what current max value is assign worst ID immediately
					wIDmax_num = wID_num
					}
				else if (wID_num == 211) {				// unless ID not 212 then assign 2nd worst immediately
 					if (wIDmax_num != 212) {
						wIDmax_num = wID_num
						}
					}
// all other thunderstorms
				else if ((wID_num >= 200) && (wID_num < 233)) {
					if ((wIDmax_num != 211) && (wIDmax_num != 212)) {
						if (wIDmax_num <233) {						// if max ID is a Thunderstorm already (but not severe) assign max of Thunderstorm
							wIDmax_num=Math::max(wIDmax_num, wID_num)
							}
						else {							// else assign Thunderstorm ID
							wIDmax_num = wID_num
							}
						}
					}
// all other weather conditions except clear and cloudy
				else if ((wID_num >= 300) && (wID_num < 782)) {
					if ((wIDmax_num >=800) || (wIDmax_num ==0)) {	// if max ID is clear/cloudy assign condition ID

						wIDmax_num = wID_num
						}
					else {											// else assign max of condition ID
						wIDmax_num=Math::max(wIDmax_num, wID_num)
						}
					}
// clear and cloudy conditions
				else if ((wID_num >= 800) && (wID_num < 805)) {
					if ((wIDmax_num >=800) || (wIDmax_num ==0)) {	// only if max ID is clear/cloudy already assign max of condition ID
						wIDmax_num=Math::max(wIDmax_num, wID_num)
						}
					}
				}
			}
		]
		if (wIDmax_num == 0) {							// late in a day WeatherForecast_Hours03 could be pointing to the following day already therefore do not update
			logInfo("wetter Update", "TomorrowMaxWetterlageID=0, not updating")
			}
		else {
			LocalWeatherAndForecast_Tomorrow_WetterlageID.postUpdate(wIDmax_num.toString)
			}


// update your items based on today_min, today_max, tomorrow_min, tomorrow_max
// e.g. GATEWAYEXTRAS_1_WetterPrognosetempmin.sendCommand(today_min as Number)
// or WeatherFctToday.postUpdate(Math::round((today_min as Number).doubleValue).toString + "°C - " + Math::round((today_max as Number).doubleValue).toString + "°C")
//
// LocalWeatherAndForecast_Today_WetterlageID and LocalWeatherAndForecast_Tomorrow_WetterlageID contain the weather forecast condition ID

I use a map file to display the weather text and dynamic icons for the graphic, e.g.

Text item=LocalWeatherAndForecast_Today_WetterlageID label="Heute [MAP(wetter.map):%s]"
Text item=LocalWeatherAndForecast_Tomorrow_WetterlageID label="Morgen [MAP(wetter.map):%s]"

Hope this helps,
Dirk

6 Likes

Hi Dirk
Could you provide your weather.map file ?

Sure, here you go:

200=Gewitter mit leichtem Regen
201=Gewitter mit Regen
202=Gewitter mit starkem Regen
210=Leichtes Gewitter
211=Gewitter
212=Starkes Gewitter
221=Phasenweises Gewitter
230=Gewitter mit leichtem Nieselregen
231=Gewitter mit Nieselregen
232=Gewitter mit starkem Nieselregen 
300=Leichter Nieselregen
301=Nieselregen
302=Starker Nieselregen
310=Leichter Nieselregen
311=Nieselregen
312=starker Nieselregen
313=Regenschauer und Nieselregen
314=Starke Regenschauer und Nieselregen
321=Schauerartiger Sprühregen
500=Leichter Regen
501=Mäßiger Regen
502=Starker Regen
503=Sehr starker Regen
504=Extremer Regen
511=Gefrierender Regen
520=Leichte Regenschauer
521=Regenschauer
522=Starke Regenschauer
531=Wiederkehrende Regenschauer
600=Leichter Schneefall
601=Schneefall
602=Starker Schneefall
611=Schneeregen
612=Graupelschauer
613=Schneeregenschauer
615=Leichter Regen und Schnee
616=Regen und Schnee
620=Leichter Schneeregen
621=Schneeregen
622=starker Schneeregen
701=Leichter Nebel/Dunst
711=Rauch
721=Dunst
731=Sand/Staubwirbel
741=Nebel
751=Sand
761=Staub
762=Vulkanasche
771=Gewitter-/Sturmböen
781=Tornado
800=klarer Himmel
801=Wenige Schleierwolken
802=Aufgelockerte Bewölkung
803=Bewölkt mit Aufhellungen
804=Bewölkt
900=Tornado
901=Tropensturm
902=Hurrikan
903=Kalt
904=Heiß
905=Windig
906=Hagel
951=Windstille
952=Leichte Brise
953=Schwache Briese
954=Mäßige Brise
955=Frische Brise
956=Starker Wind
957=Starker Wind, kurz vor Sturm
958=Sturm
959=Starker Sturm
960=Sturm
961=Heftiger Sturm
962=Hurrikan

I use german localization but should be straight forward using OpenWeathermap’s condition ID list.

Hi Dirk,
I ve been looking for such a use of openwheathermap data for a while. Thank your for your nice example. Im trying to use it in my set up, solved few issues : I think there was a “}” and an “end” missing in the rule code, also I had to add group items for gWeatherForecastTemp and other groups.
Im not so proficient in coding or openhab but tried find a solution by myself still
now stuck with this error :
2019-05-28 09:23:26.056 [INFO ] [el.core.internal.ModelRepositoryImpl] - Refreshing model ‘weather.rules’
2019-05-28 09:23:33.424 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Error during the execution of startup rule ‘Wetter Update’: String index out of range: 10

Here is how the rule looks now :slight_smile:

    rule "Wetter Update"
    when
    	System started or
    	Item LocalWeatherAndForecast_Current_LetzteMessung changed
    then
    	if ((LocalWeatherAndForecast_Current_LetzteMessung!=NULL)) {

    		Thread::sleep(25)  //wait until all weather items are properly updated

    		var today_min = -50|°C
    		var today_max = 50|°C
    		var tomorrow_min = -50|°C
    		var tomorrow_max = 50|°C
    		val String todaystr = now.toString.substring(0,10)
    		val String tomorrowstr = (now.plusHours(24)).toString.substring(0,10)

    //Today's min/max temperature
    		gWeatherForecastTime.members.filter[f | f.state.toString.substring(0,10)==todaystr].forEach[wtime | {
    			var wtemp = gWeatherForecastTemp.members.findFirst[j | j.name == wtime.name.toString + "_Temp"]
    			if (wtemp.state != NULL) {
    				if ((wtemp.state) < today_min) {
    					today_min = wtemp.state
    					}
    				if ((wtemp.state) > today_max) {
    					today_max = wtemp.state
    					}
    				}
    			}
    		]

    //Tomorrow's min/max temperature
    		gWeatherForecastTime.members.filter[f | f.state.toString.substring(0,10)==tomorrowstr].forEach[wtime | {
    			var wtemp = gWeatherForecastTemp.members.findFirst[j | j.name == wtime.name.toString + "_Temp"]
    			if (wtemp.state != NULL) {
    				if ((wtemp.state) < tomorrow_min) {
    					tomorrow_min = wtemp.state
    					}
    				if ((wtemp.state) > tomorrow_max) {
    					tomorrow_max = wtemp.state
    					}
    				}
    			}
    		]

    //Today's "worst" weather condition
    		var Number wID_num							// loop variable for today's forecast Weather Condition ID 
    		var Number wIDmax_num = 0					// day's worst weather condition ID

    		gWeatherForecastTime.members.filter[f | f.state.toString.substring(0,10)==todaystr].forEach[wtime | {
    			var wID = gWeatherForecastID.members.findFirst[j | j.name == wtime.name.toString + "_ID"]
    			if (wID.state != NULL) {
    //				logInfo("wetterMinMax", "ID item " + wID.toString)
    				wID_num = Integer::parseInt(wID.state.toString)
    // check forecast categories in order of worst priority to assign "worst" weather condition ID
    // heavy thunderstorm and thunderstorm
    				if (wID_num == 212) {				// no matter what current max value is assign worst ID immediately
    					wIDmax_num = wID_num
    					}
    				else if (wID_num == 211) {			// unless ID not 212 then assign 2nd worst immediately
     					if (wIDmax_num != 212) {
    						wIDmax_num = wID_num
    						}
    					}
    // all other thunderstorms
    				else if ((wID_num >= 200) && (wID_num < 233)) {
    					if ((wIDmax_num != 211) && (wIDmax_num != 212)) {
    						if (wIDmax_num <233) {		// if max ID is a Thunderstorm already (but not severe) assign max of Thunderstorm
    							wIDmax_num=Math::max(wIDmax_num, wID_num)
    							}
    						else {						// else assign Thunderstorm ID
    							wIDmax_num = wID_num
    							}
    						}
    					}
    // all other weather conditions except clear and cloudy
    				else if ((wID_num >= 300) && (wID_num < 782)) {
    					if ((wIDmax_num >=800) || (wIDmax_num ==0)) {	// if max ID is clear/cloudy or not used before assign condition ID
    						wIDmax_num = wID_num
    						}
    					else {											// else assign max of condition ID
    						wIDmax_num=Math::max(wIDmax_num, wID_num)
    						}
    					}
    // clear and cloudy conditions
    				else if ((wID_num >= 800) && (wID_num < 805)) {
    					if ((wIDmax_num >=800) || (wIDmax_num ==0)) {	// if max ID is clear/cloudy already or not used before assign max of condition ID
    						wIDmax_num=Math::max(wIDmax_num, wID_num)
    						}
    					}
    				}
    			}
    		]
    		if (wIDmax_num == 0) {						// late in a day WeatherForecast_Hours03 could be pointing to the following day already therefore do not update
    			logInfo("wetter Update", "TodayMaxWetterlageID=0, not updating")
    			}
    		else {
    			LocalWeatherAndForecast_Today_WetterlageID.postUpdate(wIDmax_num.toString)
    			}

    //Tomorrow's "worst" weather condition
    		wIDmax_num = 0
    		gWeatherForecastTime.members.filter[f | f.state.toString.substring(0,10)==tomorrowstr].forEach[wtime | {
    			var wID = gWeatherForecastID.members.findFirst[j | j.name == wtime.name.toString + "_ID"]
    			if (wID.state != NULL) {
    				wID_num = Integer::parseInt(wID.state.toString)
    // check forecast categories in order of worst priority to assign "worst" weather condition ID
    // heavy thunderstorm and thunderstorm
    				if (wID_num == 212) {					// no matter what current max value is assign worst ID immediately
    					wIDmax_num = wID_num
    					}
    				else if (wID_num == 211) {				// unless ID not 212 then assign 2nd worst immediately
     					if (wIDmax_num != 212) {
    						wIDmax_num = wID_num
    						}
    					}
    // all other thunderstorms
    				else if ((wID_num >= 200) && (wID_num < 233)) {
    					if ((wIDmax_num != 211) && (wIDmax_num != 212)) {
    						if (wIDmax_num <233) {						// if max ID is a Thunderstorm already (but not severe) assign max of Thunderstorm
    							wIDmax_num=Math::max(wIDmax_num, wID_num)
    							}
    						else {							// else assign Thunderstorm ID
    							wIDmax_num = wID_num
    							}
    						}
    					}
    // all other weather conditions except clear and cloudy
    				else if ((wID_num >= 300) && (wID_num < 782)) {
    					if ((wIDmax_num >=800) || (wIDmax_num ==0)) {	// if max ID is clear/cloudy assign condition ID

    						wIDmax_num = wID_num
    						}
    					else {											// else assign max of condition ID
    						wIDmax_num=Math::max(wIDmax_num, wID_num)
    						}
    					}
    // clear and cloudy conditions
    				else if ((wID_num >= 800) && (wID_num < 805)) {
    					if ((wIDmax_num >=800) || (wIDmax_num ==0)) {	// only if max ID is clear/cloudy already assign max of condition ID
    						wIDmax_num=Math::max(wIDmax_num, wID_num)
    						}
    					}
    				}
    			}
    		]
    		if (wIDmax_num == 0) {							// late in a day WeatherForecast_Hours03 could be pointing to the following day already therefore do not update
    			logInfo("wetter Update", "TomorrowMaxWetterlageID=0, not updating")
    			}
    		else {
    			LocalWeatherAndForecast_Tomorrow_WetterlageID.postUpdate(wIDmax_num.toString)
    			}


    // update your items based on today_min, today_max, tomorrow_min, tomorrow_max
    // e.g. GATEWAYEXTRAS_1_WetterPrognosetempmin.sendCommand(today_min as Number)
    WeatherFctToday.postUpdate(Math::round((today_min as Number).doubleValue).toString + "°C - " + Math::round((today_max as Number).doubleValue).toString + "°C")
    //
    LocalWeatherAndForecast_Today_WetterlageID and LocalWeatherAndForecast_Tomorrow_WetterlageID
    	}
    end

The sitemap frame:

Frame  label="Meteo" {
Text item=LocalWeatherAndForecast_Today_WetterlageID label="Astazi [MAP(weather.map):%s]"
Text item=LocalWeatherAndForecast_Tomorrow_WetterlageID label="Maine [MAP(weather.map):%s]"

}

Do you have any idea why the error happens?
Thanks
Alex

LE, after some more debugging I norowed the issue to this part
f.state.toString.substring(0,10)
this element is the cause of error, somohow the state might be null and substrig gets into error.

Has anyone fixed this bug? I’m seeing it, too.

No worries, got it resolved by myself: You need to make sure that all items of the group have a valid value (not NULL). I had more items defined than the configuration populated so some of them were NULL.

1 Like

Many thanks for sharing the scripts, it is very helpful.

I tried to populate a couple of additional Items for the min/max temperature forecast. Now my rule looks like this:

//Today's "worst" weather condition
var Integer wID_num                         // loop variable for today's forecast Weather Condition ID
var Integer wIDmax_num = 0                  // day's worst weather condition ID

var Number today_min = -50|°C
var Number today_max = 50|°C
var String today_minmax
var Number tomorrow_min = -50|°C
var Number tomorrow_max = 50|°C
var String tomorrow_minmax
var Number day2_min = -50|°C
var Number day2_max = 50|°C
var String day2_minmax
val String todaystr = now.toString.substring(0,10)
val String tomorrowstr = (now.plusHours(24)).toString.substring(0,10)
val String day2str = (now.plusHours(48)).toString.substring(0,10)


rule "Wetter Update"
when
    System started or
    Item WeatherLastMeasurement changed
then
    if ((WeatherLastMeasurement != NULL)) {

        Thread::sleep(25)  // wait until all weather items are properly updated

        // Today's min/max temperature
        gWeatherForecastTime.members.filter[f | f.state.toString.substring(0,10)==todaystr].forEach[wtime | {
            var wtemp = gWeatherForecastTemp.members.findFirst[j | j.name == wtime.name.toString + "_Temp"]
            if (wtemp.state != NULL) {
                if ((wtemp.state as Number).floatValue < today_min) {
                    today_min = (wtemp.state as Number).floatValue
                }
                if ((wtemp.state as Number).floatValue > today_max) {
                    today_max = (wtemp.state as Number).floatValue
                }
            }
        }]
        today_minmax = String::format("%1$.0f°C / %2$.0f°C", today_min, today_max)
        WeatherAndForecast_Today_TemperatureMin.postUpdate(today_min)
        WeatherAndForecast_Today_TemperatureMax.postUpdate(today_max)
        WeatherAndForecast_Today_TemperatureMinMaxString.postUpdate(today_minmax)

        // Tomorrow's min/max temperature
        gWeatherForecastTime.members.filter[f | f.state.toString.substring(0,10)==tomorrowstr].forEach[wtime | {
            var wtemp = gWeatherForecastTemp.members.findFirst[j | j.name == wtime.name.toString + "_Temp"]
            if (wtemp.state != NULL) {
                if ((wtemp.state as Number).floatValue < tomorrow_min) {
                    tomorrow_min = (wtemp.state as Number).floatValue
                }
                if ((wtemp.state as Number).floatValue > tomorrow_max) {
                    tomorrow_max = (wtemp.state as Number).floatValue
                }
            }
        }]
        tomorrow_minmax = String::format("%1$.0f°C / %2$.0f°C", today_min, today_max)
        WeatherAndForecast_Tomorrow_TemperatureMin.postUpdate(tomorrow_min)
        WeatherAndForecast_Tomorrow_TemperatureMax.postUpdate(tomorrow_max)
        WeatherAndForecast_Tomorrow_TemperatureMinMaxString.postUpdate(tomorrow_minmax)

        // Day 2 min/max temperature
        gWeatherForecastTime.members.filter[f | f.state.toString.substring(0,10)==day2str].forEach[wtime | {
            var wtemp = gWeatherForecastTemp.members.findFirst[j | j.name == wtime.name.toString + "_Temp"]
            if (wtemp.state != NULL) {
                if ((wtemp.state as Number).floatValue < day2_min) {
                    day2_min = (wtemp.state as Number).floatValue
                }
                if ((wtemp.state as Number).floatValue > day2_max) {
                    day2_max = (wtemp.state as Number).floatValue
                }
            }
        }]
        day2_minmax = String::format("%1$.0f°C / %2$.0f°C", today_min, today_max)
        WeatherAndForecast_Day2_TemperatureMin.postUpdate(day2_min)
        WeatherAndForecast_Day2_TemperatureMax.postUpdate(day2_max)
        WeatherAndForecast_Day2_TemperatureMinMaxString.postUpdate(day2_minmax)

        gWeatherForecastTime.members.filter[f | f.state.toString.substring(0,10)==todaystr].forEach[wtime | {
            var wID = gWeatherForecastID.members.findFirst[j | j.name == wtime.name.toString + "_ID"]
            if (wID.state != NULL) {
            // logInfo("wetterMinMax", "ID item " + wID.toString)
                wID_num = Integer::parseInt(wID.state.toString)
                // check forecast categories in order of worst priority to assign "worst" weather condition ID
                // heavy thunderstorm and thunderstorm
                if (wID_num == 212) {               // no matter what current max value is assign worst ID immediately
                    wIDmax_num = wID_num
                }
                else if (wID_num == 211) {          // unless ID not 212 then assign 2nd worst immediately
                    if (wIDmax_num != 212) {
                        wIDmax_num = wID_num
                    }
                }
                // all other thunderstorms
                else if ((wID_num >= 200) && (wID_num < 233)) {
                    if ((wIDmax_num != 211) && (wIDmax_num != 212)) {
                        if (wIDmax_num <233) {      // if max ID is a Thunderstorm already (but not severe) assign max of Thunderstorm
                            wIDmax_num=Math::max(wIDmax_num, wID_num)
                        }
                        else {                      // else assign Thunderstorm ID
                            wIDmax_num = wID_num
                        }
                    }
                }
                // all other weather conditions except clear and cloudy
                else if ((wID_num >= 300) && (wID_num < 782)) {
                    if ((wIDmax_num >=800) || (wIDmax_num ==0)) {   // if max ID is clear/cloudy or not used before assign condition ID
                        wIDmax_num = wID_num
                    }
                    else {                                          // else assign max of condition ID
                        wIDmax_num=Math::max(wIDmax_num, wID_num)
                    }
                }
                // clear and cloudy conditions
                else if ((wID_num >= 800) && (wID_num < 805)) {
                    if ((wIDmax_num >=800) || (wIDmax_num ==0)) {   // if max ID is clear/cloudy already or not used before assign max of condition ID
                        wIDmax_num=Math::max(wIDmax_num, wID_num)
                    }
                }
            }
        }]

        if (wIDmax_num == 0) {                      // late in a day WeatherForecast_Hours03 could be pointing to the following day already therefore do not update
            logInfo("wetter Update", "TodayMaxWetterlageID=0, not updating")
        }
        else {
            WeatherAndForecast_Today_WetterlageID.postUpdate(wIDmax_num.toString)
        }

        // Tomorrow's "worst" weather condition
        wIDmax_num = 0
        gWeatherForecastTime.members.filter[f | f.state.toString.substring(0,10)==tomorrowstr].forEach[wtime | {
            var wID = gWeatherForecastID.members.findFirst[j | j.name == wtime.name.toString + "_ID"]
            if (wID.state != NULL) {
                wID_num = Integer::parseInt(wID.state.toString)
                // check forecast categories in order of worst priority to assign "worst" weather condition ID
                // heavy thunderstorm and thunderstorm
                if (wID_num == 212) {                   // no matter what current max value is assign worst ID immediately
                    wIDmax_num = wID_num
                }
                else if (wID_num == 211) {              // unless ID not 212 then assign 2nd worst immediately
                    if (wIDmax_num != 212) {
                        wIDmax_num = wID_num
                    }
                }
                // all other thunderstorms
                else if ((wID_num >= 200) && (wID_num < 233)) {
                    if ((wIDmax_num != 211) && (wIDmax_num != 212)) {
                        if (wIDmax_num <233) {                      // if max ID is a Thunderstorm already (but not severe) assign max of Thunderstorm
                            wIDmax_num=Math::max(wIDmax_num, wID_num)
                        }
                        else {                          // else assign Thunderstorm ID
                            wIDmax_num = wID_num
                        }
                    }
                }
                // all other weather conditions except clear and cloudy
                else if ((wID_num >= 300) && (wID_num < 782)) {
                    if ((wIDmax_num >=800) || (wIDmax_num ==0)) {   // if max ID is clear/cloudy assign condition ID

                        wIDmax_num = wID_num
                    }
                    else {                                          // else assign max of condition ID
                        wIDmax_num=Math::max(wIDmax_num, wID_num)
                    }
                }
                // clear and cloudy conditions
                else if ((wID_num >= 800) && (wID_num < 805)) {
                    if ((wIDmax_num >=800) || (wIDmax_num ==0)) {   // only if max ID is clear/cloudy already assign max of condition ID
                        wIDmax_num=Math::max(wIDmax_num, wID_num)
                    }
                }
            }
        }]
        if (wIDmax_num == 0) {                          // late in a day WeatherForecast_Hours03 could be pointing to the following day already therefore do not update
            logInfo("wetter Update", "TomorrowMaxWetterlageID=0, not updating")
        }
        else {
            WeatherAndForecast_Tomorrow_WetterlageID.postUpdate(wIDmax_num.toString)
        }

        // Day 2 "worst" weather condition
        wIDmax_num = 0
        gWeatherForecastTime.members.filter[f | f.state.toString.substring(0,10)==day2str].forEach[wtime | {
            var wID = gWeatherForecastID.members.findFirst[j | j.name == wtime.name.toString + "_ID"]
            if (wID.state != NULL) {
                wID_num = Integer::parseInt(wID.state.toString)
                // check forecast categories in order of worst priority to assign "worst" weather condition ID
                // heavy thunderstorm and thunderstorm
                if (wID_num == 212) {                   // no matter what current max value is assign worst ID immediately
                    wIDmax_num = wID_num
                }
                else if (wID_num == 211) {              // unless ID not 212 then assign 2nd worst immediately
                    if (wIDmax_num != 212) {
                        wIDmax_num = wID_num
                    }
                }
                // all other thunderstorms
                else if ((wID_num >= 200) && (wID_num < 233)) {
                    if ((wIDmax_num != 211) && (wIDmax_num != 212)) {
                        if (wIDmax_num <233) {                      // if max ID is a Thunderstorm already (but not severe) assign max of Thunderstorm
                            wIDmax_num=Math::max(wIDmax_num, wID_num)
                        }
                        else {                          // else assign Thunderstorm ID
                            wIDmax_num = wID_num
                        }
                    }
                }
                // all other weather conditions except clear and cloudy
                else if ((wID_num >= 300) && (wID_num < 782)) {
                    if ((wIDmax_num >=800) || (wIDmax_num ==0)) {   // if max ID is clear/cloudy assign condition ID

                        wIDmax_num = wID_num
                    }
                    else {                                          // else assign max of condition ID
                        wIDmax_num=Math::max(wIDmax_num, wID_num)
                    }
                }
                // clear and cloudy conditions
                else if ((wID_num >= 800) && (wID_num < 805)) {
                    if ((wIDmax_num >=800) || (wIDmax_num ==0)) {   // only if max ID is clear/cloudy already assign max of condition ID
                        wIDmax_num=Math::max(wIDmax_num, wID_num)
                    }
                }
            }
        }]
        if (wIDmax_num == 0) {                          // late in a day WeatherForecast_Hours03 could be pointing to the following day already therefore do not update
            logInfo("wetter Update", "WeatherAndForecast_Day2_WetterlageID=0, not updating")
        }
        else {
            WeatherAndForecast_Day2_WetterlageID.postUpdate(wIDmax_num.toString)
        }
    }
end

When the rule is triggered, I get the following error:

[ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'Wetter Update': f != org.eclipse.smarthome.core.library.types.QuantityType

Can somebody help me with this?

Many thanks in advance!

Hi @dirkb,
I’ve tried your settings but I can’t get OpenWeatherMap working. I’ve got multiple errors after setting the rule.

2020-07-15 10:41:30.390 [INFO ] [el.core.internal.ModelRepositoryImpl] - Validation issues found in configuration model 'weather.rules', using it anyway:
There is no context to infer the closure's argument types from. Consider typing the arguments or put the closures into a typed context.
There is no context to infer the closure's argument types from. Consider typing the arguments or put the closures into a typed context.
There is no context to infer the closure's argument types from. Consider typing the arguments or put the closures into a typed context.
There is no context to infer the closure's argument types from. Consider typing the arguments or put the closures into a typed context.
There is no context to infer the closure's argument types from. Consider typing the arguments or put the closures into a typed context.
There is no context to infer the closure's argument types from. Consider typing the arguments or put the closures into a typed context.
There is no context to infer the closure's argument types from. Consider typing the arguments or put the closures into a typed context.
There is no context to infer the closure's argument types from. Consider typing the arguments or put the closures into a typed context.
There is no context to infer the closure's argument types from. Consider typing the arguments or put the closures into a typed context.
There is no context to infer the closure's argument types from. Consider typing the arguments or put the closures into a typed context.
There is no context to infer the closure's argument types from. Consider typing the arguments or put the closures into a typed context.
There is no context to infer the closure's argument types from. Consider typing the arguments or put the closures into a typed context.
2020-07-15 10:41:30.398 [INFO ] [el.core.internal.ModelRepositoryImpl] - Loading model 'weather.rules'

Do you know that problem? Maybe do you have an updated configuration that you could share?

Best wishes,
Michael

Hi all,

in the meantime I found a much simpler solution to get to daily forecasts:

Rather than picking the min/max temperatures and condition forecast from the 3-hourly forecasts, I use the “One Call API” provided by OpenWeatherMap: https://openweathermap.org/api/one-call-api
This API is also included in the free plan and can be queried with the HTTP-Binding.

The API is returning a JSON document, containing current weather data also as daily and hourly forecasts. The min/max values for the daily forecasts are already aggregated from the hourly values, so we don’t need no fancy rules to parse them. Only HTTP-Binding and JSONPATH Transformation are needed.

This is what I did:
First I used the caching feature of the HTTP-Binding to cache the API response for 10 minutes, so the API is not queried for every item:

services/http.cfg:

OpenWeatherMap_oneCallAPI_home.url=https://api.openweathermap.org/data/2.5/onecall?lat=<xx.xx>&lon=<yy.yy>&units=metric&lang=de&appid=<API KEY>
OpenWeatherMap_oneCallAPI_home.updateInterval=600000

Of course you may change the URL parameters according to your needs. The parameters are described in the API documentation linked above.

And the item definitions look like this:

String					WeatherAndForecast_Today_WetterlageID																										{http="<[OpenWeatherMap_oneCallAPI_home:600000:JSONPATH($.daily[0].weather[0].id)]"}
Number:Temperature		WeatherAndForecast_Today_TemperatureMin				"Temperatur min"										<temperature>					{http="<[OpenWeatherMap_oneCallAPI_home:600000:JSONPATH($.daily[0].temp.min)]"}
Number:Temperature		WeatherAndForecast_Today_TemperatureMax				"Temperatur max"										<temperature>					{http="<[OpenWeatherMap_oneCallAPI_home:600000:JSONPATH($.daily[0].temp.max)]"}

String					WeatherAndForecast_Tomorrow_WetterlageID																									{http="<[OpenWeatherMap_oneCallAPI_home:600000:JSONPATH($.daily[1].weather[0].id)]"}
Number:Temperature		WeatherAndForecast_Tomorrow_TemperatureMin			"Temperatur min"										<temperature>					{http="<[OpenWeatherMap_oneCallAPI_home:600000:JSONPATH($.daily[1].temp.min)]"}
Number:Temperature		WeatherAndForecast_Tomorrow_TemperatureMax			"Temperatur max"										<temperature>					{http="<[OpenWeatherMap_oneCallAPI_home:600000:JSONPATH($.daily[1].temp.max)]"}

You can query the API URL directly in a browser to find out what’s in there and to get the JSON paths of the different values. Firefox is formatting JSON in recent versions, for Chrome you might need an extension such as “JSON Viewer” or similar.

I hope, someone will find this useful.

There is binding work happening with that API.

1 Like

That’s great! Didn’t know that. As soon as the PR hits the official Binding, I can simply reconfigure my items from the HTTP Binding to the OpenWeatherMap Binding channels.

1 Like

The new API will be best in the long run, but until then, you can also use this…

Thanks Scott, I already knew this article. It’s a very interesting solution and it’s a great example to show what is possible with OpenHAB. But for my usecase I need only temperature forecasts for the next 3 days and to fiddle around with beta scripting engines and hundreds of items just to find out a handful of values seemed a bit overpowered to me - no matter if they are automatically generated or not. :wink:

Hello everybody!
I did not create a new topic and wrote here.
I have a few questions. Help me understand.

  1. Question one



    Is it possible to fix it? I understand that this is due to the resolution of the icon.

  2. And the second question
    wnd_pressure1
    When sending data to telegram, these values ​​come to me.

/ This is the line from the pressure rule & item
//rule string 
+ "\n\u23F1 *Атмосферное давление:* " +owmForecastHour03Pressure.state

//Item string
Number:Dimensionless   owmForecastHour03WindDirection   "Направление ветра на ближайшие три часа [SCALE(wind.scale):%s]"  <wind>  (Prognoz) {channel="openweathermap:weather-and-forecast:api:local:forecastHours03#wind-direction"}
_____________

/ This is the line from the wind direction rule & item
//rule string
+ "\n\uD83E\uDDED *Направление ветра:* " +owmForecastHour03WindDirection.state

//Item string
Number:Pressure	 owmForecastHour03Pressure	"Давление на ближайшие три часа [%.0f mmHg]" <pressure>	(Prognoz) {channel="openweathermap:weather-and-forecast:api:local:forecastHours03#pressure"}

The result on the screenshot.

I tried this. it no work.

//Pressure (rule string)
+ "\n\u23F1 *Атмосферное давление:* " +String::format("%.0f mmHg", (owmForecastHour03Pressure.state as DecimalType).floatValue())

With the wind it is not at all clear what to do. Help.
Thank you for your responses!

Best regvards Alexandr

You can use transformations in rules, to get your wind direction.

You can format quantity types with their own method, see the last section of this post

Thanks!
I’ve dealt with the pressure. The data display is now normal.
It didn’t work out with the direction of the wind. An empty string comes to the telegram.

wd

rule "Test Morning 09 00 Weather Send telegram Message"
when
   //Time cron "0 01 09 ? * *"
 Item Date received update
then
   var WindDirection = transform("MAP", "wind.scale", owmForecastHour03WindDirection.state.toString )

    sendTelegram("bot", " *Test wind Direction & Pressure* "  + "\n\n\uD83E\uDDED *Направление ветра:* " + WindDirection
	+ "\n\u23F1 *Атмосферное давление:* " +owmForecastHour03Pressure.state.format("%.0f mmHg").toString )
end 

What am I doing wrong?

Isn’t your SCALE transformation an umm SCALE transformation, not a MAP?

I was wrong. transform (“MAP”, “wind.map”
But the data does not come exactly. The line is empty.

wind.map

[0..22] = North
[23..45] = NorthNorthEast
[46..67] = NorthEast
[68..90] = EastNorthEast
[91..112] = East
[113..135] = EastSouthEast
[136..157] = SouthEast
[158..180] = SouthSouthEast
[181..202] = South
[203..225] = SouthSouthWest
[226..247] = SouthWest
[248..270] = WestSouthWest
[271..292] = West
[293..315] = WestNorthWest
[316..338] = NorthWest
[339..360] = NorthNorthWest

That’s not a valid MAP. It looks like a SCALE. It was a SCALE when you used it here -

Why did you change the filename? What makes you think it will work by not using it as a SCALE?

Understood! Works! Thank you for your help!

Result
urarabotaet

1 Like