Get local UV radiation information into openHAB
Update: The API used in this topic has been deprecated. I will update the example soon.
Introduction
UV Radiation leads to sun burn and is the largest cause of skin cancer.
OpenWeatherMap only provides UV information in their API for their expensive paid accounts. Wunderground provides UV field in the API, but I have not yet found a weather station that actually returns any data. Searching the web for another API lead me to the UV Index API that has been developed by Australian Alex Ershov for use with his UVIMate Android app
Pre-requisites
- Working openHAB 2 installation (the solution should work with 1.x as well, but is not tested yet)
- Legacy 1.x HTTP binding installed (for caching)
- Facebook account (to register for the API)
- Key for the UV Index API
- Latitude and longitude for your home. Use the GPS Coordinates site if you donāt know them already.
General description
The UV Index API is called every 10 minutes for updates. The resulting JSON message is parsed by a rule to populate required items.
The sunburn times differ based on skin type, according to the international Fitzpatrick scale. The time is calculated in minutes, but is displayed in a nicely formatted text through a Javascript transformation.
Note: Due to the JavaScripts the sitemap section takes 5-10 seconds to load and show the section on a raspberry Pi 2B. If you donāt like this, you can always simply show the plain value (see the items file for an example at skin type VI).
Although you could use direct JSON transformations for individual items, I have decided to only get the raw JSON and parse that using a dedicated rule. This rule is needed for more complex conversion anyway, so now all logic is in one place. Big thanks to @rlkoshak for helping me out with parsing NULL
values.
This solution can be used with or without the HTTP binding. With the binding, you can cache content and directly access the API data for multiple items without hammering/abusing the API. I have set up the binding to only allow calls every 10 minutes.
Instructions
Step 1: Setup HTTP binding
Add a new section to your http.cfg
file in the configuration services
folder.
Replace XXX with your personal API token.
Replace latitude and longitude with your home location.
http.cfg
# cache for 10 minutes (= 600000 milliseconds)
uvimateCache.url=https://uvimate.herokuapp.com/api/getUVI/51.9000/4.6000{x-access-token=XXX}
uvimateCache.updateInterval=600000
Step 2. Setup your items
Create uvi.items
in the items
configuration folder
uvi.items
Group Uvi "UV Index"
// get raw json every 10 minutes
// use http caching to prevent hitting the API too much :-)
String Uvi_Raw (Uvi) { http="<[uvimateCache:600000:REGEX((.*))]" }
// Other items are populated using a rule
// current index
Number Uvi_Uvi "Current UV Index [%.2f]" <line> (Uvi, Chart)
Number Uvi_UviMax "Expected max UV Index today [%.2f]" <line> (Uvi)
DateTime Uvi_UviMaxTime "Max UV Index Time [%1$tH:%1$tM]" <clock> (Uvi)
// burn times
Number Uvi_BurnTimeCeltic "Sunburn (Type I) [JS(duration.js):%s]" <woman_1> (Uvi)
Number Uvi_BurnTimePale "Sunburn (Type II) [JS(duration.js):%s]" <woman_2> (Uvi)
Number Uvi_BurnTimeCaucasian "Sunburn (Type III) [JS(duration.js):%s]" <woman_3> (Uvi)
Number Uvi_BurnTimeMediterranean "Sunburn (Type IV) [JS(duration.js):%s]" <woman_4> (Uvi)
Number Uvi_BurnTimeSouthAfrican "Sunburn (Type V) [JS(duration.js):%s]" <woman_5> (Uvi)
Number Uvi_BurnTimeNegro "Sunburn (Type VI) [%d mins]" <woman_6> (Uvi)
// location information
String Uvi_Location "Location [%s]" <text> (Uvi)
DateTime Uvi_Observation "Observation [%1$tH:%1$tM]" <clock> (Uvi)
DateTime Uvi_LastUpdate "Last update [%1$tH:%1$tM]" <clock> (Uvi)
Step 3: Create javascript transformation
Create a file duration.js
in your configuration transform
folder
duration.js
// computes nicely formatted duration from given minutes
// change these values for your own language
var minute = "minute"
var minutes = "minutes"
var hour = "hour"
var hours = "hours"
var day = "day"
var days = "days"
(function(i){
var d = Math.floor(i / (24 * 60));
var h = Math.floor((i / 60) - (24 * d));
var m = Math.round(i - 60 * (24 * d + h));
var result = '';
// days
if (d > 0) {
result = result + d;
if (d == 1) {
result = d + ' ' + day;
} else {
result = d + ' ' + days;
}
}
// hours
if (h > 0) {
if (result != '') {
result = result + ', ';
}
result = result + h;
if (h == 1) {
result = result + ' ' + hour;
} else {
result = result + ' ' + hours;
}
}
// minutes
if (m > 0) {
if (result != '') {
result = result + ', ';
}
result = result + m;
if (m == 1) {
result = result + ' ' + minute;
} else {
result = result + ' ' + minutes;
}
}
return result;
})(input)
Step 4: Create rule to parse JSON
Create uvi.rules
file in your configuration rules
folder.
uvi.rules
// logger title
val logger= "rules.uvi"
/*
To debug, set logging in karaf console:
log:set DEBUG org.eclipse.smarthome.model.script.rules.uvi
*/
/*
The HTTP binding is used to obtain raw JSON from the Uvimate API.
The individual items however are populated using this rule.
Some items could be easily obtained through a simple JSONPATH translation in the items file.
Other items need specific translation, for example a conversion from zulu to local time
or handling a 'null' value for a number.
*/
rule UvimateUpdated
when
Item Uvi_Raw received update
then
logDebug(logger, "Uvimate received update")
// asap
Uvi_LastUpdate.postUpdate(new DateTimeType)
// get values
val raw = Uvi_Raw.state.toString
val observation = new DateTimeType(transform("JSONPATH", "$.result.date", raw))
val location = transform("JSONPATH", "$.result.location", raw)
val uvi = transform("JSONPATH", "$.result.uviData.uvi", raw)
val uviMax = transform("JSONPATH", "$.result.uviData.uviMax", raw)
val uviMaxTime = new DateTimeType(transform("JSONPATH", "$.result.uviData.uviMaxTime", raw))
val celtic = transform("JSONPATH", "$.result.burnTime.celtic", raw)
val pale = transform("JSONPATH", "$.result.burnTime.pale", raw)
val caucasian = transform("JSONPATH", "$.result.burnTime.caucasian", raw)
val mediterranean = transform("JSONPATH", "$.result.burnTime.mediterranean", raw)
val southAfrican = transform("JSONPATH", "$.result.burnTime.southAfrican", raw)
val negro = transform("JSONPATH", "$.result.burnTime.negro", raw)
// debug
logDebug(logger, "raw : " + raw)
logDebug(logger, "observation : " + observation)
logDebug(logger, "location : " + location)
logDebug(logger, "uvi : " + uvi)
logDebug(logger, "uviMax : " + uviMax)
logDebug(logger, "uviMaxTime : " + uviMaxTime)
logDebug(logger, "celtic : " + celtic)
logDebug(logger, "pale : " + pale)
logDebug(logger, "caucasian : " + caucasian)
logDebug(logger, "mediterranean : " + mediterranean)
logDebug(logger, "southAfrican : " + southAfrican)
logDebug(logger, "negro : " + negro)
// post updates
Uvi_Observation.postUpdate(observation)
Uvi_Location.postUpdate(location)
Uvi_Uvi.postUpdate(uvi)
Uvi_UviMax.postUpdate(uviMax)
Uvi_UviMaxTime.postUpdate(uviMaxTime)
// oneliners with null will fail
if (celtic==null) Uvi_BurnTimeCeltic.postUpdate(NULL)
else Uvi_BurnTimeCeltic.postUpdate(celtic)
if (pale==null) Uvi_BurnTimePale.postUpdate(NULL)
else Uvi_BurnTimePale.postUpdate(pale)
if (caucasian==null) Uvi_BurnTimeCaucasian.postUpdate(NULL)
else Uvi_BurnTimeCaucasian.postUpdate(caucasian)
if (mediterranean==null) Uvi_BurnTimeMediterranean.postUpdate(NULL)
else Uvi_BurnTimeMediterranean.postUpdate(mediterranean)
if (southAfrican==null) Uvi_BurnTimeSouthAfrican.postUpdate(NULL)
else Uvi_BurnTimeSouthAfrican.postUpdate(southAfrican)
if (negro==null) Uvi_BurnTimeNegro.postUpdate(NULL)
else Uvi_BurnTimeNegro.postUpdate(negro)
end
Step 5: Create sitemap
Create file uvi.sitemap
in your configuration sitemaps
folder. Valuecolors are given in hex values, since the iOS app does not (yet) supported color names.
uvi.sitemap
sitemap uvi label="UV Index" {
Frame item=Uvi {
Text item=Uvi_Uvi valuecolor=[
Uvi_Uvi=="NULL"="#D3D3D3", // lightgray
<3="#006400", // darkgreen
<6="#FFD700", // gold
<8="#FF8C00", // darkorange
<11="#DC143C", // crimson (red)
>=11="#4B0082" ] // indigo
Text item=Uvi_UviMax
Text item=Uvi_UviMaxTime
Text item=Uvi_BurnTimeCeltic
Text item=Uvi_BurnTimePale
Text item=Uvi_BurnTimeCaucasian
Text item=Uvi_BurnTimeMediterranean
Text item=Uvi_BurnTimeSouthAfrican
Text item=Uvi_BurnTimeNegro
Text item=Uvi_Location
Text item=Uvi_Observation
Text item=Uvi_LastUpdate
}
}
Update 2017-02-15: Removed forecasts. Added skin type icons for burn time items. Added value colors for index in sitemap.