Sync Your Tapo TP-Link L920-5 RGBIC LED Strip with PS5 Game Covers in Home Assistant

I built a Home Assistant automation that syncs a TP-Link Tapo L920-5 (5m RGBIC) LED strip with my PlayStation 5. Whenever the PS5 starts playing a game, Home Assistant grabs the game cover art (entity picture) and extracts a color palette from it, then pushes a short RGBIC color sequence to the LED strip. The result is a simple “ambilight-style” effect that matches the game you’re currently playing.

In this post I’ll share the full setup: required entities, the palette extraction step, and the Home Assistant automation/service call to run the color sequence. It’s lightweight, fully local on the HA side (apart from fetching the cover image), and you can tweak brightness, transition speed, and number of colors easily.

Requirements: You’ll need the PlayStation Network integration in Home Assistant to provide the “now playing” game cover (entity picture). My setup runs on Home Assistant in Docker, and I recommend Home Assistant 2025.12 or newer.

entity_id=image.psn_account_spielt_gerade
led: light.ledstripe01

Let’s start with Home Assistant REST API

Edit your configuration.yaml and add a REST Sensor:

    rest:                                          
      - resource: "http://<strong>IPORHOSTNAMEFROMDOCKERCONTAINER</strong>:8787/palette?entity_id=image.psn_account_spielt_gerade&k=6"
        scan_interval: 20                            
        sensor:                                               
          - name: psn_logo_palette           
            value_template: "{{ value_json.dominant_hex }}"
            json_attributes:                                                                                                                                        
              - sequence                                                                                                                                             
              - brightness                                                                                                                                           
              - transition                                                          
              - spread                       
              - directionCode-Sprache: HTML, XML (xml)

    Replace IPORHOSTNAMEFROMDOCKERCONTAINER with the IP address or hostname of your Docker container (see the next step).

    Create Docker Container

    First, we need a python script

    sudo mkdir /opt/palette-api/
    sudo touch /opt/palette-api/palette_api.py
    sudo vi /opt/palette-api/palette_api.py
    def extract_palette(img: Image.Image, k: int = 6):
        img = img.convert('RGB').resize((260, 260))
    
        # 1) Crop: 12% Rand weg (UI/Badges/Border reduzieren)
        w, h = img.size
        m = int(min(w, h) * 0.12)
        img = img.crop((m, m, w - m, h - m))
    
        # 2) Quantize -> Kandidaten
        q = img.quantize(colors=96, method=2)
        pal = q.getpalette()
        counts = q.getcolors() or []
        counts.sort(reverse=True, key=lambda x: x[0])
    
        candidates = []
        for cnt, idx in counts[:96]:
            rgb = (pal[idx*3], pal[idx*3+1], pal[idx*3+2])
            h, s, v = rgb_to_hsv_triplet(rgb)
    
            # 3) Filter: zu grau/zu dunkel/zu hell rauswerfen
            if s < 12:
                continue
            if v < 8 or v > 98:
                continue
    
            candidates.append((cnt, rgb, h, s, v))
    
        if not candidates:
            return [(0, 120, 255)] * k, 200  # fallback blau
    
        # 4) Auswahl: Hue-divers + RGB-divers (damit Blau/Rot/Highlights reinkommen)
        picked = []
        picked_hues = []
    
        def hue_dist(a, b):
            d = abs(a - b) % 360
            return min(d, 360 - d)
    
        for cnt, rgb, h, s, v in candidates:
            if any(hue_dist(h, ph) < 22 for ph in picked_hues):
                continue
            if any(sum((a-b)**2 for a, b in zip(rgb, prgb)) < 800 for prgb in picked):
                continue
            picked.append(rgb)
            picked_hues.append(h)
            if len(picked) >= k:
                break
    
        # Wenn wir noch nicht genug haben: ohne Hue-Regel auffuellen
        if len(picked) < k:
            for cnt, rgb, h, s, v in candidates:
                if any(sum((a-b)**2 for a, b in zip(rgb, prgb)) < 600 for prgb in picked):
                    continue
                picked.append(rgb)
                if len(picked) >= k:
                    break
    
        while len(picked) < k:
            picked.append(picked[-1])
    
        # dominanter Hue = haeufigste (nach Filter)
        dominant_hue = candidates[0][2]
        return picked, dominant_hue
    
    Code-Sprache: PHP (php)

    now create the Docker Container for example Portainer -> Create new stack (Web), Name: palette-api

    version: "3.8"
    
    services:
      palette-api:
        image: python:3.11
        container_name: palette-api
        restart: unless-stopped
    
        networks:
          <strong>IOT-600</strong>:
            ipv4_address: <strong>192.168.1.60</strong>
    
        environment:
          HA_BASE_URL: "http://<strong>IPFROMYOURHOMEASSISTANTSERVER</strong>:8123"
          HA_TOKEN: "<strong>PASTE_YOUR_TOKEN_HERE</strong>"
    
        volumes:
          - /opt/palette-api/palette_api.py:/app/palette_api.py:ro
    
        command: >
          bash -lc "
          pip install --no-cache-dir fastapi uvicorn pillow requests &&
          uvicorn palette_api:app --app-dir /app --host 0.0.0.0 --port 8787
          "
    
    networks:
      <strong>IOT-600</strong>:
        external: true
    Code-Sprache: HTML, XML (xml)

    To make this work, you need a Long-Lived Access Token from Home Assistant and the hostname/IP of your Home Assistant server. Then you paste both into the container environment variables:

    Get your Home Assistant Long-Lived Access Token

    • Open Home Assistant in your browser.
    • Click your profile (bottom-left in the sidebar) — or go to Settings → People → Users → (your user).
    • Scroll down to Long-Lived Access Tokens.
    • Click Create Token, give it a name (e.g., palette-api), and confirm.
    • Copy the token immediately (you won’t be able to see it again later).
    • Now paste it into:

    HA_TOKEN: „PASTE_YOUR_TOKEN_HERE“

    Set the Home Assistant base URL (Hostname or IP)

    HA_BASE_URL must point to the URL where the Docker container can reach Home Assistant:

    If Home Assistant is on the same machine/LAN, use its LAN IP or hostname, e.g.
    http://192.168.1.50:8123 or http://homeassistant.local:8123

    Paste it into:

    HA_BASE_URL: „http://192.168.1.50:8123“

    Notes / common pitfalls

    Don’t use localhost unless the container runs in the same network namespace as Home Assistant (usually it won’t).

    Keep the token secret (don’t post it in screenshots/logs).

    If you use HTTPS externally, you can also set https://… — but the key thing is that the container must be able to reach that address.

    Update the network configuration to fit your setup. I use a macvlan network (IOT-600) because the container needs a separate IP/subnet from the Docker host—specifically an isolated IoT VLAN/network in my environment. If you don’t need that, switch to your preferred Docker networking mode.

    Now we have the palette-api Python script, the Docker container, and the REST API sensor(s) set up in Home Assistant. Next, validate your configuration (configuration.yaml) and then restart Home Assistant to make sure everything is loaded correctly.

    Once Home Assistant is back up, the last missing piece is the automation that triggers the palette extraction and sends the resulting color sequence to your lights.

    Home Assistant Automation:

    alias: "PSN -> LEDStripe: Sequence + Off on End"
    description: ""
    triggers:
      - entity_id: sensor.psn_account_spielt_gerade
        trigger: state
    actions:
      - variables:
          led: light.ledstripe01
          game_title: "{{ states('sensor.psn_account_spielt_gerade') | trim }}"
          ended: >-
            {{ game_title in ['unknown','unavailable','', None, 'None', 'none',
            'idle', 'standby', 'offline'] }}
      - choose:
          - conditions:
              - condition: template
                value_template: "{{ ended }}"
            sequence:
              - target:
                  entity_id: "{{ led }}"
                action: light.turn_off
              - stop: Game ended -> LED off
      - delay: "00:00:02"
      - target:
          entity_id: sensor.psn_logo_palette
        action: homeassistant.update_entity
      - delay: "00:00:02"
      - variables:
          seq: >-
            {{ state_attr('sensor.psn_logo_palette', 'sequence') | default([], true)
            }}
          bri: >-
            {{ state_attr('sensor.psn_logo_palette', 'brightness') | default(80,
            true) | int }}
          trans: >-
            {{ state_attr('sensor.psn_logo_palette', 'transition') | default(6000,
            true) | int }}
          spread: >-
            {{ state_attr('sensor.psn_logo_palette', 'spread') | default(3, true) |
            int }}
          direction: >-
            {{ state_attr('sensor.psn_logo_palette', 'direction') | default(1, true)
            | int }}
      - condition: template
        value_template: "{{ seq is iterable and (seq | length) >= 6 }}"
      - target:
          entity_id: "{{ led }}"
        data:
          sequence: "{{ seq }}"
          segments: 0
          brightness: "{{ bri }}"
          repeat_times: 0
          transition: "{{ trans }}"
          spread: "{{ spread }}"
          direction: "{{ direction }}"
        action: tplink.sequence_effect
    mode: restart
    Code-Sprache: JavaScript (javascript)

    Control WiZ HDMI Sync Box Dynamic Scenes with Home Assistant

    Yes — even if Home Assistant doesn’t currently show the Sync mode (e.g., “Cinematic”) as a visible state, you can still “remember” the mode inside Home Assistant and re-apply it automatically every time the WiZ HDMI Sync Box powers on.

    This workaround is useful because the WiZ HDMI Sync Box supports multiple Sync modes (Cinematic / Vibrant / Rhythmic / Relaxation), but the Home Assistant WiZ integration doesn’t always expose that mode reliably as an entity or state (depending on device model and firmware). As a result, Home Assistant often only shows basic On/Off — even though the box is switching modes internally.

    Solution idea: store the desired mode in Home Assistant, then re-set it on power-up via a local command. In practice, many users control the Sync Box using WiZ’s local UDP protocol on port 38899, sending a sceneId to select the mode — similar to the same local WiZ UDP / “setPilot” mechanism used by WiZ lights:

    • 201 = Cinematic (film-like)
    • 202 = Vibrant
    • 203 = Rhythmic
    • 204 = Relaxation

    With that, Home Assistant can reliably enforce your preferred mode whenever the Sync Box turns on, even if the integration doesn’t report the mode back.

    Step 1) Assign the Sync Box a fixed IP address
    Set a DHCP reservation for it in your router (important because we’ll be addressing it directly by IP).

    Step 2) Create a small Python script to send UDP commands
    On your Home Assistant system, create this file under /config/wiz_syncbox.py

    import socket, json, sys
    
    ip = sys.argv[1]
    scene = int(sys.argv[2])
    
    msg = {"id": 1, "method": "setPilot", "params": {"sceneId": scene}}
    
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.sendto(json.dumps(msg).encode("utf-8"), (ip, 38899))
    sock.close()Code-Sprache: JavaScript (javascript)

    Step 3) Add a shell_command to configuration.yaml
    (Replace 192.168.1.50 with your Sync Box’s IP address.)

    shell_command:
      wiz_syncbox_set_scene: "python3 /config/wiz_syncbox.py 192.168.1.50 {{ scene }}"Code-Sprache: JavaScript (javascript)

    Step 4) Automation: When your receiver turns on, set the Sync Box to your preferred dynamic scene

    alias: "Sync Box: Turn on + set Cinematic when Receiver starts playing"
    description: "Turns on the Sync Box smart plug and sets the WiZ Sync Box to a dynamic scene (e.g., Cinematic)."
    
    trigger:
      - platform: state
        entity_id: media_player.receiver
        to: "playing"
    
    condition: []
    
    action:
      # 1) Power on the Sync Box (via smart plug)
      - service: switch.turn_on
        target:
          entity_id: switch.sync_box_plug
    
      # 2) Wait for the Sync Box to boot up
      - delay: "00:00:30"
    
      # 3) Set the desired WiZ Sync mode / dynamic scene
      - service: shell_command.wiz_syncbox_set_scene
        data:
          scene: 201   # 201=Cinematic, 202=Vibrant, 203=Rhythmic, 204=Relaxation
    
    mode: singleCode-Sprache: PHP (php)

    Step 5) Test your configuration.yaml, and if everything looks OK, restart Home Assistant so the changes take effect

    Other IDs:

    WiZ HDMI TV Sync Box (TV Sync / “Sync” category)

    These IDs are specific to the Sync Box’s TV-sync modes:

    • 201 — Cinematic
    • 202 — Vibrant
    • 203 — Rhythmic
    • 204 — Relaxation

    Extra status you may see:

    • 205 — “Follow sync” (some lights in the sync area may report this while the Sync Box is driving them)

    Standard WiZ Scene IDs (commonly supported on WiZ lights)

    These are the classic WiZ “sceneId” values you’ll see across many bulbs/strips:

    • 0 — none
    • 1 Ocean
    • 2 Romance
    • 3 Sunset
    • 4 Party
    • 5 Fireplace
    • 6 Cozy
    • 7 Forest
    • 8 Pastel Colors
    • 9 Wake-up
    • 10 Bedtime
    • 11 Warm White
    • 12 Daylight
    • 13 Cool White
    • 14 Night Light
    • 15 Focus
    • 16 Relax
    • 17 True Colors
    • 18 TV Time
    • 19 Plant Growth
    • 20 Spring
    • 21 Summer
    • 22 Fall
    • 23 Deep Dive
    • 24 Jungle
    • 25 Mojito
    • 26 Club
    • 27 Christmas
    • 28 Halloween
    • 29 Candlelight
    • 30 Golden White
    • 31 Pulse
    • 32 Steampunk

    Additional / undocumented IDs (device + firmware dependent)

    36 — “Snowy Sky”

    • 37–41 (reported as supported on some RGB bulbs, but not exposed in the app / names unclear)

    So: 201–205 are the TV Sync Box / sync-related IDs, while 1–32 are the common WiZ scene IDs used by bulbs/strips, and 36+ may exist depending on firmware/device.

    Aquarium: PV-Optimized Lighting & Heating (Day) – Load Reduction After Sunset

    This automation optimizes your aquarium’s light and heater to make the most of PV surplus during the day and reduce power consumption after sunset. It’s designed for clear, reliable scheduling with sensible fallbacks—so your tank stays stable while your energy bill stays low.

    What it does

    • Uses daytime PV to run lighting and maintain temperature efficiently
    • Switches to a night-time eco mode to cut load after sunset
    • Keeps logic simple and transparent (easy to tweak in YAML)

    Important: Temperature first

    Fish and plants are sensitive to rapid temperature swings. Even small daily spikes or drops can stress the ecosystem. Please:

    • Monitor water temperature with a reliable sensor; set alerts for out-of-range values.
    • Aim for gradual changes (avoid frequent on/off cycles).
    • Improve insulation/cover (tight lid, reduced drafts, stable room temp) to keep heat in.
    • Size the heater appropriately; overspec’d heaters can cause overshoot.

    Safety note: Always test changes at a low-risk time and watch the tank closely for a few days. If temperature deviates, prioritize stability over savings by relaxing the eco schedule.

    Script:
    Go to Settings → Automations & Scenes → Scripts → + Add Script.
    ScriptName: script.aquarium_verify_after_30_minutes

    Use your own entity IDs. In this example and below:

    • Water temperature: sensor.sonoff_1002215fd9_temperature
    • Aquarium heater (switch): switch.aquarium_heizung_none
    • Aquarium heater power sensor: sensor.aquarium_heizung_leistung
    • Aquarium light 1 (switch): switch.aquarium_licht_none
    • Aquarium light 1 power sensor: sensor.aquarium_licht_leistung
    • Aquarium light 2 (switch): switch.nanoaqua_licht_none
    • Aquarium light 2 power sensor: sensor.nanoaqua_licht_leistung
    • NotifyGroup mobiles: notify.iphones
    alias: "Aquarium: Verify after 30 minutes"
    description: "Prüft 30 Min. nach dem Schalten den gewünschten Zustand und korrigiert ggf. + Push"
    mode: parallel
    sequence:
      # 1) 30 Minuten warten
      - delay: "00:30:00"
    
      # 2) Eingaben absichern + Ist-Zustand lesen
      - variables:
          ent: "{{ entity | default('') }}"
          desired: "{{ desired_state | default('') }}"
          reas: "{{ reason | default('verification') }}"
          current: "{{ states(ent) if ent else 'unknown' }}"
    
      # 3) Nur agieren, wenn ent & desired sinnvoll sind
      - choose:
          - conditions:
              - condition: template
                value_template: >
                  {{ ent != '' and desired in ['on','off'] and current != desired }}
            sequence:
              # 3a) Korrektur durchführen
              - choose:
                  - conditions: "{{ desired == 'on' }}"
                    sequence:
                      - service: switch.turn_on
                        target:
                          entity_id: "{{ ent }}"
                  - conditions: "{{ desired == 'off' }}"
                    sequence:
                      - service: switch.turn_off
                        target:
                          entity_id: "{{ ent }}"
              # 3b) Push mit Status + Leistungen
              - service: notify.iphones
                data:
                  title: "Aquarium: Korrektur ({{ reas }})"
                  message: >-
                    {{ ent }} war '{{ current }}', sollte '{{ desired }}' sein – korrigiert.
                    Temp: {{ states('sensor.sonoff_1002215fd9_temperature') }}°C |
                    Heizung: {{ states('switch.aquarium_heizung_none') }} ({{ states('sensor.aquarium_heizung_leistung') }} W) |
                    Licht1: {{ states('switch.aquarium_licht_none') }} ({{ states('sensor.aquarium_licht_leistung') }} W) |
                    Licht2: {{ states('switch.nanoaqua_licht_none') }} ({{ states('sensor.nanoaqua_licht_leistung') }} W)
    Code-Sprache: PHP (php)

    Automatiion – light off

    alias: "[AQUARIUM] Licht AUS (-60m vor Sonnenuntergang)"
    description: ""
    triggers:
      - event: sunset
        offset: "-01:00:00"
        trigger: sun
    conditions: []
    actions:
      - target:
          entity_id:
            - switch.aquarium_licht_none
            - switch.nanoaqua_licht_none
        action: switch.turn_off
      - data:
          entity: switch.aquarium_licht_none
          desired_state: "off"
          reason: lights_off
        action: script.aquarium_verify_after_30_minutes
      - data:
          entity: switch.nanoaqua_licht_none
          desired_state: "off"
          reason: lights_off
        action: script.aquarium_verify_after_30_minutes
    mode: singleCode-Sprache: CSS (css)

    Heating on

    alias: "[AQUARIUM] Heizung EIN (+60m nach Sonnenaufgang)"
    description: ""
    triggers:
      - event: sunrise
        offset: "01:00:00"
        trigger: sun
    conditions: []
    actions:
      - target:
          entity_id: switch.aquarium_heizung_none
        action: switch.turn_on
      - data:
          entity: switch.aquarium_heizung_none
          desired_state: "on"
          reason: heater_on
        action: script.aquarium_verify_after_30_minutes
    mode: singleCode-Sprache: CSS (css)

    Heating off

    alias: "[AQUARIUM] Heizung AUS (-30m vor Sonnenuntergang)"
    description: ""
    triggers:
      - event: sunset
        offset: "-00:30:00"
        trigger: sun
    conditions: []
    actions:
      - target:
          entity_id: switch.aquarium_heizung_none
        action: switch.turn_off
      - data:
          entity: switch.aquarium_heizung_none
          desired_state: "off"
          reason: heater_off
        action: script.aquarium_verify_after_30_minutes
    mode: singleCode-Sprache: CSS (css)

    Reconcile

    alias: "[AQUA] Reconcile: Zustände an Zeitplan anpassen"
    description: "Stellt nach Neustart/Offline-Phase die Sollzustände wieder her (robust)."
    mode: restart
    trigger:
      - platform: homeassistant
        event: start
      - platform: time_pattern
        minutes: "/15"
    condition: []
    variables:
      now_local: "{{ now() }}"
      # Sun-Times sicher in lokale Datetimes umwandeln
      next_rising: "{{ as_local(as_datetime(state_attr('sun.sun','next_rising'))) }}"
      next_setting: "{{ as_local(as_datetime(state_attr('sun.sun','next_setting'))) }}"
      # 'heutiges' Sunrise/Sunset ableiten:
      sunrise_today: >-
        {% set nr = next_rising %}
        {% if nr.date() == now().date() %}
          {{ nr }}
        {% else %}
          {{ nr - timedelta(days=1) }}
        {% endif %}
      sunset_today: >-
        {% set ns = next_setting %}
        {% if ns.date() == now().date() %}
          {{ ns }}
        {% else %}
          {{ ns - timedelta(days=1) }}
        {% endif %}
      # Zeitfenster laut Anforderung:
      heater_on_from: "{{ sunrise_today + timedelta(hours=1) }}"
      heater_off_at:  "{{ sunset_today - timedelta(minutes=30) }}"
      lights_on_from: "{{ today_at('09:30:00') }}"
      lights_off_at:  "{{ sunset_today - timedelta(hours=1) }}"
      # Sollzustände jetzt:
      want_heater_on: "{{ now_local >= heater_on_from and now_local < heater_off_at }}"
      want_lights_on: "{{ now_local >= lights_on_from and now_local < lights_off_at }}"
    action:
      - choose:
          - conditions: "{{ want_heater_on and is_state('switch.aquarium_heizung_none','off') }}"
            sequence:
              - service: switch.turn_on
                target:
                  entity_id: switch.aquarium_heizung_none
          - conditions: "{{ not want_heater_on and is_state('switch.aquarium_heizung_none','on') }}"
            sequence:
              - service: switch.turn_off
                target:
                  entity_id: switch.aquarium_heizung_none
      - choose:
          - conditions: "{{ want_lights_on and (is_state('switch.aquarium_licht_none','off') or is_state('switch.nanoaqua_licht_none','off')) }}"
            sequence:
              - service: switch.turn_on
                target:
                  entity_id:
                    - switch.aquarium_licht_none
                    - switch.nanoaqua_licht_none
          - conditions: "{{ not want_lights_on and (is_state('switch.aquarium_licht_none','on') or is_state('switch.nanoaqua_licht_none','on')) }}"
            sequence:
              - service: switch.turn_off
                target:
                  entity_id:
                    - switch.aquarium_licht_none
                    - switch.nanoaqua_licht_none
    Code-Sprache: PHP (php)

    Lights on

    alias: "[AQUARIUM]  Licht EIN (09:30)"
    description: ""
    triggers:
      - at: "09:30:00"
        trigger: time
    conditions: []
    actions:
      - target:
          entity_id:
            - switch.aquarium_licht_none
            - switch.nanoaqua_licht_none
        action: switch.turn_on
      - data:
          entity: switch.aquarium_licht_none
          desired_state: "on"
          reason: lights_on
        action: script.aquarium_verify_after_30_minutes
      - data:
          entity: switch.nanoaqua_licht_none
          desired_state: "on"
          reason: lights_on
        action: script.aquarium_verify_after_30_minutes
    mode: single
    Code-Sprache: CSS (css)

    7. Alarm if water is too cold (this example 22 degrees)

    alias: "[AQUARIUM]  Alarm: Temperatur unter 22°C"
    description: ""
    triggers:
      - entity_id: sensor.sonoff_1002215fd9_temperature
        below: 22
        trigger: numeric_state
    conditions: []
    actions:
      - data:
          title: "Aquarium: Temperatur-Alarm (<22°C)"
          message: >-
            Temp: {{ states('sensor.sonoff_1002215fd9_temperature') }}°C. Heizung:
            {{ states('switch.aquarium_heizung_none') }} ({{
            states('sensor.aquarium_heizung_leistung') }} W). Licht1: {{
            states('switch.aquarium_licht_none') }} ({{
            states('sensor.aquarium_licht_leistung') }} W). Licht2: {{
            states('switch.nanoaqua_licht_none') }} ({{
            states('sensor.nanoaqua_licht_leistung') }} W).
        action: notify.iphones
    mode: single
    Code-Sprache: JavaScript (javascript)

    Alert when leaving home and light is on or door/window open

    This automation sends a push alert to our iPhones when all household members have left home but something was left on or open—for example, any light is still on or a door/window/garage is open.

    How it works (overview):

    1. Tracks presence for all people via the Home Assistant Companion app (installed on each device with Location permission granted).
    2. When everyone is away, it checks a group of entities (lights, doors/windows, garage).
    3. If any of these are on/open, it sends a mobile notification to the iPhones so we can act quickly.

    Why this is useful:

    1. It’s an easy safety and energy-saving catch-all—no more second-guessing whether a light is burning or a door is ajar after you’ve already left.

    Prerequisites:

    1. Each person uses the Home Assistant Companion app on iOS, logged into the same HA instance.
    2. Location permissions enabled for presence tracking.
    3. Your lights and contact sensors (doors/windows/garage) are available in HA (ideally grouped for simple checks).
    4. Home Assistant must be reachable from the internet so iPhones can receive notifications and open deep-links reliably—e.g., via Home Assistant Cloud (Nabu Casa) or a secure reverse proxy with valid HTTPS. Ensure your HA host has a stable uplink.

    The example below uses two people, but you can extend it to any number of household members by adding them to the presence group.

    device_tracker.iphone1
    device_tracker.iphone2Code-Sprache: CSS (css)

    Mobile notification group:

    notify.iphonesCode-Sprache: CSS (css)

    You can optionally define an exclude_lights list to ignore specific lights in the “left on” check—useful for outdoor fixtures, always-on night lights, holiday decorations, or any lamp you don’t want to trigger alerts.

    Configuration note:

    Add the entity IDs you want to exclude to exclude_lights (e.g., light.porch, light.garden_spots, light.nightlight_bedroom). The automation will still check all other lights and any open doors/windows/garage.

    /dashboard-leavehome/0 is a self build dashboard which shows all open things. You can link your own dashboard there

    <code>alias: "[ALERT] Alle weg + etwas offen/AN → Hinweis (mit Ausschlussliste)"
    triggers:
      - trigger: template
        value_template: |-
          {% set d1 = is_state('device_tracker.iphone1', 'not_home')
                     and distance('device_tracker.iphone1','zone.home') > 0.1 %}
          {% set d2 = is_state('device_tracker.iphone2', 'not_home')
                     and distance('device_tracker.iphone2','zone.home') > 0.1 %}
          {{ d1 and d2 }}
        for: "00:00:30"
    conditions:
      - condition: template
        value_template: |-
          {% set lights_on = states.light
             | rejectattr('entity_id','in',exclude_lights)
             | selectattr('state','eq','on')
             | list | length %}
          {% set contacts_open = states.binary_sensor
             | selectattr('attributes.device_class','in',['door','window','opening'])
             | rejectattr('entity_id','in',exclude_contacts)
             | selectattr('state','eq','on')
             | list | length %}
          {{ lights_on > 0 or contacts_open > 0 }}
    actions:
      - variables:
          open_items: >-
            {%- set lights = states.light
                 | rejectattr('entity_id','in',exclude_lights)
                 | selectattr('state','eq','on')
                 | map(attribute='name') | list -%}
            {%- set contacts = states.binary_sensor
                 | selectattr('attributes.device_class','in',['door','window','opening'])
                 | rejectattr('entity_id','in',exclude_contacts)
                 | selectattr('state','eq','on')
                 | map(attribute='name') | list -%}
            {%- set items = (lights + contacts) -%} {%- if items | length == 0 -%}
            nichts {%- else -%} {{ items | sort | join(', ') }} {%- endif -%}
      - action: notify.iphones
        data:
          title: "🏠 Achtung: Niemand zu Hause"
          message: "Offen/AN: {{ open_items }}."
          data:
            url: /dashboard-leavehome/0
            push:
              interruption-level: time-sensitive
            apns_headers:
              apns-collapse-id: away-open
            actions:
              - action: OPEN_DASH
                title: 🔎 Jetzt prüfen
                uri: /dashboard-leavehome/0
    mode: single
    variables:
      exclude_lights:
        - light.smart_garage
        - light.example2
        - light.outdoor
      exclude_contacts: []</code>Code-Sprache: PHP (php)