Next item on my home automation todo list: weather, and forecast. No good system without that data!
After exploring the options which openHAB supports, I settled for OpenWeatherMap . Note: you need an account with OWM , the basic functionality is free, the paid options give you more and better forecast.
And of course, I install everything using Ansible , and can just repeat the entire installation if something does not work.
This setup is also used in a weather forecast for tomorrow .
First things first, define some variables with basic settings:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- name : Set OpenWeatherMap variables
set_fact :
owm_predefined_bridge_uuid : "home"
owm_predefined_forecast_uuid : "home"
owm_apikey : "{{ lookup('file', playbook_dir + '/credentials/openweathermap-apikey.txt') }}"
owm_refresh : "15"
owm_language : "en"
owm_location : "37.103448, -115.847730"
owm_location_name : "Home"
owm_forecasthours : 24
owm_forecastdays : 5
owm_uuid : False
owm_thingtypeuid : False
owm_forecast_uuid : False
owm_forecast_thingtypeuid : False
The bridge_uuid
and forecast_uuid
will become part of the Things names later on. The apikey
is the OpenWeatherMap API key. refresh
is the refresh time after which the plugin will fetch updates. language
is the plugin language. location_name
is a freely defined name which you can choose, and location
are the WGS84 coordinates of the location where you want to see the weather reported for. forecastdays
and forecasthours
depend on your OWM subscription, the free account works fine with 5/24. The other variables are for the Playbook, and not relevant here.
The OWM openHAB plugin comes in two parts, a bridge and the forecast. Let’s install the bridge first. For that we fetch all Things
, and check if the bridge is already in the list.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- name : Get things
uri :
url : "http://{{ ansible_host }}:8080/rest/things"
register : o2_things
changed_when : false
# figure out if the OpenWeatherMap bridge already exists
- name : Copy Things data
set_fact :
owm_uuid : "{{ item.UID }}"
owm_thingtypeuid : "{{ item.thingTypeUID }}"
when : item.thingTypeUID == "openweathermap:weather-api"
loop : "{{ o2_things.json }}"
loop_control :
label : "{{ item.thingTypeUID }} - {{ item.UID }}"
This uses two of the variables we created earlier.
If the bridge does not yet exist, let’s create it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# create the OpenWeatherMap bridge
- block :
- name : Set predefined UUID
set_fact :
create_item_id : "{{ owm_predefined_bridge_uuid }}"
- name : Set Item creation variables for OWM bridge
set_fact :
create_item_label : "OpenWeatherMap Account"
create_item_bridgeUID : ""
create_item_configuration : "{ \"apikey\": \"{{ owm_apikey }}\", \"refreshInterval\": \"{{ owm_refresh }}\", \"language\": \"{{ owm_language }}\" }"
create_item_properties : "{}"
create_item_UID : "openweathermap:weather-api:{{ create_item_id }}"
create_item_thingTypeUID : "openweathermap:weather-api"
create_item_channels : ""
create_item_location : "{{ owm_location_name }}"
- name : Create template
set_fact :
create_new_item : "{{ lookup('template', 'files/item-creation-without-bridge.json') }}"
- name : Create OWM bridge
uri :
url : "http://{{ ansible_host }}:8080/rest/things"
body : "{{ create_new_item }}"
body_format : json
method : POST
status_code : 200 , 201
when : owm_uuid == False
In order to create the new Thing
bridge, a JSON -encoded request must be sent to openHAB . The required structure is a bit more complex, hence I moved it into a template, and fill in all the variables before. The template looks like this:
1
2
3
4
5
6
7
8
9
{
"UID" : "{{ create_item_UID }}" ,
"thingTypeUID" : "{{ create_item_thingTypeUID }}" ,
"configuration" : {{ create_item_configuration }},
"item" : {"label" : "{{ create_item_label }}" , "groupNames" : []},
"ID" : "{{ create_item_id }}" ,
"label" : "{{ create_item_label }}" ,
"location" : "{{ create_item_location }}"
}
First part done, now the forecast. For that I reload the Things
again, just to be sure, and scan for the Bridge
and the Forecast. If the bridge was just created, the old $o2_things
does not contain this data.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# after the bridge is available, we need to re-scan "things", and retrieve the bridge data
# two possible cases:
# 1) bridge was created moments ago
# 2) bridge was already there
- name : Get things
uri :
url : "http://{{ ansible_host }}:8080/rest/things"
register : o2_things
changed_when : false
- name : Copy Things data (Bridge)
set_fact :
owm_uuid : "{{ item.UID }}"
owm_thingtypeuid : "{{ item.thingTypeUID }}"
when : item.thingTypeUID == "openweathermap:weather-api"
loop : "{{ o2_things.json }}"
loop_control :
label : "{{ item.thingTypeUID }} - {{ item.UID }}"
- name : Copy Things data (Forecast)
set_fact :
owm_forecast_uuid : "{{ item.UID }}"
owm_forecast_thingtypeuid : "{{ item.thingTypeUID }}"
when : item.thingTypeUID == "openweathermap:weather-and-forecast" and item.bridgeUID == owm_uuid
loop : "{{ o2_things.json }}"
loop_control :
label : "{{ item.thingTypeUID }} - {{ item.UID }}"
The next step is the Thing
creation for the Forecast, which looks similar to the previous step - except that the JSON is a bit different. The change is big enough that we can’t re-use the same template, it has to be a different one.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# create weather forecast
- block :
- name : Set predefined UUID
set_fact :
create_item_id : "{{ owm_predefined_forecast_uuid }}"
- name : Set Item creation variables for OWM forecast
set_fact :
create_item_label : "Weather And Forecast"
create_item_bridgeUID : "{{ owm_uuid }}"
create_item_configuration : "{ \"location\": \"{{ owm_location }}\", \"forecastHours\": \"{{ owm_forecasthours }}\", \"forecastDays\": \"{{ owm_forecastdays }}\" }"
create_item_properties : "{}"
create_item_UID : "openweathermap:weather-and-forecast:{{ create_item_id }}"
create_item_thingTypeUID : "openweathermap:weather-and-forecast"
create_item_channels : ""
create_item_location : "{{ owm_location_name }}"
- name : Create template
set_fact :
create_new_item : "{{ lookup('template', 'files/item-creation-with-bridge.json') }}"
- name : Create OWM forecast
uri :
url : "http://{{ ansible_host }}:8080/rest/things"
body : "{{ create_new_item }}"
body_format : json
method : POST
status_code : 200 , 201
when : owm_forecast_uuid == False
And the template:
1
2
3
4
5
6
7
8
9
10
{
"UID" : "{{ create_item_UID }}" ,
"thingTypeUID" : "{{ create_item_thingTypeUID }}" ,
"configuration" : {{ create_item_configuration }},
"bridgeUID" : "{{ create_item_bridgeUID }}" ,
"item" : {"label" : "{{ create_item_label }}" , "groupNames" : []},
"ID" : "{{ create_item_id }}" ,
"label" : "{{ create_item_label }}" ,
"location" : "{{ create_item_location }}"
}
That’s it. After a few seconds the log will show that the bridge comes online, and you can create Items.
There is a long list of items which can be created from here:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
Number: Temperature homeCurrentTemperature "Current temperature [%.1f %unit%]" < temperature> { channel= "openweathermap:weather-and-forecast:home:current#temperature" }
Number: Temperature homef03Temperature "Temperature in 3h [%.1f %unit%]" < temperature> { channel= "openweathermap:weather-and-forecast:home:forecastHours03#temperature" }
Number: Temperature homef06Temperature "Temperature in 6h [%.1f %unit%]" < temperature> { channel= "openweathermap:weather-and-forecast:home:forecastHours06#temperature" }
Number: Temperature homef09Temperature "Temperature in 9h [%.1f %unit%]" < temperature> { channel= "openweathermap:weather-and-forecast:home:forecastHours09#temperature" }
Number: Temperature homef12Temperature "Temperature in 12h [%.1f %unit%]" < temperature> { channel= "openweathermap:weather-and-forecast:home:forecastHours12#temperature" }
Number: Temperature homef15Temperature "Temperature in 15h [%.1f %unit%]" < temperature> { channel= "openweathermap:weather-and-forecast:home:forecastHours15#temperature" }
Number: Temperature homef18Temperature "Temperature in 18h [%.1f %unit%]" < temperature> { channel= "openweathermap:weather-and-forecast:home:forecastHours18#temperature" }
Number: Temperature homef21Temperature "Temperature in 21h [%.1f %unit%]" < temperature> { channel= "openweathermap:weather-and-forecast:home:forecastHours21#temperature" }
Number: Temperature homef24Temperature "Temperature in 24h [%.1f %unit%]" < temperature> { channel= "openweathermap:weather-and-forecast:home:forecastHours24#temperature" }
Image homeCurrentIcon "Icon" { channel= "openweathermap:weather-and-forecast:home:current#icon" }
Image homef03Icon "Icon" { channel= "openweathermap:weather-and-forecast:home:forecastHours03#icon" }
Image homef06Icon "Icon" { channel= "openweathermap:weather-and-forecast:home:forecastHours06#icon" }
Image homef09Icon "Icon" { channel= "openweathermap:weather-and-forecast:home:forecastHours09#icon" }
Image homef12Icon "Icon" { channel= "openweathermap:weather-and-forecast:home:forecastHours12#icon" }
Image homef15Icon "Icon" { channel= "openweathermap:weather-and-forecast:home:forecastHours15#icon" }
Image homef18Icon "Icon" { channel= "openweathermap:weather-and-forecast:home:forecastHours18#icon" }
Image homef21Icon "Icon" { channel= "openweathermap:weather-and-forecast:home:forecastHours21#icon" }
Image homef24Icon "Icon" { channel= "openweathermap:weather-and-forecast:home:forecastHours24#icon" }
Number: Dimensionless homeCurrentHumidity "Current humidity [%d %unit%]" < humidity> { channel= "openweathermap:weather-and-forecast:home:current#humidity" }
Number: Dimensionless homef03Humidity "Humidity in 3h [%d %unit%]" < humidity> { channel= "openweathermap:weather-and-forecast:home:forecastHours03#humidity" }
Number: Dimensionless homef06Humidity "Humidity in 6h [%d %unit%]" < humidity> { channel= "openweathermap:weather-and-forecast:home:forecastHours06#humidity" }
Number: Dimensionless homef09Humidity "Humidity in 9h [%d %unit%]" < humidity> { channel= "openweathermap:weather-and-forecast:home:forecastHours09#humidity" }
Number: Dimensionless homef12Humidity "Humidity in 12h [%d %unit%]" < humidity> { channel= "openweathermap:weather-and-forecast:home:forecastHours12#humidity" }
Number: Dimensionless homef15Humidity "Humidity in 15h [%d %unit%]" < humidity> { channel= "openweathermap:weather-and-forecast:home:forecastHours15#humidity" }
Number: Dimensionless homef18Humidity "Humidity in 18h [%d %unit%]" < humidity> { channel= "openweathermap:weather-and-forecast:home:forecastHours18#humidity" }
Number: Dimensionless homef21Humidity "Humidity in 21h [%d %unit%]" < humidity> { channel= "openweathermap:weather-and-forecast:home:forecastHours21#humidity" }
Number: Dimensionless homef24Humidity "Humidity in 24h [%d %unit%]" < humidity> { channel= "openweathermap:weather-and-forecast:home:forecastHours24#humidity" }
Number: Dimensionless homeCurrentCloudiness "Current cloudiness [%d %unit%]" < clouds> { channel= "openweathermap:weather-and-forecast:home:current#cloudiness" }
Number: Dimensionless homef03Cloudiness "Cloudiness in 3h [%d %unit%]" < clouds> { channel= "openweathermap:weather-and-forecast:home:forecastHours03#cloudiness" }
Number: Dimensionless homef06Cloudiness "Cloudiness in 6h [%d %unit%]" < clouds> { channel= "openweathermap:weather-and-forecast:home:forecastHours06#cloudiness" }
Number: Dimensionless homef09Cloudiness "Cloudiness in 9h [%d %unit%]" < clouds> { channel= "openweathermap:weather-and-forecast:home:forecastHours09#cloudiness" }
Number: Dimensionless homef12Cloudiness "Cloudiness in 12h [%d %unit%]" < clouds> { channel= "openweathermap:weather-and-forecast:home:forecastHours12#cloudiness" }
Number: Dimensionless homef15Cloudiness "Cloudiness in 15h [%d %unit%]" < clouds> { channel= "openweathermap:weather-and-forecast:home:forecastHours15#cloudiness" }
Number: Dimensionless homef18Cloudiness "Cloudiness in 18h [%d %unit%]" < clouds> { channel= "openweathermap:weather-and-forecast:home:forecastHours18#cloudiness" }
Number: Dimensionless homef21Cloudiness "Cloudiness in 21h [%d %unit%]" < clouds> { channel= "openweathermap:weather-and-forecast:home:forecastHours21#cloudiness" }
Number: Dimensionless homef24Cloudiness "Cloudiness in 24h [%d %unit%]" < clouds> { channel= "openweathermap:weather-and-forecast:home:forecastHours24#cloudiness" }
DateTime homeCurrentTS "Timestamp Current [%1$tY-%1$tm-%1$tdT%1$tH:%1$tM:%1$tS]" < time> { channel= "openweathermap:weather-and-forecast:home:current#time-stamp" }
DateTime homef03TS "Timestamp 3h [%1$tY-%1$tm-%1$tdT%1$tH:%1$tM:%1$tS]" < time> { channel= "openweathermap:weather-and-forecast:home:forecastHours03#time-stamp" }
DateTime homef06TS "Timestamp 6h [%1$tY-%1$tm-%1$tdT%1$tH:%1$tM:%1$tS]" < time> { channel= "openweathermap:weather-and-forecast:home:forecastHours06#time-stamp" }
DateTime homef09TS "Timestamp 9h [%1$tY-%1$tm-%1$tdT%1$tH:%1$tM:%1$tS]" < time> { channel= "openweathermap:weather-and-forecast:home:forecastHours09#time-stamp" }
DateTime homef12TS "Timestamp 12h [%1$tY-%1$tm-%1$tdT%1$tH:%1$tM:%1$tS]" < time> { channel= "openweathermap:weather-and-forecast:home:forecastHours12#time-stamp" }
DateTime homef15TS "Timestamp 15h [%1$tY-%1$tm-%1$tdT%1$tH:%1$tM:%1$tS]" < time> { channel= "openweathermap:weather-and-forecast:home:forecastHours15#time-stamp" }
DateTime homef18TS "Timestamp 18h [%1$tY-%1$tm-%1$tdT%1$tH:%1$tM:%1$tS]" < time> { channel= "openweathermap:weather-and-forecast:home:forecastHours18#time-stamp" }
DateTime homef21TS "Timestamp 21h [%1$tY-%1$tm-%1$tdT%1$tH:%1$tM:%1$tS]" < time> { channel= "openweathermap:weather-and-forecast:home:forecastHours21#time-stamp" }
DateTime homef24TS "Timestamp 24h [%1$tY-%1$tm-%1$tdT%1$tH:%1$tM:%1$tS]" < time> { channel= "openweathermap:weather-and-forecast:home:forecastHours24#time-stamp" }
String homeCurrentCondition "Current condition [%s]" < sun_clouds> { channel= "openweathermap:weather-and-forecast:home:current#condition" }
String homef03Condition "Condition in 3h [%s]" < sun_clouds> { channel= "openweathermap:weather-and-forecast:home:forecastHours03#condition" }
String homef06Condition "Condition in 6h [%s]" < sun_clouds> { channel= "openweathermap:weather-and-forecast:home:forecastHours06#condition" }
String homef09Condition "Condition in 9h [%s]" < sun_clouds> { channel= "openweathermap:weather-and-forecast:home:forecastHours09#condition" }
String homef12Condition "Condition in 12h [%s]" < sun_clouds> { channel= "openweathermap:weather-and-forecast:home:forecastHours12#condition" }
String homef15Condition "Condition in 15h [%s]" < sun_clouds> { channel= "openweathermap:weather-and-forecast:home:forecastHours15#condition" }
String homef18Condition "Condition in 18h [%s]" < sun_clouds> { channel= "openweathermap:weather-and-forecast:home:forecastHours18#condition" }
String homef21Condition "Condition in 21h [%s]" < sun_clouds> { channel= "openweathermap:weather-and-forecast:home:forecastHours21#condition" }
String homef24Condition "Condition in 24h [%s]" < sun_clouds> { channel= "openweathermap:weather-and-forecast:home:forecastHours24#condition" }
Number: Speed homeCurrentWindSpeed "Current wind speed [%.1f km/h]" < wind> { channel= "openweathermap:weather-and-forecast:home:current#wind-speed" }
Number: Speed homef03WindSpeed "Wind speed in 3h [%.1f km/h]" < wind> { channel= "openweathermap:weather-and-forecast:home:forecastHours03#wind-speed" }
Number: Speed homef06WindSpeed "Wind speed in 6h [%.1f km/h]" < wind> { channel= "openweathermap:weather-and-forecast:home:forecastHours06#wind-speed" }
Number: Speed homef09WindSpeed "Wind speed in 9h [%.1f km/h]" < wind> { channel= "openweathermap:weather-and-forecast:home:forecastHours09#wind-speed" }
Number: Speed homef12WindSpeed "Wind speed in 12h [%.1f km/h]" < wind> { channel= "openweathermap:weather-and-forecast:home:forecastHours12#wind-speed" }
Number: Speed homef15WindSpeed "Wind speed in 15h [%.1f km/h]" < wind> { channel= "openweathermap:weather-and-forecast:home:forecastHours15#wind-speed" }
Number: Speed homef18WindSpeed "Wind speed in 18h [%.1f km/h]" < wind> { channel= "openweathermap:weather-and-forecast:home:forecastHours18#wind-speed" }
Number: Speed homef21WindSpeed "Wind speed in 21h [%.1f km/h]" < wind> { channel= "openweathermap:weather-and-forecast:home:forecastHours21#wind-speed" }
Number: Speed homef24WindSpeed "Wind speed in 24h [%.1f km/h]" < wind> { channel= "openweathermap:weather-and-forecast:home:forecastHours24#wind-speed" }
Number: Length homeCurrentRainVolume "Current rain volume [%.1f %unit%]" < rain> { channel= "openweathermap:weather-and-forecast:home:current#rain" }
Number: Length homef03RainVolume "Rain volume in 3h [%.1f %unit%]" < rain> { channel= "openweathermap:weather-and-forecast:home:forecastHours03#rain" }
Number: Length homef06RainVolume "Rain volume in 6h [%.1f %unit%]" < rain> { channel= "openweathermap:weather-and-forecast:home:forecastHours06#rain" }
Number: Length homef09RainVolume "Rain volume in 9h [%.1f %unit%]" < rain> { channel= "openweathermap:weather-and-forecast:home:forecastHours09#rain" }
Number: Length homef12RainVolume "Rain volume in 12h [%.1f %unit%]" < rain> { channel= "openweathermap:weather-and-forecast:home:forecastHours12#rain" }
Number: Length homef15RainVolume "Rain volume in 15h [%.1f %unit%]" < rain> { channel= "openweathermap:weather-and-forecast:home:forecastHours15#rain" }
Number: Length homef18RainVolume "Rain volume in 18h [%.1f %unit%]" < rain> { channel= "openweathermap:weather-and-forecast:home:forecastHours18#rain" }
Number: Length homef21RainVolume "Rain volume in 21h [%.1f %unit%]" < rain> { channel= "openweathermap:weather-and-forecast:home:forecastHours21#rain" }
Number: Length homef24RainVolume "Rain volume in 24h [%.1f %unit%]" < rain> { channel= "openweathermap:weather-and-forecast:home:forecastHours24#rain" }
Number: Length homeCurrentSnowVolume "Current snow volume [%.1f %unit%]" < snow> { channel= "openweathermap:weather-and-forecast:home:current#snow" }
Number: Length homef03SnowVolume "Snow volume in 3h [%.1f %unit%]" < snow> { channel= "openweathermap:weather-and-forecast:home:forecastHours03#snow" }
Number: Length homef06SnowVolume "Snow volume in 6h [%.1f %unit%]" < snow> { channel= "openweathermap:weather-and-forecast:home:forecastHours06#snow" }
Number: Length homef09SnowVolume "Snow volume in 9h [%.1f %unit%]" < snow> { channel= "openweathermap:weather-and-forecast:home:forecastHours09#snow" }
Number: Length homef012SnowVolume "Snow volume in 12h [%.1f %unit%]" < snow> { channel= "openweathermap:weather-and-forecast:home:forecastHours12#snow" }
Number: Length homef15SnowVolume "Snow volume in 15h [%.1f %unit%]" < snow> { channel= "openweathermap:weather-and-forecast:home:forecastHours15#snow" }
Number: Length homef18SnowVolume "Snow volume in 18h [%.1f %unit%]" < snow> { channel= "openweathermap:weather-and-forecast:home:forecastHours18#snow" }
Number: Length homef21SnowVolume "Snow volume in 21h [%.1f %unit%]" < snow> { channel= "openweathermap:weather-and-forecast:home:forecastHours21#snow" }
Number: Length homef24SnowVolume "Snow volume in 24h [%.1f %unit%]" < snow> { channel= "openweathermap:weather-and-forecast:home:forecastHours24#snow" }