Skip to content

Instantly share code, notes, and snippets.

@jroehl
Last active February 17, 2026 08:06
Show Gist options
  • Select an option

  • Save jroehl/0937c589c8d878bd945ee7ebe0a70f4b to your computer and use it in GitHub Desktop.

Select an option

Save jroehl/0937c589c8d878bd945ee7ebe0a70f4b to your computer and use it in GitHub Desktop.
blueprint:
name: "Remote Light Control (ZHA)"
description: >
Universal ZHA remote blueprint for any remote or wall switch.
**Short press ON** turns on lights at sun-elevation-based brightness,
activates switches, and presses button entities — all in parallel.
Brightness scales automatically with the sun: full brightness at night,
dimmer during the day. No time inputs needed — adapts to seasons.
**Short press OFF** turns everything off (button target falls back to the
ON target when no separate OFF target is set). If ON and OFF commands
are set to the same value, toggle mode activates automatically —
one button toggles everything on/off.
**Double-tap actions** (optional) — assign any action to double-tapping
the ON or OFF button (scenes, scripts, dimming, etc.). Two detection
methods: software-based (250 ms delay on single taps) for simple
devices, or native double-tap commands (no delay) for remotes that
send distinct events. When no double-tap actions are set, there is
no delay at all.
**Dim up / down** (short and long press) adjusts a configurable light
target. Long press applies a multiplier for bigger steps.
Targets are auto-detected: leave a target empty to skip it — no enable
toggles needed. Dim commands default to "disabled" — fill them in to
enable dimming.
**Finding your ZHA commands:** Go to Developer Tools → Events → Listen
for `zha_event`, press each button on your remote, and note the
`command` values.
domain: automation
input:
# ── Remote device (always visible) ─────────────────────────
remote_device:
name: ZHA Remote
description: >
The ZHA remote or wall switch that triggers this automation.
selector:
device:
integration: zha
# ── Light Control ──────────────────────────────────────────
light_control:
name: Light Control
icon: mdi:lightbulb
collapsed: false
input:
toggle_target:
name: Light target (on/off)
description: >
Lights to turn on and off. Leave empty to skip light control.
default: {}
selector:
target:
entity:
domain: light
brightness_day_pct:
name: Brightness — daytime (sun high)
description: >
Brightness when the sun is high (elevation ≥ 45°).
Used as the upper bound of the brightness curve.
default: 80
selector:
number:
min: 1
max: 100
unit_of_measurement: "%"
mode: slider
brightness_night_pct:
name: Brightness — night (sun below horizon)
description: >
Brightness when the sun is below the horizon (elevation ≤ 0°).
Used as the lower bound of the brightness curve.
default: 20
selector:
number:
min: 1
max: 100
unit_of_measurement: "%"
mode: slider
color_temp_kelvin:
name: Color temperature (Kelvin)
description: >
Color temperature applied on turn-on (2584 K = warm white).
default: 2584
selector:
number:
min: 1500
max: 6500
unit_of_measurement: K
mode: slider
transition_sec:
name: Transition (seconds)
description: >
Fade duration for turn-on, turn-off, and dimming actions.
default: 1
selector:
number:
min: 0
max: 10
step: 0.1
unit_of_measurement: s
mode: slider
# ── Dimming ────────────────────────────────────────────────
dimming:
name: Dimming
icon: mdi:brightness-6
collapsed: true
input:
dim_target:
name: Dimming target
description: >
Lights to dim with the up/down buttons. Can differ from the
on/off target. Leave empty to disable dimming.
default: {}
selector:
target:
entity:
domain: light
dim_step_pct:
name: Step size (% per short press)
description: >
How much brightness changes per short press (e.g. 10 %).
default: 10
selector:
number:
min: 1
max: 50
unit_of_measurement: "%"
mode: slider
long_press_multiplier:
name: Long-press multiplier
description: >
Step size is multiplied by this on a long press
(e.g. 2 × 10 % = 20 % per long press).
default: 2
selector:
number:
min: 1
max: 5
step: 1
mode: slider
# ── Double-Tap Actions ─────────────────────────────────────
multi_tap:
name: Double-Tap Actions
icon: mdi:gesture-double-tap
collapsed: true
input:
double_tap_on_action:
name: Double-tap ON action
description: >
Action to run on double-tap of the ON / toggle button.
Leave empty to disable. When set and no native double-tap
command is configured, single taps get a ~250 ms detection delay.
default: []
selector:
action: {}
double_tap_off_action:
name: Double-tap OFF action
description: >
Action to run on double-tap of the OFF button
(separate ON/OFF mode only; ignored in toggle mode).
Leave empty to disable.
default: []
selector:
action: {}
# ── Switch & Button Targets ────────────────────────────────
switch_button_targets:
name: Switch & Button Targets
icon: mdi:toggle-switch
collapsed: true
input:
switch_target:
name: Switch target (on/off)
description: >
Switch entities to turn on/off alongside lights.
Leave empty to skip.
default: {}
selector:
target:
entity:
domain: switch
button_on_target:
name: Button target — ON press
description: >
Button entities to press when the ON button is pressed.
Leave empty to skip.
default: {}
selector:
target:
entity:
domain: button
button_off_target:
name: Button target — OFF press
description: >
Button entities to press when the OFF button is pressed.
Leave empty to use the ON target as fallback.
default: {}
selector:
target:
entity:
domain: button
# ── ZHA Command Mapping ────────────────────────────────────
zha_command_mapping:
name: ZHA Command Mapping
icon: mdi:remote
collapsed: true
description: >
ZHA event commands for each action. Find yours via Developer
Tools → Events → Listen for `zha_event`, then press each button.
Set a command to "disabled" to skip that action.
input:
on_command:
name: ON command
description: >
ZHA command for the ON action.
Hue dimmer: "on" · Some switches: "toggle".
Use Developer Tools → Events → Listen for `zha_event` to
find your device's commands.
default: "on"
selector:
select:
custom_value: true
options:
- label: "on (Hue/IKEA default)"
value: "on"
- label: "toggle"
value: "toggle"
- label: "button_1"
value: "button_1"
- label: "button_2"
value: "button_2"
off_command:
name: OFF command
description: >
ZHA command for the OFF action.
Hue dimmer: "off_with_effect" · IKEA/generic: "off".
default: "off_with_effect"
selector:
select:
custom_value: true
options:
- label: "off_with_effect (Hue default)"
value: "off_with_effect"
- label: "off (IKEA/generic)"
value: "off"
- label: "toggle"
value: "toggle"
- label: "button_1"
value: "button_1"
- label: "button_2"
value: "button_2"
dim_up_short_command:
name: Dim UP — short press command
description: >
ZHA command for dim-up short press.
Hue/IKEA: "step_with_on_off".
Set to "disabled" if your remote has no dim buttons.
default: "step_with_on_off"
selector:
select:
custom_value: true
options:
- label: "step_with_on_off (Hue/IKEA default)"
value: "step_with_on_off"
- label: "disabled"
value: "disabled"
dim_up_long_command:
name: Dim UP — long press command
description: >
ZHA command for dim-up long press.
Hue/IKEA: "move_with_on_off".
Set to "disabled" if your remote has no long press.
default: "move_with_on_off"
selector:
select:
custom_value: true
options:
- label: "move_with_on_off (Hue/IKEA default)"
value: "move_with_on_off"
- label: "disabled"
value: "disabled"
dim_down_short_command:
name: Dim DOWN — short press command
description: >
ZHA command for dim-down short press.
Hue/IKEA: "step".
Set to "disabled" if your remote has no dim buttons.
default: "step"
selector:
select:
custom_value: true
options:
- label: "step (Hue/IKEA default)"
value: "step"
- label: "disabled"
value: "disabled"
dim_down_long_command:
name: Dim DOWN — long press command
description: >
ZHA command for dim-down long press.
Hue/IKEA: "move".
Set to "disabled" if your remote has no long press.
default: "move"
selector:
select:
custom_value: true
options:
- label: "move (Hue/IKEA default)"
value: "move"
- label: "disabled"
value: "disabled"
double_tap_on_command:
name: Double-tap ON command (native)
description: >
ZHA command for hardware double-tap ON (e.g. Aqara: "double").
When received, runs the Double-tap ON action immediately — no delay.
Set to "disabled" if your remote doesn't send distinct double-tap events.
default: "disabled"
selector:
select:
custom_value: true
options:
- label: "disabled"
value: "disabled"
- label: "double (Aqara)"
value: "double"
double_tap_off_command:
name: Double-tap OFF command (native)
description: >
ZHA command for hardware double-tap OFF.
Set to "disabled" if not available.
default: "disabled"
selector:
select:
custom_value: true
options:
- label: "disabled"
value: "disabled"
- label: "double (Aqara)"
value: "double"
mode: single
max_exceeded: silent
variables:
# ── Inputs mirrored as variables ─────────────────────────────
toggle_raw: !input toggle_target
dim_raw: !input dim_target
switch_raw: !input switch_target
button_on_raw: !input button_on_target
button_off_raw: !input button_off_target
step: !input dim_step_pct
long_mult: !input long_press_multiplier
transition: !input transition_sec
color_temp: !input color_temp_kelvin
day_bright: !input brightness_day_pct
night_bright: !input brightness_night_pct
on_cmd: !input on_command
off_cmd: !input off_command
dim_up_short_cmd: !input dim_up_short_command
dim_up_long_cmd: !input dim_up_long_command
dim_down_short_cmd: !input dim_down_short_command
dim_down_long_cmd: !input dim_down_long_command
double_tap_on_raw: !input double_tap_on_action
double_tap_off_raw: !input double_tap_off_action
dbl_on_cmd: !input double_tap_on_command
dbl_off_cmd: !input double_tap_off_command
# ── Auto-detect which targets are populated ──────────────────
has_light: "{{ toggle_raw | length > 0 }}"
has_dim: "{{ dim_raw | length > 0 }}"
has_switch: "{{ switch_raw | length > 0 }}"
has_button_on: "{{ button_on_raw | length > 0 }}"
has_button_off: "{{ button_off_raw | length > 0 }}"
has_double_tap_on: "{{ double_tap_on_raw | length > 0 }}"
has_double_tap_off: "{{ double_tap_off_raw | length > 0 }}"
has_any_double_tap: "{{ has_double_tap_on or has_double_tap_off }}"
has_native_dbl: "{{ dbl_on_cmd != 'disabled' or dbl_off_cmd != 'disabled' }}"
# ── Command from the event ──────────────────────────────────
command: "{{ trigger.event.data.command }}"
is_native_dbl_cmd: "{{ (dbl_on_cmd != 'disabled' and command == dbl_on_cmd) or (dbl_off_cmd != 'disabled' and command == dbl_off_cmd) }}"
needs_software_dbl: "{{ has_any_double_tap and not has_native_dbl }}"
# ── Sun-based brightness (automatic, season-aware) ──────────
target_brightness: >-
{% set elev = state_attr('sun.sun', 'elevation') | float(0) %}
{% if elev <= 0 %}{{ night_bright }}
{% elif elev >= 45 %}{{ day_bright }}
{% else %}{{ night_bright + ((day_bright - night_bright) * elev / 45) | round(0) }}{% endif %}
# ── Dim delta from command ──────────────────────────────────
dim_delta: >-
{% if command == dim_up_short_cmd %}{{ step }}
{% elif command == dim_up_long_cmd %}{{ step * long_mult }}
{% elif command == dim_down_short_cmd %}{{ -1 * step }}
{% elif command == dim_down_long_cmd %}{{ -1 * step * long_mult }}
{% else %}0{% endif %}
# ── Command classification ────────────────────────────────────
is_on_off_cmd: "{{ command == on_cmd or (on_cmd != off_cmd and command == off_cmd) }}"
triggers:
- trigger: event
event_type: zha_event
event_data:
device_id: !input remote_device
actions:
- choose:
# ══════════════════════════════════════════════════════════
# Branch A: DIM (dedicated buttons — immediate, no delay)
# ══════════════════════════════════════════════════════════
- alias: "Adjust brightness (dedicated dim buttons)"
conditions:
- condition: template
value_template: >-
{{ has_dim and command in
[dim_up_short_cmd, dim_up_long_cmd,
dim_down_short_cmd, dim_down_long_cmd] }}
sequence:
- action: light.turn_on
target: !input dim_target
data:
brightness_step_pct: "{{ dim_delta | int }}"
transition: "{{ transition }}"
# ══════════════════════════════════════════════════════════
# Branch B: Native double-tap command (instant, no delay)
# ══════════════════════════════════════════════════════════
- alias: "Native double-tap command"
conditions:
- condition: template
value_template: "{{ is_native_dbl_cmd and (has_double_tap_on or has_double_tap_off) }}"
sequence:
- choose:
- alias: "Native double-tap ON action"
conditions:
- condition: template
value_template: "{{ dbl_on_cmd != 'disabled' and command == dbl_on_cmd and has_double_tap_on }}"
sequence: !input double_tap_on_action
- alias: "Native double-tap OFF action"
conditions:
- condition: template
value_template: "{{ dbl_off_cmd != 'disabled' and command == dbl_off_cmd and has_double_tap_off }}"
sequence: !input double_tap_off_action
# ══════════════════════════════════════════════════════════
# Branch C: ON/OFF with software multi-tap detection
# (only when double-tap actions configured + no native cmd)
# ══════════════════════════════════════════════════════════
- alias: "On/Off with software multi-tap detection"
conditions:
- condition: template
value_template: "{{ is_on_off_cmd and needs_software_dbl }}"
sequence:
- wait_for_trigger:
- trigger: event
event_type: zha_event
event_data:
device_id: !input remote_device
command: "{{ command }}"
timeout:
milliseconds: 250
- variables:
double_tapped: "{{ wait.trigger is not none }}"
is_on_side: "{{ on_cmd == off_cmd or command == on_cmd }}"
has_matching_action: >-
{{ (double_tapped and is_on_side and has_double_tap_on) or
(double_tapped and not is_on_side and has_double_tap_off) }}
- choose:
# ── Double-tap ON / toggle action ──────────────
- alias: "Double-tap ON action (software)"
conditions:
- condition: template
value_template: "{{ double_tapped and is_on_side and has_double_tap_on }}"
sequence: !input double_tap_on_action
# ── Double-tap OFF action ──────────────────────
- alias: "Double-tap OFF action (software)"
conditions:
- condition: template
value_template: "{{ double_tapped and not is_on_side and has_double_tap_off }}"
sequence: !input double_tap_off_action
# ── Single tap (timeout) or unmatched double-tap ──
default:
- choose:
# ── Toggle mode ─────────────────────────────
- alias: "Toggle lights, switches, and buttons"
conditions:
- condition: template
value_template: "{{ on_cmd == off_cmd }}"
sequence:
- parallel:
- if:
- condition: template
value_template: "{{ has_light }}"
then:
- action: light.toggle
target: !input toggle_target
data:
brightness_pct: "{{ target_brightness }}"
color_temp_kelvin: "{{ color_temp }}"
transition: "{{ transition }}"
- if:
- condition: template
value_template: "{{ has_switch }}"
then:
- action: switch.toggle
target: !input switch_target
- if:
- condition: template
value_template: "{{ has_button_on }}"
then:
- action: button.press
target: !input button_on_target
# ── Turn ON ─────────────────────────────────
- alias: "Turn on lights, switches, and buttons"
conditions:
- condition: template
value_template: "{{ on_cmd != off_cmd and command == on_cmd }}"
sequence:
- parallel:
- if:
- condition: template
value_template: "{{ has_light }}"
then:
- action: light.turn_on
target: !input toggle_target
data:
brightness_pct: "{{ target_brightness }}"
color_temp_kelvin: "{{ color_temp }}"
transition: "{{ transition }}"
- if:
- condition: template
value_template: "{{ has_switch }}"
then:
- action: switch.turn_on
target: !input switch_target
- if:
- condition: template
value_template: "{{ has_button_on }}"
then:
- action: button.press
target: !input button_on_target
# ── Turn OFF ────────────────────────────────
- alias: "Turn off lights, switches, and press buttons"
conditions:
- condition: template
value_template: "{{ on_cmd != off_cmd and command == off_cmd }}"
sequence:
- parallel:
- if:
- condition: template
value_template: "{{ has_light }}"
then:
- action: light.turn_off
target: !input toggle_target
data:
transition: "{{ transition }}"
- if:
- condition: template
value_template: "{{ has_switch }}"
then:
- action: switch.turn_off
target: !input switch_target
- if:
- condition: template
value_template: "{{ has_button_on or has_button_off }}"
then:
- if:
- condition: template
value_template: "{{ has_button_off }}"
then:
- action: button.press
target: !input button_off_target
else:
- action: button.press
target: !input button_on_target
# ── Absorb 3rd tap after matched double-tap ─────────
- if:
- condition: template
value_template: "{{ has_matching_action }}"
then:
- wait_for_trigger:
- trigger: event
event_type: zha_event
event_data:
device_id: !input remote_device
command: "{{ command }}"
timeout:
milliseconds: 250
# ══════════════════════════════════════════════════════════
# Branch D: ON/OFF immediate (no double-tap configured
# or native commands handle it — zero latency)
# ══════════════════════════════════════════════════════════
- alias: "On/Off immediate (no software multi-tap needed)"
conditions:
- condition: template
value_template: "{{ is_on_off_cmd and not needs_software_dbl }}"
sequence:
- choose:
# ── Toggle mode ─────────────────────────────────
- alias: "Toggle lights, switches, and buttons"
conditions:
- condition: template
value_template: "{{ on_cmd == off_cmd }}"
sequence:
- parallel:
- if:
- condition: template
value_template: "{{ has_light }}"
then:
- action: light.toggle
target: !input toggle_target
data:
brightness_pct: "{{ target_brightness }}"
color_temp_kelvin: "{{ color_temp }}"
transition: "{{ transition }}"
- if:
- condition: template
value_template: "{{ has_switch }}"
then:
- action: switch.toggle
target: !input switch_target
- if:
- condition: template
value_template: "{{ has_button_on }}"
then:
- action: button.press
target: !input button_on_target
# ── Turn ON ─────────────────────────────────────
- alias: "Turn on lights, switches, and buttons"
conditions:
- condition: template
value_template: "{{ on_cmd != off_cmd and command == on_cmd }}"
sequence:
- parallel:
- if:
- condition: template
value_template: "{{ has_light }}"
then:
- action: light.turn_on
target: !input toggle_target
data:
brightness_pct: "{{ target_brightness }}"
color_temp_kelvin: "{{ color_temp }}"
transition: "{{ transition }}"
- if:
- condition: template
value_template: "{{ has_switch }}"
then:
- action: switch.turn_on
target: !input switch_target
- if:
- condition: template
value_template: "{{ has_button_on }}"
then:
- action: button.press
target: !input button_on_target
# ── Turn OFF ────────────────────────────────────
- alias: "Turn off lights, switches, and press buttons"
conditions:
- condition: template
value_template: "{{ on_cmd != off_cmd and command == off_cmd }}"
sequence:
- parallel:
- if:
- condition: template
value_template: "{{ has_light }}"
then:
- action: light.turn_off
target: !input toggle_target
data:
transition: "{{ transition }}"
- if:
- condition: template
value_template: "{{ has_switch }}"
then:
- action: switch.turn_off
target: !input switch_target
- if:
- condition: template
value_template: "{{ has_button_on or has_button_off }}"
then:
- if:
- condition: template
value_template: "{{ has_button_off }}"
then:
- action: button.press
target: !input button_off_target
else:
- action: button.press
target: !input button_on_target
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment