OpenWeatherMap daily forecasts with free API using plain OpenHAB rules

In case you want to generate daily forecasts with the data provided by a free OpenWeatherMap account without relying on Jython, here’s what I did.

I create a number of proxy items that will contain the computed forecast values. These values are updated whenever a new forecast is provided by OWM or at midnight (to overcome the today → tomorrow case for forecasts).

The code below provides the following forecast data per day for up to 3 days in advance:

  1. Rain (total daily forecast)
  2. Snow (total daily forecast)
  3. Temperature (low / high)
  4. Max wind speed
  5. Pressure (low / high)
  6. Humidity (low / high)

The data are computed by assigning forecast data to the proper day. Forecast values for the current day are computed, taking into account the current measurement values.

Define the following OWM items:

DateTime  Weather_OWM_Current_ObservationTime  "Observation time [%1$tY-%1$tm-%1$td]" 
 (gWeatherCurrent)  {
	channel="openweathermap:weather-and-forecast:c06828c6:local:current#time-stamp"
}

// The following Items will store the computed daily forecast values

Number:Length Weather_OWM_Forecast_Rain_Day0 "Rain Total [%.1f mm]" <rain>
Number:Length Weather_OWM_Forecast_Rain_Day1 "Rain Total [%.1f mm]" <rain>
Number:Length Weather_OWM_Forecast_Rain_Day2 "Rain Total [%.1f mm]" <rain>
Number:Length Weather_OWM_Forecast_Rain_Day3 "Rain Total [%.1f mm]" <rain>

Number:Length Weather_OWM_Forecast_Snow_Day0 "Snow Total [%.1f mm]" <snow>
Number:Length Weather_OWM_Forecast_Snow_Day1 "Snow Total [%.1f mm]" <snow>
Number:Length Weather_OWM_Forecast_Snow_Day2 "Snow Total [%.1f mm]" <snow>
Number:Length Weather_OWM_Forecast_Snow_Day3 "Snow Total [%.1f mm]" <snow>

Number:Temperature Weather_OWM_Forecast_Temperature_Min_Day0 "Temperature Low [%.1f °C]" <temperature>
Number:Temperature Weather_OWM_Forecast_Temperature_Min_Day1 "Temperature Low [%.1f °C]" <temperature>
Number:Temperature Weather_OWM_Forecast_Temperature_Min_Day2 "Temperature Low [%.1f °C]" <temperature>
Number:Temperature Weather_OWM_Forecast_Temperature_Min_Day3 "Temperature Low [%.1f °C]" <temperature>

Number:Temperature Weather_OWM_Forecast_Temperature_Max_Day0 "Temperature High [%.1f °C]" <temperature>
Number:Temperature Weather_OWM_Forecast_Temperature_Max_Day1 "Temperature High [%.1f °C]" <temperature>
Number:Temperature Weather_OWM_Forecast_Temperature_Max_Day2 "Temperature High [%.1f °C]" <temperature>
Number:Temperature Weather_OWM_Forecast_Temperature_Max_Day3 "Temperature High [%.1f °C]" <temperature>

Number:Speed Weather_OWM_Forecast_Wind_Speed_Max_Day0 "Max Wind Speed [%.0f km/h]" <wind>
Number:Speed Weather_OWM_Forecast_Wind_Speed_Max_Day1 "Max Wind Speed [%.0f km/h]" <wind>
Number:Speed Weather_OWM_Forecast_Wind_Speed_Max_Day2 "Max Wind Speed [%.0f km/h]" <wind>
Number:Speed Weather_OWM_Forecast_Wind_Speed_Max_Day3 "Max Wind Speed [%.0f km/h]" <wind>

Number:Pressure Weather_OWM_Forecast_Pressure_Min_Day0 "Pressure Low [%.1f hPa]" <pressure>
Number:Pressure Weather_OWM_Forecast_Pressure_Min_Day1 "Pressure Low [%.1f hPa]" <pressure>
Number:Pressure Weather_OWM_Forecast_Pressure_Min_Day2 "Pressure Low [%.1f hPa]" <pressure>
Number:Pressure Weather_OWM_Forecast_Pressure_Min_Day3 "Pressure Low [%.1f hPa]" <pressure>

Number:Pressure Weather_OWM_Forecast_Pressure_Max_Day0 "Pressure High [%.1f hPa]" <pressure>
Number:Pressure Weather_OWM_Forecast_Pressure_Max_Day1 "Pressure High [%.1f hPa]" <pressure>
Number:Pressure Weather_OWM_Forecast_Pressure_Max_Day2 "Pressure High [%.1f hPa]" <pressure>
Number:Pressure Weather_OWM_Forecast_Pressure_Max_Day3 "Pressure High [%.1f hPa]" <pressure>

Number:Dimensionless Weather_OWM_Forecast_Humidity_Min_Day0 "Humidity Low [%d %unit%]" <humidity>
Number:Dimensionless Weather_OWM_Forecast_Humidity_Min_Day1 "Humidity Low [%d %unit%]" <humidity>
Number:Dimensionless Weather_OWM_Forecast_Humidity_Min_Day2 "Humidity Low [%d %unit%]" <humidity>
Number:Dimensionless Weather_OWM_Forecast_Humidity_Min_Day3 "Humidity Low [%d %unit%]" <humidity>

Number:Dimensionless Weather_OWM_Forecast_Humidity_Max_Day0 "Humidity High [%d %unit%]" <humidity>
Number:Dimensionless Weather_OWM_Forecast_Humidity_Max_Day1 "Humidity High [%d %unit%]" <humidity>
Number:Dimensionless Weather_OWM_Forecast_Humidity_Max_Day2 "Humidity High [%d %unit%]" <humidity>
Number:Dimensionless Weather_OWM_Forecast_Humidity_Max_Day3 "Humidity High [%d %unit%]" <humidity>

In your sitemap:

Text item=Weather_OWM_Forecast_Time_72h label="Forecast (3 days) [until %1$tA]" icon="sun_clouds" {
	Frame item=Weather_OWM_Current_ObservationTime label="Today [%1$tA, %1$tb %1$td]" icon="calendar" {
		Default item=Weather_OWM_Forecast_Temperature_Min_Day0  valuecolor=[
			>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
		]
		Default item=Weather_OWM_Forecast_Temperature_Max_Day0  valuecolor=[
			>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
		]
		Default item=Weather_OWM_Forecast_Rain_Day0
		Default item=Weather_OWM_Forecast_Snow_Day0
		Default item=Weather_OWM_Forecast_Wind_Speed_Max_Day0
		Default item=Weather_OWM_Forecast_Humidity_Min_Day0
		Default item=Weather_OWM_Forecast_Humidity_Max_Day0
		Default item=Weather_OWM_Forecast_Pressure_Min_Day0
		Default item=Weather_OWM_Forecast_Pressure_Max_Day0
	}
	
	Frame item=Weather_OWM_Forecast_Time_24h label="Tomorrow [%1$tA, %1$tb %1$td]" icon="calendar" {
		Default item=Weather_OWM_Forecast_Temperature_Min_Day1  valuecolor=[
			>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
		]
		Default item=Weather_OWM_Forecast_Temperature_Max_Day1  valuecolor=[
			>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
		]
		Default item=Weather_OWM_Forecast_Rain_Day1
		Default item=Weather_OWM_Forecast_Snow_Day1
		Default item=Weather_OWM_Forecast_Wind_Speed_Max_Day1
		Default item=Weather_OWM_Forecast_Humidity_Min_Day1
		Default item=Weather_OWM_Forecast_Humidity_Max_Day1
		Default item=Weather_OWM_Forecast_Pressure_Min_Day1
		Default item=Weather_OWM_Forecast_Pressure_Max_Day1
	}

	Frame item=Weather_OWM_Forecast_Time_48h label="In 2 days [%1$tA, %1$tb %1$td]" icon="calendar" {
		Default item=Weather_OWM_Forecast_Temperature_Min_Day2  valuecolor=[
			>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
		]
		Default item=Weather_OWM_Forecast_Temperature_Max_Day2  valuecolor=[
			>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
		]
		Default item=Weather_OWM_Forecast_Rain_Day2
		Default item=Weather_OWM_Forecast_Snow_Day2
		Default item=Weather_OWM_Forecast_Wind_Speed_Max_Day2
		Default item=Weather_OWM_Forecast_Humidity_Min_Day2
		Default item=Weather_OWM_Forecast_Humidity_Max_Day2
		Default item=Weather_OWM_Forecast_Pressure_Min_Day2
		Default item=Weather_OWM_Forecast_Pressure_Max_Day2
	}

	Frame item=Weather_OWM_Forecast_Time_72h label="In 3 days [%1$tA, %1$tb %1$td]" icon="calendar" {
		Default item=Weather_OWM_Forecast_Temperature_Min_Day3  valuecolor=[
			>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
		]
		Default item=Weather_OWM_Forecast_Temperature_Max_Day3  valuecolor=[
			>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
		]
		Default item=Weather_OWM_Forecast_Rain_Day3
		Default item=Weather_OWM_Forecast_Snow_Day3
		Default item=Weather_OWM_Forecast_Wind_Speed_Max_Day3
		Default item=Weather_OWM_Forecast_Humidity_Min_Day3
		Default item=Weather_OWM_Forecast_Humidity_Max_Day3
		Default item=Weather_OWM_Forecast_Pressure_Min_Day3
		Default item=Weather_OWM_Forecast_Pressure_Max_Day3
	}
}

Now comes the (rather verbose) rule that takes care of computing the forecasts for daily rain and snow, and daily low and high temperatures:

rule "Update forecast data"
when
    // Time cron "0/15 * * * * ?" or // Only for debug purposes: display results every 15 seconds
    Time cron "0 0 0 * * ?" or
    Item Weather_OWM_Forecast_Time_03h changed
then
	val String ruleTitle = "UpdateOWMForecastInfo"

    val DateTime dtNow = now
    val Number now_d = dtNow.getDayOfMonth
    val Number now_m = dtNow.getMonthOfYear
    val Number now_h = dtNow.getHourOfDay

    val DateTime dtForecast_03h = (new DateTime(Weather_OWM_Forecast_Time_03h.state.toString)).toDateTime
    val Number forecast_03h_d = dtForecast_03h.getDayOfMonth
    val Number forecast_03h_m = dtForecast_03h.getMonthOfYear
    val Number forecast_03h_h = dtForecast_03h.getHourOfDay
    
    logInfo(ruleTitle, "Update forecast info - today is {}/{} - 03h forecast is for {}/{} at {}h", now_d, now_m, forecast_03h_d, forecast_03h_m, forecast_03h_h)

    var Number forecastDayOffset = 0
    if (forecast_03h_d > now_d) { // No forecast for today
        logInfo(ruleTitle, "No forecast for today ({}/{} - at or after {}h", now_d, now_m, now_h)
        forecastDayOffset = 1
    }

    var Number i = 3

    var Number rain_day_0 = 0|mm
    var Number snow_day_0 = 0|mm
    var Number wind_speed_max_day_0 = 0|"km/h"
    var Number temp_min_day_0 =  100|°C // Make it a very high (!) temperature so it gets updated instantly
    var Number temp_max_day_0 = -100|°C // Make it a very low (!) temperature so it gets updated instantly
    var Number humidity_min_day_0 =  100 // Make it a 100% so it gets updated instantly
    var Number humidity_max_day_0 =    0 // Make it 0% so it gets updated instantly
    var Number pressure_min_day_0 =  2000|"hPa" // Make it very high (!) so it gets updated instantly
    var Number pressure_max_day_0 =     0|"hPa" // Make it 0 so it gets updated instantly

    if (forecastDayOffset == 0) { // Forecast data still today: set min and max to current values as a starting point
        temp_min_day_0 = Weather_OWM_Current_Temperature.state as Number
        temp_max_day_0 = temp_min_day_0
        humidity_min_day_0 = Weather_OWM_Current_Humidity.state as Number
        humidity_max_day_0 = humidity_min_day_0
        pressure_min_day_0 = Weather_OWM_Current_Pressure.state as Number
        pressure_max_day_0 = pressure_min_day_0
    }

    while ( forecast_03h_h + i - 3 <= 24 ) {
        // Rain: total
        val String rainItemName = "Weather_OWM_Forecast_Rain_" + (if(i<10) {"0" + i.toString } else { i.toString }) + "h"
        val Number rain = ScriptServiceUtil.getItemRegistry.getItem(rainItemName).state as Number
        rain_day_0 = rain_day_0 + rain
        // Snow: total
        val String snowItemName = "Weather_OWM_Forecast_Snow_" + (if(i<10) {"0" + i.toString } else { i.toString }) + "h"
        val Number snow = ScriptServiceUtil.getItemRegistry.getItem(snowItemName).state as Number
        snow_day_0 = snow_day_0 + snow
        // Wind speed: max
        val String windSpeedItemName = "Weather_OWM_Forecast_Wind_Speed_" + (if(i<10) {"0" + i.toString } else { i.toString }) + "h"
        val Number wind_speed = ScriptServiceUtil.getItemRegistry.getItem(windSpeedItemName).state as Number
        if (wind_speed_max_day_0 < wind_speed) wind_speed_max_day_0 = wind_speed
        // Temperature: min, max
        val String tempItemName = "Weather_OWM_Forecast_Temperature_" + (if(i<10) {"0" + i.toString } else { i.toString }) + "h"
        val Number temp = ScriptServiceUtil.getItemRegistry.getItem(tempItemName).state as Number
        if (temp_max_day_0 < temp) temp_max_day_0 = temp
        if (temp_min_day_0 > temp) temp_min_day_0 = temp
        // Humidity: min, max
        val String humidityItemName = "Weather_OWM_Forecast_Humidity_" + (if(i<10) {"0" + i.toString } else { i.toString }) + "h"
        val Number humidity = ScriptServiceUtil.getItemRegistry.getItem(humidityItemName).state as Number
        if (humidity_max_day_0 < humidity) humidity_max_day_0 = humidity
        if (humidity_min_day_0 > humidity) humidity_min_day_0 = humidity
        // Pressure: min, max
        val String pressureItemName = "Weather_OWM_Forecast_Pressure_" + (if(i<10) {"0" + i.toString } else { i.toString }) + "h"
        val Number pressure = ScriptServiceUtil.getItemRegistry.getItem(pressureItemName).state as Number
        if (pressure_max_day_0 < pressure) pressure_max_day_0 = pressure
        if (pressure_min_day_0 > pressure) pressure_min_day_0 = pressure

        logDebug(ruleTitle,
            "Weather forecast for 'today + {} day(s)' at {}h ({}h + {}h) -- {} = {}, rain: {}, snow: {}, wind speed: {}, pressure: {}, humidity: {}",
            forecastDayOffset, forecast_03h_h + i - 3, forecast_03h_h, i - 3,
            tempItemName, temp, rain, snow, wind_speed, pressure, humidity)
        i = i + 3
    }
    logInfo(ruleTitle,
        "Forecast in {} days: temperature: {} - {}, rain: {}, snow: {}, wind speed: {}, pressure: {} - {}, humidity: {} - {}",
        0 + forecastDayOffset,
        temp_min_day_0, temp_max_day_0, rain_day_0, snow_day_0, wind_speed_max_day_0, 
        pressure_min_day_0, pressure_max_day_0, humidity_min_day_0, humidity_max_day_0)

    var Number rain_day_1 = 0|mm
    var Number snow_day_1 = 0|mm
    var Number wind_speed_max_day_1 = 0|"km/h"
    var Number temp_min_day_1 =  100|°C // Make it a very high (!) temperature so it gets updated instantly
    var Number temp_max_day_1 = -100|°C // Make it a very low (!) temperature so it gets updated instantly
    var Number humidity_min_day_1 =  100 // Make it a 100% so it gets updated instantly
    var Number humidity_max_day_1 =    0 // Make it 0%  so it gets updated instantly
    var Number pressure_min_day_1 =  2000|"hPa" // Make it a 100% so it gets updated instantly
    var Number pressure_max_day_1 =     0|"hPa" // Make it 0%  so it gets updated instantly

    while ( forecast_03h_h + i - 3 <= 48 ) {
        // Rain: total
        val String rainItemName = "Weather_OWM_Forecast_Rain_" + (if(i<10) {"0" + i.toString } else { i.toString }) + "h"
        val Number rain = ScriptServiceUtil.getItemRegistry.getItem(rainItemName).state as Number
        rain_day_1 = rain_day_1 + rain
        // Snow: total
        val String snowItemName = "Weather_OWM_Forecast_Snow_" + (if(i<10) {"0" + i.toString } else { i.toString }) + "h"
        val Number snow = ScriptServiceUtil.getItemRegistry.getItem(snowItemName).state as Number
        snow_day_1 = snow_day_1 + snow
        // Wind speed: max
        val String windSpeedItemName = "Weather_OWM_Forecast_Wind_Speed_" + (if(i<10) {"0" + i.toString } else { i.toString }) + "h"
        val Number wind_speed = ScriptServiceUtil.getItemRegistry.getItem(windSpeedItemName).state as Number
        if (wind_speed_max_day_1 < wind_speed) wind_speed_max_day_1 = wind_speed
        // Temperature: min, max
        val String tempItemName = "Weather_OWM_Forecast_Temperature_" + (if(i<10) {"0" + i.toString } else { i.toString }) + "h"
        val Number temp = ScriptServiceUtil.getItemRegistry.getItem(tempItemName).state as Number
        if (temp_max_day_1 < temp) temp_max_day_1 = temp
        if (temp_min_day_1 > temp) temp_min_day_1 = temp
        // Humidity: min, max
        val String humidityItemName = "Weather_OWM_Forecast_Humidity_" + (if(i<10) {"0" + i.toString } else { i.toString }) + "h"
        val Number humidity = ScriptServiceUtil.getItemRegistry.getItem(humidityItemName).state as Number
        if (humidity_max_day_1 < humidity) humidity_max_day_1 = humidity
        if (humidity_min_day_1 > humidity) humidity_min_day_1 = humidity
        // Pressure: min, max
        val String pressureItemName = "Weather_OWM_Forecast_Pressure_" + (if(i<10) {"0" + i.toString } else { i.toString }) + "h"
        val Number pressure = ScriptServiceUtil.getItemRegistry.getItem(pressureItemName).state as Number
        if (pressure_max_day_1 < pressure) pressure_max_day_1 = pressure
        if (pressure_min_day_1 > pressure) pressure_min_day_1 = pressure

        logDebug(ruleTitle,
            "Weather forecast for 'today + {} day(s)' at {}h ({}h + {}h) -- {} = {}, rain: {}, snow: {}, wind speed: {}, pressure: {}, humidity: {}",
            1 + forecastDayOffset, forecast_03h_h + i - 3 - 24, forecast_03h_h, i - 3 - 24,
            tempItemName, temp, rain, snow, wind_speed, pressure, humidity)
        i = i + 3
    }
    logInfo(ruleTitle,
        "Forecast in {} days: temperature: {} - {}, rain: {}, snow: {}, wind speed: {}, pressure: {} - {}, humidity: {} - {}",
        1 + forecastDayOffset,
        temp_min_day_1, temp_max_day_1, rain_day_1, snow_day_1, wind_speed_max_day_1,
        pressure_min_day_1, pressure_max_day_1, humidity_min_day_1, humidity_max_day_1)

    var Number rain_day_2 = 0|mm
    var Number snow_day_2 = 0|mm
    var Number wind_speed_max_day_2 = 0|"km/h"
    var Number temp_min_day_2 =  100|°C // Make it a very high (!) temperature so it gets updated instantly
    var Number temp_max_day_2 = -100|°C // Make it a very low (!) temperature so it gets updated instantly
    var Number humidity_min_day_2 =  100 // Make it a 100% so it gets updated instantly
    var Number humidity_max_day_2 =    0 // Make it 0%  so it gets updated instantly
    var Number pressure_min_day_2 =  2000|"hPa" // Make it a 100% so it gets updated instantly
    var Number pressure_max_day_2 =     0|"hPa" // Make it 0%  so it gets updated instantly

    while ( forecast_03h_h + i - 3 <= 72 ) {
        // Rain: total
        val String rainItemName = "Weather_OWM_Forecast_Rain_" + (if(i<10) {"0" + i.toString } else { i.toString }) + "h"
        val Number rain = ScriptServiceUtil.getItemRegistry.getItem(rainItemName).state as Number
        rain_day_2 = rain_day_2 + rain
        // Snow: total
        val String snowItemName = "Weather_OWM_Forecast_Snow_" + (if(i<10) {"0" + i.toString } else { i.toString }) + "h"
        val Number snow = ScriptServiceUtil.getItemRegistry.getItem(snowItemName).state as Number
        snow_day_2 = snow_day_2 + snow
        // Wind speed: max
        val String windSpeedItemName = "Weather_OWM_Forecast_Wind_Speed_" + (if(i<10) {"0" + i.toString } else { i.toString }) + "h"
        val Number wind_speed = ScriptServiceUtil.getItemRegistry.getItem(windSpeedItemName).state as Number
        if (wind_speed_max_day_2 < wind_speed) wind_speed_max_day_2 = wind_speed
        // Temperature: min, max
        val String tempItemName = "Weather_OWM_Forecast_Temperature_" + (if(i<10) {"0" + i.toString } else { i.toString }) + "h"
        val Number temp = ScriptServiceUtil.getItemRegistry.getItem(tempItemName).state as Number
        if (temp_max_day_2 < temp) temp_max_day_2 = temp
        if (temp_min_day_2 > temp) temp_min_day_2 = temp
        // Humidity: min, max
        val String humidityItemName = "Weather_OWM_Forecast_Humidity_" + (if(i<10) {"0" + i.toString } else { i.toString }) + "h"
        val Number humidity = ScriptServiceUtil.getItemRegistry.getItem(humidityItemName).state as Number
        if (humidity_max_day_2 < humidity) humidity_max_day_2 = humidity
        if (humidity_min_day_2 > humidity) humidity_min_day_2 = humidity
        // Pressure: min, max
        val String pressureItemName = "Weather_OWM_Forecast_Pressure_" + (if(i<10) {"0" + i.toString } else { i.toString }) + "h"
        val Number pressure = ScriptServiceUtil.getItemRegistry.getItem(pressureItemName).state as Number
        if (pressure_max_day_2 < pressure) pressure_max_day_2 = pressure
        if (pressure_min_day_2 > pressure) pressure_min_day_2 = pressure

        logDebug(ruleTitle,
            "Weather forecast for 'today + {} day(s)' at {}h ({}h + {}h) -- {} = {}, rain: {}, snow: {}, wind speed: {}, pressure: {}, humidity: {}",
            2 + forecastDayOffset, forecast_03h_h + i - 3 - 48, forecast_03h_h, i - 3 - 48,
            tempItemName, temp, rain, snow, wind_speed, pressure, humidity)
        i = i + 3
    }
    logInfo(ruleTitle,
        "Forecast in {} days: temperature: {} - {}, rain: {}, snow: {}, wind speed: {}, pressure: {} - {}, humidity: {} - {}",
        2 + forecastDayOffset,
        temp_min_day_2, temp_max_day_2, rain_day_2, snow_day_2, wind_speed_max_day_2,
        pressure_min_day_2, pressure_max_day_2, humidity_min_day_2, humidity_max_day_2)

    var Number rain_day_3 = 0|mm
    var Number snow_day_3 = 0|mm
    var Number wind_speed_max_day_3 = 0|"km/h"
    var Number temp_min_day_3 =  100|°C // Make it a very high (!) temperature so it gets updated instantly
    var Number temp_max_day_3 = -100|°C // Make it a very low (!) temperature so it gets updated instantly
    var Number humidity_min_day_3 =  100 // Make it a 100% so it gets updated instantly
    var Number humidity_max_day_3 =    0 // Make it 0%  so it gets updated instantly
    var Number pressure_min_day_3 =  2000|"hPa" // Make it a 100% so it gets updated instantly
    var Number pressure_max_day_3 =     0|"hPa" // Make it 0%  so it gets updated instantly

    while ( forecast_03h_h + i - 3 <= 96 ) {
        // Rain: total
        val String rainItemName = "Weather_OWM_Forecast_Rain_" + (if(i<10) {"0" + i.toString } else { i.toString }) + "h"
        val Number rain = ScriptServiceUtil.getItemRegistry.getItem(rainItemName).state as Number
        rain_day_3 = rain_day_3 + rain
        // Snow: total
        val String snowItemName = "Weather_OWM_Forecast_Snow_" + (if(i<10) {"0" + i.toString } else { i.toString }) + "h"
        val Number snow = ScriptServiceUtil.getItemRegistry.getItem(snowItemName).state as Number
        snow_day_3 = snow_day_3 + snow
        // Wind speed: max
        val String windSpeedItemName = "Weather_OWM_Forecast_Wind_Speed_" + (if(i<10) {"0" + i.toString } else { i.toString }) + "h"
        val Number wind_speed = ScriptServiceUtil.getItemRegistry.getItem(windSpeedItemName).state as Number
        if (wind_speed_max_day_3 < wind_speed) wind_speed_max_day_3 = wind_speed
        // Temperature: min, max
        val String tempItemName = "Weather_OWM_Forecast_Temperature_" + (if(i<10) {"0" + i.toString } else { i.toString }) + "h"
        val Number temp = ScriptServiceUtil.getItemRegistry.getItem(tempItemName).state as Number
        if (temp_max_day_3 < temp) temp_max_day_3 = temp
        if (temp_min_day_3 > temp) temp_min_day_3 = temp
        // Humidity: min, max
        val String humidityItemName = "Weather_OWM_Forecast_Humidity_" + (if(i<10) {"0" + i.toString } else { i.toString }) + "h"
        val Number humidity = ScriptServiceUtil.getItemRegistry.getItem(humidityItemName).state as Number
        if (humidity_max_day_3 < humidity) humidity_max_day_3 = humidity
        if (humidity_min_day_3 > humidity) humidity_min_day_3 = humidity
        // Pressure: min, max
        val String pressureItemName = "Weather_OWM_Forecast_Pressure_" + (if(i<10) {"0" + i.toString } else { i.toString }) + "h"
        val Number pressure = ScriptServiceUtil.getItemRegistry.getItem(pressureItemName).state as Number
        if (pressure_max_day_3 < pressure) pressure_max_day_3 = pressure
        if (pressure_min_day_3 > pressure) pressure_min_day_3 = pressure

        logDebug(ruleTitle,
            "Weather forecast for 'today + {} day(s)' at {}h ({}h + {}h) -- {} = {}, rain: {}, snow: {}, wind speed: {}, pressure: {}, humidity: {}",
            3 + forecastDayOffset, forecast_03h_h + i - 3 - 72, forecast_03h_h, i - 3 - 72,
            tempItemName, temp, rain, snow, wind_speed, pressure, humidity)
        i = i + 3
    }
    logInfo(ruleTitle,
        "Forecast in {} days: temperature: {} - {}, rain: {}, snow: {}, wind speed: {}, pressure: {} - {}, humidity: {} - {}",
        3 + forecastDayOffset,
        temp_min_day_3, temp_max_day_3, rain_day_3, snow_day_3, wind_speed_max_day_3,
        pressure_min_day_3, pressure_max_day_3, humidity_min_day_3, humidity_max_day_3)

    if (forecastDayOffset == 0) {
        Weather_OWM_Forecast_Rain_Day0.postUpdate(rain_day_0)
        Weather_OWM_Forecast_Rain_Day1.postUpdate(rain_day_1)
        Weather_OWM_Forecast_Rain_Day2.postUpdate(rain_day_2)
        Weather_OWM_Forecast_Rain_Day3.postUpdate(rain_day_3)

        Weather_OWM_Forecast_Snow_Day0.postUpdate(snow_day_0)
        Weather_OWM_Forecast_Snow_Day1.postUpdate(snow_day_1)
        Weather_OWM_Forecast_Snow_Day2.postUpdate(snow_day_2)
        Weather_OWM_Forecast_Snow_Day3.postUpdate(snow_day_3)

        Weather_OWM_Forecast_Wind_Speed_Max_Day0.postUpdate(wind_speed_max_day_0)
        Weather_OWM_Forecast_Wind_Speed_Max_Day1.postUpdate(wind_speed_max_day_1)
        Weather_OWM_Forecast_Wind_Speed_Max_Day2.postUpdate(wind_speed_max_day_2)
        Weather_OWM_Forecast_Wind_Speed_Max_Day3.postUpdate(wind_speed_max_day_3)

        Weather_OWM_Forecast_Temperature_Min_Day0.postUpdate(temp_min_day_0)
        Weather_OWM_Forecast_Temperature_Min_Day1.postUpdate(temp_min_day_1)
        Weather_OWM_Forecast_Temperature_Min_Day2.postUpdate(temp_min_day_2)
        Weather_OWM_Forecast_Temperature_Min_Day3.postUpdate(temp_min_day_3)

        Weather_OWM_Forecast_Temperature_Max_Day0.postUpdate(temp_max_day_0)
        Weather_OWM_Forecast_Temperature_Max_Day1.postUpdate(temp_max_day_1)
        Weather_OWM_Forecast_Temperature_Max_Day2.postUpdate(temp_max_day_2)
        Weather_OWM_Forecast_Temperature_Max_Day3.postUpdate(temp_max_day_3)

        Weather_OWM_Forecast_Pressure_Min_Day0.postUpdate(pressure_min_day_0)
        Weather_OWM_Forecast_Pressure_Min_Day1.postUpdate(pressure_min_day_1)
        Weather_OWM_Forecast_Pressure_Min_Day2.postUpdate(pressure_min_day_2)
        Weather_OWM_Forecast_Pressure_Min_Day3.postUpdate(pressure_min_day_3)

        Weather_OWM_Forecast_Pressure_Max_Day0.postUpdate(pressure_max_day_0)
        Weather_OWM_Forecast_Pressure_Max_Day1.postUpdate(pressure_max_day_1)
        Weather_OWM_Forecast_Pressure_Max_Day2.postUpdate(pressure_max_day_2)
        Weather_OWM_Forecast_Pressure_Max_Day3.postUpdate(pressure_max_day_3)

        Weather_OWM_Forecast_Humidity_Min_Day0.postUpdate(humidity_min_day_0)
        Weather_OWM_Forecast_Humidity_Min_Day1.postUpdate(humidity_min_day_1)
        Weather_OWM_Forecast_Humidity_Min_Day2.postUpdate(humidity_min_day_2)
        Weather_OWM_Forecast_Humidity_Min_Day3.postUpdate(humidity_min_day_3)

        Weather_OWM_Forecast_Humidity_Max_Day0.postUpdate(humidity_max_day_0)
        Weather_OWM_Forecast_Humidity_Max_Day1.postUpdate(humidity_max_day_1)
        Weather_OWM_Forecast_Humidity_Max_Day2.postUpdate(humidity_max_day_2)
        Weather_OWM_Forecast_Humidity_Max_Day3.postUpdate(humidity_max_day_3)

    } else {

        Weather_OWM_Forecast_Rain_Day0.postUpdate(UNDEF)
        Weather_OWM_Forecast_Rain_Day1.postUpdate(rain_day_0)
        Weather_OWM_Forecast_Rain_Day2.postUpdate(rain_day_1)
        Weather_OWM_Forecast_Rain_Day3.postUpdate(rain_day_2)

        Weather_OWM_Forecast_Snow_Day0.postUpdate(UNDEF)
        Weather_OWM_Forecast_Snow_Day1.postUpdate(snow_day_0)
        Weather_OWM_Forecast_Snow_Day2.postUpdate(snow_day_1)
        Weather_OWM_Forecast_Snow_Day3.postUpdate(snow_day_2)

        Weather_OWM_Forecast_Wind_Speed_Max_Day0.postUpdate(UNDEF)
        Weather_OWM_Forecast_Wind_Speed_Max_Day1.postUpdate(wind_speed_max_day_0)
        Weather_OWM_Forecast_Wind_Speed_Max_Day2.postUpdate(wind_speed_max_day_1)
        Weather_OWM_Forecast_Wind_Speed_Max_Day3.postUpdate(wind_speed_max_day_2)

        Weather_OWM_Forecast_Temperature_Min_Day0.postUpdate(UNDEF)
        Weather_OWM_Forecast_Temperature_Min_Day1.postUpdate(temp_min_day_0)
        Weather_OWM_Forecast_Temperature_Min_Day2.postUpdate(temp_min_day_1)
        Weather_OWM_Forecast_Temperature_Min_Day3.postUpdate(temp_min_day_2)

        Weather_OWM_Forecast_Temperature_Max_Day0.postUpdate(UNDEF)
        Weather_OWM_Forecast_Temperature_Max_Day1.postUpdate(temp_max_day_0)
        Weather_OWM_Forecast_Temperature_Max_Day2.postUpdate(temp_max_day_1)
        Weather_OWM_Forecast_Temperature_Max_Day3.postUpdate(temp_max_day_2)

        Weather_OWM_Forecast_Pressure_Min_Day0.postUpdate(UNDEF)
        Weather_OWM_Forecast_Pressure_Min_Day1.postUpdate(pressure_min_day_0)
        Weather_OWM_Forecast_Pressure_Min_Day2.postUpdate(pressure_min_day_1)
        Weather_OWM_Forecast_Pressure_Min_Day3.postUpdate(pressure_min_day_2)

        Weather_OWM_Forecast_Pressure_Max_Day0.postUpdate(UNDEF)
        Weather_OWM_Forecast_Pressure_Max_Day1.postUpdate(pressure_max_day_0)
        Weather_OWM_Forecast_Pressure_Max_Day2.postUpdate(pressure_max_day_1)
        Weather_OWM_Forecast_Pressure_Max_Day3.postUpdate(pressure_max_day_2)

        Weather_OWM_Forecast_Humidity_Min_Day0.postUpdate(UNDEF)
        Weather_OWM_Forecast_Humidity_Min_Day1.postUpdate(humidity_min_day_0)
        Weather_OWM_Forecast_Humidity_Min_Day2.postUpdate(humidity_min_day_1)
        Weather_OWM_Forecast_Humidity_Min_Day3.postUpdate(humidity_min_day_2)

        Weather_OWM_Forecast_Humidity_Max_Day0.postUpdate(UNDEF)
        Weather_OWM_Forecast_Humidity_Max_Day1.postUpdate(humidity_max_day_0)
        Weather_OWM_Forecast_Humidity_Max_Day2.postUpdate(humidity_max_day_1)
        Weather_OWM_Forecast_Humidity_Max_Day3.postUpdate(humidity_max_day_2)

    }
end

My rule separately computes the values for a day at a time (hence the “_0”, “_1” etc. variable numbering in 4 quasi-similar code blocks). It takes care of the edge case where the first forecast value is no longer for today (then forecastDayOffset is set to 1). For this reason I can only compute the forecast values for up to 3 days in advance. The 4th day is not processed as it can only use a full forecast data set when the forecast starts at the 1st possible time slot of a day (so the 4th day uses the OWM forecast data from _75h to _96h).

Have fun!

7 Likes

And here’s a more compact version, making use of lambda functions.

import org.eclipse.smarthome.model.script.ScriptServiceUtil
import org.eclipse.xtext.xbase.lib.Functions
import java.util.List

val int MAX_DAYS = 4



// Return the set of weather forecast readings filtered on the forecastTypeStr
val Functions$Function1<String, Iterable<GenericItem>> getForecastItems = [
    forecastTypeStr |
    gWeatherForecast.allMembers.filter[ i | i.name.startsWith("Weather_OWM_Forecast_" + forecastTypeStr + "_") ]
]

val Procedures$Procedure4<String, String, Boolean, List<Number>> setDailyForecastNumberItemValue = [
    forecastTypeStr, metricName, forecast_offset, valueList |
    val int MAX_DAYS = 4 // Lambda functions don't resolve global constants!
    val String itemNameBase = "Weather_OWM_Forecast_" + forecastTypeStr + ( if (metricName == "") {""} else {"_" + metricName} ) + "_Day"
    var int i = 0
    var int index = 0
    if (forecast_offset) index += 1
    while ( (i < MAX_DAYS) && (index < MAX_DAYS) ) {
        val Number value = ( if ( (forecast_offset == true) && (i == 1) ) UNDEF else valueList.get(index) )
        val String itemName = itemNameBase + i.toString
        logDebug("setDailyForecastNumberItemValue", "Value of '" + itemName + "' is: " + ScriptServiceUtil.getItemRegistry.getItem(itemName).state.toString + " - will be set to " + value.toString)
        postUpdate( ScriptServiceUtil.getItemRegistry.getItem(itemName), value )

        i += 1
        index += 1
    }
]


// Return a filtered set of forecast values for a given day
val Functions$Function3<Iterable<GenericItem>, Number, Number, Iterable<GenericItem>> filterWxItemsForDay = [
    items, forecast_03h_hour, theDay |
    val Number fromHour = 0 + 24 * theDay
    val Number toHour = 24 + 24 * theDay
    logDebug("Lambda:filterWxItemsForDay", "About to start processing the item filtering process")
    items.filter [ i |
        val Number hr = Integer::parseInt( i.name.substring(i.name.length() - 3).substring(0,2) ) as Number
        val Number hourInDay = hr + forecast_03h_hour - 3
        if ( (hourInDay >= fromHour) && (hourInDay <= toHour) ) {
            logDebug("Lambda:filterWxItemsForDay", i.name + ": hour " + hourInDay.toString + " = " + i.state.toString)
        }

        // Result:
        (hourInDay >= fromHour) && (hourInDay <= toHour)
    ]
]

val Functions$Function1<Iterable<GenericItem>, Number> maxStateValueFrom = [
    items |
    items.map[ state as Number ].reduce[ max, v |
        if (v > max)
            v
        else
            max
    ]
]

val Functions$Function1<Iterable<GenericItem>, Number> minStateValueFrom = [
    items |
    items.map[ state as Number ].reduce[ min, v |
        if (v < min)
            v
        else
            min
    ]
]

val Functions$Function1<Iterable<GenericItem>, Number> sumStateValueFrom = [
    items |
    items.map[ state as Number ].reduce[ sum, v |
        sum + v
    ]
]


rule "Group-based weather forecast processing"
when
    // Time cron "0/15 * * * * ?" or
    Item DBG_Test_Weather_Forecast changed or // Debugging
    Time cron "0 0 0 * * ?" or
    Item Weather_OWM_Forecast_Time_03h changed
then
	val String ruleTitle = "UpdateOWMForecastInfo_Groups"


    val DateTime dtNow = now
    val Number now_d = dtNow.getDayOfMonth
    val Number now_m = dtNow.getMonthOfYear
    val Number now_h = dtNow.getHourOfDay

    val DateTime dtForecast_03h = (new DateTime(Weather_OWM_Forecast_Time_03h.state.toString)).toDateTime
    val Number forecast_03h_d = dtForecast_03h.getDayOfMonth
    val Number forecast_03h_m = dtForecast_03h.getMonthOfYear
    val Number forecast_03h_h = dtForecast_03h.getHourOfDay

    val Boolean forecastDayOffset = ( if (forecast_03h_d > now_d) true else false )
    if (forecastDayOffset) { // No forecast for today
        logInfo(ruleTitle, "No forecast for today ({}/{} - at or after {}h", now_d, now_m, now_h)
    }

    logInfo(ruleTitle,
        "Update Wx forecast info - today is {}/{} @ {}h - 03h forecast is for {}/{} at {}h",
        now_d, now_m, now_h,
        forecast_03h_d, forecast_03h_m, forecast_03h_h)

    val Iterable<GenericItem> forecastTemperature = getForecastItems.apply("Temperature")
    val List<Iterable<GenericItem>> temperature = newArrayList( null,null,null,null )
    val List<Number> temperature_min = newArrayList( 0,0,0,0 )
    val List<Number> temperature_max = newArrayList( 0,0,0,0 )

    val Iterable<GenericItem> forecastPressure = getForecastItems.apply("Pressure")
    val List<Iterable<GenericItem>> pressure = newArrayList( null,null,null,null )
    val List<Number> pressure_min = newArrayList( 0,0,0,0 )
    val List<Number> pressure_max = newArrayList( 0,0,0,0 )

    val Iterable<GenericItem> forecastHumidity = getForecastItems.apply("Humidity")
    val List<Iterable<GenericItem>> humidity = newArrayList( null,null,null,null )
    val List<Number> humidity_min = newArrayList( 0,0,0,0 )
    val List<Number> humidity_max = newArrayList( 0,0,0,0 )

    val Iterable<GenericItem> forecastCloudiness = getForecastItems.apply("Cloudiness")
    val List<Iterable<GenericItem>> cloudiness = newArrayList( null,null,null,null )
    val List<Number> cloudiness_min = newArrayList( 0,0,0,0 )
    val List<Number> cloudiness_max = newArrayList( 0,0,0,0 )

    val Iterable<GenericItem> forecastRain = getForecastItems.apply("Rain")
    val List<Iterable<GenericItem>> rain = newArrayList( null,null,null,null )
    val List<Number> rain_sum = newArrayList( 0,0,0,0 )
    
    val Iterable<GenericItem> forecastSnow = getForecastItems.apply("Snow")
    val List<Iterable<GenericItem>> snow = newArrayList( null,null,null,null )
    val List<Number> snow_sum = newArrayList( 0,0,0,0 )

    val Iterable<GenericItem> forecastWind_Speed = getForecastItems.apply("Wind_Speed")
    val List<Iterable<GenericItem>> wind_speed = newArrayList( null,null,null,null )
    val List<Number> wind_speed_max = newArrayList( 0,0,0,0 )

    val forecast_offset = forecastDayOffset

    var int i = 0
    while (i < MAX_DAYS) {
        logInfo(ruleTitle, "INFO - iteration {} - forecastDayOffset is {}", i, forecastDayOffset)

        temperature.set(i, filterWxItemsForDay.apply(forecastTemperature, forecast_03h_h, i))
        temperature_min.set(i, minStateValueFrom.apply(temperature.get(i)))
        temperature_max.set(i, maxStateValueFrom.apply(temperature.get(i)))
        logInfo(ruleTitle,"RESULT: {} FORECAST FOR DAY {} : {} -- {}",
            "Temperature", i, temperature_min.get(i), temperature_max.get(i) )

        pressure.set(i, filterWxItemsForDay.apply(forecastPressure, forecast_03h_h, i))
        pressure_min.set(i, minStateValueFrom.apply(pressure.get(i)))
        pressure_max.set(i, maxStateValueFrom.apply(pressure.get(i)))
        logInfo(ruleTitle,"RESULT: {} FORECAST FOR DAY {} : {} -- {}",
            "Pressure", i, pressure_min.get(i), pressure_max.get(i) )

        humidity.set(i, filterWxItemsForDay.apply(forecastHumidity, forecast_03h_h, i))
        humidity_min.set(i, minStateValueFrom.apply(humidity.get(i)))
        humidity_max.set(i, maxStateValueFrom.apply(humidity.get(i)))
        logInfo(ruleTitle,"RESULT: {} FORECAST FOR DAY {} : {} -- {}",
            "Humidity", i, humidity_min.get(i), humidity_max.get(i) )

        cloudiness.set(i, filterWxItemsForDay.apply(forecastCloudiness, forecast_03h_h, i))
        cloudiness_min.set(i, minStateValueFrom.apply(cloudiness.get(i)))
        cloudiness_max.set(i, maxStateValueFrom.apply(cloudiness.get(i)))
        logInfo(ruleTitle,"RESULT: {} FORECAST FOR DAY {} : {} -- {}",
            "Cloudiness", i, cloudiness_min.get(i), cloudiness_max.get(i) )

        rain.set(i, filterWxItemsForDay.apply(forecastRain, forecast_03h_h, i))
        rain_sum.set(i, sumStateValueFrom.apply(rain.get(i)))
        logInfo(ruleTitle,"RESULT: {} FORECAST FOR DAY {} : SUM {}",
            "Rain", i, rain_sum.get(i) )

        snow.set(i, filterWxItemsForDay.apply(forecastSnow, forecast_03h_h, i))
        snow_sum.set(i, sumStateValueFrom.apply(snow.get(i)))
        logInfo(ruleTitle,"RESULT: {} FORECAST FOR DAY {} : SUM {}",
            "Snow", i, snow_sum.get(i) )

        wind_speed.set(i, filterWxItemsForDay.apply(forecastWind_Speed, forecast_03h_h, i))
        wind_speed_max.set(i, maxStateValueFrom.apply(wind_speed.get(i)))
        logInfo(ruleTitle,"RESULT: {} FORECAST FOR DAY {} : MAX {}",
            "Wind_Speed", i, wind_speed_max.get(i) )

        i += 1
    }

    logInfo(ruleTitle, "INFO - Updating the item values")

    setDailyForecastNumberItemValue.apply("Temperature",    "Min",  forecast_offset, temperature_min)
    setDailyForecastNumberItemValue.apply("Temperature",    "Max",  forecast_offset, temperature_max)
    setDailyForecastNumberItemValue.apply("Pressure",       "Min",  forecast_offset, pressure_min)
    setDailyForecastNumberItemValue.apply("Pressure",       "Max",  forecast_offset, pressure_max)
    setDailyForecastNumberItemValue.apply("Humidity",       "Min",  forecast_offset, humidity_min)
    setDailyForecastNumberItemValue.apply("Humidity",       "Max",  forecast_offset, humidity_max)
    setDailyForecastNumberItemValue.apply("Cloudiness",     "Min",  forecast_offset, cloudiness_min)
    setDailyForecastNumberItemValue.apply("Cloudiness",     "Max",  forecast_offset, cloudiness_max)
    setDailyForecastNumberItemValue.apply("Rain",           "",     forecast_offset, rain_sum)
    setDailyForecastNumberItemValue.apply("Snow",           "",     forecast_offset, snow_sum)
    setDailyForecastNumberItemValue.apply("Wind_Speed",     "Max",  forecast_offset, wind_speed_max)

end

To debug the rule, I added the following Item in my home.items file:

Switch DBG_Test_Weather_Forecast "Test weather forecast rule" <sun_clouds> (gDebug)

The openweathermap.items file contains the following set of Items to hold the forecast data:

// The following Items will store the computed daily forecast values

Number:Length Weather_OWM_Forecast_Rain_Day0 "Rain Total [%.1f mm]" <rain>
Number:Length Weather_OWM_Forecast_Rain_Day1 "Rain Total [%.1f mm]" <rain>
Number:Length Weather_OWM_Forecast_Rain_Day2 "Rain Total [%.1f mm]" <rain>
Number:Length Weather_OWM_Forecast_Rain_Day3 "Rain Total [%.1f mm]" <rain>

Number:Length Weather_OWM_Forecast_Snow_Day0 "Snow Total [%.1f mm]" <snow>
Number:Length Weather_OWM_Forecast_Snow_Day1 "Snow Total [%.1f mm]" <snow>
Number:Length Weather_OWM_Forecast_Snow_Day2 "Snow Total [%.1f mm]" <snow>
Number:Length Weather_OWM_Forecast_Snow_Day3 "Snow Total [%.1f mm]" <snow>

Number:Temperature Weather_OWM_Forecast_Temperature_Min_Day0 "Temperature Low [%.1f °C]" <temperature>
Number:Temperature Weather_OWM_Forecast_Temperature_Min_Day1 "Temperature Low [%.1f °C]" <temperature>
Number:Temperature Weather_OWM_Forecast_Temperature_Min_Day2 "Temperature Low [%.1f °C]" <temperature>
Number:Temperature Weather_OWM_Forecast_Temperature_Min_Day3 "Temperature Low [%.1f °C]" <temperature>

Number:Temperature Weather_OWM_Forecast_Temperature_Max_Day0 "Temperature High [%.1f °C]" <temperature>
Number:Temperature Weather_OWM_Forecast_Temperature_Max_Day1 "Temperature High [%.1f °C]" <temperature>
Number:Temperature Weather_OWM_Forecast_Temperature_Max_Day2 "Temperature High [%.1f °C]" <temperature>
Number:Temperature Weather_OWM_Forecast_Temperature_Max_Day3 "Temperature High [%.1f °C]" <temperature>

Number:Speed Weather_OWM_Forecast_Wind_Speed_Max_Day0 "Max Wind Speed [%.0f km/h]" <wind>
Number:Speed Weather_OWM_Forecast_Wind_Speed_Max_Day1 "Max Wind Speed [%.0f km/h]" <wind>
Number:Speed Weather_OWM_Forecast_Wind_Speed_Max_Day2 "Max Wind Speed [%.0f km/h]" <wind>
Number:Speed Weather_OWM_Forecast_Wind_Speed_Max_Day3 "Max Wind Speed [%.0f km/h]" <wind>

Number:Pressure Weather_OWM_Forecast_Pressure_Min_Day0 "Pressure Low [%.1f hPa]" <pressure>
Number:Pressure Weather_OWM_Forecast_Pressure_Min_Day1 "Pressure Low [%.1f hPa]" <pressure>
Number:Pressure Weather_OWM_Forecast_Pressure_Min_Day2 "Pressure Low [%.1f hPa]" <pressure>
Number:Pressure Weather_OWM_Forecast_Pressure_Min_Day3 "Pressure Low [%.1f hPa]" <pressure>

Number:Pressure Weather_OWM_Forecast_Pressure_Max_Day0 "Pressure High [%.1f hPa]" <pressure>
Number:Pressure Weather_OWM_Forecast_Pressure_Max_Day1 "Pressure High [%.1f hPa]" <pressure>
Number:Pressure Weather_OWM_Forecast_Pressure_Max_Day2 "Pressure High [%.1f hPa]" <pressure>
Number:Pressure Weather_OWM_Forecast_Pressure_Max_Day3 "Pressure High [%.1f hPa]" <pressure>

Number:Dimensionless Weather_OWM_Forecast_Humidity_Min_Day0 "Humidity Low [%d %unit%]" <humidity>
Number:Dimensionless Weather_OWM_Forecast_Humidity_Min_Day1 "Humidity Low [%d %unit%]" <humidity>
Number:Dimensionless Weather_OWM_Forecast_Humidity_Min_Day2 "Humidity Low [%d %unit%]" <humidity>
Number:Dimensionless Weather_OWM_Forecast_Humidity_Min_Day3 "Humidity Low [%d %unit%]" <humidity>

Number:Dimensionless Weather_OWM_Forecast_Humidity_Max_Day0 "Humidity High [%d %unit%]" <humidity>
Number:Dimensionless Weather_OWM_Forecast_Humidity_Max_Day1 "Humidity High [%d %unit%]" <humidity>
Number:Dimensionless Weather_OWM_Forecast_Humidity_Max_Day2 "Humidity High [%d %unit%]" <humidity>
Number:Dimensionless Weather_OWM_Forecast_Humidity_Max_Day3 "Humidity High [%d %unit%]" <humidity>

Number:Dimensionless Weather_OWM_Forecast_Cloudiness_Min_Day0 "Cloudiness Low [%d %unit%]" <sun_clouds>
Number:Dimensionless Weather_OWM_Forecast_Cloudiness_Min_Day1 "Cloudiness Low [%d %unit%]" <sun_clouds>
Number:Dimensionless Weather_OWM_Forecast_Cloudiness_Min_Day2 "Cloudiness Low [%d %unit%]" <sun_clouds>
Number:Dimensionless Weather_OWM_Forecast_Cloudiness_Min_Day3 "Cloudiness Low [%d %unit%]" <sun_clouds>

Number:Dimensionless Weather_OWM_Forecast_Cloudiness_Max_Day0 "Cloudiness High [%d %unit%]" <sun_clouds>
Number:Dimensionless Weather_OWM_Forecast_Cloudiness_Max_Day1 "Cloudiness High [%d %unit%]" <sun_clouds>
Number:Dimensionless Weather_OWM_Forecast_Cloudiness_Max_Day2 "Cloudiness High [%d %unit%]" <sun_clouds>
Number:Dimensionless Weather_OWM_Forecast_Cloudiness_Max_Day3 "Cloudiness High [%d %unit%]" <sun_clouds>

The rule now covers 124 lines of code (excluding the lambda functions) instead of 335 in my previous version.

The weather forecast is compiled by reading the OWM channel data using a strictly defined Item naming convention: all forecast data retrieved from the OWM binding has as format Weather_OWM_Forecast_XXX_YYh where:

  • XXX is one of Temperature, Humidity, Rain, Snow, Wind_Speed, Pressure and Cloudiness; and
  • YY represents the 3-hour forecast slot info (in range 03 up to 96 in steps of 3 hours (for the default free binding). They are all members of the gWeatherForecast group.
    For example:
Group gWeatherForecast

Number:Temperature Weather_OWM_Forecast_Temperature_03h "Temperature [%.1f %unit%]" <temperature> (gWeatherForecast) {channel="openweathermap:weather-and-forecast:OWM_ID:local:forecastHours03#temperature"}
Number:Dimensionless Weather_OWM_Forecast_Humidity_03h "Humidity [%d %unit%]" <humidity> (gWeatherForecast) {channel="openweathermap:weather-and-forecast:OWM_ID:local:forecastHours03#humidity"}
Number:Speed Weather_OWM_Forecast_Wind_Speed_03h "Wind speed [%.0f %unit%]" <wind> (gWeatherForecast) {channel="openweathermap:weather-and-forecast:OWM_ID:local:forecastHours03#wind-speed"}
Number:Pressure Weather_OWM_Forecast_Pressure_03h "Pressure [%.1f hPa]" <pressure> (gWeatherForecast) {channel="openweathermap:weather-and-forecast:OWM_ID:local:forecastHours03#pressure"}
Number:Length Weather_OWM_Forecast_Rain_03h "Rain [%.1f mm]" <rain> (gWeatherForecast) {channel="openweathermap:weather-and-forecast:OWM_ID:local:forecastHours03#rain"}
Number:Length Weather_OWM_Forecast_Snow_03h "Snow [%.1f mm]" <snow> (gWeatherForecast) {channel="openweathermap:weather-and-forecast:OWM_ID:local:forecastHours03#snow"}
Number:Dimensionless Weather_OWM_Forecast_Cloudiness_03h "Cloudiness [%d %unit%]" <sun_clouds> (gWeatherForecast) {channel="openweathermap:weather-and-forecast:OWM_ID:local:forecastHours03#cloudiness"}

(Make sure to replace OWM_ID with your own OWM account identifier.)

What I see, is that the rule takes quite some time to run, and that it appears that there are double items in the registry when running filterWxItemsForDay (as I can infer from the debug logs).

1 Like

You can clean up the lambda definitions a little.

val getForecastItems = [ String forecastTypeStr |

val setDailyForcastNumberItemValue = [ String forecastTypeStr, String metricName, Boolean forecast_offset, List<Number> valueList |

It is smart enough to determine whether you are creating a Functions or a Procedure based on what the last line returns (void for Procedures). This syntax used to be supported, then in OH2 the parser started to complain. But the warnings stopped appearing in the logs around 2.3.

Okay, good to know.

However I realized that:
items.map[ state as Number ].reduce[ sum, v | sum + v ]
does not honor UoM so the sumStateValueFrom lambda must be rewritten.

Here’s one version that works:

val sumStateValueFrom = [
    Iterable<GenericItem> items |

    var Number sum = 0.0
    var int i = 0
    while (i < items.length) {
        val Number v = (items.get(i).state as QuantityType<Number>).doubleValue
        logDebug("sumStateValueFrom", " " + sum.toString + " + " + v.toString + " = " + ((sum+v) as Number).toString )
        sum = sum + v
        i++
    }
    sum
]

You should be able to do that in the map.

map[ (state as QuantityType<Number>).doubleValue ].reduce[ sum, v | sum + v ]
1 Like

Thanks! That did the job.

I still find it counterintuitive to have to cast state as QuantityType<Number> in such cases.

There’s still something weird at play.

For some Item types it appears that the filterWxItemsForDay lambda processes them twice in a row:

2019-03-01 17:49:36.644 [INFO ] [el.script.Lambda:filterWxItemsForDay] - About to start processing the item filtering process
2019-03-01 17:49:36.811 [INFO ] [el.script.Lambda:filterWxItemsForDay] - In valid day range: Wx_OWM_Forecast_Cloudiness_03h: forecast hour = 3, maps to day 0 @ 19 h - value: 32 %
2019-03-01 17:49:36.944 [INFO ] [el.script.Lambda:filterWxItemsForDay] - In valid day range: Wx_OWM_Forecast_Cloudiness_06h: forecast hour = 6, maps to day 0 @ 22 h - value: 88 %
2019-03-01 17:49:37.937 [INFO ] [el.script.Lambda:filterWxItemsForDay] - In valid day range: Wx_OWM_Forecast_Cloudiness_03h: forecast hour = 3, maps to day 0 @ 19 h - value: 32 %
2019-03-01 17:49:38.042 [INFO ] [el.script.Lambda:filterWxItemsForDay] - In valid day range: Wx_OWM_Forecast_Cloudiness_06h: forecast hour = 6, maps to day 0 @ 22 h - value: 88 %
2019-03-01 17:49:38.639 [INFO ] [.script.UpdateOWMForecastInfo_Groups] - RESULT: Cloudiness FORECAST FOR DAY 0 : 32 % -- 88 %
2019-03-01 17:49:38.644 [INFO ] [el.script.Lambda:filterWxItemsForDay] - About to start processing the item filtering process
2019-03-01 17:49:38.895 [INFO ] [el.script.Lambda:filterWxItemsForDay] - In valid day range: Wx_OWM_Forecast_Rain_03h: forecast hour = 3, maps to day 0 @ 19 h - value: 0.0 mm
2019-03-01 17:49:38.973 [INFO ] [el.script.Lambda:filterWxItemsForDay] - In valid day range: Wx_OWM_Forecast_Rain_06h: forecast hour = 6, maps to day 0 @ 22 h - value: 0.0 mm
2019-03-01 17:49:39.484 [INFO ] [.script.UpdateOWMForecastInfo_Groups] - RESULT: Rain FORECAST FOR DAY 0 : SUM 0.0

Here’s the code of the lambda:

// Return a filtered set of forecast values for a given day
val filterWxItemsForDay = [
    Iterable<GenericItem> items,    // The weather forecast items for a given forecast type (all time values)
    Number forecast_03h_hour,       // The hour-in-day offset for the first forecast values
    Number theDay |                 // The forecast day to filter the forecast values from for subsequent per-day processing

    val Number fromHour = 0 + 24 * theDay
    val Number toHour = 24 + 24 * theDay
    logInfo("Lambda:filterWxItemsForDay", "About to start processing the item filtering process")
    items.filter [ i |
        val String ext = i.name.substring(i.name.length() - 4).substring(0,3)
        var Number hr = Integer::parseInt( ext.substring(1,3) )
        if (! ext.startsWith("_") ) {
            hr += 100 * Integer::parseInt( ext.substring(0,1) )
        }
        logDebug("Lambda:filterWxItemsForDay", i.name + ": ext '" + ext + "' - hr = " + hr.toString + " - day " + theDay.toString )

        val Number hourInDay = hr + forecast_03h_hour - 3
        if ( (hourInDay >= fromHour) && (hourInDay <= toHour) ) {
            logInfo("Lambda:filterWxItemsForDay", "In valid day range: " + i.name + ": forecast hour = " + hr.toString + ", maps to day " + theDay.toString + " @ " + (hourInDay - 24 * theDay).toString + " h - value: " + i.state.toString)
        }

        // Filter per-item result (Boolean):
        (hourInDay >= fromHour) && (hourInDay <= toHour)
    ] // Lambda return type is: Iterable<GenericItem>
]

If this is the case, the code of the lambda isn’t going to help. You need to post all the Rules that call the lambda.

There is only one rule that calls it: rule "Group-based weather forecast processing" . Here’s the full code of my openweathermap.rules file:

import org.eclipse.smarthome.model.script.ScriptServiceUtil
import java.util.List

val int MAX_DAYS = 5


/* Return the set of all weather forecast readings for a specific reading (stored in forecastTypeStr).
 * This is possible since all weather forecast items obey the same naming convention:
 *   Wx_OWM_Forecast_<forecastType>_<hourInterval>h
 * where hourInterval starts at 03 and goes up to 120.
 */
val getForecastItems = [
    String forecastTypeStr |    // "Temperature", "Humidity", "Pressure", "Wind_Speed", "Rain", "Snow", "Cloudiness"

    gWeatherForecast.allMembers.filter[ i |
        i.name.startsWith("Wx_OWM_Forecast_" + forecastTypeStr + "_")
    ] as Iterable<GenericItem>
]

/* Update the state of the daily Wx_OWM_Forecast_<TYPE>[_<METRIC>]_Day<N> by setting the proper values from the provided list.
 * When the first forecast value (_03h) is no longer for the current day, we must skip the forecast for the current day (N=0).
 */
val setDailyForecastNumberItemValue = [
    String forecastTypeStr,     // "Temperature", "Humidity", "Pressure", "Wind_Speed", "Rain", "Snow", "Cloudiness"
    String metricName,          // "Min", "Max", "Sum"
    Boolean forecast_offset,    // True if no forecast info for today (offset 1 day)
    List<Number> valueList |

    val int MAX_DAYS = 5 // Lambda functions don't resolve global constants!
    val String itemNameBase = "Wx_OWM_Forecast_" + forecastTypeStr + ( if (metricName == "") {""} else {"_" + metricName} ) + "_Day"
    var int i = 0
    var int index = 0
    if (forecast_offset) index += 1
    while ( (i < MAX_DAYS) && (index < MAX_DAYS) ) {
        val String itemName = itemNameBase + i.toString
        val Boolean postUndef = ( (forecast_offset == true) && (i == 1) )
        val Number value = ( if ( postUndef ) { -1 } else { valueList.get(index) } )
        val GenericItem forecastItem = ScriptServiceUtil.getItemRegistry.getItem(itemName) as GenericItem
        logDebug("setDailyForecastNumberItemValue", "Processing '" + forecastItem + "'")
        logDebug("setDailyForecastNumberItemValue", "Value of '" + itemName + "' is: "
            + forecastItem.state.toString + " - will be set to " + value.toString)
        if (postUndef) {
            postUpdate( forecastItem, UNDEF )
        } else {
            postUpdate( forecastItem, value )
        }

        i += 1
        index += 1
    }
]


/* Return the subset of weather forecast items that apply for the day specified (from midnight to midnight),
 * where day 0 is the current day. With a free OWM account, this results in at most 8 items per day (24h / 3h forecasts = 8 daily forecasts)
 */
val filterWxItemsForDay = [
    Iterable<GenericItem> items,    // The weather forecast items for a given forecast type (all time values)
    Number forecast_03h_hour,       // The hour-in-day offset for the first forecast values
    Number theDay |                 // The forecast day to filter the forecast values from for subsequent per-day processing

    val Number fromHour = 0 + 24 * theDay
    val Number toHour = 24 + 24 * theDay
    logInfo("Lambda:filterWxItemsForDay", "About to start processing the item filtering process")
    items.filter [ i |
        val String ext = i.name.substring(i.name.length() - 4).substring(0,3)
        var Number hr = Integer::parseInt( ext.substring(1,3) )
        if (! ext.startsWith("_") ) {
            hr += 100 * Integer::parseInt( ext.substring(0,1) )
        }
        logDebug("Lambda:filterWxItemsForDay", i.name + ": ext '" + ext + "' - hr = " + hr.toString + " - day " + theDay.toString )

        val Number hourInDay = hr + forecast_03h_hour - 3
        if ( (hourInDay >= fromHour) && (hourInDay <= toHour) ) {
            logInfo("Lambda:filterWxItemsForDay", "In valid day range: " + i.name + ": forecast hour = " + hr.toString + ", maps to day " + theDay.toString + " @ " + (hourInDay - 24 * theDay).toString + " h - value: " + i.state.toString)
        }

        // Filter per-item result (Boolean):
        (hourInDay >= fromHour) && (hourInDay <= toHour)
    ] // Lambda return type is: Iterable<GenericItem>
]

/* Return the maximum from a list of Number values (preserving the Units of Measurement)
 */
val maxStateValueFrom = [
    Iterable<GenericItem> items |

    items.map[ state as Number ].reduce[ max, v |
        if (v > max)
            v
        else
            max
    ] // Lambda return type is: Number
]

/* Return the minimum from a list of Number values (preserving the Units of Measurement)
 */
val minStateValueFrom = [
    Iterable<GenericItem> items |

    items.map[ state as Number ].reduce[ min, v |
        if (v < min)
            v
        else
            min
    ] // Lambda return type is: Number
]

/* Return the sum from a list of Number values (preserving the Units of Measurement)
 */
val sumStateValueFrom = [
    Iterable<GenericItem> items |

    items.map[ (state as QuantityType<Number>).doubleValue ].reduce[ sum, v | sum + v ]
]

/* Set Wx_OWM_Current_NightState at startup
 */
rule "OpenHAB system started - astro"
when
    System started
then
    createTimer(now.plusSeconds(180)) [ |
        if (now.isAfter((Astro_Sunset_End.state as DateTimeType).zonedDateTime.toInstant.toEpochMilli) ||
            now.isBefore((Astro_Sunrise_Start.state as DateTimeType).zonedDateTime.toInstant.toEpochMilli)
        ) {
            postUpdate(Wx_OWM_Current_NightState, ON)
        } else {
            postUpdate(Wx_OWM_Current_NightState, OFF)
        }
    ]
end

/* Update Wx_OWM_Current_NightState based on the elevation of the sun (night: elevation < 0).
 * Also set the weather condition mapping from day to night based on Wx_OWM_Current_NightState
 */
rule "Update Night State"
when
    Item Astro_Sun_Elevation changed
then
    if (Astro_Sun_Elevation.state >  0|°){
        if (Wx_OWM_Current_NightState.state != OFF) {
            postUpdate(Wx_OWM_Current_NightState, OFF)
            // if Wx_OWM_Current_NightState was ON we need to update Wx_OWM_Current_Condition_Formatted too
            Wx_OWM_Current_Condition_Formatted.postUpdate(transform("MAP", "openweathermap_day.map", Wx_OWM_Current_Condition_Id.state.toString()))
        }
    } else {
        if (Wx_OWM_Current_NightState.state != ON) {
            postUpdate(Wx_OWM_Current_NightState, ON)
            Wx_OWM_Current_Condition_Formatted.postUpdate(transform("MAP", "openweathermap_night.map", Wx_OWM_Current_Condition_Id.state.toString()))
        }
    }
end

/* Provide the compass wind direction in a proxy Item (Wx_OWM_Current_Wind_Direction_Simplified) for displaying on the sitemap.
 * This is needed as the SCALE transform only accepts numeric values, whereas the OWM binding sometimes reports non-numeric values
 * in cases where the wind direction is undefined or hard to define.
 */
rule "Update simplified wind direction"
when
	Item Wx_OWM_Current_Wind_Direction changed
then
    val String ruleTitle = "UpdateOWMCurrentWindDirection"
// Update Wx_OWM_Current_Wind_Direction_Simplified if Wx_OWM_Wind_Speed is numeric
    var String status = ""
    var boolean error = true
    if (Wx_OWM_Current_Wind_Direction.state == NULL) {
        status = "(not set)"
    } else if (Wx_OWM_Current_Wind_Direction.state == UNDEF) {
        status = "(undefined)"
    } else if (Wx_OWM_Current_Wind_Direction.state instanceof Number) {
        status = transform("SCALE", "wind.scale", Wx_OWM_Current_Wind_Direction.state.toString())
        error = false
    } else { // Unexpected state type
        status = "(invalid: " + Wx_OWM_Current_Wind_Direction.state.toString() + ")"
    }
    if (error) {
        logWarn(ruleTitle, "{} has state '{}', expecting Number", Wx_OWM_Current_Wind_Direction.name, Wx_OWM_Current_Wind_Direction.state.toString())
    } else {
        logDebug(ruleTitle, "{} has Number state '{}'", Wx_OWM_Current_Wind_Direction.name, Wx_OWM_Current_Wind_Direction.state.toString())
    }
    postUpdate(Wx_OWM_Current_Wind_Direction_Simplified, status)
end


/* Compute daiy forecast values from the hourly forecast values provided by the OpenWeatherMap binding.
 *
 */
rule "Group-based weather forecast processing"
when
    Item DBG_Test_Weather_Forecast changed or // Debugging
    Time cron "0 0 0 * * ?" or
    Item Wx_OWM_Forecast_Time_03h changed
then
	val String ruleTitle = "UpdateOWMForecastInfo_Groups"

    // Compute the rule execution time, making sure we use the same 'now' value for computing month, day and hour
    val DateTime dtNow = now
    val Number now_d = dtNow.getDayOfMonth
    val Number now_m = dtNow.getMonthOfYear
    val Number now_h = dtNow.getHourOfDay

    // Compute the month, day and hour of the first forecast time (3 hour offset)
    val DateTime dtForecast_03h = (new DateTime(Wx_OWM_Forecast_Time_03h.state.toString)).toDateTime
    val Number forecast_03h_d = dtForecast_03h.getDayOfMonth
    val Number forecast_03h_m = dtForecast_03h.getMonthOfYear
    val Number forecast_03h_h = dtForecast_03h.getHourOfDay

    // If the first forecast value doesn't apply for the current day (e.g., at 22:00h the 1st forecast is for the next day at 22:00 +3h = 01:00),
    // then set 'forecastDayOffset' to 'true' for further processing. In this edge case, we won't display today's forecast as it is nonexistent.
    val Boolean forecastDayOffset = ( if (forecast_03h_d > now_d) true else false )

    // Store the forecastDayOffset value in the proxy item Wx_OWM_Forecast_Day_Offset so it is available in sitemaps and UIs:
    if (forecastDayOffset) { // No forecast for today
        logInfo(ruleTitle, "No forecast for today ({}/{} - at or after {}h", now_d, now_m, now_h)
        if (Wx_OWM_Forecast_Day_Offset.state !== ON) {
            postUpdate(Wx_OWM_Forecast_Day_Offset, ON)
        }
    } else {
        if (Wx_OWM_Forecast_Day_Offset.state !== OFF) {
            postUpdate(Wx_OWM_Forecast_Day_Offset, OFF)
        }
    }

    logInfo(ruleTitle,
        "Update Wx forecast info - today is {}/{} @ {}h - 03h forecast is for {}/{} at {}h",
        now_d, now_m, now_h,
        forecast_03h_d, forecast_03h_m, forecast_03h_h)

    // Initialize the lists that will contain the daily forecast items we want to compute.
    // With a free OWM account, you can get 5 day forecast values per 3 hours, which covers 120 hours.
    // Only if the first forecast timestamp falls within the first 3 hours of a day (the day after the current day),
    // we can compute the daily forecast for 5 days in a row. In other cases, we will compute the daily forecast for today + 4 days.
    // Today's forecast is computed by considering the current values plus the set of forecast values that fit in the current day.

    // The daily forecast values will be stored in array lists, hence we initialize them:
    //
    //  - val Iterable<GenericItem> forecast[Quantity]    contains the list of [Quantity] forecast Items, filtered from
    //                                                    the 'gWeatherForecast' Item group by means of the lambda 'getForecastItems'
    //  - val List<Iterable<GenericItem>> [quantity]      will contain the forecast[Quantity] list, but ordered in sets of forecast values
    //                                                    for a given forecast day (0 = today, 1 = tomorrow, etc), with the lambda 'filterWxItemsForDay'
    //  - val List<Number> [quantity]_min                 will contain the minimum value of [quantity] for each forecast day (Temperature, Pressure...)
    //  - val List<Number> [quantity]_max                 will contain the maximum value of [quantity] for each forecast day (Temperature, Wind Speed...)
    //  - val List<Number> [quantity]_sum                 will contain the daily sum of [quantity] for each forecast day (Rain, Snow)
    //
    // The min, max and sum are also comuted with lambda functions.

    // Temperature: compute daily min, max
    val Iterable<GenericItem> forecastTemperature = getForecastItems.apply("Temperature")
    val List<Iterable<GenericItem>> temperature = newArrayList( null,null,null,null,null )
    val List<Number> temperature_min = newArrayList( 0,0,0,0,0 )
    val List<Number> temperature_max = newArrayList( 0,0,0,0,0 )

    // Pressure: compute daily min, max
    val Iterable<GenericItem> forecastPressure = getForecastItems.apply("Pressure")
    val List<Iterable<GenericItem>> pressure = newArrayList( null,null,null,null,null )
    val List<Number> pressure_min = newArrayList( 0,0,0,0,0 )
    val List<Number> pressure_max = newArrayList( 0,0,0,0,0 )

    // Humidity: compute daily min, max
    val Iterable<GenericItem> forecastHumidity = getForecastItems.apply("Humidity")
    val List<Iterable<GenericItem>> humidity = newArrayList( null,null,null,null,null )
    val List<Number> humidity_min = newArrayList( 0,0,0,0,0 )
    val List<Number> humidity_max = newArrayList( 0,0,0,0,0 )

    // Cloudiness: compute daily min, max
    val Iterable<GenericItem> forecastCloudiness = getForecastItems.apply("Cloudiness")
    val List<Iterable<GenericItem>> cloudiness = newArrayList( null,null,null,null,null )
    val List<Number> cloudiness_min = newArrayList( 0,0,0,0,0 )
    val List<Number> cloudiness_max = newArrayList( 0,0,0,0,0 )

    // Rain: compute daily sum
    val Iterable<GenericItem> forecastRain = getForecastItems.apply("Rain")
    val List<Iterable<GenericItem>> rain = newArrayList( null,null,null,null,null )
    val List<Number> rain_sum = newArrayList( 0,0,0,0,0 )
    
    // Snow: compute daily sum
    val Iterable<GenericItem> forecastSnow = getForecastItems.apply("Snow")
    val List<Iterable<GenericItem>> snow = newArrayList( null,null,null,null,null )
    val List<Number> snow_sum = newArrayList( 0,0,0,0,0 )

    // Wind Speed: compute daily max
    val Iterable<GenericItem> forecastWind_Speed = getForecastItems.apply("Wind_Speed")
    val List<Iterable<GenericItem>> wind_speed = newArrayList( null,null,null,null,null )
    val List<Number> wind_speed_max = newArrayList( 0,0,0,0,0 )

    val forecast_offset = forecastDayOffset
    logInfo(ruleTitle, "INFO - forecastDayOffset is {}", forecastDayOffset)

    // Process the forecast values, a calendar day (from midnight to midnight) at a time
    var int i = 0
    while (i < MAX_DAYS) {
        logInfo(ruleTitle, "INFO - iteration {}", i)

        // First create the daily set of forecast values for day i
        temperature.set(i, filterWxItemsForDay.apply(forecastTemperature, forecast_03h_h, i))
        // Calculate the min / max / sum for day i:
        temperature_min.set(i, minStateValueFrom.apply(temperature.get(i)))
        temperature_max.set(i, maxStateValueFrom.apply(temperature.get(i)))
        logInfo(ruleTitle,"RESULT: {} FORECAST FOR DAY {} : {} -- {}",
            "Temperature", i, temperature_min.get(i), temperature_max.get(i) )

        pressure.set(i, filterWxItemsForDay.apply(forecastPressure, forecast_03h_h, i))
        pressure_min.set(i, minStateValueFrom.apply(pressure.get(i)))
        pressure_max.set(i, maxStateValueFrom.apply(pressure.get(i)))
        logInfo(ruleTitle,"RESULT: {} FORECAST FOR DAY {} : {} -- {}",
            "Pressure", i, pressure_min.get(i), pressure_max.get(i) )

        humidity.set(i, filterWxItemsForDay.apply(forecastHumidity, forecast_03h_h, i))
        humidity_min.set(i, minStateValueFrom.apply(humidity.get(i)))
        humidity_max.set(i, maxStateValueFrom.apply(humidity.get(i)))
        logInfo(ruleTitle,"RESULT: {} FORECAST FOR DAY {} : {} -- {}",
            "Humidity", i, humidity_min.get(i), humidity_max.get(i) )

        cloudiness.set(i, filterWxItemsForDay.apply(forecastCloudiness, forecast_03h_h, i))
        cloudiness_min.set(i, minStateValueFrom.apply(cloudiness.get(i)))
        cloudiness_max.set(i, maxStateValueFrom.apply(cloudiness.get(i)))
        logInfo(ruleTitle,"RESULT: {} FORECAST FOR DAY {} : {} -- {}",
            "Cloudiness", i, cloudiness_min.get(i), cloudiness_max.get(i) )

        rain.set(i, filterWxItemsForDay.apply(forecastRain, forecast_03h_h, i))
        rain_sum.set(i, sumStateValueFrom.apply(rain.get(i)))
        logInfo(ruleTitle,"RESULT: {} FORECAST FOR DAY {} : SUM {}",
            "Rain", i, rain_sum.get(i) )

        snow.set(i, filterWxItemsForDay.apply(forecastSnow, forecast_03h_h, i))
        snow_sum.set(i, sumStateValueFrom.apply(snow.get(i)))
        logInfo(ruleTitle,"RESULT: {} FORECAST FOR DAY {} : SUM {}",
            "Snow", i, snow_sum.get(i) )

        wind_speed.set(i, filterWxItemsForDay.apply(forecastWind_Speed, forecast_03h_h, i))
        wind_speed_max.set(i, maxStateValueFrom.apply(wind_speed.get(i)))
        logInfo(ruleTitle,"RESULT: {} FORECAST FOR DAY {} : MAX {}",
            "Wind_Speed", i, wind_speed_max.get(i) )

        // Advance one day
        i += 1
    }

    logInfo(ruleTitle, "INFO - Updating the item values")

    // Now the daily forecast values can be propagated to the proxy Items, using the lambda 'setDailyForecastNumberItemValue'.
    // This lambda will take care of the forecast offset in case it is needed
    setDailyForecastNumberItemValue.apply("Temperature",    "Min",  forecast_offset, temperature_min)
    setDailyForecastNumberItemValue.apply("Temperature",    "Max",  forecast_offset, temperature_max)
    setDailyForecastNumberItemValue.apply("Pressure",       "Min",  forecast_offset, pressure_min)
    setDailyForecastNumberItemValue.apply("Pressure",       "Max",  forecast_offset, pressure_max)
    setDailyForecastNumberItemValue.apply("Humidity",       "Min",  forecast_offset, humidity_min)
    setDailyForecastNumberItemValue.apply("Humidity",       "Max",  forecast_offset, humidity_max)
    setDailyForecastNumberItemValue.apply("Cloudiness",     "Min",  forecast_offset, cloudiness_min)
    setDailyForecastNumberItemValue.apply("Cloudiness",     "Max",  forecast_offset, cloudiness_max)
    setDailyForecastNumberItemValue.apply("Rain",           "",     forecast_offset, rain_sum)
    setDailyForecastNumberItemValue.apply("Snow",           "",     forecast_offset, snow_sum)
    setDailyForecastNumberItemValue.apply("Wind_Speed",     "Max",  forecast_offset, wind_speed_max)

end

Note: I am aware of the existence of a bug that occurs once daily. It will get fixed when I have some time; I’m now collecting debug logs over the course of 24 hours to identify the logic error. But that has nothing to do with the duplicate processing seen in filterWxItemsForDay.

Honestly, this code is very long and very complex and I have no idea how it’s supposed to work.

Based on the logs, it looks like you have Items in the items Iterable that you pass into the lambda somehow. Beyond that I don’t have the hour or two it would take to just read the full bit of code and understand how it works.

I updated the rules code and provided comments where required to better understand how the rule works. Best is to start reading only rule "Group-based weather forecast processing" and then go to the lambda functions when you encounter them in the rule execution.

I hope I managed to clarify my rule-based daily weather forecast processing.

hello,
can you please provide all current files needed? I get a lot of errors because item names seem to be wrong. Thank you very much.

PS. Would not that be something for a direct implementation in the binding itself? You would save a lot of items …

Here you are:

  1. openweathermap.items.txt (118.5 KB) → please save as $OPENHAB_CONF/items/openweathermap.items after downloading
  2. openweathermap.rules.txt (21.1 KB) → please save as $OPENHAB_CONF/rules/openweathermap.rules after downloading

As a bonus you’ll see that I now also generate range items that will display the minimum and maximum values in one go (instead of showing them separately), as with:

String Wx_OWM_Forecast_Temperature_Range_Day0 "Temperature [%s]" <temperature>

I use a rule to compute the state (value) of that Item. The only drawback is that you lose UoM conversion capabilities (e.g. wind speed in my locale is expressed in m/s while most of us are more familiarised with wind speeds in km/h (basically multiplying the values by 3.6 and changing the unit). So my sitemap will properly render min and max wind speeds in km/h but my rule hack will express the values in m/s (with 2-digit precision - something else I can’t easily change in rules).

FWIW I don’t think OpenHAB today allows multiple Number UoM values displayed in one Item while using UoM conversion. It would be great if OpenHAB would allow the following Item definitions:

Number:Speed	Wx_OWM_Forecast_Wind_Speed_Range_Day0 "Wind Speed [$1%.0f km/h - $2%.0f km/h]" <wind>	(gWeatherForecastDaily)

effectively referring to 2 Number:Speed values rendered as "[%.0f km/h - %.0f km/h]". But that’s a different story. Back to my OWM daily forecast compilation. Now comes the visualisation part.

In your sitemap:

sitemap home label="Our Home" icon="home" {

	Frame label="Weather" icon="temperature" {
	
		Text item=Wx_OWM_Current_Observation_Time label="Current weather [%1$tA, %1$tb %1$td, %1$tY]" icon="sun_clouds" {
			// Text item=Today label="[%1$tA, %1$tb %1$td, %1$tY]" icon="calendar"
			Frame label="Calendar" icon="calendar" {
				Text item=Wx_OWM_Current_Observation_Time label="Observation for [%1$tA, %1$tb %1$td]" icon="calendar"
				Text item=Wx_OWM_Current_Observation_Time label="Observation at [%1tH:%1$tm]" icon="time"
				Default item=Wx_OWM_Current_Station_Name
				Default item=Wx_OWM_Current_Station_Id
			}

			Frame label="Current weather" icon="sun_clouds" {

				Default item=Wx_OWM_Current_Temperature  label="Temperature [%.1f %unit%]"   valuecolor=[
					>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
				]
				Default item=Wx_OWM_Current_Cloudiness
				Default item=Wx_OWM_Current_Humidity			icon="humidity"		label="Humidity [%d %unit%]"
				Default item=Wx_OWM_Current_Pressure
				Default item=Wx_OWM_Current_Wind_Speed			icon="wind"			label="Windspeed [%.0f km/h]"
				Default item=Wx_OWM_Current_Wind_Direction
				Default item=Wx_OWM_Current_Wind_Direction_Simplified
				Default item=Wx_OWM_Current_Rain									label="Rain"
				Default item=Wx_OWM_Current_Snow									label="Snow"
				Default item=Wx_OWM_Current_Condition			icon="sun_clouds"	label="Condition [%s]"
				// Default item=Wx_OWM_Current_Condition_Formatted
				Default item=Wx_OWM_Current_ConditionIcon							label="Icon [%s]"
				Default item=Wx_OWM_Current_ConditionId		icon="settings"		label="Condition ID [%s]"
				// Default item=Wx_OWM_Current_Condition_Img_URL label="Current condition (new image)"
			}
		}

		// If you persist the group "gWeatherCurrent" and use influxdb persistency, then you can generate the following weekly graphs:
		Text label="Graphs [OpenHAB]" icon="chart" {
			Text label="Temperature [°C]"		icon="temperature"
			Chart item=Wx_OWM_Current_Temperature		service="influxdb"	period=W	refresh=500000
			//
			Text label="Wind speed [m/s]"		icon="wind"
			Chart item=Wx_OWM_Current_Wind_Speed		service="influxdb"	period=W	refresh=500000
			//
			Text label="Humidity [%]"		icon="humidity"
			Chart item=Wx_OWM_Current_Humidity			service="influxdb"	period=W	refresh=500000
			//
			Text label="Coud cover [%]"		icon="sun_clouds"
			Chart item=Wx_OWM_Current_Cloudiness		service="influxdb"	period=W	refresh=500000
			//
			Text label="Pressure [mBar]" icon="pressure"
			Chart item=Wx_OWM_Current_Pressure			service="influxdb"	period=W	refresh=500000
			//
			Text label="Rain [mm]" icon="rain"
			Chart item=Wx_OWM_Current_Rain				service="influxdb"	period=W	refresh=500000
			//
			Text label="Snow [mm]" icon="snow"
			Chart item=Wx_OWM_Current_Snow				service="influxdb"	period=W	refresh=500000
			//
		}

		Text item=Wx_OWM_Forecast_Time_96h label="Forecast (4 days) [until %1$tA]" icon="sun_clouds" {
			Frame item=Wx_OWM_Current_Observation_Time label="Today [%1$tA, %1$tb %1$td]" icon="calendar" visibility=[Wx_OWM_Forecast_Day_Offset==ON]  {
				Text label="Warning: OWM Forecast Day Offset is TRUE, not enough forecast data for today" icon="error" // visibility=[Wx_OWM_Forecast_Day_Offset==ON]
			}
			Frame item=Wx_OWM_Current_Observation_Time label="Today [%1$tA, %1$tb %1$td]" icon="calendar" visibility=[Wx_OWM_Forecast_Day_Offset==OFF]  {
				// Text label="OWM Forecast Day Offset is FALSE, enough forecast data for today" icon="settings" // visibility=[Wx_OWM_Forecast_Day_Offset==OFF]
				Default item=Wx_OWM_Forecast_Temperature_Range_Day0
				Default item=Wx_OWM_Forecast_Temperature_Min_Day0  valuecolor=[
					>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
				]
				Default item=Wx_OWM_Forecast_Temperature_Max_Day0  valuecolor=[
					>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
				]

				Default item=Wx_OWM_Forecast_Rain_Day0

				Default item=Wx_OWM_Forecast_Snow_Day0

				Default item=Wx_OWM_Forecast_Humidity_Range_Day0
				Default item=Wx_OWM_Forecast_Humidity_Min_Day0
				Default item=Wx_OWM_Forecast_Humidity_Max_Day0

				Default item=Wx_OWM_Forecast_Pressure_Range_Day0
				Default item=Wx_OWM_Forecast_Pressure_Min_Day0
				Default item=Wx_OWM_Forecast_Pressure_Max_Day0

				Default item=Wx_OWM_Forecast_Cloudiness_Range_Day0
				Default item=Wx_OWM_Forecast_Cloudiness_Min_Day0
				Default item=Wx_OWM_Forecast_Cloudiness_Max_Day0

				Default item=Wx_OWM_Forecast_Wind_Speed_Range_Day0
				Default item=Wx_OWM_Forecast_Wind_Speed_Min_Day0
				Default item=Wx_OWM_Forecast_Wind_Speed_Max_Day0
			}
			
			Frame item=Wx_OWM_Forecast_Time_24h label="Tomorrow [%1$tA, %1$tb %1$td]" icon="calendar" {
				Default item=Wx_OWM_Forecast_Temperature_Min_Day1  valuecolor=[
					>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
				]
				Default item=Wx_OWM_Forecast_Temperature_Max_Day1  valuecolor=[
					>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
				]
				Default item=Wx_OWM_Forecast_Temperature_Range_Day1

				Default item=Wx_OWM_Forecast_Rain_Day1

				Default item=Wx_OWM_Forecast_Snow_Day1

				Default item=Wx_OWM_Forecast_Humidity_Range_Day1
				Default item=Wx_OWM_Forecast_Humidity_Min_Day1
				Default item=Wx_OWM_Forecast_Humidity_Max_Day1

				Default item=Wx_OWM_Forecast_Pressure_Range_Day1
				Default item=Wx_OWM_Forecast_Pressure_Min_Day1
				Default item=Wx_OWM_Forecast_Pressure_Max_Day1

				Default item=Wx_OWM_Forecast_Cloudiness_Range_Day1
				Default item=Wx_OWM_Forecast_Cloudiness_Min_Day1
				Default item=Wx_OWM_Forecast_Cloudiness_Max_Day1

				Default item=Wx_OWM_Forecast_Wind_Speed_Range_Day1
				Default item=Wx_OWM_Forecast_Wind_Speed_Min_Day1
				Default item=Wx_OWM_Forecast_Wind_Speed_Max_Day1
			}

			Frame item=Wx_OWM_Forecast_Time_48h label="In 2 days [%1$tA, %1$tb %1$td]" icon="calendar" {
				Default item=Wx_OWM_Forecast_Temperature_Min_Day2  valuecolor=[
					>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
				]
				Default item=Wx_OWM_Forecast_Temperature_Max_Day2  valuecolor=[
					>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
				]
				Default item=Wx_OWM_Forecast_Temperature_Range_Day2

				Default item=Wx_OWM_Forecast_Rain_Day2

				Default item=Wx_OWM_Forecast_Snow_Day2

				Default item=Wx_OWM_Forecast_Humidity_Range_Day2
				Default item=Wx_OWM_Forecast_Humidity_Min_Day2
				Default item=Wx_OWM_Forecast_Humidity_Max_Day2

				Default item=Wx_OWM_Forecast_Pressure_Range_Day2
				Default item=Wx_OWM_Forecast_Pressure_Min_Day2
				Default item=Wx_OWM_Forecast_Pressure_Max_Day2

				Default item=Wx_OWM_Forecast_Cloudiness_Range_Day2
				Default item=Wx_OWM_Forecast_Cloudiness_Min_Day2
				Default item=Wx_OWM_Forecast_Cloudiness_Max_Day2

				Default item=Wx_OWM_Forecast_Wind_Speed_Range_Day2
				Default item=Wx_OWM_Forecast_Wind_Speed_Min_Day2
				Default item=Wx_OWM_Forecast_Wind_Speed_Max_Day2
			}

			Frame item=Wx_OWM_Forecast_Time_72h label="In 3 days [%1$tA, %1$tb %1$td]" icon="calendar" {
				Default item=Wx_OWM_Forecast_Temperature_Range_Day3
				Default item=Wx_OWM_Forecast_Temperature_Min_Day3  valuecolor=[
					>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
				]
				Default item=Wx_OWM_Forecast_Temperature_Max_Day3  valuecolor=[
					>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
				]
				Default item=Wx_OWM_Forecast_Rain_Day3

				Default item=Wx_OWM_Forecast_Snow_Day3

				Default item=Wx_OWM_Forecast_Humidity_Range_Day3
				Default item=Wx_OWM_Forecast_Humidity_Min_Day3
				Default item=Wx_OWM_Forecast_Humidity_Max_Day3

				Default item=Wx_OWM_Forecast_Pressure_Range_Day3
				Default item=Wx_OWM_Forecast_Pressure_Min_Day3
				Default item=Wx_OWM_Forecast_Pressure_Max_Day3

				Default item=Wx_OWM_Forecast_Cloudiness_Range_Day3
				Default item=Wx_OWM_Forecast_Cloudiness_Min_Day3
				Default item=Wx_OWM_Forecast_Cloudiness_Max_Day3

				Default item=Wx_OWM_Forecast_Wind_Speed_Range_Day3
				Default item=Wx_OWM_Forecast_Wind_Speed_Min_Day3
				Default item=Wx_OWM_Forecast_Wind_Speed_Max_Day3
			}

			Frame item=Wx_OWM_Forecast_Time_96h label="In 4 days [%1$tA, %1$tb %1$td]" icon="calendar" {
				Default item=Wx_OWM_Forecast_Temperature_Range_Day4
				Default item=Wx_OWM_Forecast_Temperature_Min_Day4  valuecolor=[
					>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
				]
				Default item=Wx_OWM_Forecast_Temperature_Max_Day4  valuecolor=[
					>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
				]

				Default item=Wx_OWM_Forecast_Rain_Day4

				Default item=Wx_OWM_Forecast_Snow_Day4

				Default item=Wx_OWM_Forecast_Humidity_Range_Day4
				Default item=Wx_OWM_Forecast_Humidity_Max_Day4
				Default item=Wx_OWM_Forecast_Humidity_Min_Day4

				Default item=Wx_OWM_Forecast_Pressure_Range_Day4
				Default item=Wx_OWM_Forecast_Pressure_Min_Day4
				Default item=Wx_OWM_Forecast_Pressure_Max_Day4

				Default item=Wx_OWM_Forecast_Cloudiness_Range_Day4
				Default item=Wx_OWM_Forecast_Cloudiness_Min_Day4
				Default item=Wx_OWM_Forecast_Cloudiness_Max_Day4

				Default item=Wx_OWM_Forecast_Wind_Speed_Range_Day4
				Default item=Wx_OWM_Forecast_Wind_Speed_Min_Day4
				Default item=Wx_OWM_Forecast_Wind_Speed_Max_Day4
			}

		}
	}
	// Add your other home automation stuff
}

I agree. And it would also be much more efficient in terms of resources.

Many Thanks. Now it works without errors.
When I see with what effort you have to make a prediction now, that’s really insane, even the number of items required. And then the time it takes …
It would be really useful to put everything into the binding.

This probably won’t happen for sitemaps. This also has nothing to do with UoM. It’s a limitation of all Items.

The standard approach is to create a proxy String item and a rule to set that item using the states of the two or more items.

With HABpanel, one can create a custom widget to display the two or more values. There are some very impressive weather widgets in the gallery.

Here’s a way to get hold of the current day’s minimum and maximum temperature values (as measured since midnight). You can add the code provided below to the code I provided earlier in this thread.

openweathermap.items:

// You probably already defined the following Item (it's reproduced here for your convenience):
Number:Temperature		Wx_OWM_Current_Temperature					"Temperature [%.1f %unit%]"	<temperature>	(gWeatherCurrent) {channel="openweathermap:weather-and-forecast:77e8fd82:local:current#temperature"}
// The following 2 proxy items will be updated with a rule whenever a new OWM forecast is read (once per hour):
Number:Temperature		Wx_OWM_Current_Temperature_Min				"Temperature Low Today [%.1f %unit%]"	<temperature>
Number:Temperature		Wx_OWM_Current_Temperature_Max				"Temperature High Today [%.1f %unit%]"	<temperature>

openweathermap.rules:

/* Compute daily min and max values for OWM bindings up to 'now'
 */
 rule "Daily min/max values for OWM bindings"
when
    // Time cron "0/15 * * * * ?" or // DEBUG
    // Time cron "0 1 0 * * ?" or // Run at 00:01 daily (reset the daily min/max values)
    Item Wx_OWM_Current_Observation_Time changed 
then
    val String ruleTitle = "Test daily min/max values for OWM bindings"

    val min = Wx_OWM_Current_Temperature.minimumSince(now.withTimeAtStartOfDay).state
    val max = Wx_OWM_Current_Temperature.maximumSince(now.withTimeAtStartOfDay).state

    postUpdate(Wx_OWM_Current_Temperature_Min, min)
    postUpdate(Wx_OWM_Current_Temperature_Max, max)

    logInfo(ruleTitle, "Today's temperature Min - Max values = '{}' - '{}'", min, max)
end

home.sitemap:

// Current temperature:
Default item=Wx_OWM_Current_Temperature  label="Temperature [%.1f %unit%]"   valuecolor=[
	>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
]
// Today's lowest temperature reading so far:
Default item=Wx_OWM_Current_Temperature_Min		valuecolor=[
	>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
]
// Today's highest temperature reading so far:
Default item=Wx_OWM_Current_Temperature_Max		valuecolor=[
	>=30="red", >=25="orange", >=15="green", 0="silver", <0="purple", <15="blue"
]

The color of the value changes depending on the actual temperature value (I’m working with °C).

This looks great to me.

I tried to use the openweathermap.items and openweathermap.rules from above but I don’t get data in my sitemap.

In my former openweathermap installation I used the things and items from the original binding example.

In the example from Olivier I don’t see where to put my own API-key from openweathermap. Is there a openweathermap.things file needed? Do I have to replace “77e8fd82” in all items of openweathermap.items?

My Visual Studio editor also reported some problems in the openweathermap.rules file:
The method or field Astro_Sunset_End is undefined
The method or field Astro_Sunrise_Start is undefined
The method or field Astro_Sun_Elevation is undefined
My Astro.items only know “Sunrise_Time” and “Sunset_Time”. What goes wrong here?

Kind regards,

The OWM API key should be configured in Paper UI by editing the following Thing: OpenWeatherMap Account.

You should indeed replace “77e8fd82” everywhere with the ID reported by your OWM environment. Again, by looking at one of the following Things:

  • OpenWeatherMap Account
  • Local weather and forecast

I forgot to add the astro items:

DateTime		Astro_Sunrise_Start			"Sunrise Start"				<sunrise>		(AstroSun)	{ channel="astro:sun:local:rise#start" }
DateTime		Astro_Sunrise_End			"Sunrise End"				<sunrise>		(AstroSun)	{ channel="astro:sun:local:rise#end" }

DateTime		Astro_Noon_Start			"Noon Start"				<sun>			(AstroSun)	{ channel="astro:sun:local:noon#start" }
DateTime		Astro_Noon_End				"Noon End"					<sun>			(AstroSun)	{ channel="astro:sun:local:noon#end" }

DateTime		Astro_Sunset_Start			"Sunset Start"				<sunset>		(AstroSun)	{ channel="astro:sun:local:set#start" }
DateTime		Astro_Sunset_End			"Sunset End"				<sunset>		(AstroSun)	{ channel="astro:sun:local:set#end" }

Number:Angle	Astro_Sun_Azimuth			"Azimuth"					<incline>		(AstroSun)	{ channel="astro:sun:local:position#azimuth" }
Number:Angle	Astro_Sun_Elevation			"Elevation"					<incline>		(AstroSun)	{ channel="astro:sun:local:position#elevation" }

Hi Olivier,
Thanks for your answer.

I did replace “77e8fd82” with my own API-key in the whole openweathermap.items file. (577 replacements?). But my API-key is 32 characters long?!? Yours is 8!

In Paper UI I - “Openweathermap account” can’t read the my API-key (characters hidden). When I try to edit my “Openweathermap account” (editting my Location name) in Paper UI and I save my settings I get a short message “Thing updated” and “Error 409”. When I don’t change anything but only save my settings I only get a message “Thing updated”. The “Status” in my Paper UI - Openweathermap account always is “Online”(green).

Should the API-key be replaced in other files too?

I added the astro.items to my file and they seem to work now.

Regards.

It’s not your API key but the Thing ID of your OWM binding. You can see the Thing ID for any thing defined when opening the thing overview in Paper UI at http://[your OpenHAB server name or IP address goes here]:8080/paperui/index.html#/configuration/things - look for the Thing ID in the overview, search “openweathermap” to narrow the set of things. It’s the 8-character identifier you find in all 3 items (the binding, the weather items and the UV items).