Skip to content

Package: Area Manager

Version: 2.0.0 (Refactored from Room Manager)
Description: Dynamic creation of AREA settings via MQTT (Replaces Room Manager)

Package Diagram

Executive Summary

The Area Manager is the central brain for room-level automation in EvisHomeLab. It completely replaces the legacy "Room Manager" with a dynamic, MQTT-backed architecture. It decouples Presence Detection (is someone here?) from Action Logic (what should I do?), allowing for sophisticated behaviors like "Manual Control" where presence is tracked but lights are not automated, or "Absence Detection" where lights turn off automatically but must be turned on manually. It utilizes Pyscript for complex state machine logic, offering millisecond-level responsiveness.

Process Description (Non-Technical)

How it Works

The system treats every area (room) as a state machine that transitions between Occupied, Idle, Absence, Sleep, and Do Not Disturb (DND).

1. The Four Modes

You can independently configure how each room reacts to presence: * Presence Control (Default): Lights turn ON when you enter, and OFF when you leave. * Absence Detection: Lights will never turn ON automatically. However, if you turn them on manually, they will turn OFF automatically when you leave. Great for bedrooms or media rooms. * Manual Control: Full manual control. The system tracks occupancy for analytics but will never touch the lights. * Schedule Mode: (Future) Automated based on calendar events.

2. Time-of-Day Logic

The system respects the time of day, automatically selecting the appropriate lighting scene: * Morning: Soft, warm light for waking up. * Day: Bright, cool light for focus. * Evening: Relaxing, warmer tones. * Night: Very dim, red-shifted light for minimal disruption.

3. New "Off Delay" Feature

To prevent lights from turning off suddenly if you sit still too long: 1. Idle Warning: When the presence timer expires, the system enters "Absence" state. 2. Off Action: A "Warning" scene (e.g., Dim) activates for a configurable duration (e.g., 2 minutes). * Smart Check: This ONLY happens if lights are currently ON. 3. Final Off: If no motion is detected during the delay, the lights finally turn off.

Python Logic (The Brain)

Unlike the old YAML-heavy version, V2 moves complex logic to Python (pyscript/): * area_presence.py: Handles the state machine, timers, and transitions (Occupied -> Idle -> Absence). * area_automations.py: Listens to state changes and triggers the actual scenes or actions based on the configured Mode.

Dashboard Connections

This package powers the following dashboard views:

  • Room Management (Uses 7 entities)
  • Room Management (Uses 7 entities)
  • Living Room: The Living Room dashboard is a media and comfort hub. It features in-depth environmental monitoring (Radon, VOCs, CO2) via Airthings Wave, displaying historical trends. Entertainment controls are central, with remotes for the TV and Soundbar, plus power management for the media wall. The view also includes specific controls for the fireplace, air purifier modes, and various lighting scenes, alongside standard occupancy settings. (Uses 1 entities)
  • Notifications Management: The Notification Center dashboard provides a comprehensive interface for managing the smart home's notification system. Administrators can add or remove users for mobile app notifications and define notification categories (e.g., 'Garage', 'Electricity'). The view allows for granular control over subscriptions, enabling individual users to opt-in or out of specific notification types, and includes tools to map and monitor notification-related automations. (Uses 1 entities)
  • Room Management: The Room Management dashboard serves as the administrative backend for the home's room logic. It allows users to initialize new rooms (creating necessary helper entities) or delete existing ones. It features a dynamic "Configured Rooms" section powered by auto-entities, which automatically lists all configured rooms and provides collapsible controls for their automation modes, occupancy sensors, and timeouts. (Uses 7 entities)

Architecture Diagram

The following diagram illustrates the decoupled flow. Motion Sensors trigger the State Machine in Python. The State Machine updates the Area State (Occupied/Idle). A separate Automation Listener watches this state and executes actions (Scenes) only if the Mode permits.

sequenceDiagram
    participant S as Binary Sensor
    participant P as area_presence.py
    participant M as MQTT / HA State
    participant A as area_automations.py
    participant L as Lights / Scenes

    S->>P: Motion ON
    P->>P: Cancel Timers
    P->>M: Set State = Occupied

    par Update UI
        M-->>L: Update Binary Sensor
    and Trigger Automation
        M->>A: State Changed to Occupied
    end

    alt Mode == Presence Control
        A->>A: Check Time of Day
        A->>L: Activate Morning/Day/Night Scene
    else Mode == Manual
        A->>A: Do Nothing
    end

    S->>P: Motion OFF
    P->>P: Start Idle Timer (e.g. 5min)

    opt Timer Expires
        P->>M: Set State = Absence
        P->>P: Start Off Delay Timer (e.g. 2min)

        M->>A: State Changed to Absence
        A->>A: Check if Lights are ON
        opt Lights ON
            A->>L: Activate Warning/Dim Scene
        end

        opt Off Delay Expires
            P->>L: Turn Off All Lights
        end
    end

Configuration (Source Code)

# Package: Area Automation Manager
# Version: 2.0.0 (Refactored from Room Manager)
# Description: Dynamic creation of AREA settings via MQTT (Replaces Room Manager)
# ------------------------------------------------------------------------------

# ------------------------------------------------------------------------------
# 1. GLOBAL CUSTOMIZATION (Force Text Boxes)
# ------------------------------------------------------------------------------
homeassistant:
  customize_glob:
    "number.area_*_presence_idle_time":
      mode: box
    "number.area_*_lights_presence_delay":
      mode: box
    "number.area_*_sleep_entry_delay":
      mode: box
    "number.area_*_sleep_exit_delay":
      mode: box

# ------------------------------------------------------------------------------
# 2. HELPERS (Dashboard Inputs)
# ------------------------------------------------------------------------------
input_text:
  area_mgmt_name:
    name: "Area Name"
    icon: mdi:door-open
  area_mgmt_slug:
    name: "Area ID (slug)"
    icon: mdi:identifier

input_select:
  # SOURCE: Native Home Assistant Areas
  area_mgmt_create_select:
    name: "Select Area to Initialize"
    icon: mdi:map-plus
    options:
      - "unknown"

  # SOURCE: Existing Created Areas
  area_mgmt_delete_select:
    name: "Select Area to Delete"
    icon: mdi:delete-sweep
    options:
      - "unknown"

# ------------------------------------------------------------------------------
# 3. SCRIPTS
# ------------------------------------------------------------------------------
script:
  # --- REFRESH AREA OPTIONS ---
  refresh_area_options:
    alias: "System: Refresh Area Options"
    mode: single
    sequence:
      - service: automation.trigger
        target:
          entity_id: automation.system_populate_area_list

  # --- CREATE AREA SETTINGS ---
  create_area_settings:
    alias: "System: Create Area Settings"
    icon: mdi:home-plus
    mode: single
    sequence:
      - variables:
          raw_slug: "{{ area_slug if area_slug is defined else states('input_select.area_mgmt_create_select') }}"
          # Get Friendly Name from HA Area Registry or fallback to Title Case
          friendly_name: "{{ area_name(raw_slug) or raw_slug | replace('_', ' ') | title }}"

          # SANITIZATION: Strip 'area_' or '_area' to keep slugs clean
          # e.g. 'backyard_area' -> 'backyard', 'area_51' -> '51'
          sanitized: "{{ raw_slug | replace('area_', '') | replace('_area', '') }}"

          # Final Variables
          area_slug: "{{ sanitized }}"
          area_name: "{{ friendly_name }}"

          # Pre-calculate Occupancy Options
          occupancy_options: >-
            {% set keywords = ['occupancy', 'presence', 'motion'] %}
            {% set sensors = states.binary_sensor 
                | selectattr('entity_id', 'search', keywords | join('|')) 
                | map(attribute='entity_id') | list %}
            {% set all_sensors = sensors | sort %}
            {{ (['-Select-'] + (all_sensors if all_sensors else [])) | to_json }}

          # Pre-calculate Bed Options
          # Logic: Must have 'bed' AND ('occupancy' OR 'presence')
          bed_options: >-
            {% set sensors = states.binary_sensor 
                | selectattr('entity_id', 'search', 'bed') 
                | selectattr('entity_id', 'search', 'occupancy|presence') 
                | map(attribute='entity_id') | list %}
            {% set all_sensors = sensors | sort %}
            {{ (['-Select-'] + (all_sensors if all_sensors else [])) | to_json }}

          # Pre-calculate Scene Options
          scene_options: >-
            {% set scenes = states.scene | map(attribute='entity_id') | list | sort %}
            {{ (['-Select-'] + scenes) | to_json }}

          # Pre-calculate Off Delay Options (scenes only, with "None" as first option)
          off_delay_options: >-
            {% set scenes = states.scene | map(attribute='entity_id') | list | sort %}
            {{ (['None'] + scenes) | to_json }}

      - service: system_log.write
        data:
          message: "Area Manager: Creating area '{{ area_slug }}'"
          level: warning

      # 1. Create Automation Mode Selector (Select)
      - service: mqtt.publish
        data:
          retain: true
          topic: "homeassistant/select/area_{{ area_slug }}_mode/config"
          payload: >-
            {
              "name": "{{ area_name }} Automation Mode",
              "default_entity_id": "select.area_{{ area_slug }}_automation_mode",
              "unique_id": "area_select_{{ area_slug }}_mode_v5",
              "icon": "mdi:home-lightning-bolt-outline",
              "options": ["presence-control", "absence-detection", "manual-control", "schedule-mode"],
              "command_topic": "area/{{ area_slug }}/mode/set",
              "state_topic": "area/{{ area_slug }}/mode/state",
              "availability_topic": "area/{{ area_slug }}/availability",
              "payload_available": "online",
              "device": {
                "identifiers": ["area_settings_{{ area_slug }}"],
                "name": "{{ area_name }} Settings",
                "manufacturer": "Home Assistant",
                "model": "Area Controller"
              }
            }
      # Set default
      - if:
          - condition: template
            value_template: "{{ states('select.area_' ~ area_slug ~ '_automation_mode') in ['unknown', 'unavailable', 'none'] }}"
        then:
          - service: mqtt.publish
            data:
              retain: true
              topic: "area/{{ area_slug }}/mode/state"
              payload: "presence-control"
      - delay: "00:00:00.050"

      # 2. Create Idle Time (Number)
      - service: mqtt.publish
        data:
          retain: true
          topic: "homeassistant/number/area_{{ area_slug }}_idle/config"
          payload: >-
            {
              "name": "{{ area_name }} Presence Idle Time",
              "default_entity_id": "number.area_{{ area_slug }}_presence_idle_time",
              "unique_id": "area_number_{{ area_slug }}_idle_v5",
              "device_class": "duration",
              "icon": "mdi:timer-sand",
              "min": 0,
              "max": 3600,
              "step": 1,
              "unit_of_measurement": "s",
              "command_topic": "area/{{ area_slug }}/idle/set",
              "state_topic": "area/{{ area_slug }}/idle/state",
              "availability_topic": "area/{{ area_slug }}/availability",
              "device": { "identifiers": ["area_settings_{{ area_slug }}"] }
            }
      - if:
          - condition: template
            value_template: "{{ states('number.area_' ~ area_slug ~ '_presence_idle_time') in ['unknown', 'unavailable', 'none'] }}"
        then:
          - service: mqtt.publish
            data:
              retain: true
              topic: "area/{{ area_slug }}/idle/state"
              payload: "15"
      - delay: "00:00:00.050"

      # 3. Create Delay Time (Number)
      - service: mqtt.publish
        data:
          retain: true
          topic: "homeassistant/number/area_{{ area_slug }}_delay/config"
          payload: >-
            {
              "name": "{{ area_name }} Lights Presence Delay",
              "default_entity_id": "number.area_{{ area_slug }}_lights_presence_delay",
              "unique_id": "area_number_{{ area_slug }}_delay_v5",
              "device_class": "duration",
              "icon": "mdi:lightbulb-clock",
              "min": 0,
              "max": 3600,
              "step": 1,
              "unit_of_measurement": "s",
              "command_topic": "area/{{ area_slug }}/delay/set",
              "state_topic": "area/{{ area_slug }}/delay/state",
              "availability_topic": "area/{{ area_slug }}/availability",
              "device": { "identifiers": ["area_settings_{{ area_slug }}"] }
            }
      - if:
          - condition: template
            value_template: "{{ states('number.area_' ~ area_slug ~ '_lights_presence_delay') in ['unknown', 'unavailable', 'none'] }}"
        then:
          - service: mqtt.publish
            data:
              retain: true
              topic: "area/{{ area_slug }}/delay/state"
              payload: "120"
      - delay: "00:00:00.050"

      # 4. Create Timer Display (Timestamp Sensor)
      - service: mqtt.publish
        data:
          retain: true
          topic: "homeassistant/sensor/area_{{ area_slug }}_timer/config"
          payload: >-
            {
              "name": "{{ area_name }} Timer",
              "default_entity_id": "sensor.area_{{ area_slug }}_timer",
              "unique_id": "area_sensor_{{ area_slug }}_timer_v5",
              "icon": "mdi:progress-clock",
              "device_class": "timestamp",
              "value_template": "{{ '{{' }} value if value not in ['unknown', 'unavailable', ''] else None {{ '}}' }}",
              "state_topic": "area/{{ area_slug }}/timer/state",
              "json_attributes_topic": "area/{{ area_slug }}/timer/attributes",
              "availability_topic": "area/{{ area_slug }}/availability",
              "device": { "identifiers": ["area_settings_{{ area_slug }}"] }
            }
      - delay: "00:00:00.050"
      # 5. Create Area State (Select)
      - service: mqtt.publish
        data:
          retain: true
          topic: "homeassistant/select/area_{{ area_slug }}_state/config"
          payload: >-
            {
              "name": "{{ area_name }} State",
              "default_entity_id": "select.area_{{ area_slug }}_state",
              "unique_id": "area_select_state_v5_{{ area_slug }}",
              "icon": "mdi:eye-outline",
              "options": ["Occupied", "Idle", "Absence", "Sleep", "DND"],
              "command_topic": "area/{{ area_slug }}/state/set",
              "state_topic": "area/{{ area_slug }}/state/state",
              "availability_topic": "area/{{ area_slug }}/availability",
              "payload_available": "online",
              "device": { "identifiers": ["area_settings_{{ area_slug }}"] }
            }
      - service: mqtt.publish
        data:
          retain: true
          topic: "area/{{ area_slug }}/state/state"
          payload: "Absence"
      - delay: "00:00:00.050"

      # 6. Create Occupancy (Binary Sensor)
      - service: mqtt.publish
        data:
          retain: true
          topic: "homeassistant/binary_sensor/area_{{ area_slug }}_occupancy/config"
          payload: >-
            {
              "name": "{{ area_name }} Occupancy",
              "default_entity_id": "binary_sensor.area_{{ area_slug }}_occupancy",
              "unique_id": "area_occupancy_v5_{{ area_slug }}",
              "icon": "mdi:motion-sensor",
              "device_class": "occupancy",
              "state_topic": "area/{{ area_slug }}/occupancy/state",
              "availability_topic": "area/{{ area_slug }}/availability",
              "device": { "identifiers": ["area_settings_{{ area_slug }}"] }
            }
      - delay: "00:00:00.050"
      # 7. Create Automation Switch
      - service: mqtt.publish
        data:
          retain: true
          topic: "homeassistant/switch/area_{{ area_slug }}_automation/config"
          payload: >-
            {
              "name": "{{ area_name }} Automation",
              "default_entity_id": "switch.area_{{ area_slug }}_automation",
              "unique_id": "area_switch_automation_v5_{{ area_slug }}",
              "icon": "mdi:robot",
              "command_topic": "area/{{ area_slug }}/automation/set",
              "state_topic": "area/{{ area_slug }}/automation/state",
              "availability_topic": "area/{{ area_slug }}/availability",
              "device": { "identifiers": ["area_settings_{{ area_slug }}"] }
            }
      - service: mqtt.publish
        data:
          retain: true
          topic: "area/{{ area_slug }}/automation/state"
          payload: "ON"
      - delay: "00:00:00.050"

      # 8. Create Bed Sensor (Select)
      - service: mqtt.publish
        data:
          retain: true
          topic: "homeassistant/select/area_{{ area_slug }}_bed_sensor/config"
          payload: >-
            {
              "name": "{{ area_name }} Bed Sensor",
              "default_entity_id": "select.area_{{ area_slug }}_bed_sensor",
              "unique_id": "area_select_bed_{{ area_slug }}_v5",
              "options": {{ bed_options }},
              "command_topic": "area/{{ area_slug }}/bed_sensor/set",
              "state_topic": "area/{{ area_slug }}/bed_sensor/state",
              "availability_topic": "area/{{ area_slug }}/availability",
              "device": { "identifiers": ["area_settings_{{ area_slug }}"] }
            }
      - delay: "00:00:00.050"
      # 9. Create Sleep ENTRY Delay (Number)
      - service: mqtt.publish
        data:
          retain: true
          topic: "homeassistant/number/area_{{ area_slug }}_sleep_entry_delay/config"
          payload: >-
            {
              "name": "{{ area_name }} Sleep Entry Delay",
              "default_entity_id": "number.area_{{ area_slug }}_sleep_entry_delay",
              "unique_id": "area_number_{{ area_slug }}_sleep_entry_v5",
              "device_class": "duration",
              "icon": "mdi:bed-clock",
              "min": 0,
              "max": 3600,
              "step": 1,
              "unit_of_measurement": "s",
              "command_topic": "area/{{ area_slug }}/sleep_entry_delay/set",
              "state_topic": "area/{{ area_slug }}/sleep_entry_delay/state",
              "availability_topic": "area/{{ area_slug }}/availability",
              "device": { "identifiers": ["area_settings_{{ area_slug }}"] }
            }
      - if:
          - condition: template
            value_template: "{{ states('number.area_' ~ area_slug ~ '_sleep_entry_delay') in ['unknown', 'unavailable', 'none'] }}"
        then:
          - service: mqtt.publish
            data:
              retain: true
              topic: "area/{{ area_slug }}/sleep_entry_delay/state"
              payload: "300"
      - delay: "00:00:00.050"

      # 10. Create Sleep EXIT Delay (Number)
      - service: mqtt.publish
        data:
          retain: true
          topic: "homeassistant/number/area_{{ area_slug }}_sleep_exit_delay/config"
          payload: >-
            {
              "name": "{{ area_name }} Sleep Exit Delay",
              "default_entity_id": "number.area_{{ area_slug }}_sleep_exit_delay",
              "unique_id": "area_number_{{ area_slug }}_sleep_exit_v5",
              "device_class": "duration",
              "icon": "mdi:run-fast",
              "min": 0,
              "max": 3600,
              "step": 1,
              "unit_of_measurement": "s",
              "command_topic": "area/{{ area_slug }}/sleep_exit_delay/set",
              "state_topic": "area/{{ area_slug }}/sleep_exit_delay/state",
              "availability_topic": "area/{{ area_slug }}/availability",
              "device": { "identifiers": ["area_settings_{{ area_slug }}"] }
            }
      - if:
          - condition: template
            value_template: "{{ states('number.area_' ~ area_slug ~ '_sleep_exit_delay') in ['unknown', 'unavailable', 'none'] }}"
        then:
          - service: mqtt.publish
            data:
              retain: true
              topic: "area/{{ area_slug }}/sleep_exit_delay/state"
              payload: "60"
      - delay: "00:00:00.050"

      # 11. Set Online
      - service: mqtt.publish
        data:
          retain: true
          topic: "area/{{ area_slug }}/availability"
          payload: "online"

      # 12. Create Occupancy Source (Select)
      - service: mqtt.publish
        data:
          retain: true
          topic: "homeassistant/select/area_{{ area_slug }}_occupancy_source/config"
          payload: >-
            {
              "name": "{{ area_name }} Occupancy Sensor",
              "default_entity_id": "select.area_{{ area_slug }}_occupancy_source",
              "unique_id": "area_select_occ_source_v5_{{ area_slug }}",
              "options": {{ occupancy_options }},
              "command_topic": "area/{{ area_slug }}/occupancy_source/set",
              "state_topic": "area/{{ area_slug }}/occupancy_source/state",
              "availability_topic": "area/{{ area_slug }}/availability",
              "device": { "identifiers": ["area_settings_{{ area_slug }}"] }
            }
      - delay: "00:00:00.050"
      # 13. Create DND Switch
      - service: mqtt.publish
        data:
          retain: true
          topic: "homeassistant/switch/area_{{ area_slug }}_dnd/config"
          payload: >-
            {
              "name": "{{ area_name }} Do Not Disturb",
              "default_entity_id": "switch.area_{{ area_slug }}_dnd",
              "unique_id": "area_switch_dnd_v5_{{ area_slug }}",
              "icon": "mdi:minus-circle-outline",
              "command_topic": "area/{{ area_slug }}/dnd/set",
              "state_topic": "area/{{ area_slug }}/dnd/state",
              "availability_topic": "area/{{ area_slug }}/availability",
              "device": { "identifiers": ["area_settings_{{ area_slug }}"] }
            }
      - service: mqtt.publish
        data:
          retain: true
          topic: "area/{{ area_slug }}/dnd/state"
          payload: "OFF"
      - delay: "00:00:00.050"

      # 14. Create Scene Selectors (Morning, Day, Evening, Night)
      - repeat:
          for_each: ["morning", "day", "evening", "night"]
          sequence:
            - service: mqtt.publish
              data:
                retain: true
                topic: "homeassistant/select/area_{{ area_slug }}_{{ repeat.item }}_scene/config"
                payload: >-
                  {
                    "name": "{{ area_name }} {{ repeat.item | title }} Scene",
                    "default_entity_id": "select.area_{{ area_slug }}_{{ repeat.item }}_scene",
                    "unique_id": "area_select_{{ repeat.item }}_scene_v5_{{ area_slug }}",
                    "options": {{ scene_options }},
                    "command_topic": "area/{{ area_slug }}/{{ repeat.item }}_scene/set",
                    "state_topic": "area/{{ area_slug }}/{{ repeat.item }}_scene/state",
                    "availability_topic": "area/{{ area_slug }}/availability",
                    "device": { "identifiers": ["area_settings_{{ area_slug }}"] }
                  }
            - delay: "00:00:00.050"

      # 15a. Create Action Selectors (Absence, Sleep) - Options: Turn Off / None
      - repeat:
          for_each: ["absence", "sleep"]
          sequence:
            - service: mqtt.publish
              data:
                retain: true
                topic: "homeassistant/select/area_{{ area_slug }}_{{ repeat.item }}_action/config"
                payload: >-
                  {
                    "name": "{{ area_name }} {{ repeat.item | title }} Action",
                    "default_entity_id": "select.area_{{ area_slug }}_{{ repeat.item }}_action",
                    "unique_id": "area_select_{{ repeat.item }}_action_v5_{{ area_slug }}",
                    "options": ["Turn Off", "None"],
                    "command_topic": "area/{{ area_slug }}/{{ repeat.item }}_action/set",
                    "state_topic": "area/{{ area_slug }}/{{ repeat.item }}_action/state",
                    "availability_topic": "area/{{ area_slug }}/availability",
                    "device": { "identifiers": ["area_settings_{{ area_slug }}"] }
                  }
            # Set Default to 'Turn Off' if new
            - if:
                - condition: template
                  value_template: "{{ states('select.area_' ~ area_slug ~ '_' ~ repeat.item ~ '_action') in ['unknown', 'unavailable', 'none'] }}"
              then:
                - service: mqtt.publish
                  data:
                    retain: true
                    topic: "area/{{ area_slug }}/{{ repeat.item }}_action/state"
                    payload: "Turn Off"
            - delay: "00:00:00.050"

      # 15b. Create Off Delay Action Selector - Options: Scene List + None
      - service: mqtt.publish
        data:
          retain: true
          topic: "homeassistant/select/area_{{ area_slug }}_off_delay_action/config"
          payload: >-
            {
              "name": "{{ area_name }} Off Delay Action",
              "default_entity_id": "select.area_{{ area_slug }}_off_delay_action",
              "unique_id": "area_select_off_delay_action_v5_{{ area_slug }}",
              "options": {{ off_delay_options }},
              "command_topic": "area/{{ area_slug }}/off_delay_action/set",
              "state_topic": "area/{{ area_slug }}/off_delay_action/state",
              "availability_topic": "area/{{ area_slug }}/availability",
              "device": { "identifiers": ["area_settings_{{ area_slug }}"] }
            }
      # Set Default to 'None' if new
      - if:
          - condition: template
            value_template: "{{ states('select.area_' ~ area_slug ~ '_off_delay_action') in ['unknown', 'unavailable', 'none'] }}"
        then:
          - service: mqtt.publish
            data:
              retain: true
              topic: "area/{{ area_slug }}/off_delay_action/state"
              payload: "None"
      - delay: "00:00:00.050"

      - service: script.refresh_area_options

  # --- DELETE AREA SETTINGS ---
  delete_area_settings:
    alias: "System: Delete Area Settings"
    icon: mdi:home-remove
    mode: single
    sequence:
      - variables:
          raw_slug: "{{ states('input_select.area_mgmt_delete_select') }}"
          # SAFETY: If slug starts with 'area_', strip it
          area_slug: "{{ raw_slug | replace('area_', '') if raw_slug.startswith('area_') else raw_slug }}"

      # Clear Config Topics
      - service: mqtt.publish
        data:
          retain: true
          topic: "homeassistant/select/area_{{ area_slug }}_mode/config"
          payload: ""
      - service: mqtt.publish
        data:
          retain: true
          topic: "homeassistant/number/area_{{ area_slug }}_idle/config"
          payload: ""
      - service: mqtt.publish
        data:
          retain: true
          topic: "homeassistant/number/area_{{ area_slug }}_delay/config"
          payload: ""
      - service: mqtt.publish
        data:
          retain: true
          topic: "homeassistant/sensor/area_{{ area_slug }}_timer/config"
          payload: ""
      - service: mqtt.publish
        data:
          retain: true
          topic: "homeassistant/select/area_{{ area_slug }}_state/config"
          payload: ""
      - service: mqtt.publish
        data:
          retain: true
          topic: "homeassistant/binary_sensor/area_{{ area_slug }}_occupancy/config"
          payload: ""
      - service: mqtt.publish
        data:
          retain: true
          topic: "homeassistant/switch/area_{{ area_slug }}_automation/config"
          payload: ""
      - service: mqtt.publish
        data:
          retain: true
          topic: "homeassistant/select/area_{{ area_slug }}_bed_sensor/config"
          payload: ""
      - service: mqtt.publish
        data:
          retain: true
          topic: "homeassistant/number/area_{{ area_slug }}_sleep_entry_delay/config"
          payload: ""
      - service: mqtt.publish
        data:
          retain: true
          topic: "homeassistant/number/area_{{ area_slug }}_sleep_exit_delay/config"
          payload: ""
      - service: mqtt.publish
        data:
          retain: true
          topic: "homeassistant/select/area_{{ area_slug }}_occupancy_source/config"
          payload: ""

      # Clear New Helpers (DND, Scenes, Actions)
      - service: mqtt.publish
        data:
          retain: true
          topic: "homeassistant/switch/area_{{ area_slug }}_dnd/config"
          payload: ""
      - repeat:
          for_each: ["morning", "day", "evening", "night"]
          sequence:
            - service: mqtt.publish
              data:
                retain: true
                topic: "homeassistant/select/area_{{ area_slug }}_{{ repeat.item }}_scene/config"
                payload: ""
      - repeat:
          for_each: ["absence", "sleep"]
          sequence:
            - service: mqtt.publish
              data:
                retain: true
                topic: "homeassistant/select/area_{{ area_slug }}_{{ repeat.item }}_action/config"
                payload: ""

      # Delete Availability to 'offline'
      - service: mqtt.publish
        data:
          retain: true
          topic: "area/{{ area_slug }}/availability"
          payload: ""
      - service: script.refresh_area_options

# ------------------------------------------------------------------------------
# 4. AUTOMATIONS
# ------------------------------------------------------------------------------
automation:
  # POPULATE DROPDOWNS ("Manage Areas")
  - alias: "System: Populate Area List"
    id: system_populate_area_list
    trigger:
      - platform: homeassistant
        event: start
      - platform: time_pattern
        hours: "/1"
    action:
      # 1. Populate 'Create' from HA Areas
      - service: input_select.set_options
        target:
          entity_id: input_select.area_mgmt_create_select
        data:
          options: >-
            {% set areas = areas() | sort %}
            {{ areas if areas else ['unknown'] }}

      # 2. Populate 'Delete' from created 'area_*_automation_mode' entities
      - service: input_select.set_options
        target:
          entity_id: input_select.area_mgmt_delete_select
        data:
          options: >-
            {% set mode_selectors = states.select | selectattr('object_id', 'search', '_automation_mode$') | list %}
            {% set ns = namespace(areas=[]) %}
            {% for sel in mode_selectors %}
               {% set raw_id = sel.entity_id.split('.')[1] %}
               {% set base = raw_id | replace('_automation_mode', '') %}
               {% if base.startswith('area_') %}
                  {% set slug = base[5:] %}
               {% else %}
                  {% set slug = base %}
               {% endif %}
               {% set ns.areas = ns.areas + [slug] %}
            {% endfor %}
            {{ ns.areas | unique | list if ns.areas else ['unknown'] }}

            # END OF POPULATION

  # MQTT PERSISTENCE
  - alias: "System: Area MQTT Persistence"
    id: system_area_mqtt_persistence
    mode: queued
    trigger:
      - platform: mqtt
        topic: "area/+/+/set"
      - platform: mqtt
        topic: "area/+/+/+/set"
    action:
      - variables:
          target_topic: "{{ trigger.topic | replace('/set', '/state') }}"
      - service: mqtt.publish
        data:
          topic: "{{ target_topic }}"
          payload: "{{ trigger.payload }}"
          retain: true

  # SYNC SELECT STATE -> BINARY SENSOR
  # Keeps the binary_sensor.area_X_occupancy in sync with select.area_X_state
  - alias: "System: Sync Area State to Binary Sensor"
    id: system_area_state_to_binary_sync
    mode: queued
    trigger:
      - platform: event
        event_type: state_changed
    condition:
      - condition: template
        value_template: >-
          {{ 
            trigger.event.data.entity_id.startswith("select.area_") and 
            trigger.event.data.entity_id.endswith("_state") 
          }}
    action:
      - variables:
          entity: "{{ trigger.event.data.entity_id }}"
          slug: "{{ entity.split('.')[1].replace('area_', '').replace('_state', '') }}"
          new_val: "{{ trigger.event.data.new_state.state if trigger.event.data.new_state else '' }}"
      - service: mqtt.publish
        data:
          topic: "area/{{ slug }}/occupancy/state"
          payload: "{{ 'ON' if new_val == 'Occupied' else 'OFF' }}"
          retain: true

  # AREA OCCUPANCY DETECTION
  # Uses event trigger to catch all binary_sensor state changes efficiently
  - alias: "System: Area Occupancy Sensor Trigger"
    id: system_area_occupancy_sensor_trigger
    mode: parallel
    max: 20
    trigger:
      - platform: event
        event_type: state_changed
    variables:
      sensor_id: "{{ trigger.event.data.entity_id | default('') }}"
      new_state: "{{ trigger.event.data.new_state.state if trigger.event.data.new_state else '' }}"
      # Build list of all selected occupancy sensors (only from area_* entities)
      all_selected_sensors: >-
        {% set ns = namespace(sensors=[]) %}
        {% for sel in states.select | selectattr('entity_id', 'search', '^select\\.area_.*_occupancy_source$') %}
          {% set val = states(sel.entity_id) %}
          {% if val and val not in ['-Select-', 'unknown', 'unavailable', ''] %}
            {% set ns.sensors = ns.sensors + [val] %}
          {% endif %}
        {% endfor %}
        {{ ns.sensors }}
    condition:
      # Only binary_sensors
      - condition: template
        value_template: "{{ sensor_id.startswith('binary_sensor.') }}"
      # FAST EARLY EXIT: Only continue if this sensor is actually selected
      - condition: template
        value_template: "{{ sensor_id in all_selected_sensors }}"
      # Only on/off transitions
      - condition: template
        value_template: "{{ new_state in ['on', 'off'] }}"
    action:
      - variables:
          # Find which areas use THIS specific sensor
          matched_areas: >-
            {% set ns = namespace(areas=[]) %}
            {% for sel in states.select | selectattr('entity_id', 'search', '^select\\.area_.*_occupancy_source$') %}
              {% set sel_value = states(sel.entity_id) %}
              {% if sel_value == sensor_id %}
                {% set clean_id = sel.entity_id.split('.')[1] %}
                {% set slug = clean_id[5:-17] %}
                {% set ns.areas = ns.areas + [slug] %}
              {% endif %}
            {% endfor %}
            {{ ns.areas }}
      - repeat:
          for_each: "{{ matched_areas }}"
          sequence:
            - service: pyscript.check_area_state
              data:
                area_slug: "{{ repeat.item }}"