[Claude] - Analyze MQTT Light Selector.yaml file designed to work as automation blueprint for Home Assistant OS. Refactor it improving overall code quality, fix obvious or critical bugs/mistakes, fix spelling if required and add comments that will make the code more easy to read and understand.

This commit is contained in:
2026-01-22 03:12:51 +03:00
parent eb914fe9b8
commit cf41e7b0e6

View File

@@ -1,92 +1,137 @@
# =============================================================================
# MQTT Light Selector Blueprint
# =============================================================================
# Cycles through a list of lights using MQTT button events (up/down/remind).
# The selected light flashes to provide visual feedback, then returns to its
# original state. Selection is persisted in an input_text helper.
# =============================================================================
blueprint: blueprint:
name: "Custom: MQTT Light Selector" name: "MQTT Light Selector"
description: > description: >
Cycle through a list of lights using MQTT button events (up/down). Cycle through a list of lights using MQTT button events.
Selected light is stored in an input_text helper and flashes N times
with Z interval when selected. **Features:**
- Navigate lights with up/down actions
- Remind action flashes the currently selected light
- Visual feedback via configurable flash pattern
- Persists selection in an input_text helper
- Optional state persistence across restarts (JSON storage)
- Optional "remind on idle" - up/down acts as remind if idle too long
**How it works:**
1. Press up/down to cycle through the light list
2. Selected light flashes N times to confirm selection
3. Light returns to its original on/off state after flashing
domain: automation domain: automation
input: input:
# -------------------------------------------------------------------------
# MQTT Device Configuration
# -------------------------------------------------------------------------
devices: devices:
name: "Devices" name: "MQTT Devices"
collapsed: false collapsed: false
input: input:
mqtt_topic: mqtt_topic:
name: MQTT Topic name: Primary MQTT Topic
description: Topic where button events are published description: Main topic where button events are published (e.g., `zigbee2mqtt/button/action`)
selector:
text: {}
mqtt_topic2:
name: MQTT Topic
description: Topic where button events are published
default: 'fake'
selector: selector:
text: {} text: {}
mqtt_topic2:
name: Secondary MQTT Topic (Optional)
description: >
Additional MQTT topic for a second device.
Leave as default placeholder if not using a second device.
default: "blueprint/disabled/mqtt_light_selector"
selector:
text: {}
# -------------------------------------------------------------------------
# Light Selection
# -------------------------------------------------------------------------
lights: lights:
name: "Lights" name: "Lights"
collapsed: false collapsed: false
input: input:
lights: lights:
name: Lights name: Lights to Cycle
description: List of lights to cycle through description: >
List of lights to cycle through.
Order determines navigation sequence.
selector: selector:
entity: entity:
domain: light domain: light
multiple: true multiple: true
# -------------------------------------------------------------------------
# Persistent State Configuration
# -------------------------------------------------------------------------
persistent_state: persistent_state:
name: "Persiatent State" name: "Persistent State"
collapsed: false collapsed: false
input: input:
selected_light_helper: selected_light_helper:
name: Selected Light Helper name: Selected Light Helper
description: Input_text entity to store the selected light description: >
Input_text entity to store the currently selected light entity ID.
Create one via Settings → Devices & Services → Helpers.
selector: selector:
entity: entity:
domain: input_text domain: input_text
automation_state_entity: automation_state_entity:
name: Automation state entity name: Automation State Entity (Optional)
description: The `input_text` entity will store state of the automation in JSON format. `Doesn't require any initial state, can be empty. For now each automation must have it's personal entity.` description: >
Input_text entity for storing automation state as JSON.
Used to remember light state during flash sequence.
Leave empty to disable state persistence.
default: null default: null
selector: selector:
entity: entity:
domain: domain: input_text
- input_text
# -------------------------------------------------------------------------
# Action ID Mapping
# -------------------------------------------------------------------------
action_ids: action_ids:
name: "Action IDs" name: "Action IDs"
collapsed: false collapsed: false
input: input:
action_up: action_up:
name: Up Action Identifier name: Next Light Action ID
description: Payload string for "next light" description: MQTT payload action value for selecting the next light in the list.
default: '' default: ""
selector: selector:
text: {} text: {}
action_down: action_down:
name: Down Action Identifier name: Previous Light Action ID
description: Payload string for "previous light" description: MQTT payload action value for selecting the previous light in the list.
default: '' default: ""
selector: selector:
text: {} text: {}
action_remind: action_remind:
name: Remind Action Identifier name: Remind Action ID (Optional)
description: Payload string for "current light" description: >
default: '' MQTT payload action value for flashing the current selection without changing it.
Leave empty to disable.
default: ""
selector: selector:
text: {} text: {}
# -------------------------------------------------------------------------
# Behavior Parameters
# -------------------------------------------------------------------------
params: params:
name: "Parameters" name: "Parameters"
collapsed: false collapsed: false
input: input:
transition: transition:
name: Transition Time (ms) name: Light Transition Time
description: Duration of brightness transition description: Duration of on/off transitions during flash sequence.
default: 0 default: 0
selector: selector:
number: number:
@@ -96,8 +141,11 @@ blueprint:
unit_of_measurement: ms unit_of_measurement: ms
remind_using_up_down_delay: remind_using_up_down_delay:
name: Force Remind Using Up/Down Delay name: Idle Remind Threshold
description: "If specified then `Up`/`Down` action will work like `Remind` in case if duration from the last action was greater then this value" description: >
If set, up/down actions will act as "remind" (flash current selection)
when more than this many seconds have passed since the last selection.
Set to 0 to disable this behavior.
default: 0 default: 0
selector: selector:
number: number:
@@ -108,7 +156,7 @@ blueprint:
flash_count: flash_count:
name: Flash Count name: Flash Count
description: Number of times to flash selected light description: Number of times to flash the selected light.
default: 2 default: 2
selector: selector:
number: number:
@@ -117,8 +165,8 @@ blueprint:
step: 1 step: 1
flash_interval_ms: flash_interval_ms:
name: Flash Interval (ms) name: Flash Interval
description: Interval between flashes in milliseconds description: Time between each flash on/off cycle.
default: 500 default: 500
selector: selector:
number: number:
@@ -127,42 +175,53 @@ blueprint:
step: 100 step: 100
unit_of_measurement: ms unit_of_measurement: ms
# -------------------------------------------------------------------------
# Advanced: Conditions & Callbacks
# -------------------------------------------------------------------------
actions_group: actions_group:
name: "Actions" name: "Actions"
collapsed: false collapsed: false
input: input:
condition_action: condition_action:
name: Extra Condition name: Extra Condition
description: Optional condition to check before running actions description: Optional condition that must be true for the automation to run.
default: [] default: []
selector: selector:
condition: {} condition: {}
callback_action: callback_action:
name: Callback Action name: Callback Action
description: Optional action to run after main sequence description: Optional action to run after selecting a new light (before flashing).
default: [] default: []
selector: selector:
action: {} action: {}
# =============================================================================
# Triggers: Listen for MQTT messages from configured devices
# =============================================================================
trigger: trigger:
- platform: mqtt - platform: mqtt
topic: !input mqtt_topic topic: !input mqtt_topic
id: "mqtt" id: "mqtt_primary"
- platform: mqtt - platform: mqtt
topic: !input mqtt_topic2 topic: !input mqtt_topic2
id: "mqtt" id: "mqtt_secondary"
# Apply user-defined condition before processing
condition: !input condition_action condition: !input condition_action
# Restart mode ensures rapid button presses are handled correctly
mode: restart mode: restart
# =============================================================================
# Variables: Configuration and state management
# =============================================================================
variables: variables:
# ----- Debug flag (set to true for troubleshooting) -----
# Constants.
is_debug: false is_debug: false
# Defines. # ----- Input references -----
lights: !input lights lights: !input lights
helper: !input selected_light_helper helper: !input selected_light_helper
action_up: !input action_up action_up: !input action_up
@@ -172,44 +231,61 @@ variables:
flash_interval_ms: !input flash_interval_ms flash_interval_ms: !input flash_interval_ms
transition: !input transition transition: !input transition
remind_using_up_down_delay: !input remind_using_up_down_delay remind_using_up_down_delay: !input remind_using_up_down_delay
mqtt_topic: !input mqtt_topic callback_action: !input callback_action
# JSON global state. # ----- State persistence keys (short names to save space in JSON) -----
state_key_last_was_on: 'lwo' # lwo = last_was_on, ll = last_light, lsadt = last_select_action_datetime
state_key_last_light: 'll' state_key_last_was_on: "lwo"
state_key_last_select_action_datetime: 'lsadt' state_key_last_light: "ll"
state_key_last_select_action_datetime: "lsadt"
# ----- Automation state entity and global state parsing -----
automation_state_entity: !input automation_state_entity automation_state_entity: !input automation_state_entity
automation_state_global: >
{% if automation_state_entity is not none %} # Parse the JSON state from the helper entity (or return empty dict)
{% set text = states(automation_state_entity) | string %} automation_state_global: >-
{% if text in ['unknown','unavailable','none',''] %} {% if automation_state_entity is not none %}
{{ dict() }} {% set text = states(automation_state_entity) | string %}
{% else %} {% if text in ['unknown', 'unavailable', 'none', ''] %}
{{ text | from_json }}
{% endif %}
{% else %}
{{ dict() }} {{ dict() }}
{% else %}
{{ text | from_json }}
{% endif %} {% endif %}
{% else %}
{{ dict() }}
{% endif %}
current_datetime: "{{ now() }}" current_datetime: "{{ now() }}"
# TODO alexeid: it's better to use mqtt_topic as key, but cyrilic characters require use of tranliteration # Unique key for this automation instance (based on first light in list)
# Note: Using entity_id avoids issues with special characters in MQTT topics
automation_state_key: "mqtt_light_selector:{{ lights[0] }}" automation_state_key: "mqtt_light_selector:{{ lights[0] }}"
automation_state: "{{ automation_state_global.get(automation_state_key, dict()) if automation_state_key != '' else dict() }}"
# Extract this automation's state from the global state object
automation_state: >-
{{ automation_state_global.get(automation_state_key, dict()) if automation_state_key != '' else dict() }}
# Retrieve persisted values (with defaults)
state_last_was_on: "{{ automation_state.get(state_key_last_was_on, false) | bool }}" state_last_was_on: "{{ automation_state.get(state_key_last_was_on, false) | bool }}"
state_last_light: "{{ automation_state.get(state_key_last_light, '') | string }}" state_last_light: "{{ automation_state.get(state_key_last_light, '') | string }}"
state_last_select_action_datetime: "{{ as_datetime(automation_state.get(state_key_last_select_action_datetime, current_datetime)) }}" state_last_select_action_datetime: >-
{{ as_datetime(automation_state.get(state_key_last_select_action_datetime, current_datetime)) }}
# Current index from helper (fallback to 0 if empty) # ----- Current selection from helper -----
current_light: > current_light: >-
{% set entity_id = states(helper) %} {% set entity_id = states(helper) %}
{{ entity_id if entity_id in lights else none }} {{ entity_id if entity_id in lights else none }}
current_index: >
{% set idx = lights.index(current_light) if current_light in lights else 0 %}
{{ idx }}
current_index: >-
{{ lights.index(current_light) if current_light in lights else 0 }}
# =============================================================================
# Actions: Main automation logic
# =============================================================================
action: action:
# Debug info (log if required) # ---------------------------------------------------------------------------
# Debug: Log state information if debug mode is enabled
# ---------------------------------------------------------------------------
- choose: - choose:
- conditions: - conditions:
- condition: template - condition: template
@@ -217,23 +293,38 @@ action:
sequence: sequence:
- service: persistent_notification.create - service: persistent_notification.create
data: data:
title: "Debug Info" title: "MQTT Light Selector Debug"
message: "automation_state_key = {{ automation_state_key }}" message: >
State key: {{ automation_state_key }}
Current light: {{ current_light }}
Current index: {{ current_index }}
Last light: {{ state_last_light }}
Last was on: {{ state_last_was_on }}
# ---------------------------------------------------------------------------
# MQTT Message Handler
# ---------------------------------------------------------------------------
- choose: - choose:
# MQTT -> handle the message
- conditions: - conditions:
# Handle messages from either MQTT trigger
- condition: template - condition: template
value_template: "{{ trigger.id == 'mqtt' }}" value_template: "{{ trigger.id in ['mqtt_primary', 'mqtt_secondary'] }}"
sequence: sequence:
# Extract action ID from MQTT payload
- variables: - variables:
action_id: "{{ trigger.payload_json.action }}" action_id: "{{ trigger.payload_json.action }}"
# Don't forget to restore last light state
# -----------------------------------------------------------------
# Step 1: Restore previous light state if interrupted mid-flash
# -----------------------------------------------------------------
# If automation was restarted during a flash sequence, restore
# the light to its original state before proceeding
- choose: - choose:
- conditions: - conditions:
- condition: template - condition: template
value_template: "{{ state_last_light != '' }}" value_template: "{{ state_last_light != '' }}"
sequence: sequence:
# Restore light to its previous on/off state
- choose: - choose:
- conditions: - conditions:
- condition: template - condition: template
@@ -244,18 +335,14 @@ action:
entity_id: "{{ state_last_light }}" entity_id: "{{ state_last_light }}"
data: data:
transition: "{{ transition }}" transition: "{{ transition }}"
default:
- service: light.turn_off
target:
entity_id: "{{ state_last_light }}"
data:
transition: "{{ transition }}"
- conditions: # Clear the "last light" from state since we've restored it
- condition: template
value_template: "{{ not state_last_was_on }}"
sequence:
- service: light.turn_off
target:
entity_id: "{{ state_last_light }}"
data:
transition: "{{ transition }}"
# Save persistent state.
- choose: - choose:
- conditions: - conditions:
- condition: template - condition: template
@@ -265,25 +352,34 @@ action:
target: target:
entity_id: "{{ automation_state_entity }}" entity_id: "{{ automation_state_entity }}"
data: data:
value: > value: >-
{% set new_automation_state = (automation_state | combine({ state_key_last_light: '' })) %} {% set new_state = automation_state | combine({
{% set new_automation_state = (new_automation_state | combine({ state_key_last_was_on: new_on })) %} state_key_last_light: '',
{% set new_automation_state = (new_automation_state | combine({ state_key_last_select_action_datetime: current_datetime })) %} state_key_last_was_on: state_last_was_on,
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }} state_key_last_select_action_datetime: current_datetime | string
}) %}
{{ automation_state_global | combine({ automation_state_key: new_state }) | tojson }}
# -----------------------------------------------------------------
# Do actual selection # Step 2: Process selection action (up/down/remind)
# -----------------------------------------------------------------
- choose: - choose:
- conditions: - conditions:
- condition: template - condition: template
value_template: "{{ (action_id != '') and (action_id == action_up or action_id == action_down or action_id == action_remind) }}" value_template: >-
{{ action_id != '' and action_id in [action_up, action_down, action_remind] }}
sequence: sequence:
# Calculate the new selection
- variables: - variables:
datetime_diff_seconds: > # Time since last selection (for idle remind feature)
{% set diff = current_datetime - state_last_select_action_datetime %} datetime_diff_seconds: >-
{{ diff.total_seconds() }} {{ (current_datetime - state_last_select_action_datetime).total_seconds() }}
step: >
{% if remind_using_up_down_delay != 0 and datetime_diff_seconds < remind_using_up_down_delay %} # Determine step direction:
# - If idle too long and remind_using_up_down_delay is set, treat as remind (step=0)
# - Otherwise: up=+1, down=-1, remind=0
step: >-
{% if remind_using_up_down_delay > 0 and datetime_diff_seconds > remind_using_up_down_delay %}
0 0
{% elif action_up != '' and action_id == action_up %} {% elif action_up != '' and action_id == action_up %}
1 1
@@ -292,11 +388,15 @@ action:
{% else %} {% else %}
0 0
{% endif %} {% endif %}
new_index: "{{ (current_index + step) % lights|length }}"
# Calculate new index with wraparound
new_index: "{{ (current_index + step) % (lights | length) }}"
new_light: "{{ lights[new_index] }}" new_light: "{{ lights[new_index] }}"
# Remember if light was on before we start flashing
new_on: "{{ is_state(new_light, 'on') }}" new_on: "{{ is_state(new_light, 'on') }}"
# Save persistent state. # Save state before flashing (to restore if interrupted)
- choose: - choose:
- conditions: - conditions:
- condition: template - condition: template
@@ -306,24 +406,31 @@ action:
target: target:
entity_id: "{{ automation_state_entity }}" entity_id: "{{ automation_state_entity }}"
data: data:
value: > value: >-
{% set new_automation_state = (automation_state | combine({ state_key_last_light: new_light })) %} {% set new_state = automation_state | combine({
{% set new_automation_state = (new_automation_state | combine({ state_key_last_was_on: new_on })) %} state_key_last_light: new_light,
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }} state_key_last_was_on: new_on,
state_key_last_select_action_datetime: current_datetime | string
}) %}
{{ automation_state_global | combine({ automation_state_key: new_state }) | tojson }}
# Run callback only if user provided it: think if we need to invoke callback here # Run user-defined callback action (if provided)
- choose: - choose:
- conditions: - conditions:
- condition: template - condition: template
value_template: "{{ callback_action is defined and (callback_action|length > 0) }}" value_template: "{{ callback_action is defined and (callback_action | length > 0) }}"
sequence: !input callback_action sequence: !input callback_action
# Assign new light entity id to helper value # Update the helper with the new selection
- service: input_text.set_value - service: input_text.set_value
target: target:
entity_id: "{{ helper }}" entity_id: "{{ helper }}"
data: data:
value: "{{ new_light }}" value: "{{ new_light }}"
# -----------------------------------------------------------------
# Flash sequence: Visual feedback for selection
# -----------------------------------------------------------------
- repeat: - repeat:
count: "{{ flash_count }}" count: "{{ flash_count }}"
sequence: sequence:
@@ -332,6 +439,7 @@ action:
entity_id: "{{ new_light }}" entity_id: "{{ new_light }}"
data: data:
transition: "{{ transition }}" transition: "{{ transition }}"
- delay: - delay:
milliseconds: "{{ flash_interval_ms }}" milliseconds: "{{ flash_interval_ms }}"
@@ -340,10 +448,11 @@ action:
entity_id: "{{ new_light }}" entity_id: "{{ new_light }}"
data: data:
transition: "{{ transition }}" transition: "{{ transition }}"
- delay: - delay:
milliseconds: "{{ flash_interval_ms }}" milliseconds: "{{ flash_interval_ms }}"
# Optionally turn off the light. # Restore light to original state if it was off
- choose: - choose:
- conditions: - conditions:
- condition: template - condition: template
@@ -355,7 +464,7 @@ action:
data: data:
transition: "{{ transition }}" transition: "{{ transition }}"
# Save persistent state. # Clear state after successful completion
- choose: - choose:
- conditions: - conditions:
- condition: template - condition: template
@@ -365,8 +474,10 @@ action:
target: target:
entity_id: "{{ automation_state_entity }}" entity_id: "{{ automation_state_entity }}"
data: data:
value: > value: >-
{% set new_automation_state = (automation_state | combine({ state_key_last_light: '' })) %} {% set new_state = automation_state | combine({
{% set new_automation_state = (new_automation_state | combine({ state_key_last_was_on: new_on })) %} state_key_last_light: '',
{{ automation_state_global | combine({ automation_state_key: new_automation_state }) | tojson }} state_key_last_was_on: new_on,
state_key_last_select_action_datetime: current_datetime | string
}) %}
{{ automation_state_global | combine({ automation_state_key: new_state }) | tojson }}