This page is part of archived documentation for openHAB 3.3. Go to the current stable version
# Wunderground Update Receiver Binding
Many personal weather stations or similar devices are only capable of submitting measurements to the wunderground.com (opens new window) update site.
This binding enables acting as a receiver of updates from devices that post measurements to https://rtupdate.wunderground.com/weatherstation/updateweatherstation.php (opens new window). If the hostname is configurable - as on weather stations based on the Fine Offset Electronics WH2600-IP - this is simple, otherwise you have to set up dns such that it resolves the above hostname to your server, without preventing the server from resolving the proper ip if you want to forward the request.
The server thus listens at http(s)😕/
The request is in itself simple to parse, so by redirecting it to your openHAB server you can intercept the values and use them to control items in your home. E.g. use measured wind-speed to close an awning or turn on the sprinkler system after some time without rain. This binding allows you to mix and match products from various manufacturers that otherwise have a closed system.
If you wish to pass the measurements on to rtupdate.wunderground.com (opens new window), you can use a simple rule that triggers on the wundergroundupdatereceiver:wundergroundUpdateReceiver:<channel-id>:metadata#last-query-trigger
to do so.
It can also be used to submit the same measurements to multiple weather services via multiple rules.
# Supported Things
Any device that sends weather measurement updates to the wunderground.com (opens new window) update URLs is supported. It is easiest to use with devices that have a configurable target address, but can be made to work with any internet-connected device, that gets its dns server via DHCP or where the DNS server can be set.
# Discovery
The binding starts listening at the above-mentioned URI as soon as it is initialized. Any request with an unregistered stationId is recorded and if auto-discovery is enabled appears in the inbox, otherwise can be registered when a manual scan is initiated. For each request parameter a channel is generated, based on a list of known parameters from https://support.weather.com/s/article/PWS-Upload-Protocol?language=en_US (opens new window) and other observed parameters from various devices. If you have a device that submits a parameter that is unknown in the current version of the binding please feel free to submit an issue to have it added.
While discovery is active, either in the background or during a manual scan, any request parameters that don't have a channel associated with them are added to the thing's channels. This supports using multiple devices that submit measurements to the same station ID. The thing is the wunderground account, not the individual devices submitting measurements.
# Thing Configuration
The only configurable value is the station id, which should match the one configured on the device. If you don't plan on submitting measurements to wunderground.com (opens new window), it can be any unique non-empty string value, otherwise it must be the actual station ID.
# Channels
Each measurement type the wunderground.com (opens new window) update service accepts has a channel.
The channels must be named exactly as the request parameter they receive.
I.e. the wind speed channel must be named windspeedmph
as that is the request parameter name defined by Wunderground in their API.
The channel name set up in the binding should be considered an id with no semantic content other than pointing to the wounderground API.
Additionally there is a receipt timestamp and a trigger channel.
# Request parameters are mapped to one of the following channel-types:
# Normal channel-types:
Request parameter | Channel type id | Type | Label | Description | Group |
---|---|---|---|---|---|
winddir | wind-direction | Number:Angle | Current Wind Direction | Current wind direction | Wind |
windspeedmph | wind-speed | Number:Speed | Current Wind Speed | Current wind speed, using software specific time period. | Wind |
windgustmph | wind-gust-speed | Number:Speed | Current Gust Speed | Current wind gust speed, using software specific time period. | Wind |
windgustdir | wind-gust-direction | Number:Angle | Gust Direction | Current wind gust direction expressed as an angle using software specific time period. | Wind |
tempf | temperature | Number:Temperature | Outdoor Temperature | Current outdoor temperature | Temperature |
indoortempf | indoor-temperature | Number:Temperature | Indoor Temperature | Indoor temperature. | Temperature |
rainin | rain | Number:Length | Hourly Rain | Rain over the past hour. | Rain |
dailyrainin | rain-daily | Number:Length | Daily Rain | Rain since the start of the day. | Rain |
solarradiation | solarradiation | Number:Intensity | Solar Radiation | Solar radiation | Sun |
UV | uv | Number:Dimensionless | UV Index | UV index. | Sun |
humidity | humidity | Number:Dimensionless | Humidity | Humidity in %. | Humidity |
indoorhumidity | indoor-humidity | Number:Dimensionless | Indoor Humidity | Indoor humidity in %. | Humidity |
baromin |
# Advanced channel-types:
Request parameter | Channel type id | Type | Label | Description | Group |
---|---|---|---|---|---|
windspdmph_avg2m | wind-speed-avg-2min | Number:Speed | Wind Speed 2min Average | 2 minute average wind speed. | Wind |
winddir_avg2m | wind-direction-avg-2min | Number:Angle | Wind Direction 2min Average | 2 minute average wind direction. | Wind |
windgustmph_10m | wind-gust-speed-10min | Number:Speed | Gust Speed 10min Average | 10 minute average gust speed. | Wind |
windgustdir_10m | wind-gust-direction-10min | Number:Angle | Gust Direction 10min Average | 10 minute average gust direction. | Wind |
windchillf | wind-chill | Number:Temperature | Wind Chill | The apparent wind chill temperature. | Temperature |
soiltempf | soil-temperature | Number:Temperature | Soil Temperature | Soil temperature. | Temperature |
weeklyrainin | rain-weekly | Number:Length | Weekly Rain | Rain since the start of this week. | Rain |
monthlyrainin | rain-monthly | Number:Length | Monthly Rain | Rain since the start if this month. | Rain |
yearlyrainin | rain-yearly | Number:Length | Yearly Rain | Rain since the start of this year. | Rain |
weather | metar | String | METAR Weather Report | METAR formatted weather report | Sun_Clouds |
clouds | clouds | String | Cloud Cover | METAR style cloud cover. | Sun_Clouds |
visibility | visibility | Number:Length | Visibility | Visibility. | Sun_Clouds |
dewptf | dew-point | Number:Temperature | Dew Point | Outdoor dew point. | Humidity |
soilmoisture | soil-moisture | Number:Dimensionless | Soil Moisture | Soil moisture in %. | Moisture |
leafwetness | leafwetness | Number:Dimensionless | Leaf Wetness | Leaf wetness in %. | Moisture |
AqNO | nitric-oxide | Number:Dimensionless | Nitric Oxide | Nitric Oxide ppm. | Pollution |
AqNO2T | nitrogen-dioxide-measured | Number:Dimensionless | Nitrogen Dioxide | Nitrogen Dioxide, true measure ppb. | Pollution |
AqNO2 | nitrogen-dioxide-nox-no | Number:Dimensionless | NO2 X computed | NO2 computed, NOx-NO ppb. | Pollution |
AqNO2Y | nitrogen-dioxide-noy-no | Number:Dimensionless | NO2 Y computed, NOy-NO ppb | NO2 computed, NOy-NO ppb. | Pollution |
AqNOX | nitrogen-oxides | Number:Dimensionless | Nitrogen Oxides | Nitrogen Oxides ppb. | Pollution |
AqNOY | total-reactive-nitrogen | Number:Dimensionless | Total Reactive Nitrogen | Total reactive nitrogen. | Pollution |
AqNO3 | no3-ion | Number:Density | NO3 ion | NO3 ion (nitrate, not adjusted for ammonium ion) µG/m3. | Pollution |
AqSO4 | so4-ion | Number:Density | SO4 ion | SO4 ion (sulfate, not adjusted for ammonium ion) µG/m3. | Pollution |
AqSO2 | sulfur-dioxide | Number:Dimensionless | Sulfur Dioxide | Sulfur Dioxide, conventional ppb. | Pollution |
AqSO2T | sulfur-dioxide-trace-levels | Number:Dimensionless | Sulfur Dioxide Trace Levels | Sulfur Dioxide, trace levels ppb. | Pollution |
AqCO | carbon-monoxide | Number:Dimensionless | Carbon Monoxide | Carbon Monoxide, conventional ppm. | Pollution |
AqCOT | carbon-monoxide-trace-levels | Number:Dimensionless | Carbon Monoxide Trace Levels | Carbon Monoxide, trace levels ppb. | Pollution |
AqEC | elemental-carbon | Number:Density | Elemental Carbon | Elemental Carbon, PM2.5 µG/m3. | Pollution |
AqOC | organic-carbon | Number:Density | Organic Carbon | Organic Carbon, not adjusted for oxygen and hydrogen, PM2.5 µG/m3. | Pollution |
AqBC | black-carbon | Number:Density | Black Carbon | Black Carbon at 880 nm, µG/m3. | Pollution |
AqUV-AETH | aethalometer | Number:Density | Second Channel of Aethalometer | second channel of Aethalometer at 370 nm, µG/m3. | Pollution |
AqPM2.5 | pm2_5-mass | Number:Density | PM2.5 Mass | PM2.5 mass, µG/m3. | Pollution |
AqPM10 | pm10-mass | Number:Density | PM10 Mass | PM10 mass, µG/m3. | Pollution |
AqOZONE | ozone | Number:Dimensionless | Ozone | Ozone, ppb. | Pollution |
# Metadata channel-types:
Request parameter | Channel type id | Type | Label | Description | Group |
---|---|---|---|---|---|
dateutc | dateutc | String | Last Updated | The date and time of the last update in UTC as submitted by the device. This can be 'now'. | Metadata |
softwaretype | softwaretype | String | Software Type | A software type string from the device | Metadata |
rtfreq | realtime-frequency | Number | Realtime Frequency | How often does the device submit measurements | Metadata |
lowbatt | system:low-battery | Switch | Low Battery | Low battery warning with possible values on (low battery) and off (battery ok) | Metadata |
# Synthetic channel-types. These are programmatically added:
Channel type id | Type | Channel type | Label | Description | Group |
---|---|---|---|---|---|
dateutc-datetime | dateutc-datetime | state | Last Updated as DateTime | The date and time of the last update in UTC as submitted by the device converted to a DateTime value. In case of 'now', the current time is used. | Metadata |
last-received-datetime | DateTime | state | Last Received | The date and time of the last update. | Metadata |
last-query-state | String | state | The last query | The query part of the last request from the device | Metadata |
last-query-trigger | String | trigger | The last query | The query part of the last request from the device | Metadata |
The trigger channel's payload is the last querystring, so the following dsl rule script would send the measurements on to wunderground.com (opens new window):
val requestQuery = receivedEvent
sendHttpGetRequest("https://rtupdate.wunderground.com/weatherstation/updateweatherstation.php?" + requestQuery)
The PASSWORD, action and realtime parameters are ignored, as they are wunderground technical constants, that devices must send. The ID parameter is used to identify the correct thing the request pertains to, i.e. the stationId configuration value.
As described by the wunderground specification a device can submit multiple values for the outdoor temperature, soil temperature, soil moisture and leaf wetness channels by inserting an index number into the name of the request parameter, fx. tempf can be temp1f, temp2f, etc. This is supported by the discovery mechanism, creating a channel for each of the values.
# Examples
# Thing file
Configuration using thing and item files is not the recommended method, as you have to manually replicate the configuration discovery produces. Channels must be named as the request parameters in the channel type table, otherwise the binding will not be able to update with values from requests. So the request parameter names submitted by your particular device(s) need to be found before being able to write appropriate thing files. You need to intercept a request from your devices(s) using something like wireshark. Both thing and item files must be created manually to produce a result practically identical to the one produced through automatic discovery.
Assuming you have intercepted a request such as https://rtupdate.wunderground.com/weatherstation/updateweatherstation.php?ID=MYSTATIONID&PASSWORD=XXXXXX&windspeedmph=3.11&dateutc=2021-02-07%2014:04:03&softwaretype=WH2600%20V2.2.8&action=updateraw&realtime=1&rtfreq=5
, you can configure a thing to intercept the request thus:
Thing wundergroundupdatereceiver:wundergroundUpdateReceiver:ATHINGID "Foo" [stationId="MYSTATIONID"] {
Channels:
Type wind-speed : windspeedmph []
Type dateutc : dateutc []
Type softwaretype : softwaretype []
Type realtime-frequency : rtfreq []
Type dateutc-datetime : dateutc-datetime []
Type last-received-datetime : last-received-datetime []
Type last-query-state : last-query-state []
Type last-query-trigger : last-query-trigger []
}
The pattern for a given channel is Type <channel type id> : <request paramter> []
from the channel types table.
Casing of the request parameter is significant.
None of the current channels take config.
# Item file
Number:Speed WuBinding_WeatherStation_WindSpeed "Current Wind Speed [%.2f %unit%]" <wind> { channel="wundergroundupdatereceiver:wundergroundUpdateReceiver:ATHINGID:windspeedmph" }
DateTime WuBinding_LastRecieved "Last Recieved Time [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS]" <time> { channel="wundergroundupdatereceiver:wundergroundUpdateReceiver:ATHINGID:last-received-datetime" }
The binding tries to post received values as the item types described in the channel types table, so attaching a channel that takes a given type to an item of a different type has undefined behaviour.
# Rule examples
You can use the trigger channel to create a rule to calculate additional values. Create an new manual Item with a meaningful id, fx. WundergroundUpdateReceiverBinging_HeatIndex with a Number type. Create a rule that triggers when the trigger channel is updated and the following DSL:
if ( (WH2k6_OutdoorTemperature.state != NULL) && (WH2k6_OutdoorTemperature.state != UNDEF) &&
(WH2k6_Humidity.state != NULL) && (WH2k6_Humidity.state != UNDEF)
)
{
val Double tempf = (WH2k6_OutdoorTemperature.state as QuantityType<Number>).toUnit("°F").doubleValue
val Double humidity = (WH2k6_Humidity.state as QuantityType<Number>).toUnit("%").doubleValue
// https://www.wpc.ncep.noaa.gov/html/heatindex_equation.shtml
if ( tempf>=80.0 ) {
var Double heatindex = -42.379 + 2.04901523*tempf + 10.14333127*humidity - 0.22475541*tempf*humidity - 0.00683783*tempf*tempf - 0.05481717*humidity*humidity + 0.00122874*tempf*tempf*humidity + 0.00085282*tempf*humidity*humidity - 0.00000199*tempf*tempf*humidity*humidity;
if ( (humidity <= 13.0) && (tempf >= 80.0) && (tempf <= 112.0) ) {
heatindex = heatindex - ((13.0 - humidity)/4.0) * (Math::sqrt(17.0-Math::abs(tempf-95.0)/17.0));
} else if ( (humidity >= 85.0) && (tempf >= 80.0) && (tempf <= 87.0) ) {
heatindex = heatindex + ((humidity - 85.0)/10.0)*((87.0-tempf)/5.0);
}
WundergroundUpdateReceiverBinging_HeatIndex.postUpdate(heatindex + "°F")
} else {
WundergroundUpdateReceiverBinging_HeatIndex.postUpdate(UNDEF)
}
}
You would then have to trigger another rule to submit the original request with any calculated values appended.
You can also define a transformation to fx. get a cardinal direction (N, S, W, E):
(function(s){
if ( (s == "NULL") || (s == "UNDEF") )
{
return undefined;
}
else
{
var dir = ["N ⬇️", "NNO ⬇️", "NO ↙️", "ONO ⬅️", "O ⬅️", "OSO ⬅️", "SO ↖️", "SSO ⬆️", "S ⬆️", "SSW ⬆️", "SW ↗️", "WSW ➡️", "W ➡️", "WNW ➡️", "NW ↘️", "NNW ⬇️"]; var wind = parseInt(s.split(" ")[0]);
var winddiroffset = (wind + (360.0/32.0)) % 360.0;
var winddiridx = Math.floor(winddiroffset / (360.0/16.0));
var winddir = dir[winddiridx];
return winddir + ' ('+ wind +'°)';
}
})(input)
The examples were kindly provided by MikeTheTux.