Skip to content

Package: Room Manager

Version: 1.2.0
Description: Dynamic creation of room settings via MQTT

Package Diagram

Executive Summary

The Room Automation Manager is a dynamic configuration engine that enables the decentralized creation of smart room controllers. Unlike static YAML configuration, this package allows administrators to "initialize" a room directly from the dashboard. This triggers a script that publishes MQTT Discovery payloads to Home Assistant, effectively factory-generating a standardized suite of entities for that room (Automation Mode, Idle Timers, Occupancy Sensors, etc.) without requiring a system restart. It acts as a "Room Controller Factory," ensuring every smart room has a consistent interface and logic structure.

Process Description (Non-Technical)

This system allows you to turn any standard "Area" in your home into a fully functional Smart Room with a single click. 1. Select & Initialize: Go to the Room Management dashboard, pick a room (e.g., "Kitchen") from the list, and click "Initialize". 2. Automatic Creation: The system instantly builds a "Control Panel" for that room behind the scenes. It creates: * Mode Switch: To set the room to "Automatic", "Manual", or "Guest Mode". * Timers: Dials to set how long lights stay on (e.g., 5 minutes). * Sensor Links: Dropdown menus to choose which Motion Sensor and Bed Sensor belongs to this room. 3. Instant Control: New controls appear immediately. You can now tweak the "Kitchen" settings without ever touching a code file. If you delete the room, all these controls vanish cleanly.

Dashboard Connections

  • Room Management: The primary admin interface. Used to Create (Initialize) and Delete rooms. It also lists all active rooms and their configuration state.
  • Settings (System): Displays the global list of active rooms and their current modes (Occupied/Idle).

Embedded Card: Configured Rooms List

The following auto-entities configuration is used to dynamically list all rooms:

type: custom:auto-entities
show_empty: true
card:
  type: entities
  show_header_toggle: false
filter:
  template: |
    {% set ns = namespace(rows=[]) %}
    {% set mode_selectors = states.select | selectattr('entity_id','search','automation_mode') | sort(attribute='entity_id') | list %}

    {% for sel in mode_selectors %}
      {# Extract base id and normalize to room_key #}
      {% set raw_id = sel.entity_id.split('.')[1] %}
      {% set base = raw_id.replace('_automation_mode','') %}
      {% if base.startswith('room_') %}
        {% set room_key = base[5:] %}
      {% else %}
        {% set room_key = base %}
      {% endif %}
      {% set name = room_key.replace('_',' ') | title %}

      {# Entity IDs #}
      {% set state_select = 'select.room_' ~ room_key ~ '_state' %}
      {% set auto_switch  = 'switch.room_' ~ room_key ~ '_automation' %}
      {% set occ_sensor   = 'binary_sensor.room_' ~ room_key ~ '_occupancy' %}
      {% set idle_entity  = 'number.room_' ~ room_key ~ '_presence_idle_time' %}
      {% set delay_entity = 'number.room_' ~ room_key ~ '_lights_presence_delay' %}
      {% set bed_s        = 'select.room_' ~ room_key ~ '_bed_sensor' %}
      {% set sleep_entry  = 'number.room_' ~ room_key ~ '_sleep_entry_delay' %}
      {% set sleep_exit   = 'number.room_' ~ room_key ~ '_sleep_exit_delay' %}
      {% set occ_source   = 'select.room_' ~ room_key ~ '_occupancy_source' %}
      {% set timer_entity = 'sensor.room_' ~ room_key ~ '_timer' %}

      {% set entities = [] %}

      {# 1. Mode #}
      {% set entities = entities + [{'entity': sel.entity_id, 'name': 'Mode'}] %}

      {# 2. Switch #}
      {% if states[auto_switch] is defined %}
        {% set entities = entities + [{'entity': auto_switch, 'name': 'Automation Enabled'}] %}
      {% endif %}

      {# 3. State #}
      {% if states[state_select] is defined %}
        {% set entities = entities + [{'entity': state_select, 'name': 'Current State'}] %}
      {% endif %}

      {# 4. Occupancy #}
      {% if states[occ_sensor] is defined %}
        {% set entities = entities + [{'entity': occ_sensor, 'name': 'Occupancy'}] %}
      {% endif %}

      {# 5. Occupancy Source #}
      {% if states[occ_source] is defined %}
        {% set entities = entities + [{'entity': occ_source, 'name': 'Occupancy Sensor'}] %}
      {% endif %}

      {# 6. Timer Bar (Conditional Row) #}
      {% if states[timer_entity] is defined %}
         {% set entities = entities + [{
            'type': 'conditional',
            'conditions': [
              {'entity': timer_entity, 'state_not': 'unavailable'},
              {'entity': timer_entity, 'state_not': 'unknown'},
              {'entity': timer_entity, 'state_not': 'none'},
              {'entity': timer_entity, 'state_not': ''}
            ],
            'row': {
               'entity': timer_entity,
               'name': 'Timer'
            }
         }] %}
      {% endif %}

      {# 7. Config Numbers #}
      {% if states[idle_entity] is defined %}
        {% set entities = entities + [{'entity': idle_entity, 'name': 'Idle Time (sec)'}] %}
      {% endif %}
      {% if states[delay_entity] is defined %}
        {% set entities = entities + [{'entity': delay_entity, 'name': 'Off Delay (sec)'}] %}
      {% endif %}

      {# 8. Bed Sensor & Sleep Timers (Conditional) #}
      {% if states[bed_s] is defined %}
        {% set entities = entities + [{'entity': bed_s, 'name': 'Bed Occupancy Sensor'}] %}

        {# Sleep Entry Delay #}
        {% if states[sleep_entry] is defined %}
          {% set entities = entities + [{
             'type': 'conditional',
             'conditions': [
                {'entity': bed_s, 'state_not': '-Select-'},
                {'entity': bed_s, 'state_not': 'unknown'},
                {'entity': bed_s, 'state_not': 'unavailable'}
             ],
             'row': {
                'entity': sleep_entry,
                'name': 'Sleep Entry Delay (sec)'
             }
          }] %}
        {% endif %}

        {# Sleep Exit Delay #}
        {% if states[sleep_exit] is defined %}
          {% set entities = entities + [{
             'type': 'conditional',
             'conditions': [
                {'entity': bed_s, 'state_not': '-Select-'},
                {'entity': bed_s, 'state_not': 'unknown'},
                {'entity': bed_s, 'state_not': 'unavailable'}
             ],
             'row': {
                'entity': sleep_exit,
                'name': 'Sleep Exit Delay (sec)'
             }
          }] %}
        {% endif %}
      {% endif %}

      {# Create the Group #}
      {% set group = {
        'type': 'custom:fold-entity-row',
        'head': {'type':'section', 'label': name},
        'entities': entities
      } %}
      {% set ns.rows = ns.rows + [group] %}
    {% endfor %}

    {{ ns.rows | to_json }}

Architecture Diagram

The sequence diagram below illustrates the Room Initialization Flow. When a user clicks "Initialize" for a specific room (e.g., "Kitchen"), the create_room_settings script triggers. It loops through a definition of required entities (Automation Mode, Idle Timer, Occupancy Sensor Link) and publishes MQTT Configuration Payloads to the homeassistant/ discovery topic. Home Assistant detects these payloads and dynamically creates the entities in its registry. Finally, the script publishes default state values (retained) to ensure the new controls are immediately usable.

sequenceDiagram
    participant User as 👤 Admin (Dashboard)
    participant Script as 📜 Script: create_room
    participant MQTT as 📡 MQTT Broker
    participant HA as 🏠 Home Assistant (Discovery)

    User->>Script: Run(room_slug="kitchen")
    activate Script

    rect rgb(20, 20, 20)
        note right of Script: 1. Entity Construction Loop
        Script->>MQTT: Publish config: select.room_kitchen_mode
        MQTT-->>HA: Discovery: New Entity (select...mode)
        Script->>MQTT: Publish config: number.room_kitchen_idle
        MQTT-->>HA: Discovery: New Entity (number...idle)
        Script->>MQTT: Publish config: binary_sensor.room_kitchen_occupancy
        MQTT-->>HA: Discovery: New Entity (binary_sensor...occupancy)
    end

    rect rgb(30, 30, 50)
        note right of Script: 2. Default State Injection (Retained)
        Script->>MQTT: Publish state: mode = "presence-control"
        Script->>MQTT: Publish state: idle_time = 120s
        Script->>MQTT: Publish state: occupancy = OFF
    end

    deactivate Script
    HA-->>User: UI Updates (New Controls Appear)

Configuration (Source Code)

```yaml

Package: Room Automation Manager

Version: 1.2.0

Description: Dynamic creation of room settings via MQTT

Dependencies: MQTT, input_text.room_mgmt_name, automation.system_populate_room_list

------------------------------------------------------------------------------

------------------------------------------------------------------------------

1. GLOBAL CUSTOMIZATION (Force Text Boxes)

------------------------------------------------------------------------------

homeassistant: customize_glob: "number._presence_idle_time": mode: box "number._lights_presence_delay": mode: box "number._sleep_entry_delay": mode: box "number._sleep_exit_delay": mode: box

------------------------------------------------------------------------------

2. HELPERS (Dashboard Inputs)

------------------------------------------------------------------------------

input_text: room_mgmt_name: name: "Room Name" icon: mdi:door-open room_mgmt_slug: name: "Room ID (slug)" icon: mdi:identifier

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

# SOURCE: Existing Created Rooms room_mgmt_delete_select: name: "Select Room to Delete" icon: mdi:delete-sweep options: - "unknown"

------------------------------------------------------------------------------

3. SCRIPTS

------------------------------------------------------------------------------

script: # --- CREATE ROOM SETTINGS --- create_room_settings: alias: "System: Create Room Settings" icon: mdi:home-plus mode: single sequence: - variables: # Get the selected Area ID (slug) directly from the dropdown room_slug: "{{ states('input_select.room_mgmt_create_select') }}" # Use the Area Name as the Friendly Name (Title Case) room_name: "{{ area_name(room_slug) }}"

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

  # 1.1 Create Automation Master Switch (Switch)
  - service: mqtt.publish
    data:
      retain: true
      topic: "homeassistant/switch/room_{{ room_slug }}_automation/config"
      payload: >-
        {
          "name": "{{ room_name }} Automation",
          "object_id": "room_{{ room_slug }}_automation",
          "unique_id": "room_switch_{{ room_slug }}_automation_v1",
          "icon": "mdi:robot",
          "command_topic": "room/{{ room_slug }}/automation/set",
          "state_topic": "room/{{ room_slug }}/automation/state",
          "availability_topic": "room/{{ room_slug }}/availability",
          "payload_on": "ON",
          "payload_off": "OFF",
          "device": { "identifiers": ["room_settings_{{ room_slug }}"] }
        }
  # Default to ON if new
  - if:
      - condition: template
        value_template: "{{ states('switch.room_' ~ room_slug ~ '_automation') in ['unknown', 'unavailable', 'none'] }}"
    then:
      - service: mqtt.publish
        data:
          retain: true
          topic: "room/{{ room_slug }}/automation/state"
          payload: "ON"
  - delay: "00:00:00.050"

  # 2. Create Idle Time Slider (Number)
  - service: mqtt.publish
    data:
      retain: true
      topic: "homeassistant/number/room_{{ room_slug }}_idle/config"
      payload: >-
        {
          "name": "{{ room_name }} Presence Idle Time",
          "object_id": "room_{{ room_slug }}_presence_idle_time",
          "unique_id": "room_number_{{ room_slug }}_idle_v5",
          "device_class": "duration",
          "icon": "mdi:timer-sand",
          "min": 0,
          "max": 1800,
          "step": 1,
          "unit_of_measurement": "s",
          "command_topic": "room/{{ room_slug }}/idle/set",
          "state_topic": "room/{{ room_slug }}/idle/state",
          "availability_topic": "room/{{ room_slug }}/availability",
          "device": { "identifiers": ["room_settings_{{ room_slug }}"] }
        }
  # Default Value
  - if:
      - condition: template
        value_template: "{{ states('number.room_' ~ room_slug ~ '_presence_idle_time') in ['unknown', 'unavailable', 'none'] }}"
    then:
      - service: mqtt.publish
        data:
          retain: true
          topic: "room/{{ room_slug }}/idle/state"
          payload: "15"
  - delay: "00:00:00.050"

  # 3. Create Delay Time (Number)
  - service: mqtt.publish
    data:
      retain: true
      topic: "homeassistant/number/room_{{ room_slug }}_delay/config"
      payload: >-
        {
          "name": "{{ room_name }} Lights Presence Delay",
          "object_id": "room_{{ room_slug }}_lights_presence_delay",
          "unique_id": "room_number_{{ room_slug }}_delay_v5",
          "device_class": "duration",
          "icon": "mdi:lightbulb-clock",
          "min": 0,
          "max": 3600,
          "step": 1,
          "unit_of_measurement": "s",
          "command_topic": "room/{{ room_slug }}/delay/set",
          "state_topic": "room/{{ room_slug }}/delay/state",
          "availability_topic": "room/{{ room_slug }}/availability",
          "device": { "identifiers": ["room_settings_{{ room_slug }}"] }
        }
  - if:
      - condition: template
        value_template: "{{ states('number.room_' ~ room_slug ~ '_lights_presence_delay') in ['unknown', 'unavailable', 'none'] }}"
    then:
      - service: mqtt.publish
        data:
          retain: true
          topic: "room/{{ room_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/room_{{ room_slug }}_timer/config"
      payload: >-
        {
          "name": "{{ room_name }} Timer",
          "object_id": "room_{{ room_slug }}_timer",
          "unique_id": "room_sensor_{{ room_slug }}_timer_v4",
          "icon": "mdi:progress-clock",
          "device_class": "timestamp",
          "value_template": "{{ '{{' }} value if value not in ['unknown', 'unavailable', ''] else None {{ '}}' }}",
          "state_topic": "room/{{ room_slug }}/timer/state",
          "json_attributes_topic": "room/{{ room_slug }}/timer/attributes",
          "availability_topic": "room/{{ room_slug }}/availability",
          "device": { "identifiers": ["room_settings_{{ room_slug }}"] }
        }
  - delay: "00:00:00.050"
  # 5. Create Room State (Select)
  - service: mqtt.publish
    data:
      retain: true
      topic: "homeassistant/select/room_{{ room_slug }}_state/config"
      payload: >-
        {
          "name": "{{ room_name }} State",
          "object_id": "room_{{ room_slug }}_state",
          "unique_id": "room_select_state_v4_{{ room_slug }}",
          "icon": "mdi:eye-outline",
          "options": ["Occupied", "Idle", "Absence", "Sleep"],
          "command_topic": "room/{{ room_slug }}/state/set",
          "state_topic": "room/{{ room_slug }}/state/state",
          "availability_topic": "room/{{ room_slug }}/availability",
          "payload_available": "online",
          "device": { "identifiers": ["room_settings_{{ room_slug }}"] }
        }
  - service: mqtt.publish
    data:
      retain: true
      topic: "room/{{ room_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/room_{{ room_slug }}_occupancy/config"
      payload: >-
        {
          "name": "{{ room_name }} Occupancy",
          "object_id": "room_{{ room_slug }}_occupancy",
          "unique_id": "room_occupancy_v4_{{ room_slug }}",
          "icon": "mdi:motion-sensor",
          "device_class": "occupancy",
          "state_topic": "room/{{ room_slug }}/occupancy/state",
          "payload_on": "ON",
          "payload_off": "OFF",
          "availability_topic": "room/{{ room_slug }}/availability",
          "payload_available": "online",
          "device": { "identifiers": ["room_settings_{{ room_slug }}"] }
        }
  - service: mqtt.publish
    data:
      retain: true
      topic: "room/{{ room_slug }}/occupancy/state"
      payload: "OFF"
  - delay: "00:00:00.050"

  # 9. Create Bed Sensor (Select)
  - service: mqtt.publish
    data:
      retain: true
      topic: "homeassistant/select/room_{{ room_slug }}_bed_sensor/config"
      payload: >-
        {
          "name": "{{ room_name }} Bed Sensor ID",
          "object_id": "room_{{ room_slug }}_bed_sensor",
          "unique_id": "room_select_bed_sensor_v4_{{ room_slug }}",
          "icon": "mdi:bed",
          "options": ["-Select-"],
          "command_topic": "room/{{ room_slug }}/bed_sensor/set",
          "state_topic": "room/{{ room_slug }}/bed_sensor/state",
          "availability_topic": "room/{{ room_slug }}/availability",
          "device": { "identifiers": ["room_settings_{{ room_slug }}"] }
        }
  - delay: "00:00:00.050"
  # 10. Create Sleep Entry Delay (Number)
  - service: mqtt.publish
    data:
      retain: true
      topic: "homeassistant/number/room_{{ room_slug }}_sleep_entry_delay/config"
      payload: >-
        {
          "name": "{{ room_name }} Sleep Entry Delay",
          "object_id": "room_{{ room_slug }}_sleep_entry_delay",
          "unique_id": "room_number_sleep_entry_delay_v4_{{ room_slug }}",
          "icon": "mdi:bed-clock",
          "min": 0, "max": 3600, "step": 15, "unit_of_measurement": "s",
          "command_topic": "room/{{ room_slug }}/sleep_entry_delay/set",
          "state_topic": "room/{{ room_slug }}/sleep_entry_delay/state",
          "availability_topic": "room/{{ room_slug }}/availability",
          "device": { "identifiers": ["room_settings_{{ room_slug }}"] }
        }
  - if:
      - condition: template
        value_template: "{{ states('number.room_' ~ room_slug ~ '_sleep_entry_delay') in ['unknown', 'unavailable', 'none'] }}"
    then:
      - service: mqtt.publish
        data:
          retain: true
          topic: "room/{{ room_slug }}/sleep_entry_delay/state"
          payload: "300"
  - delay: "00:00:00.050"

  # 11. Create Sleep Exit Delay (Number)
  - service: mqtt.publish
    data:
      retain: true
      topic: "homeassistant/number/room_{{ room_slug }}_sleep_exit_delay/config"
      payload: >-
        {
          "name": "{{ room_name }} Sleep Exit Delay",
          "object_id": "room_{{ room_slug }}_sleep_exit_delay",
          "unique_id": "room_number_sleep_exit_delay_v4_{{ room_slug }}",
          "icon": "mdi:run-fast",
          "min": 0, "max": 3600, "step": 15, "unit_of_measurement": "s",
          "command_topic": "room/{{ room_slug }}/sleep_exit_delay/set",
          "state_topic": "room/{{ room_slug }}/sleep_exit_delay/state",
          "availability_topic": "room/{{ room_slug }}/availability",
          "device": { "identifiers": ["room_settings_{{ room_slug }}"] }
        }
  - if:
      - condition: template
        value_template: "{{ states('number.room_' ~ room_slug ~ '_sleep_exit_delay') in ['unknown', 'unavailable', 'none'] }}"
    then:
      - service: mqtt.publish
        data:
          retain: true
          topic: "room/{{ room_slug }}/sleep_exit_delay/state"
          payload: "60"
  - delay: "00:00:00.050"

  # 12. Set Online & Refresh Lists
  - service: mqtt.publish
    data:
      retain: true
      topic: "room/{{ room_slug }}/availability"
      payload: "online"

  # 13. Create Occupancy Source (Select)
  - service: mqtt.publish
    data:
      retain: true
      topic: "homeassistant/select/room_{{ room_slug }}_occupancy_source/config"
      payload: >-
        {
          "name": "{{ room_name }} Occupancy Sensor",
          "object_id": "room_{{ room_slug }}_occupancy_source",
          "unique_id": "room_select_occ_source_v1_{{ room_slug }}",
          "icon": "mdi:motion-sensor-off",
          "options": ["-Select-"],
          "command_topic": "room/{{ room_slug }}/occupancy_source/set",
          "state_topic": "room/{{ room_slug }}/occupancy_source/state",
          "availability_topic": "room/{{ room_slug }}/availability",
          "device": { "identifiers": ["room_settings_{{ room_slug }}"] }
        }
  - delay: "00:00:00.050"
  # Trigger Refresh to populate options
  - service: script.refresh_room_options

# --- DELETE ROOM SETTINGS --- delete_room_settings: alias: "System: Delete Room Settings" icon: mdi:home-remove mode: single sequence: - variables: raw_slug: "{{ states('input_select.room_mgmt_delete_select') }}" # SAFETY: If slug starts with 'room_', strip it to get the clean ID room_slug: "{{ raw_slug | replace('room_', '') if raw_slug.startswith('room_') else raw_slug }}"

  # Clear Config Topics
  - service: mqtt.publish
    data:
      retain: true
      topic: "homeassistant/select/room_{{ room_slug }}_mode/config"
      payload: ""
  - service: mqtt.publish
    data:
      retain: true
      topic: "homeassistant/number/room_{{ room_slug }}_idle/config"
      payload: ""
  - service: mqtt.publish
    data:
      retain: true
      topic: "homeassistant/number/room_{{ room_slug }}_delay/config"
      payload: ""
  - service: mqtt.publish
    data:
      retain: true
      topic: "homeassistant/sensor/room_{{ room_slug }}_timer/config"
      payload: ""
  - service: mqtt.publish
    data:
      retain: true
      topic: "homeassistant/select/room_{{ room_slug }}_state/config"
      payload: ""
  - service: mqtt.publish
    data:
      retain: true
      topic: "homeassistant/binary_sensor/room_{{ room_slug }}_occupancy/config"
      payload: ""
  - service: mqtt.publish
    data:
      retain: true
      topic: "homeassistant/switch/room_{{ room_slug }}_automation/config"
      payload: ""
  # Cleanup New Bed Sensor Select
  - service: mqtt.publish
    data:
      retain: true
      topic: "homeassistant/select/room_{{ room_slug }}_bed_sensor/config"
      payload: ""

  # RESTORED: Cleanup for deprecated entities to remove legacy retained messages
  - service: mqtt.publish
    data:
      retain: true
      topic: "homeassistant/text/room_{{ room_slug }}_lux_sensor/config"
      payload: ""
  - service: mqtt.publish
    data:
      retain: true
      topic: "homeassistant/number/room_{{ room_slug }}_lux_threshold/config"
      payload: ""
  - service: mqtt.publish
    data:
      retain: true
      topic: "homeassistant/select/room_{{ room_slug }}_light_target/config"
      payload: ""
  # END RESTORED

  - service: mqtt.publish
    data:
      retain: true
      topic: "homeassistant/text/room_{{ room_slug }}_bed_sensor/config"
      payload: ""
  - service: mqtt.publish
    data:
      retain: true
      topic: "homeassistant/number/room_{{ room_slug }}_sleep_exit_delay/config"
      payload: ""
  - service: mqtt.publish
    data:
      retain: true
      topic: "homeassistant/select/room_{{ room_slug }}_occupancy_source/config"
      payload: ""

  # Set Offline
  - service: mqtt.publish
    data:
      retain: true
      topic: "room/{{ room_slug }}/availability"
      payload: "offline"

  # Refresh Lists
  # PURGE LEGACY TEXT BED SENSORS (Fixes MQTT Warnings)
  - repeat:
      for_each: "{{ room_slugs }}"
      sequence:
        - service: mqtt.publish
          data:
            retain: true
            topic: "homeassistant/text/room_{{ repeat.item }}_bed_sensor/config"
            payload: ""

  # Refresh Lists

  - delay: "00:00:02"
  - service: automation.trigger
    target:
      entity_id: automation.system_populate_room_list
    data:
      skip_condition: true

# --- REFRESH OPTIONS FOR SELECTS --- refresh_room_options: alias: "System: Refresh Room Options" mode: single sequence: - variables: # FILTER: Binary Sensors (Occupancy/Presence/Motion OR 'presence'/'occupancy' in name) sensor_list: >- {% set ns = namespace(items=['-Select-']) %} {% for s in states.binary_sensor %} {% set dev_class = s.attributes.device_class | default('') %} {% set name = s.name | lower %} {% if dev_class in ['occupancy', 'presence', 'motion'] or 'presence' in name or 'occupancy' in name %} {% set ns.items = ns.items + [s.entity_id] %} {% endif %} {% endfor %} {{ ns.items | sort | list }}

      # FILTER: Bed Sensors (Must have 'bed' AND ('occupancy' OR 'presence') in name/id)
      bed_sensor_list: >-
        {% set ns = namespace(items=['-Select-']) %}
        {% for s in states.binary_sensor %}
           {% set name = s.entity_id | lower %}
           {% if 'bed' in name and ('occupancy' in name or 'presence' in name) %}
             {% set ns.items = ns.items + [s.entity_id] %}
           {% endif %}
        {% endfor %}
        {{ ns.items | sort | list }}

      # Find all active room slugs
      room_slugs: >-
        {% set ns = namespace(rooms=[]) %}
        {% set selectors = states.select | selectattr('object_id', 'search', '_automation_mode$') | list %}
        {% for sel in selectors %}
           {% set raw_id = sel.entity_id.split('.')[1] %}
           {% set base = raw_id | replace('_automation_mode', '') %}
           {% if base.startswith('room_') %}
              {% set slug = base[5:] %}
           {% else %}
              {% set slug = base %}
           {% endif %}
           {% set ns.rooms = ns.rooms + [slug] %}
        {% endfor %}
        {{ ns.rooms | unique | list }}

  # Loop through each room and update its config topic with new options
  - repeat:
      for_each: "{{ room_slugs }}"
      sequence:
        # 1. Update Occupancy Source Options
        - service: mqtt.publish
          data:
            topic: "homeassistant/select/room_{{ repeat.item }}_occupancy_source/config"
            retain: true
            payload: >-
              {
                "name": "{{ area_name(repeat.item) | default(repeat.item) }} Occupancy Sensor",
                "object_id": "room_{{ repeat.item }}_occupancy_source",
                "unique_id": "room_select_occ_source_v1_{{ repeat.item }}",
                "icon": "mdi:motion-sensor-off",
                "options": {{ sensor_list | to_json }},
                "command_topic": "room/{{ repeat.item }}/occupancy_source/set",
                "state_topic": "room/{{ repeat.item }}/occupancy_source/state",
                "availability_topic": "room/{{ repeat.item }}/availability",
                "device": { "identifiers": ["room_settings_{{ repeat.item }}"] }
              }

        # 2. Update Bed Sensor Options
        - service: mqtt.publish
          data:
            topic: "homeassistant/select/room_{{ repeat.item }}_bed_sensor/config"
            retain: true
            payload: >-
              {
                "name": "{{ area_name(repeat.item) | default(repeat.item) }} Bed Sensor ID",
                "object_id": "room_{{ repeat.item }}_bed_sensor",
                "unique_id": "room_select_bed_sensor_v4_{{ repeat.item }}",
                "icon": "mdi:bed",
                "options": {{ bed_sensor_list | to_json }},
                "command_topic": "room/{{ repeat.item }}/bed_sensor/set",
                "state_topic": "room/{{ repeat.item }}/bed_sensor/state",
                "availability_topic": "room/{{ repeat.item }}/availability",
                "device": { "identifiers": ["room_settings_{{ repeat.item }}"] }
              }

# ... (Helper Scripts set_room_timer, etc. remain the same) ... set_room_timer: alias: "System: Set Room Timer" mode: parallel fields: room_slug: description: "Room Slug (e.g. bathroom)" required: true sequence: - variables: seconds: "{{ states('number.' ~ room_slug ~ '_lights_presence_delay') | int(0) }}" - service: mqtt.publish data: topic: "room/{{ room_slug }}/timer/attributes" payload: >- { "duration": "{{ seconds }}" } retain: true - service: mqtt.publish data: topic: "room/{{ room_slug }}/timer/state" payload: "{{ (now() + timedelta(seconds=seconds)).isoformat() }}" retain: true

cancel_room_timer: alias: "System: Cancel Room Timer" mode: parallel fields: room_slug: description: "Room Slug" required: true sequence: - service: mqtt.publish data: topic: "room/{{ room_slug }}/timer/attributes" payload: "{}" retain: true - service: mqtt.publish data: topic: "room/{{ room_slug }}/timer/state" payload: "unknown" retain: true

set_room_state: alias: "System: Set Room State" mode: parallel fields: room_slug: description: "Room Slug" required: true state: description: "Occupied, Idle, or Absence" required: true sequence: - service: mqtt.publish data: topic: "room/{{ room_slug }}/state/set" payload: "{{ state }}" retain: true

set_room_occupancy: alias: "System: Set Room Occupancy" mode: parallel fields: room_slug: description: "Room Slug" required: true occupied: description: "True (ON) or False (OFF)" required: true sequence: - service: mqtt.publish data: topic: "room/{{ room_slug }}/occupancy/state" payload: "{{ 'ON' if occupied else 'OFF' }}" retain: true

------------------------------------------------------------------------------

4. AUTOMATIONS

------------------------------------------------------------------------------

automation: # Keeps the MQTT Selects/Numbers in sync (State Persistence) - alias: "System: Room MQTT Persistence" id: system_room_mqtt_persistence mode: parallel trigger: - platform: mqtt topic: "room/#" condition: - condition: template value_template: "{{ trigger.topic.endswith('/set') }}" action: - service: mqtt.publish data: topic: "{{ trigger.topic[:-4] }}/state" payload: "{{ trigger.payload }}" retain: true

  • alias: "System: Populate Room Lists" id: system_populate_room_list trigger:

    • platform: homeassistant event: start
    • platform: time_pattern hours: "/1" action: # Define room_slugs for use in cleanup loop
    • variables: room_slugs: >- {% set ns = namespace(rooms=[]) %} {% set mode_selectors = states.select | selectattr('object_id', 'search', 'automation_mode$') | list %} {% for sel in mode_selectors %} {% set raw_id = sel.entity_id.split('.')[1] %} {% set base = raw_id | replace('_automation_mode', '') %} {% if base.startswith('room') %} {% set slug = base[5:] %} {% else %} {% set slug = base %} {% endif %} {% set ns.rooms = ns.rooms + [slug] %} {% endfor %} {{ ns.rooms | unique | list }}

    # 1. Populate 'Create' dropdown with Native Areas - service: input_select.set_options target: entity_id: input_select.room_mgmt_create_select data: options: > {# Get list of all area IDs #} {{ (['unknown'] + areas() | sort | list) }}

    # 2. Populate 'Delete' dropdown with Existing Rooms - service: input_select.set_options target: entity_id: input_select.room_mgmt_delete_select data: # UPDATED: Fixed populate logic to properly strip prefixes options: > {% set ns = namespace(rooms=[]) %} {% set mode_selectors = states.select | selectattr('object_id', 'search', 'automation_mode$') | list %} {% for sel in mode_selectors %} {# Extract slug. Handles "select.bathroom_automation_mode" or "select.room_bathroom_automation_mode" #} {% set raw_id = sel.entity_id.split('.')[1] %} {% set base = raw_id | replace('_automation_mode', '') %} {% if base.startswith('room') %} {% set slug = base[5:] %} {% else %} {% set slug = base %} {% endif %} {% set ns.rooms = ns.rooms + [slug] %} {% endfor %} {{ (['unknown'] + ns.rooms | unique | list) }} # 3. Purge Legacy Text Bed Sensors (Fixes MQTT Warnings) - repeat: for_each: "{{ room_slugs }}" sequence: - service: mqtt.publish data: retain: true topic: "homeassistant/text/room_{{ repeat.item }}_bed_sensor/config" payload: ""

    • service: script.refresh_room_options