Zum Inhalt

YAML – Beschattung

Dokumentation: Beschattung

Package: Skripte & Hitzeschutz

Quelle in HA: /config/packages/beschattung.yaml

Enthält: - script.beschattung_cover_setzen — generischer Baustein für alle Rollläden/Markisen - script.beschattung_fenster_offen — Position sichern & Lüftungsposition bei offenem Fenster - script.beschattung_komfortsperren_loeschen — alle Komfortsperren löschen und Abwesenheitslogik auslösen - binary_sensor.beschattung_hitzeschutz_noetig — Trigger-Template mit Forecast-Auswertung und Selbsthaltung

script:
# =====================================================================
# 🌤️  BESCHATTUNG – Wiederverwendbare Skripte
# =====================================================================
# Dieses Package buendelt Skripte, die von mehreren Beschattungs-
# Automationen (Wohnzimmer, Martha, Schlafzimmer, ...) gemeinsam
# genutzt werden, damit die eigentliche Cover-Ansteuerungs-Logik
# nur an EINER Stelle gepflegt werden muss.
# =====================================================================

  beschattung_cover_setzen:
    alias: "Beschattung: Cover Position setzen"
    icon: mdi:blinds
    mode: parallel
    max: 10
    description: >-
      Generischer Baustein fuer die Beschattungs-Automationen: setzt ein
      Cover (Rollo/Markise/Terrassentuer) auf eine Zielposition. Optional
      werden dabei ein Lauf-Marker (input_boolean, das waehrend der Fahrt
      z.B. Fenstersensor-Events ausblendet) gesetzt/zurueckgesetzt und ein
      Rueckkehrwert-Helper (input_number) mit der Zielposition befuellt,
      damit eine spaetere Rueckkehr-Automation weiss, wohin sie fahren
      soll. Danach folgt ein Logbook-Eintrag. Nur wenn ein Lauf-Marker
      angegeben wurde, folgt zusaetzlich ein Nachlauf-Delay, bevor der
      Marker wieder zurueckgesetzt wird -- ohne Marker kehrt das Skript
      sofort nach dem Logbook-Eintrag zurueck, damit aufrufende
      Automationen (die auf das Skript-Ende warten) nicht unnoetig
      ausgebremst werden. Marker und Rueckkehrwert sind rein optional --
      werden sie beim Aufruf nicht angegeben, macht das Skript einfach
      nur "Position setzen + loggen" (z.B. fuer einfaches Markise-Oeffnen
      ohne Fenstersensor-Interferenz).

    fields:
      cover_entity:
        name: Cover
        description: "Das Cover (Rollo, Markise, Terrassentuer), das positioniert
          werden soll."
        required: true
        selector:
          entity:
            domain: cover

      ziel_position:
        name: Zielposition
        description: "Zielposition in Prozent. 0 = geschlossen, 100 = offen."
        required: true
        selector:
          number:
            min: 0
            max: 100
            mode: slider
            step: 1
            unit_of_measurement: "%"

      marker_entity:
        name: "Lauf-Marker (optional)"
        description: >-
          input_boolean, das waehrend der Cover-Fahrt auf 'on' gesetzt
          wird, damit andere Automationen Fenstersensor-Events waehrend
          der Fahrt ignorieren koennen. Nur wenn dieses Feld gesetzt ist,
          wartet das Skript anschliessend das Nachlauf-Delay ab. Leer
          lassen, wenn kein Marker benoetigt wird -- das Skript kehrt
          dann sofort nach dem Logbook-Eintrag zurueck.
        required: false
        selector:
          entity:
            domain: input_boolean

      rueckkehr_entity:
        name: "Rueckkehrwert-Helper (optional)"
        description: >-
          input_number, in dem die gesetzte Zielposition gespeichert
          wird, damit eine spaetere Rueckkehr-Automation weiss, wohin
          sie zurueckfahren soll. Leer lassen, wenn nicht benoetigt.
        required: false
        selector:
          entity:
            domain: input_number

      log_nachricht:
        name: "Logbook-Nachricht (optional)"
        description: >-
          Eigener Text fuer den Logbook-Eintrag. Wenn leer, wird
          automatisch "<Cover-Name> -> <Position>%" erzeugt.
        required: false
        selector:
          text:

      log_label:
        name: "Logbook-Absender (optional)"
        description: "Name, unter dem der Logbook-Eintrag erscheint. Standard: 'Beschattung'."
        required: false
        selector:
          text:

      delay_sekunden:
        name: "Nachlauf-Delay in Sekunden (optional)"
        description: >-
          Wartezeit, bevor der Lauf-Marker wieder zurueckgesetzt wird.
          Wirkt sich nur aus, wenn auch ein Lauf-Marker angegeben ist.
          Standard: 30 Sekunden.
        required: false
        selector:
          number:
            min: 0
            max: 300
            mode: box
            step: 5
            unit_of_measurement: "s"

    sequence:
    - alias: "Hilfsvariablen berechnen (Marker, Rueckkehrwert, Label, Delay, Logbook-Text)"
      variables:
        _marker: "{{ marker_entity | default('') }}"
        _rueckkehr: "{{ rueckkehr_entity | default('') }}"
        _label: "{{ log_label | default('Beschattung', true) }}"
        _delay: "{{ (delay_sekunden | default(30)) | int }}"
        _nachricht: >-
          {{ log_nachricht if (log_nachricht is defined and log_nachricht | trim != '')
             else (state_attr(cover_entity, 'friendly_name') ~ ' → ' ~ (ziel_position | int) ~ '%') }}

    - alias: "Lauf-Marker aktivieren, falls angegeben"
      if:
      - condition: template
        value_template: "{{ _marker != '' }}"
      then:
      - alias: "Marker einschalten"
        action: input_boolean.turn_on
        target:
          entity_id: "{{ _marker }}"

    - alias: "Zielposition im Rueckkehrwert-Helper speichern, falls angegeben"
      if:
      - condition: template
        value_template: "{{ _rueckkehr != '' }}"
      then:
      - alias: "Rueckkehrwert setzen"
        action: input_number.set_value
        target:
          entity_id: "{{ _rueckkehr }}"
        data:
          value: "{{ ziel_position | int }}"

    - alias: "Cover auf Zielposition fahren"
      action: cover.set_cover_position
      target:
        entity_id: "{{ cover_entity }}"
      data:
        position: "{{ ziel_position | int }}"

    - alias: "Logbuch-Eintrag schreiben"
      action: logbook.log
      data:
        name: "{{ _label }}"
        entity_id: "{{ cover_entity }}"
        message: "{{ _nachricht }}"

    - alias: "Nachlauf-Delay abwarten und Lauf-Marker zuruecksetzen, falls angegeben"
      if:
      - condition: template
        value_template: "{{ _marker != '' }}"
      then:
      - alias: "Nachlauf-Delay abwarten"
        delay:
          seconds: "{{ _delay }}"
      - alias: "Marker ausschalten"
        action: input_boolean.turn_off
        target:
          entity_id: "{{ _marker }}"

  beschattung_komfortsperren_loeschen:
    alias: "Beschattung: Komfortsperren löschen"
    description: >
      Löscht alle manuellen Komfortsperren.
      Fährt die Markise ein wenn niemand zuhause ist.
    icon: mdi:lock-open-remove
    mode: single
    sequence:
    - action: input_boolean.turn_off
      target:
        entity_id:
        - input_boolean.beschattung_sperre_kueche
        - input_boolean.beschattung_sperre_wohnzimmer_links
        - input_boolean.beschattung_sperre_wohnzimmer_rechts
        - input_boolean.beschattung_sperre_martha
        - input_boolean.beschattung_sperre_schlafzimmer
    - if:
      - condition: state
        entity_id: binary_sensor.beschattung_jemand_zuhause
        state: "off"
      - condition: numeric_state
        entity_id: cover.markise
        attribute: current_position
        below: 10
      then:
      - action: cover.open_cover
        target:
          entity_id: cover.markise
      - action: logbook.log
        data:
          name: Beschattung
          entity_id: cover.markise
          message: "Markise eingefahren: Abwesenheitsmodus"
    - action: logbook.log
      data:
        name: Beschattung
        entity_id: input_select.beschattung_betriebsmodus
        message: "Alle Komfortsperren gelöscht"

  beschattung_fenster_offen:
    alias: "Beschattung: Fenster geöffnet  Position sichern & Lüftungsposition"
    icon: mdi:window-open-variant
    mode: parallel
    max: 10
    description: >-
      Generische Fensterlogik fuer die Beschattung: Wird ein Fenster
      geoeffnet oder gekippt, sichert dieses Skript zuerst IMMER die
      aktuelle Cover-Position in einem input_number-Helper. Steht das
      Cover tiefer als die gewuenschte Lueftungsposition (minus Toleranz),
      faehrt es auf die Lueftungsposition hoch.

    fields:
      cover_entity:
        name: Cover
        required: true
        selector:
          entity:
            domain: cover

      position_speicher_entity:
        name: Positions-Speicher
        required: true
        selector:
          entity:
            domain: input_number

      lueftungs_position:
        name: Lüftungsposition
        required: true
        selector:
          number:
            min: 0
            max: 100
            mode: slider
            step: 1
            unit_of_measurement: "%"

      toleranz:
        name: "Toleranz (optional)"
        required: false
        selector:
          number:
            min: 0
            max: 20
            mode: box
            step: 1

      marker_entity:
        name: "Lauf-Marker (optional)"
        required: false
        selector:
          entity:
            domain: input_boolean

      log_nachricht:
        name: "Logbook-Nachricht (optional)"
        required: false
        selector:
          text:

      log_label:
        name: "Logbook-Absender (optional)"
        required: false
        selector:
          text:

      delay_sekunden:
        name: "Nachlauf-Delay in Sekunden (optional)"
        required: false
        selector:
          number:
            min: 0
            max: 300
            mode: box
            step: 5
            unit_of_measurement: "s"

    sequence:
    - alias: "Hilfsvariablen berechnen"
      variables:
        _marker: "{{ marker_entity | default('') }}"
        _label: "{{ log_label | default('Beschattung', true) }}"
        _delay: "{{ (delay_sekunden | default(30)) | int }}"
        _toleranz: "{{ (toleranz | default(3)) | int }}"
        _pos_aktuell: "{{ state_attr(cover_entity, 'current_position') | int(100) }}"
        _nachricht: >-
          {{ log_nachricht if (log_nachricht is defined and log_nachricht | trim != '')
             else (state_attr(cover_entity, 'friendly_name') ~ ' → Lüftungsposition '
             ~ (lueftungs_position | int) ~ '% (Fenster geöffnet)') }}

    - alias: "Lauf-Marker aktivieren, falls angegeben"
      if:
      - condition: template
        value_template: "{{ _marker != '' }}"
      then:
      - action: input_boolean.turn_on
        target:
          entity_id: "{{ _marker }}"

    - alias: "Aktuelle Position im Speicher-Helper sichern"
      action: input_number.set_value
      target:
        entity_id: "{{ position_speicher_entity }}"
      data:
        value: "{{ _pos_aktuell }}"

    - alias: "Auf Lüftungsposition hochfahren, falls Cover zu weit unten"
      if:
      - condition: template
        value_template: "{{ _pos_aktuell < (lueftungs_position | int - _toleranz | int) }}"
      then:
      - action: cover.set_cover_position
        target:
          entity_id: "{{ cover_entity }}"
        data:
          position: "{{ lueftungs_position | int }}"
      - action: logbook.log
        data:
          name: "{{ _label }}"
          entity_id: "{{ cover_entity }}"
          message: "{{ _nachricht }}"

    - alias: "Nachlauf-Delay abwarten und Lauf-Marker zuruecksetzen, falls angegeben"
      if:
      - condition: template
        value_template: "{{ _marker != '' }}"
      then:
      - delay:
          seconds: "{{ _delay }}"
      - action: input_boolean.turn_off
        target:
          entity_id: "{{ _marker }}"

template:
# Hitzeschutz-Binary-Sensor: EIN wenn Forecast >= 7 Stunden >= Schwelle bis Sonnenuntergang.
# Selbsthaltung bis Abkühlung (30 Min durchgehend 1°C unter Schwelle) oder Sonnenuntergang.
- triggers:
  - trigger: time_pattern
    id: zeit
    minutes: "/15"
  - trigger: state
    id: forecast_update
    entity_id: weather.forecast_home
  - trigger: event
    id: reload
    event_type: event_template_reloaded
  - trigger: homeassistant
    id: start
    event: start
  - trigger: state
    id: schwelle_geaendert
    entity_id: input_number.beschattung_schwelle_aussen_hitzeschutz
  - trigger: template
    id: abkuehlung
    value_template: >-
      {{ states('sensor.beschattung_aussentemperatur') | float(99)
         <= states('input_number.beschattung_schwelle_aussen_hitzeschutz') | float(27)
      - 1 }}
    for:
      minutes: 30

  actions:
  - action: weather.get_forecasts
    target:
      entity_id: weather.forecast_home
    data:
      type: hourly
    response_variable: forecast_data

  binary_sensor:
  - name: "Beschattung Hitzeschutz Noetig"
    unique_id: beschattung_hitzeschutz_noetig
    icon: mdi:sun-thermometer
    state: >-
      {% set benoetigt = 7 %}
      {% set schwelle = states('input_number.beschattung_schwelle_aussen_hitzeschutz') | float(27) %}
      {% set puffer_min = states('input_number.beschattung_hitzeschutz_sonnenuntergang_puffer') | int(60) %}
      {% set sunset_raw = state_attr('sun.sun', 'next_setting') %}
      {% set sunset = as_datetime(sunset_raw) if sunset_raw else now() %}
      {% set sonne_ok = state_attr('sun.sun', 'elevation') | float(0) > 0
         and now().timestamp() < sunset.timestamp() - puffer_min * 60 %}
      {% set ns = namespace(n=0) %}
      {% for h in (forecast_data | default({})).get('weather.forecast_home', {}).get('forecast', []) %}
        {% if as_datetime(h.datetime) < sunset and h.temperature | float(-99) >= schwelle %}
          {% set ns.n = ns.n + 1 %}
        {% endif %}
      {% endfor %}
      {% set war_an = this.state == 'on' %}
      {{ trigger.id | default('') != 'abkuehlung'
         and sonne_ok
         and (ns.n >= benoetigt or war_an) }}
    attributes:
      heisse_stunden: >-
        {% set schwelle = states('input_number.beschattung_schwelle_aussen_hitzeschutz') | float(27) %}
        {% set sunset_raw = state_attr('sun.sun', 'next_setting') %}
        {% set sunset = as_datetime(sunset_raw) if sunset_raw else now() %}
        {% set ns = namespace(n=0) %}
        {% for h in (forecast_data | default({})).get('weather.forecast_home', {}).get('forecast', []) %}
          {% if as_datetime(h.datetime) < sunset and h.temperature | float(-99) >= schwelle %}
            {% set ns.n = ns.n + 1 %}
          {% endif %}
        {% endfor %}
        {{ ns.n }}