Home Assistant Frost Alert
Dieses mal machen wir eine kleine Automation mit HomeAssistant und Open Weather Map. Ich möchte mich benachrichtigen lassen, wenn es am nächsten Morgen zwischen 5 und 8 Uhr unter zwei Grad Celsius hat. Warum? Damit ich nicht kratzen muss.
Und zwar habe ich eine Art Autofahrstuhl als Garage. So ähnlich wie einen Duplex-Parker, aber eben so, dass das ganze Gestell mit zwei Autos nach Oben ausgefahren wird und ich habe den untersten Stellplatz. Das dauert sehr lange. Also parke ich einfach an der Straße wenn es nicht gerade so kalt ist, dass ich die Scheiben Kratzen muss.
Alternativ kann man natürlich auch seinen Wecker entsprechend Vor-stellen, damit man trotzdem pünktlich zur Arbeit kommt. Also wenn man keine Garage hat – of course.
Vorgehen
Ich beschreibe hier nachfolgend auch, wie ich zum Ergebnis gekommen bin, damit ihr alles nachvollziehen könnt und ggf. leichter die Transferleistung für andere Anwendungen erbringen könnt. Außerdem kann ich auch, wenn ich es vergesse, alles nochmal nachlesen.
Vorraussetzung
Ich setzte für diesen Beitrag schon etwas Grundkenntniss mit HomeAssistant voraus. Ihr müsst außerdem die Smartphone App am laufen haben.
Variante 1 – Frost Alert mit met.no
Es gibt eine Integration Namens met.no vom Meteorologischen Institut Norwegen. Dieser gibt Stündliche Temperaturen im Forecast durch. Wie man damit einen Frost Alert macht könnt ihr bei smartebude.de nachlesen.
Bei diesem Ansatz wird aber nur die niedrigste Temperatur der nächsten Stunden herangezogen. Falls es also am Abend kälter ist als am Morgen, ist es ein false positive. Also können wir auch eine etwas coolere Lösung basteln.
Variante 2 – OpenWeatherMap
Die integration von OpenWeatherMap hat mehrere Möglichkeiten, einen API Call zu machen. Eigentlich würde mir der Daily Forecast vollkommen reichen, für diese Anwendung brauche ich aber den Hourly Forecast. Dazu richte ich eine Zweite Instanz der Integration ein.
Dazu gehen wir unter Einstellungen zu den Integrationen und fügen mit dem Button unten rechts noch einmal OpenWeatherMap hinzu
Wir können den gleichen API Key benutzen wie zuvor (Kostenlos bei openweathermap registrieren, falls ihr noch keinen Habt und unter API einen oder mehrere Erstellen). Ggf. werden eure Koordinaten automatisch ausgefüllt. Der API request ist onecall-hourly und die Sprache habe ich persönlich auf de gestellt. Als Namen habe ich hier den Standard gelassen und später beide Instanzen einmal zu -daily und -hourly umbenannt.
Wenn wir nun unter Entwicklerwerkzeuge auf den Reiter Zustände wechseln und nach openweathermap suchen, finden wir das folgende Bild:
Uns interessiert der json string und das Zeitformat
Wir sehen, dass es in der Entität weather.openweathermap_2 (Der Name kann bei euch anders sein) den punkt forecast mit ganz viele Datensätzen gibt.
Anhand des Zeitstempels (datetime) erkennen wir, es gibt für jede Stunde einen Block – perfekt für uns.
Entwicklung des Template
Wir wollen also die Temperaturen zwischen 5 und 8 Uhr rauslesen und davon das Minimum wissen. Wenn das unter 2°C liegt, soll eine Notification ans Smartphone rausgehen. Um das umzusetzen, brauchen wir ein Template in unserer Automatisierung. Und um Templates zu entwickeln, hat HomeAssistant ein praktisches Tool.
Wir wechseln auf Entwicklerwerkzeuge – Template und löschen das Demo Template. Ich habe den obersten Kommentar stehen gelassen, weil ich den gleich ohnehin benutze.
Bei mir ist der Auslöser, wenn ich mit der App in meine Home-Zone komme. Also wenn ich im Prinzip kurz vor Zuhause bin. Das kann immer zu Unterschiedlichen Zeiten sein und natürlich nicht jeden Tag. Da wir auch jeden Tag ein neues Datum haben, brauchen wir eine gewisse Dynamik. Die Zeit soll aber immer die gleiche Sein (Hier 5 Uhr bis 8 Uhr). Für das Template brauchen wir also erstmal das Datum.
Heute und Morgen
Im Template Editor können wir also erstmal den aktuellen Zeitpunkt abfragen, denn dafür gibt es eine Funktion:
{#Aktueller Zeitpunkt#}
now: {{now()}}
Das obere ist einfach ein Kommentar, dann folgt Plain Text und eine Print-Funktion {{ … }}
Rechts sehen wir das Ergebnis in Echtzeit.
Das ist offensichtlich kein Datum – das können wir aber leicht Anpassen mit dem entsprechenen Befehl:
{#Aktueller Zeitpunkt#}
now: {{now()}}
{#Heute#}
today: {{now().date()}}
Das Datum haben wir also schon einmal. Doch wir wissen ja, das wir bei dem datetime String im openweathermap json string noch eine Zeitangabe für die Stunde brauchen. Dazu können wir einfach – und das ist jetzt echt die Stümper Methode – einen Text an den String anhängen.
{#Aktueller Zeitpunkt#}
now: {{now()}}
{#Heute#}
today: {{now().date()}}
{#Heute + String#}
today: {{now().date() | string + " test"}}
Voila, das funktioniert. Jetzt gucken wir uns einfach den korrekten String an den wir brauchen und ändern das entsprechend bei uns.
{#Aktueller Zeitpunkt#}
now: {{now()}}
{#Heute#}
today: {{now().date()}}
{#Heute + String#}
today: {{now().date() | string + "T05:00:00+00:00"}}
Damit das ganze für den morgigen Tag passt, müssen wir natürlich heute plus 1 Tag machen. Dazu gibt es auch eine Funktion:
{#Aktueller Zeitpunkt#}
now: {{now()}}
{#Heute#}
today: {{now().date()}}
{#Heute + String#}
today: {{now().date() | string + "T05:00:00+00:00"}}
{#Morgen + String#}
tomorrow: {{(now().date() + timedelta(days=1)) | string + "T05:00:00+00:00"}}
API auslesen
Um ganz sicher zu gehen, können wir nun mal den direkten vergleich machen. Dazu lesen wir den tatsächlichen String aus der OpenWeaterMap API Json aus und vergleichen den mit unserer Eingabe.
from owm json: {{ state_attr('weather.openweathermap_2', 'forecast')[14]["datetime"]}}
Der Codeschnipsel sucht im JSON String von weather.openweathermap_2 nach forecast und nimmt das attribut datetime vom Eintrag 14. -> Das entspricht 5 Uhr Morgens.
Achtung: Die Zahl 14 kann abweichen, je nachdem wie spät es bei euch ist, bzw. wann der letzte API Call zu einem Update geführt hat.
Nun vergleichen wir es mit unserem gesuchten Datum:
{#Aktueller Zeitpunkt#}
now: {{now()}}
{#Heute#}
today: {{now().date()}}
{#Heute + String#}
today: {{now().date() | string + "T05:00:00+00:00"}}
{#Morgen + String#}
tomorrow: {{(now().date() + timedelta(days=1)) | string + "T05:00:00+00:00"}}
{#Aus OWM API#}
from owm json: {{ state_attr('weather.openweathermap_2', 'forecast')[14]["datetime"]}}
test if match: {{ state_attr('weather.openweathermap_2', 'forecast')[14]["datetime"] == (now().date() + timedelta(days=1)) | string + "T05:00:00+00:00" }}
Das Scheint zu funktionieren.
Variablen
Der nächste Schritt hat einen neuen Befehl. Und zwar nutzen wir nicht mehr den Print befehl, sondern nutzen nun Variablen, damit wir nicht alles Mehrfach schreiben müssen:
start: {% set start = (now().date()+ timedelta(days=1)) | string + "T05:00:00+00:00"%}{{start}}
end: {% set end = (now().date()+ timedelta(days=1)) | string + "T08:00:00+00:00"%}{{end}}
Hier speichere ich das Anfangsdatum (5 Uhr Morgen früh) in eine Variable namens start und das Enddatum (8 Uhr morgen früh) in eine Variable namens end. Zum Überprüfen ist jeweils der printbefehl mit der Variable hinten angefügt. Soweit so gut.
Liste
Nun kommt die Liste. Wir suchen ja mehrere Datensätze. Dazu gibt es eine Funktion – select attribute:
start: {% set start = (now().date()+ timedelta(days=1)) | string + "T05:00:00+00:00"%}{{start}}
end: {% set end = (now().date()+ timedelta(days=1)) | string + "T08:00:00+00:00"%}{{end}}
Liste die Start entspricht:
{{ state_attr('weather.openweathermap_2', 'forecast') | selectattr("datetime", "eq", start) | list }}
Hier durchsuchen wir alle Elemente innerhalb forecast und vergleichen Sie mit unserer Variable start. Zum vergleichen nutzen wir den Befehl eq (equal). Die Befehle in den HomeAssistant Templates orientieren sich an der Jinja Nomenklatur. Wer sich mehr damit beschäftigen möchte, findet hier die Dokumentation.
Das funktioniert soweit auch – im Ergebnis Bereich sehen wir das gesuchte Element mit allen Attributen.
Als nächstes suchen wir alle Elemente die größer als der Start und kleiner als das Ende sind:
Liste:
{{ state_attr('weather.openweathermap_2', 'forecast') | selectattr("datetime", ">=", start) | selectattr("datetime", "<=", end) | list }}
Man sieht, die Liste wird länger.
Wir brauchen aber nur die Temperaturen, dazu können wir den map(attribute) befehl benutzen:
Temperaturen:
{{ state_attr('weather.openweathermap_2', 'forecast') | selectattr("datetime", ">=", start) | selectattr("datetime", "<=", end) | map(attribute="temperature") | list }}
Fast fertig! Jetzt müssen wir nur noch den niedrigsten Wert suchen und auch dafür gibt es eine Funktion: Die min Funktion.
temp tomorror between 5 and 8:
{{state_attr('weather.openweathermap_2', 'forecast') | selectattr("datetime", ">=", start) | selectattr("datetime", "<=", end) | map(attribute="temperature") | list | min }}
Somit haben wir unser Template entwickelt. Jetzt fehlt nur noch der Vergleich, mit der Grenztempertur:
kleiner 2?:
{{(state_attr('weather.openweathermap_2', 'forecast') | selectattr("datetime", ">=", start) | selectattr("datetime", "<=", end) | map(attribute="temperature") | list | min) < 2 }}
In meinem Fall sind es morgen früh 3,6°C und das ist größer als 2°C – also ist das Statement falsch – false.
Automation
Unter Einstellungen – Automatisierung & Szenen können wir mit dem Button unten rechts eine neue Automatisierung erstellen.
Der Auslöser ist bei mir die Zone, hier könnt ihr aber auch eine Feste Uhrzeit oder was auch immer einstellen. Unter Bedingung wähle ich Template und füge die beiden Variablen, so wie die letzte Zeile ein.
{% set start = (now().date()+ timedelta(days=1)) | string + "T05:00:00+00:00"%}
{% set end = (now().date()+ timedelta(days=1)) | string + "T08:00:00+00:00"%}
{{(state_attr('weather.openweathermap_2', 'forecast') | selectattr("datetime", ">=", start) | selectattr("datetime", "<=", end) | map(attribute="temperature") | list | min) < 2 }}
Das ganze können wir mit einem Klick auf die Menüpunkte auf der Kachel und dem Punkt Testen direkt ausprobieren. Es sollte eine Meldung erscheinen:
Was in meinem Fall korrekt ist.
Bei Aktion wähle ich noch die Notification und füge eine Entsprechende Meldung ein. (HIer im YAML Erstellt).
service: notify.mobile_app_daniel_phone
data:
message: >-
Morgen früh gibt's Frost. Park lieber in der Garage!
title: Winter is Coming!
Damit funktioniert das ganze eigentlich schon. Viel Spaß beim Automatisieren!
Cherry on Top
Wer in der Notification noch den genauen Temperaturwert und vll sogar die Uhrzeit wissen will, muss das Template nicht in der Automatisierung sondern in einem Sensor (actually in dreien) ausführen und die Werte zwischenspeichern. Dazu bauchen wir den File Editor in Home Assistant und gehen in die Config.yaml
Falls ihr noch keine Template-Sensoren habt, legen wir dazu einen neuen Block an. Ansonsten kommen die neuen Sensor einfach zu den anderen Template Sensoren. Der Code ist nun für den Sensor angepasst:
template:
sensor:
#--- ggf andere Sensoren...
- name: Frost Alert
unique_id: frost_alert
state: >
{% set start = (now().date()+ timedelta(days=1)) | string + "T04:00:00+00:00"%}
{% set end = (now().date()+ timedelta(days=1)) | string + "T08:00:00+00:00"%}
{% set temp_frost_alert = (state_attr('weather.openweathermap_2', 'forecast') | selectattr("datetime", ">=", start) | selectattr("datetime", "<=", end) | map(attribute="temperature") | list | min)%}
{% set result_frost_alert = temp_frost_alert < 4 %}
{{result_frost_alert}}
- name: Frost Temperature
unique_id: frost_temperature
state: >
{% set start = (now().date()+ timedelta(days=1)) | string + "T04:00:00+00:00"%}
{% set end = (now().date()+ timedelta(days=1)) | string + "T08:00:00+00:00"%}
{% set temp_frost_alert = (state_attr('weather.openweathermap_2', 'forecast') | selectattr("datetime", ">=", start) | selectattr("datetime", "<=", end) | map(attribute="temperature") | list | min)%}
{{temp_frost_alert}}
- name: Frost Time
unique_id: frost_time
state: >
{% set start = (now().date()+ timedelta(days=1)) | string + "T04:00:00+00:00"%}
{% set end = (now().date()+ timedelta(days=1)) | string + "T08:00:00+00:00"%}
{% set temp_frost_alert = (state_attr('weather.openweathermap_2', 'forecast') | selectattr("datetime", ">=", start) | selectattr("datetime", "<=", end) | map(attribute="temperature") | list | min)%}
{% set result_frost_alert = temp_frost_alert < 4 %}
{% set time_frost_alert = (state_attr('weather.openweathermap_2', 'forecast') | selectattr("datetime", ">=", start) | selectattr("datetime", "<=", end) | selectattr("temperature", "eq", temp_frost_alert)| map(attribute="datetime") | list | join )%}
{{as_timestamp(time_frost_alert) | timestamp_custom("%H:%M")}}
Als nächtes müssen wir Speichern und danach über Entwicklerwerkzeuge – YAML neu starten. Vergesst nicht vorher die Config zu prüfen.
Wenn alles wieder da ist können wir bei Entwicklerwerkzeuge – Zustände die neuen Sensoren suchen (frost_) und schauen, ob alles funktioniert.
In unserer Automatisierung können wir nun einfach den Zustand des Sensors frost_alert auf true oder false checken:
Für die Notification nutzen wir wieder YAML: Den Service müsst ihr natürlich für euch Anpassen.
service: notify.mobile_app_daniel_phone
data:
title: BEHOLD! Jack Frost attacks!
message: Park mal besser in der Garage, es wird Kalt! Um {{states("sensor.frost_time")}} Uhr wird es {{states("sensor.frost_temperature")}} °C
Thats it!
Update:
Ich habe die Grenztemperatur auf 4° angehoben und den Zeitraum auf 4 bis 8 verlängert. Ich musste heute morgen nämlich kratzen 🙁