Comprehensive Wunderground using HTTP Binding Example

WARNING: Wunderground has eliminated their free API keys. If you already have an API key it will still work for some unknown amount of time but if you don’t already have a key this tutorial will not work for you.

This tutorial will illustrate how I currently use the HTTP binding to get weather information from Wunderground, bypassing the various weather bindings which, if the postings on the forum are any indication, are a bit flakey and provide incomplete information.

You could apply this to other weather services, though the JSONPATH or XPATH transforms will likely be different.

Note that the snazzy weather webview that one can configure to use with the Weather Binding is not supported with this approach.

Prerequisites

Obtain an API key from Wunderground..

Lat/Lon

There are two ways you can specify your location in the API calls. The first is via latitude/longitude which will have Wunderground choose the weather station closest to you. Determine your latitude and longitude. Depending on how variable your weather may be, using the automatically discovered lat/lon by the Astro Binding may work well.

Station ID

The second lets you specify the weather station yourself. To do this search for your location and select “Change Station”

image

Select the station you desire to pull from and click on it. When the page comes up look at the URL. It will end with ?query=pws:<station id> where <station id> is the unique identifier for that station. We will need this later.

Create the Query URLs

The format of the Wunderground API call URLs is documented here and takes the form of:

http://api.wunderground.com/api/<Your_Key>/<features>/<settings>/q/<query>.<format>

Where the parts of the URL in < > get replaces with your specific information. For our purposes:

  • features you will likely care about are conditions and forecast or forecast10day but pay attention to the rest, there is a wealth of information available (NOTE: not all features are available for the free tier including forecast10day)

  • settings is where you will set the language and where you can enable the use of personal weather stations in the case where you want to include or specify a weather station that is controlled by an individual for current conditions

  • query is where you use the lat/lon obtained above or the specific weather station ID or one of the other documented options (e.g. city name, zip code, air port code, etc. For example, to use a lat/lon <query> would become <lat>,<lon> (e.g. 37.8,-122.4). To use a weather station use pws:<id> (e.g. pws:KCASANFR70).

  • format can be either json or xml; for this tutorial we will be using json

Now that you are armed with the URL info you need let’s set up OH.

NOTE: See @binderth’s post below for how to get support for localization (language and units).

HTTP Binding Config

Install the HTTP binding.

We will be using the caching configuration because we don’t want to make a new call to the Wunderground API for every Item we define. The free tier limits one to 500 API calls a day so take this into account when setting the updateInterval. Note that each feature that you pull down from Wunderground counts as a separate API call so the more features you desire the less frequently you can poll for the data without paying.

Edit services/http.cfg and add the following two lines for each feature you will be using from Wunderground:

<name>.url=http://api.wunderground.com/api/<Your Key>/<feature>/q/<query>.json
<name>.updateInterval=<interval in msec>

Replace all the parts in < > with your appropriate values.

With 500 calls per day this means you can make an API call roughly every three minutes, or 180000 msec, to stay within your limits.

For example, if I were to query for conditions and forecast I will have:

weatherConditions.url=http://api.wunderground.com/api/<Your API>/conditions/q/<query>.json
weatherConditions.updateInterval=360000

weatherForecast.url=http://api.wunderground.com/api/<Your API/forecast/q/<query>.json
weatherForecast.updateInterval=360000

Things

There are no Things required as the HTTP binding is a 1.x version binding and does not support Things. When/if a 2.x version of the HTTP binding gets built I will try to remember and come back and revise this tutorial accordingly.

Items

For this tutorial we are pulling the API data down as JSON so the JSONPATH Transformation must be installed. Also, because Wunderground puts the % after the humidity value, the RegEx Transformation](http://docs.openhab.org/addons/transformations/regex/readme.html) must also be installed.

The following Items are not comprehensive. See the Conditions, Forecast, etc. documentation for the full list of the data returned and the format of the data. There is even a link at the bottom that generates an example return for San Francisco.

Number vWeather_Temp "Outside Temp [%.0f °F]"
    <temperature> (gChart)
    { http="<[weatherConditions:360000:JSONPATH($.current_observation.temp_f)]" }
    
Number vWeather_Temp_Max "Max Outside Temp [%.0f °F]"
	<temperature>

Number vWeather_Temp_Min "Min Outside Temp [%.0f °F]"
	<temperature>
    
String vWeather_Conditions "Current Conditions [%s]"
	<wunderground>
    { http="<[weatherConditions:360000:JSONPATH($.current_observation.weather)]" }
    
Switch vIsCloudy "Sunlight"
	<iscloudy>

Number vWeather_Humidity "Outside Humidity [%d%%]"
    <humidity> (gChart)
    { http="<[weatherConditions:360000:REGEX(.*\"relative_humidity\":\"(.*)%\".*)]" }
    
String vWeather_Wind "Wind [%s]"
	<wind>
    { http="<[weatherConditions:360000:JSONPATH($.current_observation.wind_string)]" }
    
Number vWeather_Wind_MPH "Wind Speed [%.1f MPH]"
    (gChart)
    { http="<[weatherConditions:360000:JSONPATH($.current_observation.wind_mph)]" }
    
Number vWeather_Wind_Gusts "Wind Gusts [%.1f MPH]"
    (gChart)
    { http="<[weatherConditions:360000:JSONPATH($.current_observation.wind_gust_mph)]" }
    
Number vWeather_Pressure "Barometric Pressure [%.1f in]"
    <line> (gChart)
    { http="<[weatherConditions:360000:JSONPATH($.current_observation.pressure_in)]" }
    
String vWeather_Pressure_Trend "Barometric Pressure Trend [MAP(weather.map):%s]"
    <line>
    { http="<[weatherConditions:360000:JSONPATH($.current_observation.pressure_trend)]" }
    
Number vWeather_Dewpoint "Dew Point [%.1f °F]"
    <temperature> (gChart)
    { http="<[weatherConditions:360000:JSONPATH($.current_observation.dewpoint_f)]" }
    
Number vWeather_Feel "Feels like [%.0f °F]"
    <temperature> (gChart)
    { http="<[weatherConditions:360000:JSONPATH($.current_observation.feelslike_f)]" }
    
Number vWeather_Visibility "Visibility [%.1f miles]"
    <sun_clouds>
    { http="<[weatherConditions:360000:JSONPATH($.current_observation.visibility_mi)]" }
    
Number vWeather_SolarRad "Solar Radiation [%d]"
    <sun>
    { http="<[weatherConditions:360000:JSONPATH($.current_observation.solarradiation)]" }
    
Number vWeather_UV "UV Index [JS(uv.js):%s]"
	<sun>
    { http="<[weatherConditions:360000:JSONPATH($.current_observation.UV)]" }
    
Number vWeather_PrecipToday "Precipitation So Far [%.1f in]"
    <rain> (gChart)
    { http="<[weatherConditions:360000:JSONPATH($.current_observation.precip_today_in)]" }
    
String vWeather_Today_Conditions "Today Conditions [%s]"
	<wunderground>
    { http="<[weatherForecast:360000:JSONPATH($.forecast.simpleforecast.forecastday[0].conditions)]" }
    
Number vWeather_Today_TempHigh "Today High [%.0f °F]"
    <temperature>
    { http="<[weatherForecast:360000:JSONPATH($.forecast.simpleforecast.forecastday[0].high.fahrenheit)]" }
    
Number vWeather_Today_TempLow "Today Low [%.0f °F]"
    <temperature>
    { http="<[weatherForecast:360000:JSONPATH($.forecast.simpleforecast.forecastday[0].low.fahrenheit)]" }
    
Number vWeather_Today_QPF "Today Precip QPF [%.1f in]"
    <rain>
    { http="<[weatherForecast:360000:JSONPATH($.forecast.simpleforecast.forecastday[0].qpf_allday.in)]" }
    
Number vWeather_Today_Snow "Today Snow [%.1f in]"
    <climate-on>
    { http="<[weatherForecast:360000:JSONPATH($.forecast.simpleforecast.forecastday[0].snow_allday.in)]" }    

String vWeather_Tomorrow_Conditions "Tomorrow Conditions [%s]"
	<wunderground>
    { http="<[weatherForecast:360000:JSONPATH($.forecast.simpleforecast.forecastday[1].conditions)]" }
    
Number vWeather_Tomorrow_TempHigh "Tomorrow High [%.0f °F]"
    <temperature>
    { http="<[weatherForecast:360000:JSONPATH($.forecast.simpleforecast.forecastday[1].high.fahrenheit)]" }
    
Number vWeather_Tomorrow_TempLow "Tomorrow Low [%.0f °F]"
    <temperature>
    { http="<[weatherForecast:360000:JSONPATH($.forecast.simpleforecast.forecastday[1].low.fahrenheit)]" }
    
Number vWeather_Tomorrow_QPF "Tomorrow Precip QPF [%.1f in]"
    <rain>
    { http="<[weatherForecast:360000:JSONPATH($.forecast.simpleforecast.forecastday[1].qpf_allday.in)]" }
    
Number vWeather_Tomorrow_Snow "Tomorrow Snow [%.1f in]"
    <climate-on>
    { http="<[weatherForecast:360000:JSONPATH($.forecast.simpleforecast.forecastday[1].snow_allday.in)]" }

Notes:

  • I use the Group Based Persistence Design Pattern to designate those Items I want to chart using InfluxDB+Grafana.
  • I track the daily min and max and current cloudy conditions using Unbound Items.
  • I created a set of dynamic icons that correspond with the Conditions Item, more on this below
  • The refresh interval on the Items Binding config matches those set in the http.cfg but they don’t have to. They can poll more frequently but they will be pulling the same information from the cache for some of those polls. I usually choose to use the same value or the value in http.cfg/2 (e.g. in this case 180,000) which makes sure that an Item doesn’t have to wait as long in the case that the Item polls just before the binding pulls down new data.
  • I use a MAP transform to convert the values for barometric pressure’s direction from +, -, 0 to nice human readable words.

Each of the HTTP Items except for humidity uses a JSONPATH transformation to extract the desired value for that Item. Humidity uses REGEX in order to strip off the % from the value so it can be assigned to a Number Item. Theoretically one could use REGEX for all the Items but the JSONPATH is more self documented.

Icons

Wunderground provides ten sets of conditions icons. Unfortunately, they are in gif format and I couldn’t find any licensing information. So I’m afraid you will have to go through the tedious process of converting these icons into png or svg and creating a version of each to cover all of the possible conditions (the weather.map file below will help).

Brute Force

The procedure I followed was to right click to download the icon files and copied them to conf/icons/classic. I used the mogrify command line utility (comes with Ubuntu 17, ymmv) to convert them into png files. I don’t know if mogrify supports svg. The full command I used was:

mogrify -format png *.gif

Then I started the tedious process of creating all the dynamic icons versions for all the Wundergound conditions. And I’m certain I haven’t covered them all yet, particularly for the forecast conditions like “Chance of Rain” and such.

Make sure to create a default wunderground.png which will be chosen when no other icon matches. I made a copy of the error icon from the default OH icon set.

Then make a copy of the desired icon for each condition listed in weather.map below. Copy over all of the “Chance of” icons to a wunderground version. For example “chancesnow.png” would be copied to wunderground-chance\ of\ snow.png. Note that the icon must be all lower case. Use escaped spaces in the names for Conditions that include spaces (which is almost all of them). You will end up with a list of icons that looks something like:

On Demand

At some point I might figure out if it is feasible to download the icons on demand from wundergound (they provide the icon url in their JSON data) and convert them to png as needed. This could be done with something like the following, which won’t work for me because I run in Docker so the Exec binding isn’t much use to me and I don’t want to figure out how to convert a gif to a png in Rules.

Items:

String vWeather_Conditions_Icon
    { http="<[weatherConditions:360000:JSONPATH($.current_observation.icon_url)]"}

String vWeather_Today_Conditions_Icon
    { http="<[weatherForecast:360000:JSONPATH($.forecast.simpleforecast.forecastday[0].icon_url)]" }

String vWeather_Tomorrow_Conditions_Icon
    { http="<[weatherForecast:360000:JSONPATH($.forecast.simpleforecast.forecastday[1].icon_url)]" }

String vWeather_Conditions "Current Conditions [%s]"
	<currcond>
    { http="<[weatherConditions:360000:JSONPATH($.current_observation.weather)]" }

String vWeather_Today_Conditions "Today Conditions [%s]"
	<todaycond>
    { http="<[weatherForecast:360000:JSONPATH($.forecast.simpleforecast.forecastday[0].conditions)]" }

String vWeather_Tomorrow_Conditions "Tomorrow Conditions [%s]"
	<tomorrowcond>
    { http="<[weatherForecast:360000:JSONPATH($.forecast.simpleforecast.forecastday[1].conditions)]" }

Note the change of the icons for the Conditions Items

Rules:

import org.eclipse.xtext.xbase.lib.Functions

val Functions$Function2<String, String, Boolean> getIcon = [ url, path |
    executeCommandLine("wget -O " + path + " " + url, 10000)
    executeCommandLine("mogrify -format png " + path, 10000)
]

rule "Download current conditions icon"
when
    Item vWeather_Conditions_Icon changed
then
    getIcon.apply("/opt/openhab2/conf/icons/classic/currcond.gif", vWeather_Conditions_Icon.state.toString)
end

rule "Download today's conditions icon"
when
    Item vWeather_Today_Conditions_Icon changed
then
    getIcon.apply("/opt/openhab2/conf/icons/classic/todaycond.gif", vWeather_Today_Conditions_Icon.state.toString)
end

rule "Download tomorrow's conditions icon"
when
    Item vWeather_Tomorrow_Conditions_Icon changed
then
    getIcon.apply("/opt/openhab2/conf/icons/classic/tomorrowcond.gif", vWeather_Tomorrow_Conditions_Icon.state.toString)
end

Notes:

  • I don’t know how well this works with browser caching. I think it should work but I could see the icon not updating without a hard reload of the page. If you try it out please let me know and I’ll update accordingly.
  • I just typed in the above. It likely contains errors. Let me know if you get it to work.

Cached (PREFERRED)

A third approach that works even within the Docker is to pre-download the iconset from wunderground and then use a Rules to copy the icons to the proper filename as they are needed.

This time place the icons that have been converted from gif to png in an out of the way folder. I chose conf/icons/classic/wunderground. Leave their names unchanged.

Items:

Same Items as listed above under “On Demand”

Rules:

The rules are largely the same as above only now we parse the icon we care about out of the Icon Item and copy that file to the proper wunderground named file.

import org.eclipse.xtext.xbase.lib.Functions

val Functions$Function2<String, String, Boolean> copyIcon = [ url, cond |
	val parts = url.split("/")
	val icon = parts.get(parts.size-1).replace("gif", "png")

	var source = "/openhab/conf/icons/classic/wunderground/"+icon
	var dest   = "/openhab/conf/icons/classic/wunderground-"+cond.toLowerCase+".png"

	var command = "cp@@" + source + "@@" + dest	
	var results = executeCommandLine(command, 10000)
	if(results != "") {
		logError("weather", "Failed to copy over icon file: " + command + "\n" + results)
		false
	}
	else true
]

rule "Copy current conditions icon"
when
	Item vWeather_Conditions_Icon received update
then
	copyIcon.apply(vWeather_Conditions_Icon.state.toString, vWeather_Conditions.state.toString)
end

rule "Copy today conditions icon"
when
	Item vWeather_Today_Conditions_Icon received update
then
	copyIcon.apply(vWeather_Today_Conditions_Icon.state.toString, vWeather_Today_Conditions.state.toString)
end

rule "Copy tomorrow conditions icon"
when
	Item vWeather_Conditions_Icon received update
then
	copyIcon.apply(vWeather_Tomorrow_Conditions_Icon.state.toString, vWeather_Tomorrow_Conditions.state.toString)
end

This approach is a little more full proof than the previous approaches and is my preferred approch.

Rules

The rules I have set up to process the weather info are pretty simple. I have one rule to calculate the day’s min and max temperature and populate those Items and another that determines whether it is cloudy which is an application of the Separation of Behaviors Design Pattern.

rule "Update min and max temps"
when
    Time cron "1 0 0 * * ? *" or
    Item vWeather_Temp changed
then
    vWeather_Temp_Min.postUpdate(vWeather_Temp.minimumSince(now.withTimeAtStartOfDay, "influxdb").state)
    vWeather_Temp_Max.postUpdate(vWeather_Temp.maximumSince(now.withTimeAtStartOfDay, "influxdb").state)
end

rule "Is it cloudy outside?"
when
	Item vWeather_Conditions changed
then
	logInfo(logName, "New weather conditions: " + vWeather_Conditions.state.toString)
		    
	val isCloudy = transform("MAP", "weather.map", vWeather_Conditions.state.toString)
	val newState = if(isCloudy == null || isCloudy == "false") OFF else ON
		
	if(newState != vIsCloudy.state) {
		logInfo(logName, "Setting isCloudy to " + newState.toString) 
		vIsCloudy.postUpdate(newState)
	}
end

Theory of operation for “Update min and max temps”: Whenever the observed temperature changes and at one second after midnight the rule triggers. The rule pulls the minimumSince midnight from influxdb and assigns it to the Min item and does the same for the max.

Theory of operation for “Is it cloudy outside?”: I use this rule to set a flag used by my lighting rules to turn on/off certain lamps when it is cloudy. I placed all possible weather condition strings into a map file which transforms each to either a “true” or a “false” indicating whether that condition should be treated as cloudy or not. I transform the current conditions using this map file and if the transform returns “false” or null I set isCloudy to OFF, otherwise I set it to ON.

weather.map

NULL=unknown

# Pressure: Note, if you have uninitialzed Items they may be transformed to "falling"
-=falling
+=rising
0=steady

# Cloudy or not
Light\ Drizzle=true
Heavy\ Drizzle=true
Drizzle=true

Light\ Rain=true
Heavy\ Rain=true
Rain=true

Light\ Snow=true
Heavy\ Snow=true
Snow=true

Light\ Snow\ Grains=true
Heavy\ Snow\ Grains=true
Snow\ Grains=true

Light\ Ice\ Crystals=true
Heavy\ Ice\ Crystals=true
Ice\ Crystals=true

Light\ Ice\ Pellets=true
Heavy\ Ice\ Pellets=true
Ice\ Pellets=true

Light\ Hail=true
Heavy\ Hail=true
Hail=true

Light\ Mist=true
Heavy\ Mist=true
Mist=true

Light\ Fog=true
Heavy\ Fog=true
Fog=true

Light\ Fog\ Patches=true
Heavy\ Fog\ Patches=true
Fog\ Patches=true

Light\ Smoke=false
Heavy\ Smoke=true
Smoke=true

Light\ Volcanic\ Ash=false
Heavy\ Volcanic\ Ash=true
Volcanic\ Ash=true

Light\ Widespread\ Dust=false
Heavy\ Widespread\ Dust=true
Widespread\ Dust=true

Light\ Sand=false
Heavy\ Sand=true
Sand=true

Light\ Haze=false
Heavy\ Haze=true
Haze=false

Light\ Spray=false
Heavy\ Spray=false
Spray=false

Light\ Dust\ Whirls=false
Heavy\ Dust\ Whirls=true
Dust\ Whirls=false

Light\ Sandstorm=false
Heavy\ Sandstorm=true
Sandstorm=true

Light\ Low\ Drifting\ Snow=true
Heavy\ Low\ Drifting\ Snow=true
Low\ Drifting\ Snow= true

Light\ Low\ Drifting\ Widespread\ Dust=true
Heavy\ Low\ Drifting\ Widespread\ Dust=true
Low\ Drifting\ Widespread\ Dust=true

Light\ Low\ Drifting\ Sand=true
Heavy\ Low\ Drifting\ Sand=true
Low\ Drifting\ Sand=true

Light\ Blowing\ Snow=true
Heavy\ Blowing\ Snow=true
Blowing\ Snow=true

Light\ Blowing\ Widespread\ Dust=true
Heavy\ Blowing\ Widespread\ Dust=true
Blowing\ Widespread\ Dust=true

Light\ Blowing\ Sand=true
Heavy\ Blowing\ Sand=true
Blowing\ Sand=true

Light\ Rain\ Mist=true
Heavy\ Rain\ Mist=true
Rain\ Mist=true

Light\ Rain\ Showers=true
Heavy\ Rain\ Showers=true
Rain\ Showers=true

Light\ Snow\ Showers=true
Heavy\ Snow\ Showers=true
Snow\ Showers=true

Light\ Snow\ Blowing\ Snow\ Mist=true
Heavy\ Snow\ Blowing\ Snow\ Mist=true
Snow\ Blowing\ Snow\ Mist=true

Light\ Ice\ Pellet\ Showers=true
Heavy\ Ice\ Pellet\ Showers=true
Ice\ Pellet\ Showers=true

Light\ Hail\ Showers=true
Heavy\ Hail\ Showers=true
Hail\ Showers=true

Light\ Small\ Hail\ Showers=true
Heavy\ Small\ Hail\ Showers=true
Small\ Hail\ Showers=true

Light\ Thunderstorm=true
Heavy\ Thunderstorm=true
Thunderstorm=true

Light\ Thunderstorms\ and\ Rain=true
Heavy\ Thunderstorms\ and\ Rain=true
Thunderstorms\ and\ Rain=true

Light\ Thunderstorms\ and\ Snow=true
Heavy\ Thunderstorms\ and\ Snow=true
Thunderstorms\ and\ Snow=true

Light\ Thunderstorms\ and\ Ice\ Pellets=true
Heavy\ Thunderstorms\ and\ Ice\ Pellets=true
Thunderstorms\ and\ Ice\ Pellets=true

Light\ Thunderstorms\ with\ Hail=true
Heavy\ Thunderstorms\ with\ Hail=true
Thunderstorms\ with\ Hail=true

Light\ Thunderstorms\ with\ Small\ Hail=true
Heavy\ Thunderstorms\ with\ Small\ Hail=true
Thunderstorms\ with\ Small\ Hail=true

Light\ Freezing\ Drizzle=true
Heavy\ Freezing\ Drizzle=true
Freezing\ Drizzle=true

Light\ Freezing\ Rain=true
Heavy\ Freezing\ Rain=true
Freezing\ Rain=true

Light\ Freezing\ Fog=true
Heavy\ Freezing\ Fog=true
Freezing\ Fog=true

Patches\ of\ Fog=true
Shallow\ Fog=true
Partial\ Fog=true
Overcast=true
Clear=false
Partly\ Cloudy=true
Mostly\ Cloudy=true
Scattered\ Clouds=false
Small\ Hail=true
Squalls=true
Funnel\ Cloud=true
Unknown\ Precipitation=true
Unknown=false

Sitemap

There is nothing special about the sitemap. For completeness, here is the weather section of my current one.

		Text item=vWeather_Conditions {
			Frame item=vTimeOfDay {
				Text item=vMorning_Time
				Text item=vSunrise_Time
				Text item=vEvening_Time
				Text item=vSunset_Time
				Text item=vNight_Time
				Text item=vBed_Time
			}
			Frame item=vWeather_Conditions {
				Text item=vIsCloudy
				Text item=vWeather_Temp
				Text item=vWeather_Temp_Max
				Text item=vWeather_Temp_Min
				Text item=vWeather_Feel
				Text item=vWeather_Dewpoint
				Text item=vWeather_Conditions
				Text item=vWeather_Humidity
				Text item=vWeather_PrecipToday
				Text item=vWeather_Wind
				Text item=vWeather_Pressure_Trend
				Text item=vWeather_Visibility
				Text item=vWeather_SolarRad
				Text item=vWeather_UV	
			}			
			Frame label=Forecast {
			    Text item=vWeather_Today_Conditions
			    Text item=vWeather_Tomorrow_Conditions
			    Text item=vWeather_Today_TempHigh
			    Text item=vWeather_Tomorrow_TempHigh
			    Text item=vWeather_Today_TempLow		
			    Text item=vWeather_Tomorrow_TempLow    
			    Text item=vWeather_Today_QPF
			    Text item=vWeather_Tomorrow_QPF
			    Text item=vWeather_Today_Snow
			    Text item=vWeather_Tomorrow_Snow
		    }
		}
11 Likes

It occurred to me one could also save the icon to a wunderground-current\ conditions.png file and make the rule a little smarter to not retrieve and replace the file if it already exists. Fetching the icons doesn’t count against your API limit but you may want to lower the network traffic anyway.

Yet another approach would be to split the difference.

  1. Download all the icons and convert them to png or gif as above and put them in your icons/classic folder
  2. In the rules above, instead of doing a wget, copy the wunderground supplied named icon to the full text of the condition named file that OH needs. This will work even in the Dockerized case because the cp command is always there. :slight_smile:

More to follow…

OK. Got it to work. Updating the original post. It occurred to me that we want to replace the icons on every update because wunderground uses different versions of the icons at night and during the day.

4 Likes

Definitely second this approach, I always prefer HTTP binding when possible. Great write-up!!

1 Like

Perfect. I already used your previous setup with much pleasure. Thanks for setting me on the right path in steering away from the binding. Much more control this way.

Let me just add a few of my findings especially for the people venturing in this direction (and you should): Other than the pws, one can also -at least in Europe- come across the ‘zmw’ code and an api call would then look like e.g.
api.wunderground.com/api//q/zmw:00000.1.06370.json

Supposedly that code can change so be careful in using it

Also, choose your weatherstation wisely: If you let weatherunderground pick ‘the nearest to you’ that might not always be the nearest, but then again, the nearest does not need to be the best. I live in a small village and even I have choice of 14 weatherstations in my village alone and then there is even a close by military airbase that has a a weatherstation: some weatherstations do not provide all the data and others may have hours between updates

Weatherunderground actually does a quality control. If it is a good weatherstation, you may find a ‘goldstar’ medal to the left of the station name
Screenshot_2017-08-15_13-12-58
Anyway, great write up

1 Like

Thanks for the additional info. Wunderground can be a little opaque in this regard.

Your cautions about the stations is well founded. I live right up against the front range of the Rocky Mountains across from an airport. I can even see the building that has the weather station from the corner of my street. But we have such varied microclimates in this area that I couldn’t use that station to control my irrigation. They get a lot more rain then we do.

1 Like

microclimates indeed something to take into account, Fortunately I found a “goldstar” station maybe two hundred meters away, but it is right across from a pond… could be different microclimate, there is another goldstar, maybe 700 meters away, but I am in a leafier surrounding. I see an easy 1.5 degree difference between my well placed DS18B20 and the temp at the Goldstar station.
Hard to say if it is a different microclimate or just placement of sensors or maybe an inaccuracy. Nevertheless the general outcomes are pretty much in line. I have shied away from the majority of non-goldstar stations: Often incomplete data that sometimes hardly changes over time (is that wind really coming constantly from 270 degrees since yesterday???)

Certainly for something like irrigation you need a bit more accurate data. But… I am in the netherlands. If it doesnt rain now, it will rain in an hour

Ha! I’m in the high desert. If it’s raining now you probably should start building an arc. :grin:

:slight_smile: especially in the desert you want to get your irrigation right: not being wasteful but also not let yr plants dry.

Arc… well we have dikes and Hans Brinkers’ kids to stick their fingers in it if necessary

I didn’t see this mentioned, but another way to limit API calls is to combine multiple features into a single request. In the URL, instead of /<feature>/, you can use /<feature1>/<feature2>/, and modify the transforms as needed. Details for this are in the link you have under the Create the Query URLs section. In addition to the binding, I use /alerts/conditions/ to pull down and then parse out (with JSONPATH):

  • $.current_observation.solarradiation - last I looked this was missing from the binding, and it removed my need to calculate cloud cover. I mainly use it for adjusting dimmer levels based on outside brightness (lux).

  • $.alerts[0].message - for sending out notifications of severe weather alerts

2 Likes

Thanks! I never read the API closely enough to realize one could do that with the features. I just assumed it was one per call.

How do you install the transformations? I googled it, and all I can find is documentation on how the transformations work once they’re installed. Also, I don’t think I have services/exec.cfg (I’m using OH1). Can I safely ignore that step? Thanks.

These instructions are for OH 2. From this point forward I think you can assume that anything that is a new post, unless otherwise stated, will be for OH 2.

I believe all transformations come with 1.x and are not separately installable.

I never referred to exec.cfg as there isn’t one for OH 2 either and these instructions do not use the exec binding anyway. Instead they use the executeCommandLine action.

$.alerts[0].message - for sending out notifications of severe weather alerts

Since alerts may occur multiple times I wonder how your setting looks like, espacially for all the aler items: Currently I am using the following but not happy with it:

String vAlertWeather_Type1 			"Unwetterart [MAP(wu_alarms_code.map):%s]"  <weatherwarnung>		(gUnwetterH)    { http="<[weatherAlerts:420000:JSONPATH($.alerts[0].type)]" }
String vAlertWeather_Meteoalarm_Name1"Warnung [MAP(wu_alarms_code.map):%s]"		<weather>		(gUnwetterH)    { http="<[weatherAlerts:420000:JSONPATH($.alerts[0].wtype_meteoalarm_name)]" }
//Number vAlertWeather_Meteoalarm1 	"Warnart [%s]"								<temperature>	(gUnwetterH)    { http="<[weatherAlerts:420000:JSONPATH($.alerts[0].wtype_meteoalarm)]" }
String vAlertWeather_Type2 			"Unwetterart [MAP(wu_alarms_code.map):%s]"  <weatherwarnung>		(gUnwetterH)    { http="<[weatherAlerts:420000:JSONPATH($.alerts[1].type)]" }
String vAlertWeather_Meteoalarm_Name2"Warnung [MAP(wu_alarms_code.map):%s]"		<weather>		(gUnwetterH)    { http="<[weatherAlerts:420000:JSONPATH($.alerts[1].wtype_meteoalarm_name)]" }
//Number vAlertWeather_Meteoalarm2 	"Warnart [%s]"								<temperature>	(gUnwetterH)    { http="<[weatherAlerts:420000:JSONPATH($.alerts[1].wtype_meteoalarm)]" }

I would leave the JSON unparsed in a String Item and use a rule to parse out all the alerts and generate a single combined alerts String.

Creating separate items like you have will result in errors in the logs when there is fewer than two alerts and you will completely miss alerts when there is more than two.

If I understand your question correctly, I iterate through all of the alerts when there are multiples delivered in the API.

ITEMS:

String      Weather_Alert_Message_Raw      "Severe Weather Alert (Raw) [%s]"	    <text>  (gWeather,gNotification)
String      Weather_Alert_Message          "Severe Weather Alert [%s]"	            <text>  (gWeather,gNotification)    
String      Weather_Alert_NWS_VTEC         "Severe Weather Alert NWS VTEC [%s]"	    <text>  (gWeather,gNotification)
String      Weather_Alert_Type             "Severe Weather Alert Type [%s]"	        <text>  (gWeather,gNotification)
DateTime    Weather_Alert_Expiration       "Severe Weather Alert Expiration [%1$tA, %1$tB %1$te, %1$tY at %1$tl:%1$tM%1$tp]"	<calendar>  (gWeather,gNotification)

RULES:

rule "Alert: Check for weather alert"
when
	Time cron "0 1/5 * * * ?"
    //or
    //Item Virtual_Switch_1 changed
then
    //logDebug("Rules", "Alert: Check for weather alert: Starting")
    val String rawMessage = executeCommandLine("/bin/sh@@-c@@/usr/bin/curl -s -X GET https://api.wunderground.com/api/<api key>/alerts/conditions/q/pws:KOHHINCK2.json",60000)
    //logDebug("Rules", "Alert: Check for weather alert: Testing: rawMessage=[{}]",rawMessage)
    if (rawMessage.length > 0 && !rawMessage.contains("High Load")) {
        val String tempLux = transform("JSONPATH", "$.current_observation.solarradiation", rawMessage)
        //logDebug("Rules", "Alert: Check for weather alert: Testing: tempLux=[{}]",tempLux)
        if (tempLux != null && tempLux != "--") {
            Outside_Bloomsky_Lux_Raw.sendCommand(tempLux)
            //logDebug("Rules", "Alert: Check for weather alert: Testing: Outside_Bloomsky_Lux=[{}]",Outside_Bloomsky_Lux.state)
        }
        if (rawMessage.contains("message")) {
            Weather_Alert_Message_Raw.sendCommand(rawMessage)
            var i = 0
            while (i < Integer::parseInt(transform("JSONPATH", "$.response.features.alerts", rawMessage))) {
                logDebug("Rules", "Alert: Check for weather alert: Testing: starting loop. Alert=[{}]",i)
                val String parsedMessage = transform("JSONPATH", "$.alerts[" + i + "].message", rawMessage)
                //logDebug("Rules", "Alert: Check for weather alert: Testing: parsedMessage=[{}]",parsedMessage)
                if (!parsedMessage.contains("Small Craft")) {
                    val String weatherAlertDescription = transform("JSONPATH", "$.alerts[" + i + "].description", rawMessage)
                    Weather_Alert_Type.sendCommand(weatherAlertDescription)
                    val String weatherAlertType = transform("JSONPATH", "$.alerts[" + i + "].type", rawMessage)
                    Weather_Alert_NWS_VTEC.sendCommand(weatherAlertType)
                    val DateTime expirationTime = new DateTime(Long::parseLong(transform("JSONPATH", "$.alerts[" + i + "].expires_epoch", rawMessage)) * 1000L)
                    //logDebug("Rules", "Alert: Check for weather alert: Testing: rawExpirationTime=[{}]",rawExpirationTime)
                    Weather_Alert_Expiration.sendCommand(expirationTime.toString)
                    val String doubleParsedMessage = parsedMessage.replaceAll("(?s)\nLat...Lon.*","").replaceAll("\n\\.\\.\\.","").replaceAll("\\.\\.\\.",", ").replaceAll("\\*"," ").replaceAll("for,.*[Uu]ntil","until")
                    //logDebug("Rules", "Alert: Check for weather alert: Testing: doubleParsedMessage=[{}]",doubleParsedMessage)
                    Weather_Alert_Message.sendCommand(doubleParsedMessage)
                }
                i = i + 1
            }
        }
        else {
            logDebug("Rules", "Alert: Check for weather alert: No alert found")
        }
    }
    else {
        logInfo("Rules", "Alert: Check for weather alert: Communication with Weather Underground failed.")
    }

    /*
    example alert
    
    {
        "response": {
            "version":"0.1",
            "termsofService":"http://www.wunderground.com/weather/api/d/terms.html",
            "features": {
                "alerts": 1
                }
        },
        "query_zone": "020",
        "alerts": [
                {
                    "type": "FOG",
                    "description": "Dense Fog Advisory",
                    "date": "10:01 PM EDT on October 3, 2016",
                    "date_epoch": "1475546460",
                    "expires": "9:00 AM EDT on October 04, 2016",
                    "expires_epoch": "1475586000",
                    "tz_short":"EDT",
                    "tz_long":"America/New_York",
                    "message": "\u000A...Dense fog advisory in effect from 2 am to 9 am EDT Tuesday...\u000A\u000AThe National Weather Service in Cleveland has issued a dense fog\u000Aadvisory...which is in effect from 2 am to 9 am EDT Tuesday.\u000A\u000A* Visibilities...dropping below a quarter of a mile in low spots\u000A especially near rivers and ponds.\u000A\u000A* Timing...fog will develop and thicken through the night\u000A\u000A* impacts...expect dense fog to form especially in river valleys\u000A and other low lying locations. Visibility will drop below a\u000A quarter mile and remain low through about 830 am\u000A\u000APrecautionary/preparedness actions...\u000A\u000AA dense fog advisory is issued when visibilities will frequently\u000Abe reduced to less than one quarter mile. If driving...slow\u000Adown...use your headlights...and leave plenty of distance ahead\u000Aof you.\u000A\u000A\u000A\u000A\u000A",
                    "phenomena": "FG",
                    "significance": "Y"
                }
                "ZONES": []
        ]
    }
        
    https://espanol.wunderground.com/weather/api/d/docs?d=data/alerts
    typeTranslated

    HUR	Hurricane Local Statement
    TOR	Tornado Warning
    TOW	Tornado Watch
    WRN	Severe Thunderstorm Warning
    SEW	Severe Thunderstorm Watch
    WIN	Winter Weather Advisory
    FLO	Flood Warning
    WAT	Flood Watch / Statement
    WND	High Wind Advisory
    SVR	Severe Weather Statement
    HEA	Heat Advisory
    FOG	Dense Fog Advisory
    SPE	Special Weather Statement
    FIR	Fire Weather Advisory
    VOL	Volcanic Activity Statement
    HWW	Hurricane Wind Warning
    REC	Record Set
    REP	Public Reports
    PUB	Public Information Statement
    */
end

rule "Alert: Severe weather alert"
when
    Item Weather_Alert_Message changed
then		
    logDebug("Rules", "Alert: Weather_Alert_Message=[{}], Weather_Alert_NWS_VTEC=[{}]",Weather_Alert_Message.state.toString,Weather_Alert_NWS_VTEC.state.toString)
    SMS_Notification.sendCommand(Weather_Alert_Message.state.toString)
    if (Presence.state.toString != "Away" && (Mode.state.toString != "Night" || "HUR TOR WRN FLO SVR FIR VOL HWW REP PUB".contains(Weather_Alert_NWS_VTEC.state.toString))) {
        Audio_Notification.sendCommand(Weather_Alert_Message.state.toString)
    }
end

After trying to get alerts in GERMANY over weeks I can say, there is no alert delivered on the json stream. If I take any other city in Europe, does not matter which I take, I am able to receive alerts.
Someone can confirm this?
Looking at the current map https://www.wunderground.com/severe/europe.asp you will see all other countries arround GERMANY do have alerts … quite strange.

Similar topic (but no solution)… https://apicommunity.wunderground.com/weatherapi/topics/no-alert-messages-in-europe

Did you already try the relatively new Weather Underground Binding @rlkoshak? Because it is dedicated to WU I’d presume it should be more complete and less generic. I haven’t tried it myself yet. :blush:

When I wrote the above it was in response to several other users having a lot of problems with that binding, the old weather binding, and the yahoo binding. I’ve not kept up with it so have no idea if the kinks have been worked out. I’m happy with my implementation and have no plans to switch until perhaps there is a breaking change in the HTTP binding.

Even if one were to use the wunderground binding, the logic above that makes sure the right icon is used with the conditions data would still apply.

I have been using this setup ever since you posted it on august 15 and I can honestly say I have no problems at all anymore with obtaining weather data, as opposed to using the weather binding which gave me frequent problems, so thanks a bundle for that.

One thing that I did notice is that apparently… maybe weather station related or region related, there might be small differences in the name of the JSON items that are being sent. I for one couldnt get part of the precipitation data, until I checked the JSON for my station.
So if anyone using this set up (and you should) encounters some data to always stay blank, check what the json of the station you are using is actually sending.

Anyway, this setup has been a life saver for me

1 Like