blueprint:
  name: Motion Light – Tradfri Tunable White Kompatibel
  description: >
    Bewegungslicht-Blueprint für Tunable-White-Leuchten.
    Helligkeit und Kelvin werden für IKEA Tradfri getrennt gesendet.
    Ein Smart-Reassert korrigiert anschließend nur tatsächlich abweichende
    Lampen. Der optionale Nacht-Helper hat Vorrang vor der Uhrzeit. Der beim
    Einschalten gewählte Tag-/Nachtmodus bleibt bis zum finalen Ausschalten
    gespeichert.
  domain: automation
  homeassistant:
    min_version: 2024.10.0
  input:
    entities:
      name: Geräte und Entitäten
      icon: mdi:motion-sensor
      collapsed: false
      input:
        motion_sensors:
          name: Bewegungs- oder Präsenzsensoren
          description: >
            Ein oder mehrere binary_sensor-Entities; aktiv ist der Zustand
            "on". Erst wenn alle Sensoren exakt "off" sind, wird ausgeschaltet.
          selector:
            entity:
              filter:
                - domain: binary_sensor
              multiple: true
        light_entities:
          name: Lampen
          description: >
            Eine oder mehrere light-Entities; einzelne Lampen und
            Home-Assistant-Light-Groups sind zulässig.
          selector:
            entity:
              filter:
                - domain: light
              multiple: true
        enable_helper:
          name: Enable-/Disable-Helper (optional)
          description: >
            Wenn ausgewählt, arbeitet die Automation nur bei "on".
            Leer bedeutet ausdrücklich: kein Helper.
          default: ""
          selector:
            entity:
              filter:
                - domain: input_boolean

    night_mode:
      name: Tag- und Nachtmodus
      icon: mdi:weather-night
      collapsed: false
      input:
        night_mode_enabled:
          name: Zeitgesteuerten Nachtmodus aktivieren
          description: >
            Gilt ohne Nacht-Helper. Ein ausgewählter Nacht-Helper hat Vorrang.
          default: true
          selector:
            boolean:
        night_start:
          name: Nachtmodus beginnt
          description: >
            24:00 beziehungsweise 24:00:00 wird intern als 00:00:00 behandelt.
          default: "22:00:00"
          selector:
            time:
        night_end:
          name: Nachtmodus endet
          description: >
            24:00 beziehungsweise 24:00:00 wird intern als 00:00:00 behandelt.
          default: "06:00:00"
          selector:
            time:
        night_mode_helper:
          name: Nachtmodus-Helper (optional)
          description: >
            Wenn ausgewählt, bedeutet "on" Nacht und "off" Tag; die Uhrzeit
            wird dann nicht ausgewertet.
          default: ""
          selector:
            entity:
              filter:
                - domain: input_boolean

    normal_light:
      name: Normales Licht
      icon: mdi:lightbulb-on
      collapsed: true
      input:
        normal_brightness_pct:
          name: Helligkeit
          default: 80
          selector:
            number:
              min: 1
              max: 100
              step: 1
              mode: slider
              unit_of_measurement: "%"
        normal_color_temp_kelvin:
          name: Farbtemperatur
          default: 3500
          selector:
            color_temp:
              unit: kelvin
              min: 2000
              max: 6500
        normal_transition:
          name: Einschalt-Transition
          default: 1
          selector:
            number:
              min: 0
              max: 10
              step: 0.1
              mode: box
              unit_of_measurement: s

    night_light:
      name: Nachtlicht
      icon: mdi:lightbulb-night
      collapsed: true
      input:
        night_brightness_pct:
          name: Helligkeit
          default: 15
          selector:
            number:
              min: 1
              max: 100
              step: 1
              mode: slider
              unit_of_measurement: "%"
        night_color_temp_kelvin:
          name: Farbtemperatur
          default: 2200
          selector:
            color_temp:
              unit: kelvin
              min: 2000
              max: 6500
        night_transition:
          name: Einschalt-Transition
          default: 1
          selector:
            number:
              min: 0
              max: 10
              step: 0.1
              mode: box
              unit_of_measurement: s

    turn_off:
      name: Ausschaltverhalten
      icon: mdi:lightbulb-off
      collapsed: false
      input:
        no_motion_delay:
          name: Zeit ohne Bewegung
          default:
            minutes: 5
          selector:
            duration:
        warning_enabled:
          name: Warn-Dimmen aktivieren
          default: true
          selector:
            boolean:
        warning_duration:
          name: Dauer des Warn-Dimmens
          default:
            seconds: 10
          selector:
            duration:
        warning_transition:
          name: Transition beim Warn-Dimmen
          default: 1
          selector:
            number:
              min: 0
              max: 10
              step: 0.1
              mode: box
              unit_of_measurement: s
        warning_level_pct:
          name: Warn-Helligkeit
          description: >
            Prozent der Zielhelligkeit des gespeicherten Tag-/Nachtmodus.
          default: 25
          selector:
            number:
              min: 1
              max: 100
              step: 1
              mode: slider
              unit_of_measurement: "% des Zielwerts"
        off_transition:
          name: Ausschalt-Transition
          default: 2
          selector:
            number:
              min: 0
              max: 30
              step: 0.1
              mode: box
              unit_of_measurement: s
        off_retry_delay:
          name: Verzögerung bis zur Ausschaltkontrolle
          description: >
            Danach werden standardmäßig nur Lampen erneut ausgeschaltet, die
            Home Assistant noch als eingeschaltet meldet.
          default:
            seconds: 3
          selector:
            duration:
        force_off_retry:
          name: Zweiten Ausschaltbefehl immer senden
          description: >
            Sendet den zweiten Off-Befehl auch an bereits als "off" gemeldete
            Lampen. Das kann Restglimmen beheben, bei manchen Tradfri-Lampen
            aber ein kurzes Aufblitzen verursachen.
          default: false
          selector:
            boolean:

    tradfri:
      name: Tradfri-Kompatibilität
      icon: mdi:zigbee
      collapsed: false
      input:
        color_command_delay:
          name: Verzögerung Helligkeit → Kelvin
          description: >
            Der Kelvin-Befehl enthält keine erneute Helligkeitsangabe.
          default:
            seconds: 1
          selector:
            duration:
        brightness_check_delay:
          name: Verzögerung bis zur Helligkeitskontrolle
          description: >
            Ab dem ersten Helligkeitsbefehl gemessen. Mindestens so lang wie
            Kelvin-Verzögerung und gewünschte Einschalt-Transition wählen.
          default:
            seconds: 3
          selector:
            duration:
        brightness_tolerance:
          name: Helligkeitstoleranz
          description: Home-Assistant-Skala von 0 bis 255.
          default: 10
          selector:
            number:
              min: 0
              max: 50
              step: 1
              mode: slider

trigger_variables:
  motion_trigger_entities: !input motion_sensors

# Ein Aggregat-Trigger feuert nur beim Übergang
# "kein Sensor on" -> "mindestens ein Sensor on".
triggers:
  - trigger: template
    value_template: >-
      {% set ns = namespace(any_on=false) %}
      {% for entity_id in motion_trigger_entities %}
        {% if is_state(entity_id, 'on') %}
          {% set ns.any_on = true %}
        {% endif %}
      {% endfor %}
      {{ ns.any_on }}

variables:
  motion_entities: !input motion_sensors
  lights: !input light_entities
  enable_helper_entity: !input enable_helper
  night_helper_entity: !input night_mode_helper
  use_timed_night_mode: !input night_mode_enabled
  night_start_time: !input night_start
  night_end_time: !input night_end
  normal_brightness: !input normal_brightness_pct
  normal_kelvin: !input normal_color_temp_kelvin
  normal_on_transition: !input normal_transition
  night_brightness: !input night_brightness_pct
  night_kelvin: !input night_color_temp_kelvin
  night_on_transition: !input night_transition
  no_motion_duration: !input no_motion_delay
  use_warning: !input warning_enabled
  warning_transition_seconds: !input warning_transition
  warning_factor: !input warning_level_pct
  brightness_tolerance_value: !input brightness_tolerance
  off_transition_seconds: !input off_transition
  force_off_retry_enabled: !input force_off_retry

conditions: []

# Ein einzelner Lauf hält den gewählten Modus bis zum vollständigen Ausschalten.
# Bewegung während Warte-/Warnphasen wird intern verarbeitet.
mode: single
max_exceeded: silent

actions:
  # Leere Helper-Auswahl ist kein versehentlich erfüllter State-Test.
  - condition: template
    value_template: >-
      {% set selected = enable_helper_entity is string
        and enable_helper_entity | length > 0 %}
      {{ not selected or is_state(enable_helper_entity, 'on') }}

  # Pflichtauswahlen zusätzlich absichern: niemals mit leeren Targets laufen.
  - condition: template
    value_template: >-
      {{ motion_entities | count > 0 and lights | count > 0 }}

  # Modus-Snapshot: Helper vor Uhrzeit; start=end bedeutet kein Zeitfenster.
  - variables:
      night_mode_active: >-
        {% set selected = night_helper_entity is string
          and night_helper_entity | length > 0 %}
        {% if selected %}
          {{ is_state(night_helper_entity, 'on') }}
        {% elif not (use_timed_night_mode | bool) %}
          {{ false }}
        {% else %}
          {# today_at() akzeptiert 24:00 nicht; Mitternacht normalisieren. #}
          {% set normalized_start = '00:00:00'
            if night_start_time in ['24:00', '24:00:00']
            else night_start_time %}
          {% set normalized_end = '00:00:00'
            if night_end_time in ['24:00', '24:00:00']
            else night_end_time %}
          {% set start = today_at(normalized_start) %}
          {% set end = today_at(normalized_end) %}
          {% set current = now() %}
          {% if start < end %}
            {{ start <= current < end }}
          {% elif start > end %}
            {{ current >= start or current < end }}
          {% else %}
            {{ false }}
          {% endif %}
        {% endif %}
      resolved_lights: >-
        {% set ns = namespace(ids=[]) %}
        {% for entity_id in lights %}
          {% set members = state_attr(entity_id, 'entity_id') %}
          {% if members is iterable and members is not string
                and members | count > 0 %}
            {% set ns.ids = ns.ids + (members | list) %}
          {% else %}
            {% set ns.ids = ns.ids + [entity_id] %}
          {% endif %}
        {% endfor %}
        {{ expand(ns.ids)
          | selectattr('domain', 'eq', 'light')
          | map(attribute='entity_id')
          | unique
          | list }}

  - variables:
      target_brightness_pct: >-
        {{ (night_brightness if night_mode_active | bool
          else normal_brightness) | int }}
      target_color_temp_kelvin: >-
        {{ (night_kelvin if night_mode_active | bool
          else normal_kelvin) | int }}
      target_on_transition: >-
        {{ (night_on_transition if night_mode_active | bool
          else normal_on_transition) | float }}
      warning_brightness_pct: >-
        {% set value =
          (target_brightness_pct | float * warning_factor | float / 100)
          | round(0) | int %}
        {{ [1, value] | max }}

  - variables:
      initial_brightness_targets: >-
        {% set target = target_brightness_pct | float * 255 / 100 %}
        {% set tolerance = brightness_tolerance_value | float %}
        {% set ns = namespace(ids=[]) %}
        {% for entity_id in resolved_lights %}
          {% set reported = state_attr(entity_id, 'brightness') %}
          {% if states(entity_id) != 'on'
                or reported is none
                or not is_number(reported)
                or (((reported | float) - target) | abs) > tolerance %}
            {% set ns.ids = ns.ids + [entity_id] %}
          {% endif %}
        {% endfor %}
        {{ ns.ids }}

  # Sofort: nur Helligkeit und Transition, niemals Kelvin.
  - if:
      - condition: template
        value_template: "{{ initial_brightness_targets | count > 0 }}"
    then:
      - action: light.turn_on
        target:
          entity_id: "{{ initial_brightness_targets }}"
        data:
          brightness_pct: "{{ target_brightness_pct }}"
          transition: "{{ target_on_transition }}"

  # Beide Delays beginnen am selben Punkt: Kelvin bei t=1 s, Check bei t=3 s.
  - parallel:
      - sequence:
          - delay: !input color_command_delay
          - condition: template
            value_template: >-
              {% set selected = enable_helper_entity is string
                and enable_helper_entity | length > 0 %}
              {{ (not selected or is_state(enable_helper_entity, 'on'))
                and resolved_lights | count > 0 }}
          # Tradfri: Kelvin und Transition, aber weiterhin keine Helligkeit.
          - action: light.turn_on
            target:
              entity_id: "{{ resolved_lights }}"
            data:
              color_temp_kelvin: "{{ target_color_temp_kelvin }}"
              transition: "{{ target_on_transition }}"

      - sequence:
          - delay: !input brightness_check_delay
          - condition: template
            value_template: >-
              {% set selected = enable_helper_entity is string
                and enable_helper_entity | length > 0 %}
              {{ not selected or is_state(enable_helper_entity, 'on') }}
          - variables:
              reassert_targets: >-
                {% set target = target_brightness_pct | float * 255 / 100 %}
                {% set tolerance = brightness_tolerance_value | float %}
                {% set ns = namespace(ids=[]) %}
                {% for entity_id in resolved_lights %}
                  {% set reported = state_attr(entity_id, 'brightness') %}
                  {% if states(entity_id) != 'on'
                        or reported is none
                        or not is_number(reported)
                        or (((reported | float) - target) | abs) > tolerance %}
                    {% set ns.ids = ns.ids + [entity_id] %}
                  {% endif %}
                {% endfor %}
                {{ ns.ids }}
          # Nur abweichende Lampen: wie beim ersten Einschalten zunächst
          # Helligkeit plus Transition, danach Kelvin als separater Befehl.
          - if:
              - condition: template
                value_template: "{{ reassert_targets | count > 0 }}"
            then:
              - action: light.turn_on
                target:
                  entity_id: "{{ reassert_targets }}"
                data:
                  brightness_pct: "{{ target_brightness_pct }}"
                  transition: "{{ target_on_transition }}"
              - delay: !input color_command_delay
              - if:
                  - condition: template
                    value_template: >-
                      {% set selected = enable_helper_entity is string
                        and enable_helper_entity | length > 0 %}
                      {{ (not selected
                        or is_state(enable_helper_entity, 'on'))
                        and reassert_targets | count > 0 }}
                then:
                  # Tradfri: Kelvin bleibt auch beim Reassert getrennt.
                  - action: light.turn_on
                    target:
                      entity_id: "{{ reassert_targets }}"
                    data:
                      color_temp_kelvin: "{{ target_color_temp_kelvin }}"
                      transition: "{{ target_on_transition }}"

  # Mode-Latch: Dieser Lauf umfasst den kompletten Beleuchtungszyklus.
  # Zeit- oder Helper-Wechsel ändern target_* deshalb nicht mehr. Neue Bewegung
  # wird innerhalb der Schleife behandelt und startet keinen neuen Lauf.
  - repeat:
      while:
        - condition: template
          value_template: >-
            {% set selected = enable_helper_entity is string
              and enable_helper_entity | length > 0 %}
            {{ not selected or is_state(enable_helper_entity, 'on') }}
      sequence:
        # Erst alle exakt "off"; unknown/unavailable blockiert das Ausschalten.
        - wait_template: >-
            {% set selected = enable_helper_entity is string
              and enable_helper_entity | length > 0 %}
            {% set disabled = selected
              and not is_state(enable_helper_entity, 'on') %}
            {% set ns = namespace(all_off=(motion_entities | count > 0)) %}
            {% for entity_id in motion_entities %}
              {% if not is_state(entity_id, 'off') %}
                {% set ns.all_off = false %}
              {% endif %}
            {% endfor %}
            {{ disabled or ns.all_off }}

        - if:
            - condition: template
              value_template: >-
                {% set selected = enable_helper_entity is string
                  and enable_helper_entity | length > 0 %}
                {{ not selected or is_state(enable_helper_entity, 'on') }}
          then:
            # Bereits verstrichene Inaktivität während der Tradfri-Prüfung
            # wird von der konfigurierten Nachlaufzeit abgezogen.
            - variables:
                no_motion_remaining_seconds: >-
                  {% set d = no_motion_duration %}
                  {% set total =
                    (d.get('days', 0) | float * 86400)
                    + (d.get('hours', 0) | float * 3600)
                    + (d.get('minutes', 0) | float * 60)
                    + (d.get('seconds', 0) | float)
                    + (d.get('milliseconds', 0) | float / 1000) %}
                  {% set ns = namespace(latest=0) %}
                  {% for sensor in expand(motion_entities) %}
                    {% set changed = as_timestamp(sensor.last_changed, 0) %}
                    {% if changed > ns.latest %}
                      {% set ns.latest = changed %}
                    {% endif %}
                  {% endfor %}
                  {% set elapsed = as_timestamp(now()) - ns.latest
                    if ns.latest > 0 else 0 %}
                  {% set remaining = total - elapsed %}
                  {{ 0 if remaining <= 0
                    else remaining | round(0, 'ceil') | int }}

            # Bewegung beendet den Nachlauf sofort; die Schleife beginnt erneut.
            - wait_template: >-
                {% set selected = enable_helper_entity is string
                  and enable_helper_entity | length > 0 %}
                {% set disabled = selected
                  and not is_state(enable_helper_entity, 'on') %}
                {% set ns = namespace(any_on=false) %}
                {% for entity_id in motion_entities %}
                  {% if is_state(entity_id, 'on') %}
                    {% set ns.any_on = true %}
                  {% endif %}
                {% endfor %}
                {{ disabled or ns.any_on }}
              timeout:
                seconds: "{{ no_motion_remaining_seconds }}"
              continue_on_timeout: true

            # wait.completed bleibt auch dann true, wenn ein sehr kurzer
            # Bewegungsimpuls beim nächsten Schritt schon wieder "off" ist.
            - variables:
                no_motion_interrupted: "{{ wait.completed }}"

            # Bei Bewegung im normalen Nachlauf werden nur Lampen korrigiert,
            # deren Rückmeldung wirklich vom gespeicherten Ziel abweicht.
            - variables:
                motion_restore_targets: >-
                  {% set target = target_brightness_pct | float * 255 / 100 %}
                  {% set tolerance = brightness_tolerance_value | float %}
                  {% set ns = namespace(ids=[]) %}
                  {% for entity_id in resolved_lights %}
                    {% set reported = state_attr(entity_id, 'brightness') %}
                    {% if states(entity_id) != 'on'
                          or reported is none
                          or not is_number(reported)
                          or (((reported | float) - target) | abs) > tolerance %}
                      {% set ns.ids = ns.ids + [entity_id] %}
                    {% endif %}
                  {% endfor %}
                  {{ ns.ids }}
            - if:
                - condition: template
                  value_template: >-
                    {% set selected = enable_helper_entity is string
                      and enable_helper_entity | length > 0 %}
                    {{ (not selected or is_state(enable_helper_entity, 'on'))
                      and (no_motion_interrupted | bool)
                      and motion_restore_targets | count > 0 }}
              then:
                - action: light.turn_on
                  target:
                    entity_id: "{{ motion_restore_targets }}"
                  data:
                    brightness_pct: "{{ target_brightness_pct }}"
                - delay: !input color_command_delay
                - if:
                    - condition: template
                      value_template: >-
                        {% set selected = enable_helper_entity is string
                          and enable_helper_entity | length > 0 %}
                        {{ (not selected
                          or is_state(enable_helper_entity, 'on'))
                          and motion_restore_targets | count > 0 }}
                  then:
                    - action: light.turn_on
                      target:
                        entity_id: "{{ motion_restore_targets }}"
                      data:
                        color_temp_kelvin: "{{ target_color_temp_kelvin }}"
                        transition: "{{ target_on_transition }}"

            # Nur ein echter Timeout bei weiterhin vollständig inaktiven Sensoren
            # darf Warn-Dimmen und den finalen Ausschaltpfad betreten.
            - if:
                - condition: template
                  value_template: >-
                    {% set selected = enable_helper_entity is string
                      and enable_helper_entity | length > 0 %}
                    {% set ns = namespace(all_off=(motion_entities | count > 0)) %}
                    {% for entity_id in motion_entities %}
                      {% if not is_state(entity_id, 'off') %}
                        {% set ns.all_off = false %}
                      {% endif %}
                    {% endfor %}
                    {{ (not selected or is_state(enable_helper_entity, 'on'))
                      and not (no_motion_interrupted | bool)
                      and ns.all_off }}
              then:
                # Ohne Warn-Dimmen bleibt dieses Flag false. Die Variable wird
                # vor dem verschachtelten If angelegt, damit dessen Update im
                # gesamten Script-Lauf sichtbar bleibt.
                - variables:
                    warning_interrupted: false

                # Warn-Dimmen basiert direkt auf dem gespeicherten Zielwert.
                - if:
                    - condition: template
                      value_template: "{{ use_warning | bool }}"
                  then:
                    - variables:
                        warning_targets: >-
                          {% set ns = namespace(ids=[]) %}
                          {% for entity_id in resolved_lights %}
                            {% if is_state(entity_id, 'on') %}
                              {% set ns.ids = ns.ids + [entity_id] %}
                            {% endif %}
                          {% endfor %}
                          {{ ns.ids }}
                    - if:
                        - condition: template
                          value_template: >-
                            {{ warning_targets | count > 0 }}
                      then:
                        - action: light.turn_on
                          target:
                            entity_id: "{{ warning_targets }}"
                          data:
                            brightness_pct: "{{ warning_brightness_pct }}"
                            transition: "{{ warning_transition_seconds }}"
                    - wait_template: >-
                        {% set selected = enable_helper_entity is string
                          and enable_helper_entity | length > 0 %}
                        {% set disabled = selected
                          and not is_state(enable_helper_entity, 'on') %}
                        {% set ns = namespace(any_on=false) %}
                        {% for entity_id in motion_entities %}
                          {% if is_state(entity_id, 'on') %}
                            {% set ns.any_on = true %}
                          {% endif %}
                        {% endfor %}
                        {{ disabled or ns.any_on }}
                      timeout: !input warning_duration
                      continue_on_timeout: true
                    - variables:
                        warning_interrupted: "{{ wait.completed }}"

                # Bei Bewegung im Warnfenster wird exakt der gespeicherte Modus
                # wiederhergestellt, nicht aus der Warnhelligkeit zurückgerechnet.
                - if:
                    - condition: template
                      value_template: >-
                        {% set selected = enable_helper_entity is string
                          and enable_helper_entity | length > 0 %}
                        {{ use_warning | bool
                          and (not selected
                            or is_state(enable_helper_entity, 'on'))
                          and (warning_interrupted | bool)
                          and resolved_lights | count > 0 }}
                  then:
                    - action: light.turn_on
                      target:
                        entity_id: "{{ resolved_lights }}"
                      data:
                        brightness_pct: "{{ target_brightness_pct }}"
                        transition: "{{ warning_transition_seconds }}"
                    - delay: !input color_command_delay
                    - if:
                        - condition: template
                          value_template: >-
                            {% set selected = enable_helper_entity is string
                              and enable_helper_entity | length > 0 %}
                            {{ (not selected
                              or is_state(enable_helper_entity, 'on'))
                              and resolved_lights | count > 0 }}
                      then:
                        # Tradfri: auch beim Restore Helligkeit und Kelvin trennen.
                        - action: light.turn_on
                          target:
                            entity_id: "{{ resolved_lights }}"
                          data:
                            color_temp_kelvin: >-
                              {{ target_color_temp_kelvin }}
                            transition: "{{ warning_transition_seconds }}"

                # Ohne neue Bewegung beginnt jetzt der einzige finale Off-Pfad.
                - if:
                    - condition: template
                      value_template: >-
                        {% set selected = enable_helper_entity is string
                          and enable_helper_entity | length > 0 %}
                        {% set ns = namespace(all_off=(motion_entities | count > 0)) %}
                        {% for entity_id in motion_entities %}
                          {% if not is_state(entity_id, 'off') %}
                            {% set ns.all_off = false %}
                          {% endif %}
                        {% endfor %}
                        {{ (not selected
                          or is_state(enable_helper_entity, 'on'))
                          and not (warning_interrupted | bool)
                          and ns.all_off and lights | count > 0 }}
                  then:
                    - action: light.turn_off
                      target:
                        entity_id: "{{ lights }}"
                      data:
                        transition: "{{ off_transition_seconds }}"

                    # Auch der Off-Retry ist bewegungsreaktiv. Bewegung erhält
                    # den alten Snapshot, solange der Off-Pfad nicht vollständig
                    # abgeschlossen wurde.
                    - wait_template: >-
                        {% set selected = enable_helper_entity is string
                          and enable_helper_entity | length > 0 %}
                        {% set disabled = selected
                          and not is_state(enable_helper_entity, 'on') %}
                        {% set ns = namespace(any_on=false) %}
                        {% for entity_id in motion_entities %}
                          {% if is_state(entity_id, 'on') %}
                            {% set ns.any_on = true %}
                          {% endif %}
                        {% endfor %}
                        {{ disabled or ns.any_on }}
                      timeout: !input off_retry_delay
                      continue_on_timeout: true
                    - variables:
                        off_retry_interrupted: "{{ wait.completed }}"

                    - if:
                        - condition: template
                          value_template: >-
                            {% set selected = enable_helper_entity is string
                              and enable_helper_entity | length > 0 %}
                            {{ (not selected
                              or is_state(enable_helper_entity, 'on'))
                              and (off_retry_interrupted | bool)
                              and resolved_lights | count > 0 }}
                      then:
                        - action: light.turn_on
                          target:
                            entity_id: "{{ resolved_lights }}"
                          data:
                            brightness_pct: "{{ target_brightness_pct }}"
                        - delay: !input color_command_delay
                        - if:
                            - condition: template
                              value_template: >-
                                {% set selected =
                                  enable_helper_entity is string
                                  and enable_helper_entity | length > 0 %}
                                {{ (not selected
                                  or is_state(enable_helper_entity, 'on'))
                                  and resolved_lights | count > 0 }}
                          then:
                            - action: light.turn_on
                              target:
                                entity_id: "{{ resolved_lights }}"
                              data:
                                color_temp_kelvin: >-
                                  {{ target_color_temp_kelvin }}
                                transition: "{{ target_on_transition }}"
                      else:
                        - if:
                            - condition: template
                              value_template: >-
                                {% set selected =
                                  enable_helper_entity is string
                                  and enable_helper_entity | length > 0 %}
                                {% set ns = namespace(
                                  all_off=(motion_entities | count > 0)) %}
                                {% for entity_id in motion_entities %}
                                  {% if not is_state(entity_id, 'off') %}
                                    {% set ns.all_off = false %}
                                  {% endif %}
                                {% endfor %}
                                {{ (not selected
                                  or is_state(enable_helper_entity, 'on'))
                                  and not (off_retry_interrupted | bool)
                                  and ns.all_off and lights | count > 0 }}
                          then:
                            - variables:
                                off_retry_targets: >-
                                  {% if force_off_retry_enabled | bool %}
                                    {{ lights }}
                                  {% else %}
                                    {% set ns = namespace(ids=[]) %}
                                    {% for entity_id in resolved_lights %}
                                      {% if is_state(entity_id, 'on') %}
                                        {% set ns.ids = ns.ids + [entity_id] %}
                                      {% endif %}
                                    {% endfor %}
                                    {{ ns.ids }}
                                  {% endif %}
                            - if:
                                - condition: template
                                  value_template: >-
                                    {{ off_retry_targets | count > 0 }}
                              then:
                                - action: light.turn_off
                                  target:
                                    entity_id: "{{ off_retry_targets }}"
                            # Auch ohne Retry-Target muss die Repeat-Schleife enden.
                            - stop: Beleuchtungszyklus vollständig beendet
