feat: add debug mode and clarify docs in MQTT Button Control
- Add optional debug notifications for trigger and light-branch decisions - Document index-based action/entity mapping and two-topic support - Add configuration reference table and debug-mode section to README
This commit is contained in:
@@ -4,19 +4,42 @@ Controls lights and switches using Zigbee2MQTT button devices with multiple acti
|
||||
|
||||
## Features
|
||||
|
||||
- Map multiple action IDs to different lights/switches
|
||||
- Supports light, switch, and input_boolean entities
|
||||
- Visual feedback via blink indication when pressing already-active light
|
||||
- Tracks last interacted entity in an input_text helper
|
||||
- Supports multiple MQTT topics (multiple buttons)
|
||||
- Map multiple action IDs to different lights/switches by index position
|
||||
- Supports `light`, `switch`, and `input_boolean` entities
|
||||
- Visual feedback via blink indication when pressing an already-active light after an idle timeout
|
||||
- Optionally tracks last interacted entity in an `input_text` helper
|
||||
- Supports two MQTT topics for two physical buttons
|
||||
- Optional debug notifications for troubleshooting
|
||||
|
||||
## How It Works
|
||||
|
||||
1. Receives MQTT messages with action IDs from Zigbee buttons
|
||||
2. Maps action ID to corresponding entity by index position
|
||||
3. Toggles the matched entity (light/switch/input_boolean)
|
||||
4. Optionally blinks light if it's already on (idle timeout feature)
|
||||
1. Receives MQTT messages from one or two Zigbee buttons.
|
||||
2. Reads `action` from the payload and looks it up in the configured **Action IDs** list.
|
||||
3. The matched index is used to pick the corresponding entity from the **Switches** list.
|
||||
4. Toggles the matched entity (`light` / `switch` / `input_boolean`).
|
||||
5. For lights, if the light is already on and has been on for longer than the configured idle timeout, the light is briefly blinked instead of toggled, to indicate it is already active.
|
||||
6. If an `input_text` helper is configured, the matched entity ID is written to it.
|
||||
|
||||
## Configuration
|
||||
|
||||
| Input | Description |
|
||||
| --- | --- |
|
||||
| **MQTT Topic** | Primary topic (e.g. `zigbee2mqtt/my_button1`). |
|
||||
| **MQTT Topic 2 (Optional)** | Second topic. Leave the placeholder to disable the second trigger. |
|
||||
| **Action IDs** | Ordered list of action strings emitted by the button (e.g. `single`, `double`, `hold`). |
|
||||
| **Switches** | Entities matched to **Action IDs** by index — index `0` maps to action `0`, and so on. |
|
||||
| **Last Interacted Entity Text Helper (optional)** | `input_text` that receives the last toggled entity ID. Leave empty to skip. |
|
||||
| **Timeout for indicator blink** | Idle seconds after which a press on an already-on light blinks instead of toggling. `0` disables blinking. |
|
||||
| **Count of blinks** | Number of blink cycles. `0` also disables blinking (every press just toggles). |
|
||||
| **Interval between blinks** | Delay between turn off / turn on in milliseconds. |
|
||||
| **Enable Debug Notifications** | When on, posts a persistent notification per trigger and per light-branch decision. |
|
||||
|
||||
The number of **Action IDs** should match the number of **Switches**. Out-of-range or unknown actions are ignored safely.
|
||||
|
||||
## Debug Mode
|
||||
|
||||
Enable **Enable Debug Notifications** to surface the resolved action, target index, target entity, and (for lights) the elapsed-time / blink decision in Home Assistant's persistent notifications panel. Disable when not troubleshooting to avoid notification noise.
|
||||
|
||||
## Author
|
||||
|
||||
Alexei Dolgolyov (dolgolyov.alexei@gmail.com)
|
||||
Alexei Dolgolyov (<dolgolyov.alexei@gmail.com>)
|
||||
|
||||
@@ -6,10 +6,13 @@
|
||||
|
||||
blueprint:
|
||||
name: "Custom: MQTT Button Control"
|
||||
description: Control a Zigbee2MQTT device with multiple actions, that allows to toggle lights and switches and store entity identifier of last interacted switch/light.
|
||||
description: >
|
||||
Control a Zigbee2MQTT device with multiple actions: toggle lights,
|
||||
switches and input_booleans, and optionally remember the entity that
|
||||
was last interacted with in an input_text helper.
|
||||
domain: automation
|
||||
input:
|
||||
|
||||
|
||||
mqtt_group:
|
||||
name: "MQTT"
|
||||
collapsed: false
|
||||
@@ -19,7 +22,7 @@ blueprint:
|
||||
description: The MQTT topic for your Zigbee button (e.g., zigbee2mqtt/my_button1).
|
||||
selector:
|
||||
text:
|
||||
|
||||
|
||||
mqtt_topic2:
|
||||
name: MQTT Topic 2 (Optional)
|
||||
description: >
|
||||
@@ -28,7 +31,7 @@ blueprint:
|
||||
default: "blueprint/disabled/mqtt_button_control"
|
||||
selector:
|
||||
text:
|
||||
|
||||
|
||||
devices:
|
||||
name: "Primary"
|
||||
collapsed: false
|
||||
@@ -40,19 +43,19 @@ blueprint:
|
||||
selector:
|
||||
text:
|
||||
multiple: true
|
||||
|
||||
|
||||
switches:
|
||||
name: Switches
|
||||
description: "The list of switches to control. Next types are supported: `light`, `switch`, `input_boolean`"
|
||||
default: []
|
||||
selector:
|
||||
entity:
|
||||
domain:
|
||||
domain:
|
||||
- light
|
||||
- switch
|
||||
- input_boolean
|
||||
multiple: true
|
||||
|
||||
multiple: true
|
||||
|
||||
outputs:
|
||||
name: "Outputs"
|
||||
collapsed: false
|
||||
@@ -64,7 +67,7 @@ blueprint:
|
||||
selector:
|
||||
entity:
|
||||
domain: input_text
|
||||
|
||||
|
||||
common:
|
||||
name: "Common"
|
||||
collapsed: false
|
||||
@@ -78,8 +81,8 @@ blueprint:
|
||||
min: 0
|
||||
max: 100
|
||||
step: 1
|
||||
unit_of_measurement: "s"
|
||||
|
||||
unit_of_measurement: "s"
|
||||
|
||||
blink_count:
|
||||
name: Count of blinks
|
||||
description: "Count of blinks to indicate active light"
|
||||
@@ -88,8 +91,8 @@ blueprint:
|
||||
number:
|
||||
min: 0
|
||||
max: 5
|
||||
step: 1
|
||||
|
||||
step: 1
|
||||
|
||||
blink_interval:
|
||||
name: Interval between blinks
|
||||
description: "Interval between indicator blinks (in ms)"
|
||||
@@ -98,18 +101,36 @@ blueprint:
|
||||
number:
|
||||
min: 0
|
||||
max: 1000
|
||||
step: 50
|
||||
unit_of_measurement: "ms"
|
||||
step: 50
|
||||
unit_of_measurement: "ms"
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Debug
|
||||
# -------------------------------------------------------------------------
|
||||
debug_group:
|
||||
name: "Debug"
|
||||
collapsed: true
|
||||
input:
|
||||
enable_debug_notifications:
|
||||
name: Enable Debug Notifications
|
||||
description: >
|
||||
Send persistent notifications for debugging automation behavior.
|
||||
Shows received action, resolved target entity and blink decision.
|
||||
default: false
|
||||
selector:
|
||||
boolean:
|
||||
|
||||
trigger:
|
||||
- platform: mqtt
|
||||
topic: !input mqtt_topic
|
||||
id: button_1
|
||||
- platform: mqtt
|
||||
topic: !input mqtt_topic2
|
||||
enabled: "{{ mqtt_topic2 != '' }}"
|
||||
|
||||
id: button_2
|
||||
enabled: "{{ mqtt_topic2 not in ['', 'blueprint/disabled/mqtt_button_control'] }}"
|
||||
|
||||
mode: restart
|
||||
|
||||
|
||||
condition:
|
||||
- condition: template
|
||||
value_template: "{{ 'action' in trigger.payload_json }}"
|
||||
@@ -117,21 +138,59 @@ condition:
|
||||
action:
|
||||
- variables:
|
||||
action_id: "{{ trigger.payload_json.action }}"
|
||||
|
||||
|
||||
switches: !input switches
|
||||
action_ids: !input action_ids
|
||||
|
||||
target_index: >
|
||||
{% if action_id in action_ids %}
|
||||
{{ action_ids.index(action_id) }}
|
||||
{% else %}
|
||||
-1
|
||||
{% endif %}
|
||||
target_entity: "{{ switches[target_index] if target_index != -1 else none }}"
|
||||
last_interacted_text: !input last_interacted_text
|
||||
|
||||
is_debug: false
|
||||
|
||||
is_debug: !input enable_debug_notifications
|
||||
|
||||
# Resolve action_id → switch index. Whitespace-controlled and coerced
|
||||
# to int so subscripting `switches[target_index]` is safe.
|
||||
target_index: >-
|
||||
{%- if action_id in action_ids -%}
|
||||
{{ action_ids.index(action_id) }}
|
||||
{%- else -%}
|
||||
-1
|
||||
{%- endif -%}
|
||||
# Bounds-check the index: action_ids may have more entries than switches.
|
||||
target_entity: >-
|
||||
{%- set idx = target_index | int(-1) -%}
|
||||
{%- if idx >= 0 and idx < (switches | length) -%}
|
||||
{{ switches[idx] }}
|
||||
{%- else -%}
|
||||
{{ none }}
|
||||
{%- endif -%}
|
||||
has_last_interacted_text: "{{ last_interacted_text is string and last_interacted_text | trim != '' }}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Debug: trigger received
|
||||
# ---------------------------------------------------------------------------
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ is_debug }}"
|
||||
sequence:
|
||||
- service: persistent_notification.create
|
||||
data:
|
||||
notification_id: "mqtt_button_control_debug"
|
||||
title: "MQTT Button Control Debug"
|
||||
message: >
|
||||
Trigger: {{ trigger.id }}
|
||||
|
||||
Topic: {{ trigger.topic }}
|
||||
|
||||
action_id: {{ action_id }}
|
||||
|
||||
action_ids: {{ action_ids }}
|
||||
|
||||
switches: {{ switches }}
|
||||
|
||||
target_index: {{ target_index | int(-1) }}
|
||||
|
||||
target_entity: {{ target_entity if target_entity is not none else 'none' }}
|
||||
|
||||
has_last_interacted_text: {{ has_last_interacted_text }}
|
||||
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
@@ -139,12 +198,18 @@ action:
|
||||
sequence:
|
||||
- variables:
|
||||
entity_type: "{{ target_entity.split('.')[0] }}"
|
||||
|
||||
- service: input_text.set_value
|
||||
data:
|
||||
entity_id: "{{ last_interacted_text }}"
|
||||
value: "{{ target_entity }}"
|
||||
|
||||
|
||||
# Persist last interacted entity only when a helper is configured.
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ has_last_interacted_text }}"
|
||||
sequence:
|
||||
- service: input_text.set_value
|
||||
data:
|
||||
entity_id: "{{ last_interacted_text }}"
|
||||
value: "{{ target_entity }}"
|
||||
|
||||
- choose:
|
||||
# Light
|
||||
- conditions:
|
||||
@@ -153,14 +218,29 @@ action:
|
||||
sequence:
|
||||
- variables:
|
||||
timeout_for_indication_blink: !input timeout_for_indication_blink
|
||||
seconds_elapsed: >
|
||||
{{ (as_timestamp(now()) - as_timestamp(states[target_entity].last_changed)) | int }}
|
||||
should_blink: "{{ timeout_for_indication_blink != 0 and seconds_elapsed > timeout_for_indication_blink }}"
|
||||
blink_count: !input blink_count
|
||||
blink_timeout: !input blink_interval
|
||||
is_light_on: "{{ is_state(target_entity, 'on') }}"
|
||||
|
||||
# Debug
|
||||
# Inline state access — HA stringifies State objects when
|
||||
# stored in `variables:`, so `.last_changed` must be read
|
||||
# within a single template render.
|
||||
seconds_elapsed: >-
|
||||
{%- set s = states[target_entity] -%}
|
||||
{%- if s is not none -%}
|
||||
{{ (as_timestamp(now()) - as_timestamp(s.last_changed)) | int(0) }}
|
||||
{%- else -%}
|
||||
0
|
||||
{%- endif -%}
|
||||
# Blink only when both the idle timeout AND a positive
|
||||
# blink count are configured; otherwise fall through to a
|
||||
# plain toggle (a 0 count would otherwise run an empty
|
||||
# repeat and leave the light untouched).
|
||||
should_blink: >-
|
||||
{{ timeout_for_indication_blink != 0
|
||||
and (blink_count | int(0)) > 0
|
||||
and (seconds_elapsed | int(0)) > timeout_for_indication_blink }}
|
||||
|
||||
# Debug: light branch state
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
@@ -168,13 +248,25 @@ action:
|
||||
sequence:
|
||||
- service: persistent_notification.create
|
||||
data:
|
||||
title: "Debug Info"
|
||||
notification_id: "mqtt_button_control_debug_light"
|
||||
title: "MQTT Button Control Debug (light)"
|
||||
message: >
|
||||
seconds_elapsed = {{ seconds_elapsed }},
|
||||
should_blink = {{ should_blink }}
|
||||
|
||||
target_entity: {{ target_entity }}
|
||||
|
||||
is_light_on: {{ is_light_on }}
|
||||
|
||||
seconds_elapsed: {{ seconds_elapsed }}
|
||||
|
||||
timeout_for_indication_blink: {{ timeout_for_indication_blink }}
|
||||
|
||||
should_blink: {{ should_blink }}
|
||||
|
||||
blink_count: {{ blink_count }}
|
||||
|
||||
blink_timeout (ms): {{ blink_timeout }}
|
||||
|
||||
- choose:
|
||||
# Blink
|
||||
# Blink (indicate light is already on after idle timeout)
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ should_blink and is_light_on }}"
|
||||
@@ -182,7 +274,6 @@ action:
|
||||
- repeat:
|
||||
count: "{{ blink_count }}"
|
||||
sequence:
|
||||
|
||||
- service: light.turn_off
|
||||
target:
|
||||
entity_id: "{{ target_entity }}"
|
||||
@@ -190,7 +281,7 @@ action:
|
||||
transition: 0
|
||||
- delay:
|
||||
milliseconds: "{{ blink_timeout }}"
|
||||
|
||||
|
||||
- service: light.turn_on
|
||||
target:
|
||||
entity_id: "{{ target_entity }}"
|
||||
@@ -198,13 +289,13 @@ action:
|
||||
transition: 0
|
||||
- delay:
|
||||
milliseconds: "{{ blink_timeout }}"
|
||||
|
||||
# Actually toggle
|
||||
|
||||
# Actually toggle
|
||||
default:
|
||||
- service: light.toggle
|
||||
target:
|
||||
entity_id: "{{ target_entity }}"
|
||||
|
||||
|
||||
# Switch
|
||||
- conditions:
|
||||
- condition: template
|
||||
@@ -213,7 +304,7 @@ action:
|
||||
- service: switch.toggle
|
||||
target:
|
||||
entity_id: "{{ target_entity }}"
|
||||
|
||||
|
||||
# Input Boolean
|
||||
- conditions:
|
||||
- condition: template
|
||||
@@ -221,4 +312,4 @@ action:
|
||||
sequence:
|
||||
- service: input_boolean.toggle
|
||||
target:
|
||||
entity_id: "{{ target_entity }}"
|
||||
entity_id: "{{ target_entity }}"
|
||||
|
||||
Reference in New Issue
Block a user