Skip to content

Deutscher Wetterdienst notifications in openHAB and Telegram

The "Deutscher Wetterdienst" DWD (German Weather Service) provices a service which sends out alarms for upcoming events, like heavy rain, storm, blizzard, strong winds ect. This service is only available for Germany. And it has a binding for openHAB. Time to integrate this, activate a number of regions I'm interested in, and send notifications to our mobile phones.

At this point I'm really happy that I deploy my openHAB with Ansible, I can use the Template module and loop over the Items, Things and Rules.

The Binding can "track" multiple regions, so called Cell IDs. I'm interested in 4 different Cells (the list is available here), your mileage might vary. For each reagion there can be multiple messages - and although every single example I found only assumes there is only one message (warningCount=1), I already had a situation where a Cell had two warnings. That information likely goes missing if you set warningCount to 1. Given how much configuration overhead it is when you add more channels, I can understand why most examples stay at "1" for warningCount. Doubling the number doubles the number of Items (12 -> 24). However since I deploy everything in a template, that's not a problem here.

I started this with a generous warningCount=5 - and in my templates I just loop from 1 to 5, and over every Cell, and generate all the necessary Things and Rules for every channel.

Ok, the details:

 

Binding

First I need to install the Binding. That's easy by using the API, and Ansible. You can also do that using the Paper UI.

- name: Get list of available and installed extensions
  uri:
    url: "http://{{ ansible_host }}:8080/rest/extensions"
  register: oh2_extensions
  changed_when: false

- name: Install extensions
  uri:
    url: "http://{{ ansible_host }}:8080/rest/extensions/{{ item }}/install"
    method: POST
  when: "not (oh2_extensions.json|byattr('id', item))[0].installed"
  loop:
    - binding-dwdunwetter
  register: oh2_install_extensions

The two Tasks above will first fetch a list of all available and installed extensions from the openHAB host in $ansible_host. Then the "binding-dwdunwetter" extension is installed, if it is not yet installed. The list of extensions is longer in my case, but I broke down the list to just show this example.

Things

Next up: the Things definition. As mentioned before, I'm interested in multiple Cell IDs. Therefore in my "dwd.things" file I have an Ansible list with the entries, and loop over them when creating the Things definitions. When the file is deployed, Ansible will create the final version using the data in the list.

{% set dwds = [('Ort 1', 'ort1', '111000001'),
               ('Ort 2', 'ort2', '111000002'),
               ('Ort 3', 'ort3', '111000003'),
               ('Ort 4', 'ort4', '111000004')
              ] %}

{% for dwd in dwds %}
dwdunwetter:dwdwarnings:{{ dwd[1] }} "Warnings {{ dwd[0] }}" [ cellId="{{ dwd[2] }}", refresh=30, warningCount=5 ]
{% endfor %}

The deployed version of the file, from "/etc/openhab2/things/dwd.things":

dwdunwetter:dwdwarnings:ort1 "Warnings Ort 1" [ cellId="111000001", refresh=30, warningCount=5 ]
dwdunwetter:dwdwarnings:ort2 "Warnings Ort 2" [ cellId="111000002", refresh=30, warningCount=5 ]
dwdunwetter:dwdwarnings:Ort3 "Warnings Ort 3" [ cellId="111000003", refresh=30, warningCount=5 ]
dwdunwetter:dwdwarnings:ort4 "Warnings Ort 4" [ cellId="111000004", refresh=30, warningCount=5 ]

Items

Now that I have 4 different Things for the 4 locations, I need to create the Items for all of them. Each Thing provides 12 channels - per message. I have 5 of the messages. That makes 4 * 12 * 5 = 240 Items to declare. But in reality it's just two loops and 12 Item lines, everything else is done by Ansible when the Template is deployed:

{% for dwd in dwds %}
{% for chan in range(1,6) %}

// DWD: {{ dwd[0] }}
// Channel: {{ chan }}
Switch DWDWarning{{ dwd[1] }}{{ chan }} "Weather warning" { channel="dwdunwetter:dwdwarnings:{{ dwd[1] }}:warning{{ chan }}" }
String Warning{{ dwd[1] }}Serverity{{ chan }} "Severity[%s]" { channel="dwdunwetter:dwdwarnings:{{ dwd[1] }}:severity{{ chan }}" }
String Warning{{ dwd[1] }}Beschreibung{{ chan }} "[%s]" { channel="dwdunwetter:dwdwarnings:{{ dwd[1] }}:description{{ chan }}" }
DateTime Warning{{ dwd[1] }}Ausgabedatum{{ chan }} "Issued at [%s]" { channel="dwdunwetter:dwdwarnings:{{ dwd[1] }}:effective{{ chan }}" }
DateTime Warning{{ dwd[1] }}GueltigAb{{ chan }} "Valid from [%s]" { channel="dwdunwetter:dwdwarnings:{{ dwd[1] }}:onset{{ chan }}" }
DateTime Warning{{ dwd[1] }}GueltigBis{{ chan }} "Valid to [%s]" { channel="dwdunwetter:dwdwarnings:{{ dwd[1] }}:expires{{ chan }}" }
String Warning{{ dwd[1] }}Typ{{ chan }} "Type [%s]" { channel="dwdunwetter:dwdwarnings:{{ dwd[1] }}:event{{ chan }}" }
String Warning{{ dwd[1] }}Titel{{ chan }} "[%s]" { channel="dwdunwetter:dwdwarnings:{{ dwd[1] }}:headline{{ chan }}" }
Number:Length Warning{{ dwd[1] }}HoeheAb{{ chan }} "Height from [%d m]" { channel="dwdunwetter:dwdwarnings:{{ dwd[1] }}:altitude{{ chan }}" }
Number:Length Warning{{ dwd[1] }}HoeheBis{{ chan }} "Height to [%d m]" { channel="dwdunwetter:dwdwarnings:{{ dwd[1] }}:ceiling{{ chan }}" }
String Warning{{ dwd[1] }}Urgency{{ chan }} "[%s]" { channel="dwdunwetter:dwdwarnings:{{ dwd[1] }}:urgency{{ chan }}" }
String Warning{{ dwd[1] }}Instruction{{ chan }} "Additional information: [%s]" { channel="dwdunwetter:dwdwarnings:{{ dwd[1] }}:instruction{{ chan }}" }
// end channel
{% endfor %}

// end: {{ dwd[0] }}
{% endfor %}

The $dwd loop runs over the 4 entries in $dwds, which defines the Cells. The $chan loop runs over the channels 1-5 (the range() function will not reach the final 6). The deployed file in "/etc/openhab2/items/dwd.items":

// DWD: Ort 1
// Channel: 1
Switch DWDWarningort11 "Weather warning" { channel="dwdunwetter:dwdwarnings:ort1:warning1" }
String Warningort1Serverity1 "Severity[%s]" { channel="dwdunwetter:dwdwarnings:ort1:severity1" }
String Warningort1Beschreibung1 "[%s]" { channel="dwdunwetter:dwdwarnings:ort1:description1" }
DateTime Warningort1Ausgabedatum1 "Issued at [%s]" { channel="dwdunwetter:dwdwarnings:ort1:effective1" }
DateTime Warningort1GueltigAb1 "Valid from [%s]" { channel="dwdunwetter:dwdwarnings:ort1:onset1" }
DateTime Warningort1GueltigBis1 "Valid to [%s]" { channel="dwdunwetter:dwdwarnings:ort1:expires1" }
String Warningort1Typ1 "Type [%s]" { channel="dwdunwetter:dwdwarnings:ort1:event1" }
String Warningort1Titel1 "[%s]" { channel="dwdunwetter:dwdwarnings:ort1:headline1" }
Number:Length Warningort1HoeheAb1 "Height from [%d m]" { channel="dwdunwetter:dwdwarnings:ort1:altitude1" }
Number:Length Warningort1HoeheBis1 "Height to [%d m]" { channel="dwdunwetter:dwdwarnings:ort1:ceiling1" }
String Warningort1Urgency1 "[%s]" { channel="dwdunwetter:dwdwarnings:ort1:urgency1" }
String Warningort1Instruction1 "Additional information: [%s]" { channel="dwdunwetter:dwdwarnings:ort1:instruction1" }
// end channel

// DWD: Ort 1
// Channel: 2
Switch DWDWarningort12 "Weather warning" { channel="dwdunwetter:dwdwarnings:ort1:warning2" }
String Warningort1Serverity2 "Severity[%s]" { channel="dwdunwetter:dwdwarnings:ort1:severity2" }
String Warningort1Beschreibung2 "[%s]" { channel="dwdunwetter:dwdwarnings:ort1:description2" }
DateTime Warningort1Ausgabedatum2 "Issued at [%s]" { channel="dwdunwetter:dwdwarnings:ort1:effective2" }
DateTime Warningort1GueltigAb2 "Valid from [%s]" { channel="dwdunwetter:dwdwarnings:ort1:onset2" }
DateTime Warningort1GueltigBis2 "Valid to [%s]" { channel="dwdunwetter:dwdwarnings:ort1:expires2" }
String Warningort1Typ2 "Type [%s]" { channel="dwdunwetter:dwdwarnings:ort1:event2" }
String Warningort1Titel2 "[%s]" { channel="dwdunwetter:dwdwarnings:ort1:headline2" }
Number:Length Warningort1HoeheAb2 "Height from [%d m]" { channel="dwdunwetter:dwdwarnings:ort1:altitude2" }
Number:Length Warningort1HoeheBis2 "Height to [%d m]" { channel="dwdunwetter:dwdwarnings:ort1:ceiling2" }
String Warningort1Urgency2 "[%s]" { channel="dwdunwetter:dwdwarnings:ort1:urgency2" }
String Warningort1Instruction2 "Additional information: [%s]" { channel="dwdunwetter:dwdwarnings:ort1:instruction2" }
// end channel

These two, and 18 more, for every Cell and channel. And if a Cell changes, or I add a new one, or if we move to another location, or I decide to have more messages: I only need to change one setting, and re-deploy everything. This kind of native functionality I'm really missing in openHAB.

Rules

Everything is online, what about the alarms when a notification arrives. The Binding provides a Switch named "warning", which goes ON when there is a warning. However this switch stays ON when one warning is replaced with another one - which is not uncommon when a warning is updated. Not useful for notifications. The Binding also provides a trigger channel "Update", which is fired every time there is an update. The rule for a Trigger:

rule "New DWD Warnung"
when
     Channel 'dwdunwetter:dwdwarnings:<Cell ID>:updated<Channel Number>' triggered NEW
then
    // New Warning send a push notification to everyone
end

Except I need 20 of these rules. There might be a way to handle this using Groups and just one Rule in openHAB, but since I already use Ansible Templates to create the Things and Items, I just created Rules the same way:

rule "New DWD Warnung ({{ dwd[0] }} - {{ chan }})"
when
     Channel 'dwdunwetter:dwdwarnings:{{ dwd[1] }}:updated{{ chan }}' triggered NEW
then
    // New Warning send a push notification to everyone
    var String meldung_ausgabedatum = "" + Warning{{ dwd[1] }}Ausgabedatum{{ chan }}.state.format("%1$td.%1$tm.%1$tY %1$tH:%1$tM")
    var String meldung_gueltigab = "" + Warning{{ dwd[1] }}GueltigAb{{ chan }}.state.format("%1$td.%1$tm.%1$tY %1$tH:%1$tM")
    var String meldung_gueltigbis = "" + Warning{{ dwd[1] }}GueltigBis{{ chan }}.state.format("%1$td.%1$tm.%1$tY %1$tH:%1$tM")
    var meldung = "Warnung für {{ dwd[0] }}\n\n"
    meldung = meldung + "Ausgegeben:\n`" + meldung_ausgabedatum + "`\n"
    meldung = meldung + "Ab: `" + meldung_gueltigab + "`\n"
    meldung = meldung + "Bis: `" + meldung_gueltigbis + "`\n"
    meldung = meldung + "\n" + Warning{{ dwd[1] }}Titel{{ chan }}.state.toString + "\n\n"
    meldung = meldung + Warning{{ dwd[1] }}Instruction{{ chan }}.state.toString
    logInfo("DWD", meldung)

    val telegramAction = getActions("telegram","telegram:telegramBot:HA_Bot")
    telegramAction.sendTelegram(Long::parseLong(TELEGRAM_CHANNEL_HA), "%s", meldung)
end

This creates a Rule for every Cell and Channel, and deploys everything into "/etc/openhab2/rules/dwd.rules". Excerpt from the final version of the file:

rule "New DWD Warnung (Ort 1 - 1)"
when
     Channel 'dwdunwetter:dwdwarnings:ort1:updated1' triggered NEW
then
    // New Warning send a push notification to everyone
    var String meldung_ausgabedatum = "" + Warningort1Ausgabedatum1.state.format("%1$td.%1$tm.%1$tY %1$tH:%1$tM")
    var String meldung_gueltigab = "" + Warningort1GueltigAb1.state.format("%1$td.%1$tm.%1$tY %1$tH:%1$tM")
    var String meldung_gueltigbis = "" + Warningort1GueltigBis1.state.format("%1$td.%1$tm.%1$tY %1$tH:%1$tM")
    var meldung = "Warnung für Ort 1\n\n"
    meldung = meldung + "Ausgegeben:\n`" + meldung_ausgabedatum + "`\n"
    meldung = meldung + "Ab: `" + meldung_gueltigab + "`\n"
    meldung = meldung + "Bis: `" + meldung_gueltigbis + "`\n"
    meldung = meldung + "\n" + Warningort1Titel1.state.toString + "\n\n"
    meldung = meldung + Warningort1Instruction1.state.toString
    logInfo("DWD", meldung)

    val telegramAction = getActions("telegram","telegram:telegramBot:HA_Bot")
    telegramAction.sendTelegram(Long::parseLong(TELEGRAM_CHANNEL_HA), "%s", meldung)
end

This creates a nicely formatted message for Telegram (instructions for a Telegram bot are here), and sends it out to our group (stored in TELEGRAM_CHANNEL_HA) where we get the notification.

Example:

Trackbacks

No Trackbacks

Comments

Display comments as Linear | Threaded

No comments

Add Comment

Enclosing asterisks marks text as bold (*word*), underscore are made via _word_.
E-Mail addresses will not be displayed and will only be used for E-Mail notifications.
To leave a comment you must approve it via e-mail, which will be sent to your address after submission.
Form options