Smart Home lokal · Teil 4 von 8

Heizplan ist nicht Heizbeginn: Zieltemperatur, Vorheizen, Ankunft

Ein Zeitplan sagt, wann ein Raum warm sein soll, nicht wann geheizt wird. Wie ein Zielsensor je Raum daraus Vorheizen, Schaltpunkte und ein Ankunftsfenster macht, und warum das Haus nach einer Abwesenheit trotzdem kalt war.

Nach einer mehrtägigen Abwesenheit hatte die Heizung alles getan, was wir ihr aufgetragen hatten, und das Haus war trotzdem kalt. Vier Stunden vor der eingetragenen Rückkehr bekam jedes Ventil pünktlich seinen Komfortwert. Im Verlauf sah man, was danach geschah: Büro, Essbereich und Gästezimmer fielen nacheinander wieder auf 16 °C, bevor jemand zu Hause war. Bei der Ankunft stand nur noch das Wohnzimmer auf Komfort.

Technisch war nichts schiefgegangen. Der Fehler lag im Denken, ausgerechnet bei der Idee, auf der unsere Heizungssteuerung beruht: Der Zeitplan sagt, wann es warm sein soll. Wann geheizt wird, rechnet das System.

Eine Zahl je Raum

Jeder der fünf Räume mit Heizkörperventil hat einen Template-Sensor sensor.<raum>_zieltemperatur. Er trägt die ganze Entscheidung und zeigt in einer Zeile, was das System gerade will. Die Automationen darüber entscheiden nicht, was ein Raum will, sondern nur, wie der Wert ans Ventil kommt (dazu Teil 3). Fühlt sich ein Raum falsch an, lesen wir zuerst diesen Sensor, statt Automationen zu durchsuchen.

Die Zweige werden der Reihe nach geprüft, der erste Treffer gewinnt:

  1. Abwesenheit: Abwesenheitstemperatur, bei uns 16 °C
  2. Heizperiode aus: Absenkwert
  3. Ankunftsfenster: Komfort
  4. Komfortzeit laut schedule-Helfer: Komfort
  5. Vorheizen fällig: Komfort
  6. sonst Absenkung

Gekürzt sieht das im Büro so aus:

{% set komfort  = states('input_number.buro_komforttemperatur') | float(21.5) %}
{% set absenk   = states('input_number.absenktemperatur') | float(16) %}
{% set abwesend = is_state('binary_sensor.abwesenheit', 'on') %}
{% set ab_temp  = states('input_number.abwesenheitstemperatur') | float(16) %}
{# ... Ankunftsfenster, siehe unten #}
{% if abwesend %}                                          {{ ab_temp }}
{% elif not is_state('input_boolean.heizperiode', 'on') %} {{ absenk }}
{% elif ankunft %}                                         {{ komfort }}
{% elif is_state('schedule.buro_komfortzeit', 'on') %}     {{ komfort }}
{% else %}
  {% set ist    = state_attr('climate.heizung_buro', 'current_temperature') %}
  {% set start  = state_attr('schedule.buro_komfortzeit', 'next_event') %}
  {% set faktor = states('input_number.vorheizen_minuten_je_grad') | float(30) %}
  {# ... fehlt ist oder start, gilt die Absenkung #}
  {% set rueckstand   = komfort - (ist | float) %}
  {% set vorlauf      = [rueckstand * faktor, 120] | min %}
  {% set bis          = ((start | as_datetime) - now()).total_seconds() / 60 %}
  {% set laeuft_schon = (this.state | float(-99)) >= (komfort - 0.01) %}
  {{ komfort if ((rueckstand > 0 and bis <= vorlauf)
                 or (laeuft_schon and bis <= 120)) else absenk }}
{% endif %}

Ob gerade Abwesenheit herrscht, rechnet nicht jeder Sensor selbst. Anfangs stand dieselbe Rechnung wortgleich in allen fünf. Jetzt liegt sie in binary_sensor.abwesenheit, und die Sensoren fragen nur dessen Zustand ab.

Der Zeitplan sagt, wann es warm sein soll

Die Komfortzeiten liegen in schedule-Helfern, einem je Raum. Eingetragen wird, wann der Raum warm sein soll, im Büro etwa 08:00 bis 17:00. Den Heizbeginn rechnet der Zielsensor aus dem Rückstand:

Rückstand = Komforttemperatur − gemessene Raumtemperatur
Vorlauf   = Rückstand × input_number.vorheizen_minuten_je_grad (30), höchstens 120 min
Vorheizen, wenn Rückstand > 0 und der Fensterbeginn näher liegt als der Vorlauf

Den Fensterbeginn liefert das Attribut next_event des Helfers. Rechenbeispiel aus unserem Entwurf, Büro mit Fenster ab 08:00 und 21,5 °C:

Raum misst Rückstand Vorlauf Heizbeginn
16,5 °C 5,0 K 150, gedeckelt auf 120 min 06:00
20,0 °C 1,5 K 45 min 07:15
21,5 °C 0 keiner gar nicht

Der Deckel verhindert, dass ein stark ausgekühlter Raum die Heizung mitten in der Nacht anwirft. Deshalb steht er fest in der Vorlage: Er ist eine Sicherung, kein Stellwert. Die Kennzahl dagegen ist ein Helfer, an dem man dreht, bis die Messungen passen.

Den Vorheizfall hat Claude vor dem Einbau über die Vorlagen-Schnittstelle erzwungen, mit einem Testfenster fünfzehn Minuten voraus und 26 °C Komfort: Der Sensor sprang auf 26,0 und nach dem Zurücksetzen auf 16,0. Mit Fenstern sieben bis neun Stunden voraus blieben alle fünf bei 16,0.

Das Vorheizen ist nur so gut wie der Ist-Wert. Im Gästezimmer zeigte das Ventil bei laufendem Heizkörper 25,8 °C, der Raumfühler an der Innenwand 21,5 °C. Ein zu warmer Ist-Wert heißt kleinerer Rückstand und zu später Heizbeginn. Das Gästezimmer rechnet deshalb seit dem 11.09. mit dem Raumfühler, der Essbereich mit dem Mittel seiner beiden Ventile. Büro und Schlafzimmer nahmen bis zum 27.09. den Wert ihres AVM-Ventils, obwohl beide längst einen Raumfühler hatten. Im Büro meldete das Ventil nachts stur 21,0 °C, der Raum hatte 22,6 bis 23,4. Das Büro wurde also jeden Morgen vorgeheizt, obwohl es schon wärmer war als sein Komfortwert. Aufgefallen ist das beim Schreiben dieser Serie, seitdem rechnen beide mit dem Raumfühler.

Die Rechnung hat einen Preis. Ein Komfortfenster ab 06:00 im Essbereich, einem Glasanbau, heißt im Winter Heizbeginn gegen 04:00. Und mit einem Mittagsfenster ab 12:00 kann das Vorheizen schon um 10:00 anlaufen, zwei Stunden nach dem Morgenfenster. An kalten Tagen wird der Raum dann praktisch von 06:00 bis 13:00 durchgeheizt.

Wird ein Raum regelmäßig zu spät warm, bekommt er eine eigene Kennzahl. Das Schlafzimmer hat seit dem 25.09. 50 Minuten je Grad und höchstens 180 Minuten, beide als Helfer. Bei 21 °C in der Nacht beginnt das Vorheizen für 08:00 damit gegen 06:20, bei 18 °C um 05:00. Den Grundsatz, den Deckel fest in die Vorlage zu schreiben, haben wir dabei selbst aufgeweicht. Die Geschichte dazu steht in Teil 2.

Schaltpunkte statt Dauerschreiben

Anfangs lief die Automation alle fünf Minuten und schrieb, sobald der Wunschwert um mindestens 0,5 K vom eingestellten abwich. Das hatte zwei Folgen. Ein Handgriff am Ventil wurde spätestens nach fünf Minuten zurückgedreht. Und ein Ventil auf off ließ die Automation absichtlich in Ruhe. Nachdem die Zeitpläne in der FRITZ!Box gelöscht waren, gab es aber nichts mehr, das es zurückgeholt hätte.

Seit dem 09.09. arbeitet die Steuerung wie ein klassisches Thermostat. Geschrieben wird nur an einem Schaltpunkt, also immer dann, wenn sich ein Zielsensor ändert: Das Vorheizen beginnt, ein Fenster endet, die Heizperiode wird umgelegt, jemand verstellt eine Wunschtemperatur. Dazwischen bleibt ein Handgriff stehen. Wer hochdreht, hat es warm, und wer ausschaltet, hat es aus, jeweils bis zum nächsten Schaltpunkt. Dort wird auch ein Ventil auf off wieder geweckt. Die Ventile sind reine Stellglieder und stehen dauerhaft in heat.

- id: heizung_raeume_regeln
  mode: queued
  triggers:
    - trigger: state
      entity_id:
        - sensor.buro_zieltemperatur
        - sensor.schlafzimmer_zieltemperatur
        # ... Wohnzimmer, Gästezimmer
  conditions:
    # Nur echte Wertwechsel. Ein Neustart oder eine kurze Nichtverfügbarkeit
    # ist kein Schaltpunkt und darf keinen Handgriff überschreiben.
    - condition: template
      value_template: >-
        {{ trigger.from_state is not none and trigger.to_state is not none
           and trigger.to_state.state not in ['unknown', 'unavailable']
           and trigger.from_state.state not in ['unknown', 'unavailable']
           and (trigger.to_state.state | float(-99)
                - trigger.from_state.state | float(-99)) | abs >= 0.1 }}
  actions:
    # ... Ventil des auslösenden Raums auf heat, Sollwert schreiben,
    # nach zwei Minuten einmal nachfassen, Merker setzen

Die Bedingung ist der Kern: Nur ein Wechsel zwischen zwei gültigen Zahlen zählt. Erscheint der Zielsensor nach einem Neustart von Home Assistant ohne Vorzustand oder über unknown neu, ist das kein Schaltpunkt, ebenso wenig eine kurze Nichtverfügbarkeit. Angefasst wird nur der Raum, dessen Zielwert sich geändert hat, sonst überschriebe ein Schaltpunkt im Büro einen Handgriff im Schlafzimmer. Der Essbereich mit zwei Ventilen hat eine eigene Automation mit derselben Bedingung.

Auch eine Planänderung ist ein Schaltpunkt: Das Mittagsfenster im Essbereich haben wir eingetragen, als es schon lief, und beide Ventile bekamen sofort ihren Sollwert. Wie zwischen zwei Schaltpunkten nachgeregelt wird, ohne einen Handgriff zu überfahren, steht in Teil 3.

Die Haltelogik: Vorheizen darf nicht flackern

Mit den Schaltpunkten wurde ein Detail wichtig, das vorher egal war. Beim Vorheizen wird der Raum wärmer, und mit dem Rückstand schrumpft der Vorlauf, bis der Fensterbeginn wieder weiter entfernt liegt als der Vorlauf. Der Zielwert fiele zurück auf Absenkung und spränge kurz darauf wieder auf Komfort. Beim Schreiben alle fünf Minuten war das harmlos. Seit dem 09.09. ist jeder dieser Sprünge ein Schaltpunkt: ein Funkbefehl, eine Ventilbewegung, ein überschriebener Handgriff.

Deshalb liest der Sensor seinen eigenen letzten Zustand über this.state (laeuft_schon im Ausschnitt oben). Steht er schon auf Komfort und liegt der Fensterbeginn innerhalb des Deckels, bleibt er dort. Geprüft im Schlafzimmer: Raum 22,9 °C bei Komfort 23, noch 170 Minuten bis 08:00. Der Rückstand von 0,1 K ergäbe fünf Minuten Vorlauf, also Absenkung. Die Haltelogik hielt 23.

Nach dem Ende eines Komfortfensters steht der Sensor ebenfalls auf Komfort, aber der nächste Beginn liegt Stunden entfernt, jenseits des Deckels. Dort greift die Haltelogik nicht, und der Wert fällt wie gewollt ab.

Abwesenheit: eintragen, wann man zurück ist

Für eine geplante Abwesenheit nutzen wir nicht die Anwesenheitserkennung, denn ein eingetragenes Datum ist verlässlicher als Telefone im WLAN. Beginn und Rückkehr stehen in input_datetime.abwesenheit_von und …_bis, dazu kommen input_number.abwesenheitstemperatur mit 16 °C und input_number.abwesenheit_ruckkehr_vorlauf mit vier Stunden.

Weniger als 16 °C bringt wenig. Bei mehreren Tagen liegt die Ersparnis fast ganz darin, die Komfortfenster zu unterdrücken, nicht in den letzten zwei Grad. Und ein kühles Haus mit steigender Feuchte ist genau die Lage, in der Schimmel entsteht, während niemand lüften kann.

Der Rückkehrvorlauf beendet die Abwesenheit rechnerisch vier Stunden vor der eingetragenen Rückkehr: binary_sensor.abwesenheit ist nur an, solange bis dahin mehr als der Vorlauf bleibt. Danach übernimmt der normale Plan samt Vorheizen. Eingetragen wird, wann man da ist, und wann geheizt werden muss, rechnet das System. So war es gedacht.

Das Ankunftsfenster

Angenommen, man kommt an einem Werktag um 19 Uhr zurück. Mit vier Stunden Vorlauf endet die Abwesenheit um 15 Uhr, und ab dann gilt der Wochenplan:

Raum Komfortzeit werktags Stand um 19 Uhr
Büro 08:00–17:00 seit 17:00 wieder 16 °C
Schlafzimmer 08:00–16:00 seit 16:00 wieder Absenkung
Essbereich abends 17:00–18:00 seit 18:00 wieder 16 °C
Wohnzimmer 15:00–22:00 Komfort

Nach genau diesem Muster war das Haus bei uns zur Ankunft kalt. Der Vorlauf heizt auf das Ende der Abwesenheit, nicht auf die Ankunft. Danach greift sofort der Wochenplan, und der weiß nichts von einer Rückkehr.

Die Regel jetzt:

Abwesenheit endet  = Ankunftszeit − Vorlauf
Ankunftsfenster    = von da an bis Ankunftszeit + Nachlauf
darin gilt überall Komfort, unabhängig vom Wochenplan

Mit vier Stunden Vorlauf und drei Stunden Nachlauf ist es im Beispiel von 15:00 bis 22:00 überall warm, danach übernimmt der normale Plan. Der Nachlauf steht in input_number.abwesenheit_nachlauf (0 bis 12 Stunden, Standard 3):

{% set a_bis  = states('input_datetime.abwesenheit_bis') %}
{% set a_vor  = states('input_number.abwesenheit_ruckkehr_vorlauf') | float(0) %}
{% set a_nach = states('input_number.abwesenheit_nachlauf') | float(3) %}
{% set a_ende = (a_bis | as_datetime | as_local)
                if a_bis not in ['unknown', 'unavailable'] else none %}
{% set ankunft = a_ende is not none
                 and now() >= (a_ende - timedelta(hours = a_vor))
                 and now() <  (a_ende + timedelta(hours = a_nach)) %}

Die untere Grenze now() >= a_ende - Vorlauf ist wichtig. Ohne sie wäre das Fenster bei einer künftig eingetragenen Abwesenheit die ganze Zeit vorher offen, und bis zur Abreise stünde jeder Raum auf Komfort.

Die Rechnung steht in einem einzigen Ausdruck statt in einer Verzweigung. Als Grund haben wir damals in Doku und Kommentare geschrieben, ein {% set %} innerhalb eines {% if %} wirke in Jinja nicht nach außen. Beim Schreiben dieser Serie stellte sich heraus, dass das nicht stimmt. In Home Assistant ergibt {% set x = 1 %}{% if true %}{% set x = 2 %}{% endif %}{{ x }} den Wert 2, denn ein if bildet keinen eigenen Gültigkeitsbereich. Kapseln tun nur Schleifen: Mit {% for %} statt {% if %} bleibt es bei 1, dort hilft namespace(). Die Ausdrücke funktionieren trotzdem, nur ihre Begründung war falsch.

Geprüft hat Claude das mit verstellter Ankunftszeit und abgeschalteten Heizungsautomationen, damit kein Ventil angefasst wird:

Ankunftszeit Erwartung Ergebnis
jetzt Fenster offen alle fünf Zielsensoren auf Komfort
vor 4 Stunden Nachlauf abgelaufen überall 16,0
vor 2 Stunden Nachlauf läuft noch alle fünf auf Komfort

binary_sensor.abwesenheit blieb dabei immer aus. Das Fenster schaltet die Abwesenheit also nicht versehentlich ein.

Die Heizperiode als Hauptschalter

input_boolean.heizperiode entstand aus einem Fehler von Claude. Am 31.08. hatte die neue Regelung die beiden Ventile im Essbereich auf 23 °C gestellt, während die Therme im Sommerbetrieb lief. Ein Ventil, das gegen ein unerreichbares Ziel regelt, steht offen und fährt dauernd nach. Das kostet Motor und Batterie und heizt nichts.

Ist der Schalter aus, gilt überall der Absenkwert, gleich was ein Zeitplan sagt. Das Ankunftsfenster steht bewusst dahinter, damit im Sommer keine Rückkehr die Heizung anwirft. Umgelegt wird im Herbst von Hand. Eine Außentemperatur gibt es erst seit dem 20.09. (eine Vorhersage von Met.no), und der Schalter hängt bisher nicht daran.

Er sagt ohnehin nur, was Home Assistant will. Ob Wärme ankommt, entscheidet die Therme mit ihrer eigenen Sommer-Winter-Umschaltung, und die sieht Home Assistant nicht.

Lernen aus Messungen, entschieden von Hand

Ob 30 Minuten je Grad stimmen, sieht man erst, wenn man die Aufheizung messen kann. Zwei Raumfühler meldeten ab Werk erst bei 1,0 K Änderung, seit dem 21.09. schon bei 0,2 K (mehr in Teil 5). Seitdem schreibt sensor.gastezimmer_aufheizrate mit, die Ableitung der Raumtemperatur in °C/h über einen derivative-Helfer mit 30 Minuten Zeitfenster. Dazu kommen drei sensor.heizkorperwarme_*, jeweils Ventilkopf minus Raumfühler an einem AVM-Ventil. Sie ersetzen einen Fühler am Vorlaufrohr, der hier nicht möglich ist: Je heißer der Heizkörper, desto weiter liegt der Kopf über dem Raum.

Dabei wurde klar, was unserer Formel grundsätzlich fehlt. Die Therme fährt ihre Vorlauftemperatur nach einer eigenen Kennlinie, abhängig von der Außentemperatur. Der Rückstand sagt, wie weit ein Raum aufholen muss, aber nicht, wie schnell er es kann:

Rückstand Vorlauf der Therme Folge
milder Tag klein niedrig, wenig Leistung zu kurz geplant, am 21.09. passiert
kalter Tag groß hoch, viel Leistung Maximum geplant, vielleicht zu früh

Im Winter müsste der Faktor also womöglich sinken statt steigen. Eine einzelne Zahl ist damit nicht bloß ungenau, sondern im Aufbau falsch. Gebraucht wird eine Kennlinie Faktor = f(Außentemperatur) mit zwei oder drei Stützpunkten aus Messungen.

Eine Automatik, die den Faktor selbst nachstellt, haben wir bewusst nicht gebaut. Bei zwei oder drei Heizphasen je Woche jagt sie dem Rauschen nach, und eine Steuerung, die ihre Parameter still verändert, lässt sich nicht mehr beurteilen. Gemessen wird automatisch, entschieden von Hand.

Bedienung im Alltag

Helfer wofür
input_boolean.heizperiode im Herbst einschalten; aus heißt überall Absenkung
schedule.<raum>_komfortzeit wann der Raum warm sein soll, nicht wann geheizt wird
input_number.<raum>_komforttemperatur wie warm
input_number.absenktemperatur wie kühl außerhalb der Komfortzeit; Schlafzimmer und Gästezimmer haben eine eigene
input_number.vorheizen_minuten_je_grad wie früh vorgeheizt wird; höher heißt früher
input_number.schlafzimmer_vorheizen_minuten_je_grad, …_vorheizen_maximal dasselbe nur fürs Schlafzimmer
input_datetime.abwesenheit_von, …_bis Beginn der Abwesenheit und Rückkehr
input_number.abwesenheit_ruckkehr_vorlauf, …_nachlauf wie lange vor und nach der Rückkehr überall geheizt wird

Wird ein Raum zu spät warm, ist die Kennzahl zu niedrig, wird er zu früh warm, ist sie zu hoch. Wer zwischendurch mehr Wärme will, stellt das Ventil direkt. Das hält bis zum nächsten Schaltpunkt.

Was wir mitnehmen

  • Pro Raum eine Zahl, die sagt, was das System will. Die Automationen schreiben nur, und die Fehlersuche beginnt mit einer Zeile.
  • Den Zeitplan als „warm ab“ lesen, nicht als „heizen ab“. Den Heizbeginn rechnet der Rückstand, ein fester Deckel sichert ab.
  • Nur bei echten Wertwechseln schreiben. Ein Neustart ist kein Schaltpunkt, und ein Handgriff hält bis zum nächsten Wechsel.
  • Wer auf Wertwechsel schaltet, muss Flackern verhindern. Mit this.state hält ein Template-Sensor einen begonnenen Zustand.
  • Auf die Ankunft heizen, nicht auf das Ende der Abwesenheit, und danach noch einige Stunden warm halten.
  • Die Aufheizrate hängt an der Außentemperatur, nicht nur am Rückstand. Selbstlernende Parameter erst, wenn genug gemessen ist.