Add Philips Hue Tap Dial Z2M blueprint
This commit is contained in:
@@ -0,0 +1,503 @@
|
||||
blueprint:
|
||||
name: Z2M - Philips Hue Tap Dial Switch - Light Control (MQTT Direct)
|
||||
description: >
|
||||
Control up to 4 lights and run custom actions with a Philips Hue Tap Dial Switch via Zigbee2MQTT.
|
||||
|
||||
**Why use this version?**
|
||||
Zigbee2MQTT 2.0+ deprecated and removed the legacy `action` sensor. This blueprint connects directly to the MQTT topic, meaning it works flawlessly with the newest Z2M updates without needing legacy workarounds!
|
||||
|
||||
**Features:**
|
||||
- Assign a Light to each button to Toggle it.
|
||||
- Set Custom Actions for Single, Double, and Long Presses on all 4 buttons.
|
||||
- Automatically updates an `input_text` helper to remember the last used light.
|
||||
- Rotating the dial dims the most recently interacted light smoothly.
|
||||
- **Fallback Light**: A default light to dim if the dial is turned before any buttons are pressed.
|
||||
|
||||
**REQUIREMENT:** Create an `input_text` helper in Home Assistant (Settings > Devices & Services > Helpers) and select it below in the "Last Light Helper" input.
|
||||
domain: automation
|
||||
input:
|
||||
mqtt_topic:
|
||||
name: MQTT Topic
|
||||
description: "The base MQTT topic of the Tap Dial. Usually `zigbee2mqtt/Friendly_Name` (e.g., `zigbee2mqtt/Living Room Dial`). You can find this in Z2M under the device's 'MQTT Info' tab."
|
||||
default: "zigbee2mqtt/Hue Tap Dial"
|
||||
|
||||
helper_last_light:
|
||||
name: Last Light Helper (input_text)
|
||||
description: "Required. An input_text helper to remember the last activated light for the dial to dim."
|
||||
selector:
|
||||
entity:
|
||||
domain: input_text
|
||||
|
||||
fallback_light:
|
||||
name: Fallback Light (Optional)
|
||||
description: "A default light to dim if the dial is rotated before any buttons have been pressed."
|
||||
default: ""
|
||||
selector:
|
||||
entity:
|
||||
domain: light
|
||||
|
||||
dial_step_pct:
|
||||
name: Dial Step Size (%)
|
||||
description: Brightness percentage change for a slow/single step rotation.
|
||||
default: 10
|
||||
selector:
|
||||
number:
|
||||
min: 1
|
||||
max: 50
|
||||
unit_of_measurement: "%"
|
||||
mode: slider
|
||||
|
||||
dial_fast_pct:
|
||||
name: Dial Fast Step Size (%)
|
||||
description: Brightness percentage change for a fast rotation.
|
||||
default: 25
|
||||
selector:
|
||||
number:
|
||||
min: 1
|
||||
max: 100
|
||||
unit_of_measurement: "%"
|
||||
mode: slider
|
||||
|
||||
# =================
|
||||
# BUTTON 1 (1 Dot)
|
||||
# =================
|
||||
light_1:
|
||||
name: Button 1 - Target Light
|
||||
description: Light to toggle on Single Press and control via the Dial. (Leave empty if only using custom actions).
|
||||
default: ""
|
||||
selector:
|
||||
entity:
|
||||
domain: light
|
||||
button_1_single_press:
|
||||
name: Button 1 - Extra Single Press Actions
|
||||
description: Additional custom actions to run alongside the light toggle.
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
button_1_double_press:
|
||||
name: Button 1 - Double Press Actions
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
button_1_long_press:
|
||||
name: Button 1 - Long Press Actions
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
|
||||
# =================
|
||||
# BUTTON 2 (2 Dots)
|
||||
# =================
|
||||
light_2:
|
||||
name: Button 2 - Target Light
|
||||
default: ""
|
||||
selector:
|
||||
entity:
|
||||
domain: light
|
||||
button_2_single_press:
|
||||
name: Button 2 - Extra Single Press Actions
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
button_2_double_press:
|
||||
name: Button 2 - Double Press Actions
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
button_2_long_press:
|
||||
name: Button 2 - Long Press Actions
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
|
||||
# =================
|
||||
# BUTTON 3 (3 Dots)
|
||||
# =================
|
||||
light_3:
|
||||
name: Button 3 - Target Light
|
||||
default: ""
|
||||
selector:
|
||||
entity:
|
||||
domain: light
|
||||
button_3_single_press:
|
||||
name: Button 3 - Extra Single Press Actions
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
button_3_double_press:
|
||||
name: Button 3 - Double Press Actions
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
button_3_long_press:
|
||||
name: Button 3 - Long Press Actions
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
|
||||
# =================
|
||||
# BUTTON 4 (4 Dots)
|
||||
# =================
|
||||
light_4:
|
||||
name: Button 4 - Target Light
|
||||
default: ""
|
||||
selector:
|
||||
entity:
|
||||
domain: light
|
||||
button_4_single_press:
|
||||
name: Button 4 - Extra Single Press Actions
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
button_4_double_press:
|
||||
name: Button 4 - Double Press Actions
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
button_4_long_press:
|
||||
name: Button 4 - Long Press Actions
|
||||
default: []
|
||||
selector:
|
||||
action: {}
|
||||
|
||||
# Single mode allows our Wait For Trigger double click logic to operate natively.
|
||||
mode: single
|
||||
max_exceeded: silent
|
||||
|
||||
trigger:
|
||||
- platform: mqtt
|
||||
topic: !input mqtt_topic
|
||||
|
||||
condition:
|
||||
# Verify that the MQTT payload contains a valid action (ignores battery/linkquality pings)
|
||||
- condition: template
|
||||
value_template: "{{ trigger.payload_json is not none and trigger.payload_json.action is defined and trigger.payload_json.action != '' }}"
|
||||
|
||||
action:
|
||||
- variables:
|
||||
light_1: !input light_1
|
||||
light_2: !input light_2
|
||||
light_3: !input light_3
|
||||
light_4: !input light_4
|
||||
helper_last_light: !input helper_last_light
|
||||
fallback_light: !input fallback_light
|
||||
dial_step_pct: !input dial_step_pct
|
||||
dial_fast_pct: !input dial_fast_pct
|
||||
|
||||
# Extract action string from MQTT payload for clean condition checks
|
||||
action_type: "{{ trigger.payload_json.action }}"
|
||||
|
||||
# Determine which light to dim (Last used helper, OR Fallback Light)
|
||||
target_dim_light: >-
|
||||
{% set helper_val = states(helper_last_light) %}
|
||||
{% if helper_val not in ['unknown', 'unavailable', ''] and helper_val != 'none' %}
|
||||
{{ helper_val }}
|
||||
{% elif fallback_light != '' and fallback_light != none %}
|
||||
{{ fallback_light }}
|
||||
{% else %}
|
||||
none
|
||||
{% endif %}
|
||||
|
||||
- choose:
|
||||
# ==================================
|
||||
# BUTTON 1
|
||||
# ==================================
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ action_type == 'button_1_press_release' }}"
|
||||
sequence:
|
||||
# First, update the memory helper so the dial is primed immediately
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ light_1 != '' and light_1 != none }}"
|
||||
sequence:
|
||||
- service: input_text.set_value
|
||||
target:
|
||||
entity_id: "{{ helper_last_light }}"
|
||||
data:
|
||||
value: "{{ light_1 }}"
|
||||
# Await potential Double-Click directly via MQTT
|
||||
- wait_for_trigger:
|
||||
- platform: mqtt
|
||||
topic: !input mqtt_topic
|
||||
payload: "button_1_press_release"
|
||||
value_template: "{{ value_json.action | default('') }}"
|
||||
timeout: "00:00:00.500"
|
||||
continue_on_timeout: true
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ wait.trigger is not none }}"
|
||||
sequence:
|
||||
# DOUBLE CLICK TRIGGERED
|
||||
- choose: []
|
||||
default: !input button_1_double_press
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ wait.trigger is none }}"
|
||||
sequence:
|
||||
# SINGLE CLICK TRIGGERED
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ light_1 != '' and light_1 != none }}"
|
||||
sequence:
|
||||
- service: light.toggle
|
||||
target:
|
||||
entity_id: "{{ light_1 }}"
|
||||
- choose: []
|
||||
default: !input button_1_single_press
|
||||
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ action_type == 'button_1_hold' }}"
|
||||
sequence:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ light_1 != '' and light_1 != none }}"
|
||||
sequence:
|
||||
- service: input_text.set_value
|
||||
target:
|
||||
entity_id: "{{ helper_last_light }}"
|
||||
data:
|
||||
value: "{{ light_1 }}"
|
||||
- choose: []
|
||||
default: !input button_1_long_press
|
||||
|
||||
|
||||
# ==================================
|
||||
# BUTTON 2
|
||||
# ==================================
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ action_type == 'button_2_press_release' }}"
|
||||
sequence:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ light_2 != '' and light_2 != none }}"
|
||||
sequence:
|
||||
- service: input_text.set_value
|
||||
target:
|
||||
entity_id: "{{ helper_last_light }}"
|
||||
data:
|
||||
value: "{{ light_2 }}"
|
||||
- wait_for_trigger:
|
||||
- platform: mqtt
|
||||
topic: !input mqtt_topic
|
||||
payload: "button_2_press_release"
|
||||
value_template: "{{ value_json.action | default('') }}"
|
||||
timeout: "00:00:00.500"
|
||||
continue_on_timeout: true
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ wait.trigger is not none }}"
|
||||
sequence:
|
||||
- choose: []
|
||||
default: !input button_2_double_press
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ wait.trigger is none }}"
|
||||
sequence:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ light_2 != '' and light_2 != none }}"
|
||||
sequence:
|
||||
- service: light.toggle
|
||||
target:
|
||||
entity_id: "{{ light_2 }}"
|
||||
- choose: []
|
||||
default: !input button_2_single_press
|
||||
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ action_type == 'button_2_hold' }}"
|
||||
sequence:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ light_2 != '' and light_2 != none }}"
|
||||
sequence:
|
||||
- service: input_text.set_value
|
||||
target:
|
||||
entity_id: "{{ helper_last_light }}"
|
||||
data:
|
||||
value: "{{ light_2 }}"
|
||||
- choose: []
|
||||
default: !input button_2_long_press
|
||||
|
||||
|
||||
# ==================================
|
||||
# BUTTON 3
|
||||
# ==================================
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ action_type == 'button_3_press_release' }}"
|
||||
sequence:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ light_3 != '' and light_3 != none }}"
|
||||
sequence:
|
||||
- service: input_text.set_value
|
||||
target:
|
||||
entity_id: "{{ helper_last_light }}"
|
||||
data:
|
||||
value: "{{ light_3 }}"
|
||||
- wait_for_trigger:
|
||||
- platform: mqtt
|
||||
topic: !input mqtt_topic
|
||||
payload: "button_3_press_release"
|
||||
value_template: "{{ value_json.action | default('') }}"
|
||||
timeout: "00:00:00.500"
|
||||
continue_on_timeout: true
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ wait.trigger is not none }}"
|
||||
sequence:
|
||||
- choose: []
|
||||
default: !input button_3_double_press
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ wait.trigger is none }}"
|
||||
sequence:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ light_3 != '' and light_3 != none }}"
|
||||
sequence:
|
||||
- service: light.toggle
|
||||
target:
|
||||
entity_id: "{{ light_3 }}"
|
||||
- choose: []
|
||||
default: !input button_3_single_press
|
||||
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ action_type == 'button_3_hold' }}"
|
||||
sequence:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ light_3 != '' and light_3 != none }}"
|
||||
sequence:
|
||||
- service: input_text.set_value
|
||||
target:
|
||||
entity_id: "{{ helper_last_light }}"
|
||||
data:
|
||||
value: "{{ light_3 }}"
|
||||
- choose: []
|
||||
default: !input button_3_long_press
|
||||
|
||||
|
||||
# ==================================
|
||||
# BUTTON 4
|
||||
# ==================================
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ action_type == 'button_4_press_release' }}"
|
||||
sequence:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ light_4 != '' and light_4 != none }}"
|
||||
sequence:
|
||||
- service: input_text.set_value
|
||||
target:
|
||||
entity_id: "{{ helper_last_light }}"
|
||||
data:
|
||||
value: "{{ light_4 }}"
|
||||
- wait_for_trigger:
|
||||
- platform: mqtt
|
||||
topic: !input mqtt_topic
|
||||
payload: "button_4_press_release"
|
||||
value_template: "{{ value_json.action | default('') }}"
|
||||
timeout: "00:00:00.500"
|
||||
continue_on_timeout: true
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ wait.trigger is not none }}"
|
||||
sequence:
|
||||
- choose: []
|
||||
default: !input button_4_double_press
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ wait.trigger is none }}"
|
||||
sequence:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ light_4 != '' and light_4 != none }}"
|
||||
sequence:
|
||||
- service: light.toggle
|
||||
target:
|
||||
entity_id: "{{ light_4 }}"
|
||||
- choose: []
|
||||
default: !input button_4_single_press
|
||||
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ action_type == 'button_4_hold' }}"
|
||||
sequence:
|
||||
- choose:
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ light_4 != '' and light_4 != none }}"
|
||||
sequence:
|
||||
- service: input_text.set_value
|
||||
target:
|
||||
entity_id: "{{ helper_last_light }}"
|
||||
data:
|
||||
value: "{{ light_4 }}"
|
||||
- choose: []
|
||||
default: !input button_4_long_press
|
||||
|
||||
|
||||
# ==================================
|
||||
# DIAL ROTATE LEFT (DIM DOWN)
|
||||
# ==================================
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ action_type in ['dial_rotate_left_step', 'dial_rotate_left_slow', 'dial_rotate_left_fast'] }}"
|
||||
sequence:
|
||||
- condition: template
|
||||
value_template: "{{ target_dim_light != 'none' }}"
|
||||
- service: light.turn_on
|
||||
target:
|
||||
entity_id: "{{ target_dim_light }}"
|
||||
data:
|
||||
brightness_step_pct: >-
|
||||
{% if 'fast' in action_type %}
|
||||
{{ -1 * (dial_fast_pct | int) }}
|
||||
{% else %}
|
||||
{{ -1 * (dial_step_pct | int) }}
|
||||
{% endif %}
|
||||
continue_on_error: true
|
||||
|
||||
|
||||
# ==================================
|
||||
# DIAL ROTATE RIGHT (DIM UP)
|
||||
# ==================================
|
||||
- conditions:
|
||||
- condition: template
|
||||
value_template: "{{ action_type in ['dial_rotate_right_step', 'dial_rotate_right_slow', 'dial_rotate_right_fast'] }}"
|
||||
sequence:
|
||||
- condition: template
|
||||
value_template: "{{ target_dim_light != 'none' }}"
|
||||
- service: light.turn_on
|
||||
target:
|
||||
entity_id: "{{ target_dim_light }}"
|
||||
data:
|
||||
brightness_step_pct: >-
|
||||
{% if 'fast' in action_type %}
|
||||
{{ dial_fast_pct | int }}
|
||||
{% else %}
|
||||
{{ dial_step_pct | int }}
|
||||
{% endif %}
|
||||
continue_on_error: true
|
||||
Reference in New Issue
Block a user