Raumklima-Alarme mit Nachprüfung und Entwarnung in Home Assistant
Zwölf Räume mit Temperatur- und Feuchtesensoren, vier Arten von Alarm (zu feucht, zu trocken, zu kalt, zu warm) und mehrere Handys als Empfänger: Das ist schnell eine Flut aus Meldungen, wenn man die Sache naiv baut. Ich habe die Aufgabe auf drei Automationen verteilt. Eine meldet, eine gibt Entwarnung, und eine dritte prüft regelmäßig nach, was die ersten beiden verpasst haben könnten.
Was du brauchst
- Temperatur- und Feuchtesensoren in den Räumen (bei mir Wandthermostate, Zigbee o. ä.)
- Pro Meldekategorie einen
input_numberfür Alarmschwelle und einen für Entwarnungsschwelle, dazu Haltezeiten - Pro Kategorie zwei
input_text-Helfer: einen für bereits gemeldete Räume, einen für Kandidaten der Nachprüfung - Einen
input_booleanzum Abschalten aller Alarme und einen Sensor für die Nachtruhe (binary_sensor.raumklima_nachtruhe) - Optional für den Lüftungstipp: die Integration
thermal_comfortfür die absolute Luftfeuchte innen und außen sowie eineninput_numberfür den Luftwechsel pro Raum
So funktioniert es
Alle Schwellen und Haltezeiten stehen in Helfern, nicht in der Automation. Bei mir gelten 70 % als zu feucht (Entwarnung bei 65 %), 25 % als zu trocken (Entwarnung bei 30 %), 17 °C als zu kalt (Entwarnung bei 18 °C) und 26 °C als zu warm (Entwarnung bei 25 °C). Die Entwarnungsschwellen liegen bewusst innerhalb der Alarmschwellen. Mit dieser Hysterese flattert die Meldung nicht in Gegenrichtung. Die Haltezeit liegt bei 60 Minuten für Feuchte und 30 Sekunden für Temperatur, die Entwarnung wartet 30 Minuten.
Die Alarm-Automation hat vier Trigger, einen pro Kategorie, jeweils über alle Sensoren zugleich. Aus der auslösenden Entität errechnet sie einen Kurzcode für den Raum (kue, wz, beg). Bereits gemeldete Räume stehen als kommaseparierte Liste in einem input_text je Kategorie. Steht der Code dort, passiert nichts. Das ist der Wiederholungsschutz. Die Entwarnung entfernt den Code wieder, wenn der Wert die Entwarnungsschwelle erreicht hat. Die Nachtruhe unterdrückt alles außer Kälte. Beim Bad im Erdgeschoss gibt es noch eine Ausnahme: Läuft nach dem Duschen die Lüftungsfrist mit offenem Fenster, bleibt die Feuchtemeldung stumm.
Eine Bedingung verwirft Flanken aus unavailable und unknown. Dort zeigt sich der Nachteil flankengesteuerter Alarme: Fällt ein Sensor während der Haltezeit kurz aus, geht die Flanke verloren. Genau dafür gibt es die Nachprüfung. Sie läuft alle 30 Minuten, fünf Minuten nach jedem Start und am Ende der Nachtruhe und prüft alle Räume direkt gegen die Schwellen. Ein Raum wird erst gemeldet, wenn er bei zwei aufeinanderfolgenden Durchläufen auffällig war. Das ersetzt dort die Haltezeit. In der Nachtruhe sendet sie nichts, merkt sich den Zustand aber und holt die Meldung nach Ende der Ruhezeit nach, sofern er dann noch besteht.
Bei „zu feucht“ hängt an der Meldung ein Lüftungstipp: Er rechnet aus der absoluten Feuchte im Raum und im Garten, der Raumtemperatur und einem Luftwechsel pro Raum (Vorgabe 2,0 pro Stunde), wie lange das Fenster offen sein müsste. Ziel ist die Entwarnungsschwelle, wenn Lüften sie erreichen kann, sonst der halbe Weg zum erreichbaren Wert. Bringt Lüften weniger als fünf Prozentpunkte, entfällt der Zusatz ganz: Dann ist es draußen genauso feucht, und der Rat wäre falsch. Bei „zu trocken“ wäre Lüften der falsche Rat, deshalb gibt es dort keinen Tipp.
Die Meldung selbst nennt die Richtung im Klartext (zu feucht, zu trocken, zu kalt, zu warm) und den Messwert, nicht die Schwelle. Der Schwellwert neben dem Messwert war nur Rauschen. Die Warnung „zu warm“ geht nur an ein Handy, die anderen Arten an alle Empfänger.
Die Automation
Im Beispiel stehen drei Räume und zwei Empfänger. Für jeden weiteren Raum ergänzt du Sensor, Kurzcode, Name und die Zuordnungen in den Variablen. Die Rechnung des Lüftungstipps steht nur in der Alarm-Automation vollständig, die Nachprüfung verwendet dieselbe.
alias: "Raumklima: Alarme"
description: >-
Meldet zu hohe/niedrige Luftfeuchte und zu kalte/warme Räume. Bereits gemeldete
Räume stehen als Kurzcode in input_text.raumklima_gemeldet_* (Wiederholungsschutz),
die Entwarnung entfernt sie wieder. Nachtruhe unterdrückt alles außer Kälte.
triggers:
- trigger: numeric_state
id: feuchte_hoch
entity_id: [sensor.kueche_luftfeuchte, sensor.wohnzimmer_luftfeuchte, sensor.bad_eg_luftfeuchte]
above: input_number.raumklima_schwelle_feuchte_hoch
for: "{{ '%02d:%02d:00' % ((states('input_number.raumklima_haltezeit_feuchte') | int(60)) // 60, (states('input_number.raumklima_haltezeit_feuchte') | int(60)) % 60) }}"
- trigger: numeric_state
id: feuchte_tief
entity_id: [sensor.kueche_luftfeuchte, sensor.wohnzimmer_luftfeuchte, sensor.bad_eg_luftfeuchte]
below: input_number.raumklima_schwelle_feuchte_tief
for: "{{ '%02d:%02d:00' % ((states('input_number.raumklima_haltezeit_feuchte') | int(60)) // 60, (states('input_number.raumklima_haltezeit_feuchte') | int(60)) % 60) }}"
- trigger: numeric_state
id: kalt
entity_id: [sensor.kueche_temperatur, sensor.wohnzimmer_temperatur, sensor.bad_eg_temperatur]
below: input_number.raumklima_schwelle_temperatur_kalt
for: {seconds: "{{ states('input_number.raumklima_haltezeit_temperatur') | int(30) }}"}
- trigger: numeric_state
id: warm
entity_id: [sensor.kueche_temperatur, sensor.wohnzimmer_temperatur, sensor.bad_eg_temperatur]
above: input_number.raumklima_schwelle_temperatur_warm
for: {seconds: "{{ states('input_number.raumklima_haltezeit_temperatur') | int(30) }}"}
conditions:
- {condition: state, entity_id: input_boolean.raumklima_alarme_aus, state: "off"}
- alias: Nur echte Flanken – nicht aus unavailable/unknown heraus
condition: template
value_template: >-
{{ trigger is defined and trigger.platform == 'numeric_state'
and trigger.from_state is not none and is_number(trigger.from_state.state) }}
actions:
- variables:
codes: "{{ states(liste_entity).split(',') | reject('eq', '') | list }}"
- alias: Raum bekannt und noch nicht gemeldet
condition: template
value_template: "{{ code != '' and code not in codes }}"
- alias: Nachtruhe – nur Kälte darf durch
condition: or
conditions:
- {condition: trigger, id: kalt}
- {condition: state, entity_id: binary_sensor.raumklima_nachtruhe, state: "off"}
- alias: "Bad EG: nach dem Duschen bei offenem Fenster keine Feuchtemeldung"
condition: or
conditions:
- condition: not
conditions: [{condition: trigger, id: feuchte_hoch}]
- {condition: template, value_template: "{{ code != 'beg' }}"}
- {condition: state, entity_id: input_boolean.raumklima_bad_eg_dusch_sperre, state: "off"}
- action: input_text.set_value
target: {entity_id: "{{ liste_entity }}"}
data: {value: "{{ (codes + [code]) | join(',') }}"}
- repeat:
for_each: "{{ ['notify.mobile_app_handy_1', 'notify.mobile_app_handy_2'] if tid != 'warm' else ['notify.mobile_app_handy_1'] }}"
sequence:
- action: "{{ repeat.item }}"
continue_on_error: true
data:
title: "{{ 'Luftfeuchtigkeitsalarm' if tid in ['feuchte_hoch', 'feuchte_tief'] else 'Temperaturalarm' }}"
message: "{{ raum }} ist bei {{ wert }} {{ einheit }} – {{ lage_map.get(tid, '') }}.{{ lueftungstipp }}"
mode: queued
max: 15
max_exceeded: silent
variables:
namen: {kue: Küche, wz: Wohnzimmer, beg: Bad EG}
code_von_sensor:
sensor.kueche_luftfeuchte: kue
sensor.wohnzimmer_luftfeuchte: wz
sensor.bad_eg_luftfeuchte: beg
sensor.kueche_temperatur: kue
sensor.wohnzimmer_temperatur: wz
sensor.bad_eg_temperatur: beg
liste_map:
feuchte_hoch: input_text.raumklima_gemeldet_feuchte_hoch
feuchte_tief: input_text.raumklima_gemeldet_feuchte_tief
kalt: input_text.raumklima_gemeldet_kalt
warm: input_text.raumklima_gemeldet_warm
einheit_map: {feuchte_hoch: "%", feuchte_tief: "%", kalt: "°C", warm: "°C"}
tid: "{{ trigger.id if trigger is defined and trigger.id is defined else '' }}"
sensor_id: "{{ trigger.entity_id if trigger is defined and trigger.entity_id is defined else '' }}"
code: "{{ code_von_sensor.get(sensor_id, '') }}"
raum: "{{ namen.get(code, sensor_id) }}"
wert: "{{ trigger.to_state.state if trigger is defined and trigger.to_state is defined else '' }}"
liste_entity: "{{ liste_map.get(tid, '') }}"
einheit: "{{ einheit_map.get(tid, '') }}"
lage_map: {feuchte_hoch: zu feucht, feuchte_tief: zu trocken, kalt: zu kalt, warm: zu warm}
abs_von_code:
kue: sensor.thermal_comfort_kueche_absolute_luftfeuchtigkeit
wz: sensor.thermal_comfort_wohnzimmer_absolute_luftfeuchtigkeit
beg: sensor.thermal_comfort_bad_eg_absolute_luftfeuchtigkeit
temp_von_code:
kue: sensor.kueche_temperatur
wz: sensor.wohnzimmer_temperatur
beg: sensor.bad_eg_temperatur
lw_von_code:
kue: input_number.raumklima_luftwechsel_kueche
wz: input_number.raumklima_luftwechsel_wohnzimmer
beg: input_number.luftentfeuchter_luftwechsel
lueftungstipp: >-
{% set ai = states(abs_von_code.get(code, 'unknown')) | float(0) %}
{% set ao = states('sensor.thermal_comfort_garten_absolute_luftfeuchtigkeit') | float(0) %}
{% set t = states(temp_von_code.get(code, 'unknown')) | float(-100) %}
{% set n = states(lw_von_code.get(code, 'unknown')) | float(0) %}
{% set zrh = states('input_number.raumklima_entwarnung_feuchte_hoch') | float(65) %}
{% set mg = states('input_number.raumklima_lueften_mindestgewinn') | float(5) %}
{% if tid == 'feuchte_hoch' and ai > 0 and ao > 0 and t > -50 and n > 0 and ai > ao %}
{% set sd = 6.112 * e ** (17.62 * t / (243.12 + t)) %}
{% set saet = 216.7 * sd / (273.15 + t) %}
{% set gewinn = (ai - ao) / saet * 100 %}
{% set zabs = zrh / 100 * saet %}
{% set zhalb = ai - 0.5 * (ai - ao) %}
{% set ziel = zabs if zabs > ao else zhalb %}
{% set dauer = ([[ -60 / n * log((ziel - ao) / (ai - ao)), 0 ] | max, 60 ] | min | round(0) | int) if ai > ziel else 0 %}
{% set erreicht = (ao + (ai - ao) * e ** (-1 * n * dauer / 60)) / saet * 100 %}
{% if gewinn > mg and dauer >= 1 %} Fenster etwa {{ dauer }} Minuten weit auf, das bringt dich auf rund {{ erreicht | round(0) | int }} %.{% endif %}
{% endif %}alias: "Raumklima: Entwarnung"
description: >-
Gegenstück zu "Raumklima: Alarme". Meldet, wenn ein gemeldeter Raum die
Entwarnungsschwelle erreicht hat, und nimmt ihn aus input_text.raumklima_gemeldet_*.
Die Entwarnungsschwellen liegen innerhalb der Alarmschwellen (Hysterese).
triggers:
- trigger: numeric_state
id: feuchte_hoch
entity_id: [sensor.kueche_luftfeuchte, sensor.wohnzimmer_luftfeuchte, sensor.bad_eg_luftfeuchte]
below: input_number.raumklima_entwarnung_feuchte_hoch
for: "{{ '%02d:%02d:00' % ((states('input_number.raumklima_haltezeit_entwarnung') | int(30)) // 60, (states('input_number.raumklima_haltezeit_entwarnung') | int(30)) % 60) }}"
- trigger: numeric_state
id: feuchte_tief
entity_id: [sensor.kueche_luftfeuchte, sensor.wohnzimmer_luftfeuchte, sensor.bad_eg_luftfeuchte]
above: input_number.raumklima_entwarnung_feuchte_tief
for: "{{ '%02d:%02d:00' % ((states('input_number.raumklima_haltezeit_entwarnung') | int(30)) // 60, (states('input_number.raumklima_haltezeit_entwarnung') | int(30)) % 60) }}"
- trigger: numeric_state
id: kalt
entity_id: [sensor.kueche_temperatur, sensor.wohnzimmer_temperatur, sensor.bad_eg_temperatur]
above: input_number.raumklima_entwarnung_temperatur_kalt
for: "{{ '%02d:%02d:00' % ((states('input_number.raumklima_haltezeit_entwarnung') | int(30)) // 60, (states('input_number.raumklima_haltezeit_entwarnung') | int(30)) % 60) }}"
- trigger: numeric_state
id: warm
entity_id: [sensor.kueche_temperatur, sensor.wohnzimmer_temperatur, sensor.bad_eg_temperatur]
below: input_number.raumklima_entwarnung_temperatur_warm
for: "{{ '%02d:%02d:00' % ((states('input_number.raumklima_haltezeit_entwarnung') | int(30)) // 60, (states('input_number.raumklima_haltezeit_entwarnung') | int(30)) % 60) }}"
actions:
- variables:
codes: "{{ states(liste_entity).split(',') | reject('eq', '') | list }}"
- alias: Nur für Räume, die als gemeldet vorgemerkt sind
condition: template
value_template: "{{ code != '' and code in codes }}"
- alias: Nachtruhe – nur Kälte darf durch
condition: or
conditions:
- {condition: trigger, id: kalt}
- {condition: state, entity_id: binary_sensor.raumklima_nachtruhe, state: "off"}
- action: input_text.set_value
target: {entity_id: "{{ liste_entity }}"}
data: {value: "{{ (codes | reject('eq', code) | list) | join(',') }}"}
- repeat:
for_each: "{{ ['notify.mobile_app_handy_1', 'notify.mobile_app_handy_2'] if tid != 'warm' else ['notify.mobile_app_handy_1'] }}"
sequence:
- action: "{{ repeat.item }}"
continue_on_error: true
data:
title: "{{ 'Luftfeuchtigkeit wieder normal' if tid in ['feuchte_hoch', 'feuchte_tief'] else 'Temperatur wieder normal' }}"
message: "Entwarnung: {{ raum }} ist wieder bei {{ wert }} {{ einheit }}"
mode: queued
max: 15
max_exceeded: silent
variables:
namen: {kue: Küche, wz: Wohnzimmer, beg: Bad EG}
code_von_sensor:
sensor.kueche_luftfeuchte: kue
sensor.wohnzimmer_luftfeuchte: wz
sensor.bad_eg_luftfeuchte: beg
sensor.kueche_temperatur: kue
sensor.wohnzimmer_temperatur: wz
sensor.bad_eg_temperatur: beg
liste_map:
feuchte_hoch: input_text.raumklima_gemeldet_feuchte_hoch
feuchte_tief: input_text.raumklima_gemeldet_feuchte_tief
kalt: input_text.raumklima_gemeldet_kalt
warm: input_text.raumklima_gemeldet_warm
einheit_map: {feuchte_hoch: "%", feuchte_tief: "%", kalt: "°C", warm: "°C"}
tid: "{{ trigger.id if trigger is defined and trigger.id is defined else '' }}"
sensor_id: "{{ trigger.entity_id if trigger is defined and trigger.entity_id is defined else '' }}"
code: "{{ code_von_sensor.get(sensor_id, '') }}"
raum: "{{ namen.get(code, sensor_id) }}"
wert: "{{ trigger.to_state.state if trigger is defined and trigger.to_state is defined else '' }}"
liste_entity: "{{ liste_map.get(tid, '') }}"
einheit: "{{ einheit_map.get(tid, '') }}"alias: "Raumklima: Nachprüfung"
description: >-
Sicherheitsnetz für "Raumklima: Alarme". Läuft alle 30 Minuten, nach jedem Start
und am Ende der Nachtruhe. Ein Raum wird erst gemeldet, wenn er bei zwei
aufeinanderfolgenden Durchläufen auffällig war (input_text.raumklima_kandidat_*).
triggers:
- {trigger: time_pattern, id: turnus, minutes: "/30"}
- {trigger: homeassistant, id: start, event: start}
- {trigger: state, id: ruhe_ende, entity_id: binary_sensor.raumklima_nachtruhe, to: "off"}
conditions:
- {condition: state, entity_id: input_boolean.raumklima_alarme_aus, state: "off"}
actions:
- if: [{condition: trigger, id: start}]
then:
- delay: "00:05:00" # Sensoren nach dem Neustart erst melden lassen
- repeat:
for_each: [feuchte_hoch, feuchte_tief, kalt, warm]
sequence:
- variables:
kat: "{{ repeat.item }}"
k: "{{ kategorien[repeat.item] }}"
sens: "{{ sensoren_map[kategorien[repeat.item].art] }}"
sw: "{{ states(kategorien[repeat.item].schwelle) | float(0) }}"
ew: "{{ states(kategorien[repeat.item].entwarnung) | float(0) }}"
ueber: "{{ kategorien[repeat.item].ueber }}"
- variables:
aktuell: >-
{% set ns = namespace(l=[]) %}
{% for c, e in sens.items() %}
{% if is_number(states(e)) and ((states(e) | float(0) > sw) if ueber else (states(e) | float(0) < sw)) %}
{% set ns.l = ns.l + [c] %}
{% endif %}
{% endfor %}
{{ ns.l }}
gemeldet: "{{ states(k.liste).split(',') | reject('eq', '') | list }}"
kandidaten: "{{ states(k.kandidat).split(',') | reject('eq', '') | list }}"
ruhe: "{{ kat != 'kalt' and is_state('binary_sensor.raumklima_nachtruhe', 'on') }}"
- variables:
entwarnt: >-
{% set ns = namespace(l=[]) %}
{% for c in gemeldet %}
{% set e = sens.get(c, '') %}
{% if e != '' and is_number(states(e)) and ((states(e) | float(0) < ew) if ueber else (states(e) | float(0) > ew)) %}
{% set ns.l = ns.l + [c] %}
{% endif %}
{% endfor %}
{{ ns.l }}
neu: >-
{% set ns = namespace(l=[]) %}
{% for c in aktuell %}
{% if c not in gemeldet and c in kandidaten
and not (kat == 'feuchte_hoch' and c == 'beg' and is_state('input_boolean.raumklima_bad_eg_dusch_sperre', 'on')) %}
{% set ns.l = ns.l + [c] %}
{% endif %}
{% endfor %}
{{ [] if ruhe else ns.l }}
- variables:
raus: "{{ [] if ruhe else entwarnt }}"
- alias: Kandidaten für den nächsten Durchlauf merken
action: input_text.set_value
target: {entity_id: "{{ k.kandidat }}"}
data: {value: "{{ aktuell | join(',') }}"}
- alias: Gemeldet-Liste fortschreiben
action: input_text.set_value
target: {entity_id: "{{ k.liste }}"}
data: {value: "{{ ((gemeldet + neu) | unique | reject('in', raus) | list) | join(',') }}"}
- repeat:
for_each: "{{ neu }}"
sequence:
- variables:
msg: "{{ namen.get(repeat.item, repeat.item) }} ist bei {{ states(sens[repeat.item]) }} {{ k.einheit }} – {{ k.lage }}."
# hier folgt derselbe Lüftungstipp wie in "Raumklima: Alarme", Ziel ist die Entwarnungsschwelle ew
- action: notify.mobile_app_handy_1
continue_on_error: true
data: {title: "{{ k.titel }}", message: "{{ msg }}"}
- if: "{{ kat != 'warm' }}"
then:
- action: notify.mobile_app_handy_2
continue_on_error: true
data: {title: "{{ k.titel }}", message: "{{ msg }}"}
- repeat:
for_each: "{{ raus }}"
sequence:
- variables:
msg2: "Entwarnung: {{ namen.get(repeat.item, repeat.item) }} ist wieder bei {{ states(sens[repeat.item]) }} {{ k.einheit }}"
- action: notify.mobile_app_handy_1
continue_on_error: true
data: {title: "{{ k.titel_ok }}", message: "{{ msg2 }}"}
- if: "{{ kat != 'warm' }}"
then:
- action: notify.mobile_app_handy_2
continue_on_error: true
data: {title: "{{ k.titel_ok }}", message: "{{ msg2 }}"}
mode: single
max_exceeded: silent
variables:
namen: {kue: Küche, wz: Wohnzimmer, beg: Bad EG}
sensoren_map:
feuchte:
kue: sensor.kueche_luftfeuchte
wz: sensor.wohnzimmer_luftfeuchte
beg: sensor.bad_eg_luftfeuchte
temp:
kue: sensor.kueche_temperatur
wz: sensor.wohnzimmer_temperatur
beg: sensor.bad_eg_temperatur
kategorien:
feuchte_hoch:
art: feuchte
ueber: true
einheit: "%"
schwelle: input_number.raumklima_schwelle_feuchte_hoch
entwarnung: input_number.raumklima_entwarnung_feuchte_hoch
liste: input_text.raumklima_gemeldet_feuchte_hoch
kandidat: input_text.raumklima_kandidat_feuchte_hoch
titel: Luftfeuchtigkeitsalarm
titel_ok: Luftfeuchtigkeit wieder normal
lage: zu feucht
feuchte_tief:
art: feuchte
ueber: false
einheit: "%"
schwelle: input_number.raumklima_schwelle_feuchte_tief
entwarnung: input_number.raumklima_entwarnung_feuchte_tief
liste: input_text.raumklima_gemeldet_feuchte_tief
kandidat: input_text.raumklima_kandidat_feuchte_tief
titel: Luftfeuchtigkeitsalarm
titel_ok: Luftfeuchtigkeit wieder normal
lage: zu trocken
kalt:
art: temp
ueber: false
einheit: "°C"
schwelle: input_number.raumklima_schwelle_temperatur_kalt
entwarnung: input_number.raumklima_entwarnung_temperatur_kalt
liste: input_text.raumklima_gemeldet_kalt
kandidat: input_text.raumklima_kandidat_kalt
titel: Temperaturalarm
titel_ok: Temperatur wieder normal
lage: zu kalt
warm:
art: temp
ueber: true
einheit: "°C"
schwelle: input_number.raumklima_schwelle_temperatur_warm
entwarnung: input_number.raumklima_entwarnung_temperatur_warm
liste: input_text.raumklima_gemeldet_warm
kandidat: input_text.raumklima_kandidat_warm
titel: Temperaturalarm
titel_ok: Temperatur wieder normal
lage: zu warmAnpassen
- Trage deine Räume und Sensoren ein und vergib pro Raum einen eindeutigen Kurzcode.
- Schwellen, Hysterese und Haltezeiten ändern sich über die Helfer, ohne Neustart.
- Brauchst du die Nachtruhe nicht, lässt du die Bedingung weg und den Trigger
ruhe_endeder Nachprüfung. Ohnethermal_comfortentfällt der Lüftungstipp.
Stolpersteine
Die Zuordnung Sensor → Kurzcode muss in allen drei Automationen übereinstimmen. Eine Änderung nur an einer Stelle führt dazu, dass Räume gemeldet, aber nie entwarnt werden. Das Kandidaten-Verfahren der Nachprüfung kostet im Zweifel eine Runde (30 Minuten) Verzögerung, ist dafür aber unempfindlich gegen kurze Ausreißer. Außerdem erlauben input_text-Helfer höchstens 255 Zeichen. Bei zwölf Räumen mit Kurzcodes von drei Zeichen ist das genug.
Unterstützen
Diese Tools entstehen in meiner Freizeit und bleiben kostenlos, offen und cloud-frei. Wenn dir eins davon einen Nachmittag gespart hat, freue ich mich über einen Kaffee oder ein Stück Pizza.
Kaffee oder PizzaWeitere Beiträge
- Bad: Duschen erkennen, Luftentfeuchter steuern, ans Lüften erinnern Vier zusammenhängende Automationen für das Bad im Erdgeschoss: Sie erkennen das Duschen, regeln den Luftentfeuchter nach Feuchte, Fenster und Nachtruhe und erinnern ans Lüften, wenn es nötig ist.
- Marstek Offline Endpoint 2.0: Weboberfläche, entschlüsselte Messwerte und MQTT für Home Assistant Der Endpoint beantwortet nicht mehr nur den Upload der Batterie, er liest ihn auch aus: jeder Wert, den ein Venus an die Marstek-Cloud schickt, landet benannt in einer lokalen Datenbank, mit Kurven, Benachrichtigungen und Home-Assistant-Anbindung.
- Rollläden morgens: Arbeitstag und freier Tag unterscheiden An Arbeitstagen soll der Sollzustand „Rollladen oben“ um 5 Uhr gelten, an freien Tagen erst um 7 Uhr. Zwei Zeitautomationen mit der Workday-Integration und ein Merker pro Rollladen erledigen das.