Last active
February 17, 2026 08:06
-
-
Save jroehl/0937c589c8d878bd945ee7ebe0a70f4b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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