Install OpenWeatherMap in openHAB, using Ansible

Posted by ads' corner on Thursday, 2019-01-24
Posted in [Ansible][Linux][Openhab]

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" }

Categories: [Ansible] [Linux] [Openhab]