Files
ha-blueprints/aqara-h2-eu-dimmer-z2m-light-control.yaml
T

251 lines
8.6 KiB
YAML

blueprint:
name: Z2M - Aqara H2 Rotary Dimmer (KD-R01D) - Light Control (MQTT Direct)
description: >
Control your lights with the Aqara H2 EU Rotary Dimmer (connected via Zigbee2MQTT).
**No Action Sensor Required!**
Because modern Zigbee2MQTT setups no longer expose the legacy action sensor, this blueprint
listens directly to your device's MQTT topic. This has a major benefit: Home Assistant
won't discard identical, rapid rotation events (which typically happens with sensors due to state
deduplication), making the dimming feel incredibly smooth and responsive.
**Setup:**
Find your device's "Friendly name" in Z2M. Your MQTT topic is usually `zigbee2mqtt/` followed
by that name (e.g., `zigbee2mqtt/Bedroom dimmer`).
domain: automation
input:
mqtt_topic:
name: Device MQTT Topic
description: >
The base MQTT topic for your dimmer. Typically `zigbee2mqtt/` followed by the
Friendly Name of your device. (e.g. `zigbee2mqtt/Bedroom dimmer`)
default: zigbee2mqtt/Bedroom dimmer
selector:
text: {}
target_light:
name: Target Light(s)
description: The light, group, or area of lights to dim when rotating the knob.
selector:
target:
entity:
domain: light
rotation_sensitivity:
name: Rotation Sensitivity
description: Multiplier for how much the brightness changes per degree of rotation. (Higher = faster dimming).
default: 0.2
selector:
number:
min: 0.05
max: 2.0
step: 0.05
rotation_deadzone:
name: Rotation Deadzone (Anti-knock)
description: The minimum rotation angle (in degrees) required to trigger a dimming action. Increase this if you accidentally dim the lights when clicking the knob.
default: 20
selector:
number:
min: 0
max: 45
step: 1
invert_rotation:
name: Invert Rotation
description: Enable this to reverse the dimming direction (e.g. right to dim down, left to brighten).
default: false
selector:
boolean: {}
dim_transition:
name: Dimming Transition (seconds)
description: Adds a transition time to smooth out the dimming steps as you turn the dial.
default: 0.2
selector:
number:
min: 0
max: 5
step: 0.1
enable_default_single_press:
name: Enable Default Single Press (Toggle)
description: If enabled, a single press will automatically toggle the target light on/off. Disable this if you purely want to use the Custom Single Press action below instead.
default: true
selector:
boolean: {}
single_press_action:
name: Custom Single Press Action
description: Additional action(s) to run when the knob is pressed once.
default: []
selector:
action: {}
double_press_action:
name: Double Press Action
description: Action(s) to run when the knob is double pressed.
default: []
selector:
action: {}
hold_action:
name: Long Press (Hold) Action
description: Action(s) to run when the knob is held down.
default: []
selector:
action: {}
release_action:
name: Release Action
description: Action(s) to run when the knob is released after a long press.
default: []
selector:
action: {}
# Parallel mode ensures fast rotational clicks don't interrupt each other
mode: parallel
max: 15
trigger:
- platform: mqtt
topic: !input mqtt_topic
payload: "action"
value_template: >
{{ 'action'
if value_json is defined
and value_json.action is defined
and value_json.action is not none
and (value_json.action | string | length > 0)
else 'ignore' }}
action:
- variables:
# Safely extract payload variables directly from the MQTT JSON message
payload: "{{ trigger.payload_json if trigger.payload_json is defined else {} }}"
raw_action_type: "{{ payload.action | default('') }}"
# Time since the previous MQTT event triggered this automation.
# Genuine stop_rotating events immediately follow rotation events.
# The H2 bug emits a stray stop_rotating after the device has been idle.
# Null/missing action packets are filtered at the MQTT trigger above,
# so only genuine action events affect this timer.
idle_seconds: >
{{ as_timestamp(now()) -
as_timestamp(
this.attributes.last_triggered
| default(as_datetime(0), true)
)
}}
# Work around the H2 occasionally reporting the first single press
# after being idle as stop_rotating.
action_type: >
{% if raw_action_type == 'stop_rotating' and idle_seconds | float > 1 %}
single
{% else %}
{{ raw_action_type }}
{% endif %}
raw_angle: "{{ payload.action_rotation_angle | default(0) | float(0) }}"
# Pull inputs into variables so they can be safely evaluated in templates below
sens: !input rotation_sensitivity
invert: !input invert_rotation
enable_toggle: !input enable_default_single_press
deadzone: !input rotation_deadzone
# Calculate the final brightness step dynamically based on the rotation direction
scaled_step: >
{% set step = (raw_angle * sens) | int(0) %}
{{ (step * -1) if invert else step }}
- choose:
# --- SINGLE PRESS ---
- conditions:
- condition: template
value_template: "{{ action_type == 'single' }}"
sequence:
- choose:
- conditions:
- condition: template
value_template: "{{ enable_toggle }}"
sequence:
- service: light.toggle
target: !input target_light
- choose:
- conditions: []
sequence: !input single_press_action
# --- DOUBLE PRESS ---
- conditions:
- condition: template
value_template: "{{ action_type == 'double' }}"
sequence:
- choose:
- conditions: []
sequence: !input double_press_action
# --- HOLD (LONG PRESS) ---
- conditions:
- condition: template
value_template: "{{ action_type == 'hold' }}"
sequence:
- choose:
- conditions: []
sequence: !input hold_action
# --- RELEASE (AFTER HOLD) ---
- conditions:
- condition: template
value_template: "{{ action_type == 'release' }}"
sequence:
- choose:
- conditions: []
sequence: !input release_action
# --- ROTATION DIMMING ---
- conditions:
- condition: template
value_template: "{{ action_type == 'rotation' }}"
sequence:
- choose:
# A click can occasionally appear as a small rotation followed
# immediately by stop_rotating. Only treat it as a single press
# if the stop_rotating packet actually follows.
- conditions:
- condition: template
value_template: "{{ (raw_angle | abs) < (deadzone | float(0)) }}"
sequence:
- wait_for_trigger:
- platform: mqtt
topic: !input mqtt_topic
payload: "stop_rotating"
value_template: "{{ value_json.action | default('') }}"
timeout:
milliseconds: 500
continue_on_timeout: false
- choose:
- conditions:
- condition: template
value_template: "{{ enable_toggle }}"
sequence:
- service: light.toggle
target: !input target_light
- choose:
- conditions: []
sequence: !input single_press_action
# Normal rotation above the configured deadzone.
default:
# Only fire the turn_on service if there was a registrable angle change
- condition: template
value_template: "{{ scaled_step != 0 }}"
- service: light.turn_on
target: !input target_light
data:
brightness_step_pct: "{{ scaled_step }}"
transition: !input dim_transition