Instantly share code, notes, and snippets.
Forked from schiberis/zha-ikea-four-button-remote-styrbar.yaml
Last active
October 11, 2025 23:53
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save cpw/f167ce8a5d2464f7ee9a51f3b17f337c to your computer and use it in GitHub Desktop.
• UP/DOWN (short): ON / OFF • UP/DOWN (hold): fast and smooth brightness stepping that stops immediately on release (implemented with `mode: restart` for zero run-on) • LEFT/RIGHT (short): cycle through 6 preset RGB colors (wrap-around; uses an input_number helper to track index) • LEFT/RIGHT (long press): optional custom actions (empty by defa…
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: ZHA – IKEA STYRBAR all controls & 6-color palette (modern syntax) | |
| description: > | |
| Smooth, responsive control for the IKEA STYRBAR (square) remote on ZHA. | |
| • UP/DOWN (short): ON / OFF | |
| • UP/DOWN (hold): fast and smooth brightness stepping that stops immediately on release | |
| (implemented with `mode: restart` for zero run-on) | |
| • LEFT/RIGHT (short): cycle through 3 preset colour temperatures (wrap-around; uses an input_number helper to track index) | |
| • LEFT/RIGHT (long press): optional custom actions (empty by default) | |
| Setup notes: | |
| 1) If you want LEFT/RIGHT color cycling, create an *input_number* helper with min=1, max=3, step=1. | |
| Example entity id: input_number.styrbar_color_index (you can choose another; select it below). | |
| 2) Choose your 3 colour temperatures. | |
| 3) Adjust hold speed with “Tick interval (ms)” and “Step size (%)”. | |
| domain: automation | |
| input: | |
| remote: | |
| name: Remote (ZHA device) | |
| selector: | |
| device: | |
| integration: zha | |
| manufacturer: IKEA of Sweden | |
| model: Remote Control N2 | |
| multiple: false | |
| light: | |
| name: Light(s) | |
| selector: | |
| target: | |
| entity: | |
| domain: light | |
| # Stores the current palette index (1..6) for LEFT/RIGHT cycling | |
| color_index_helper: | |
| name: Color index helper (1–3) | |
| description: Create/select an input_number with min=1, max=3, step=1 to remember the active palette color. | |
| default: input_number.styrbar_color_index | |
| selector: | |
| entity: | |
| domain: input_number | |
| # 6-color palette (RGB) | |
| palette_temp1: { name: Temp 1 K, default: 2700, selector: { color_temp: { unit: kelvin } } } | |
| palette_temp2: { name: Temp 2 K, default: 3000, selector: { color_temp: { unit: kelvin } } } | |
| palette_temp3: { name: Temp 3 K, default: 4000, selector: { color_temp: { unit: kelvin } } } | |
| # Hold behavior (tuning) | |
| tick_ms: | |
| name: Tick interval (ms) | |
| description: Time between brightness steps while holding. Smaller = faster/smoother. | |
| default: 10 | |
| selector: | |
| number: | |
| min: 5 | |
| max: 200 | |
| step: 5 | |
| unit_of_measurement: ms | |
| mode: slider | |
| step_pct: | |
| name: Step size (% per tick) | |
| description: Amount of brightness changed per tick while holding. | |
| default: 5 | |
| selector: | |
| number: | |
| min: 1 | |
| max: 20 | |
| step: 1 | |
| unit_of_measurement: '%' | |
| mode: slider | |
| # Optional custom actions on long-press LEFT/RIGHT | |
| button_left_long: | |
| name: Left button – long press (custom action) | |
| default: [] | |
| selector: { action: {} } | |
| button_right_long: | |
| name: Right button – long press (custom action) | |
| default: [] | |
| selector: { action: {} } | |
| # Restart mode ensures release events cancel the running hold loop immediately (no run-on) | |
| mode: restart | |
| max_exceeded: silent | |
| trigger: | |
| - platform: event | |
| event_type: zha_event | |
| event_data: | |
| device_id: !input remote | |
| action: | |
| # -------- Modern-syntax variables (scoped to this run) -------- | |
| - variables: | |
| color_index_entity: !input color_index_helper | |
| tick_ms_input: !input tick_ms | |
| step_pct_input: !input step_pct | |
| tick_s: "{{ (tick_ms_input | float(10)) / 1000 }}" | |
| step_up: "{{ step_pct_input | int(5) }}" | |
| step_down: "{{ (step_pct_input | int(5)) * -1 }}" | |
| cap_ticks: 1200 # ~12s watchdog at 10ms ticks (failsafe) | |
| cmd: "{{ trigger.event.data.command }}" | |
| cluster: "{{ trigger.event.data.cluster_id }}" | |
| args: "{{ trigger.event.data.args | default([]) }}" | |
| - choose: | |
| # --- UP short → ON --- | |
| - conditions: "{{ cluster == 6 and cmd == 'on' }}" | |
| sequence: | |
| - service: light.turn_on | |
| target: !input light | |
| data: { transition: 0.1 } | |
| # --- DOWN short → OFF --- | |
| - conditions: "{{ cluster == 6 and cmd == 'off' }}" | |
| sequence: | |
| - service: light.turn_off | |
| target: !input light | |
| # --- HOLD UP (brightness +) — step on timeout, stop on release/opposite --- | |
| - conditions: "{{ cluster == 8 and cmd == 'move_with_on_off' }}" | |
| sequence: | |
| - variables: { _i: 0 } | |
| - repeat: | |
| sequence: | |
| # Wait first; step only if still holding | |
| - wait_for_trigger: | |
| # Release / stop | |
| - platform: event | |
| event_type: zha_event | |
| event_data: { device_id: !input remote, cluster_id: 8, command: "stop" } | |
| - platform: event | |
| event_type: zha_event | |
| event_data: { device_id: !input remote, cluster_id: 8, command: "stop_with_on_off" } | |
| # Opposite hold also cancels | |
| - platform: event | |
| event_type: zha_event | |
| event_data: { device_id: !input remote, cluster_id: 8, command: "move" } | |
| timeout: "{{ tick_s }}" | |
| continue_on_timeout: true | |
| - choose: | |
| - conditions: "{{ not wait.completed }}" | |
| sequence: | |
| - service: light.turn_on | |
| target: !input light | |
| data: { brightness_step_pct: "{{ step_up }}", transition: 0 } | |
| - variables: { _i: "{{ _i + 1 }}" } | |
| until: | |
| - condition: template | |
| value_template: "{{ wait.completed or (_i | int) >= (cap_ticks | int) }}" | |
| # --- HOLD DOWN (brightness −) — step on timeout, stop on release/opposite --- | |
| - conditions: "{{ cluster == 8 and cmd == 'move' }}" | |
| sequence: | |
| - variables: { _i: 0 } | |
| - repeat: | |
| sequence: | |
| - wait_for_trigger: | |
| # Release / stop | |
| - platform: event | |
| event_type: zha_event | |
| event_data: { device_id: !input remote, cluster_id: 8, command: "stop" } | |
| - platform: event | |
| event_type: zha_event | |
| event_data: { device_id: !input remote, cluster_id: 8, command: "stop_with_on_off" } | |
| # Opposite hold also cancels | |
| - platform: event | |
| event_type: zha_event | |
| event_data: { device_id: !input remote, cluster_id: 8, command: "move_with_on_off" } | |
| timeout: "{{ tick_s }}" | |
| continue_on_timeout: true | |
| - choose: | |
| - conditions: "{{ not wait.completed }}" | |
| sequence: | |
| - service: light.turn_on | |
| target: !input light | |
| data: { brightness_step_pct: "{{ step_down }}", transition: 0 } | |
| - variables: { _i: "{{ _i + 1 }}" } | |
| until: | |
| - condition: template | |
| value_template: "{{ wait.completed or (_i | int) >= (cap_ticks | int) }}" | |
| # --- LEFT/RIGHT short → palette cycle (wrap) --- | |
| - conditions: "{{ cluster == 5 and cmd == 'press' and args in [[256,13,0],[257,13,0]] }}" | |
| sequence: | |
| - variables: | |
| curr_idx: "{{ states(color_index_entity) | int(1) }}" | |
| next_idx: > | |
| {% if args == [256,13,0] %} | |
| {{ 1 if curr_idx >= 3 else curr_idx + 1 }} | |
| {% else %} | |
| {{ 3 if curr_idx <= 1 else curr_idx - 1 }} | |
| {% endif %} | |
| - service: input_number.set_value | |
| target: { entity_id: !input color_index_helper } | |
| data: { value: "{{ next_idx }}" } | |
| - choose: | |
| - conditions: "{{ next_idx == 1 }}" | |
| sequence: | |
| - service: light.turn_on | |
| target: !input light | |
| data: { color_temp_kelvin: !input palette_temp1, transition: 0 } | |
| - conditions: "{{ next_idx == 2 }}" | |
| sequence: | |
| - service: light.turn_on | |
| target: !input light | |
| data: { color_temp_kelvin: !input palette_temp2, transition: 0 } | |
| - conditions: "{{ next_idx == 3 }}" | |
| sequence: | |
| - service: light.turn_on | |
| target: !input light | |
| data: { color_temp_kelvin: !input palette_temp3, transition: 0 } | |
| # --- LEFT/RIGHT long press (optional custom) --- | |
| - conditions: "{{ cluster == 5 and cmd == 'hold' }}" | |
| sequence: | |
| - choose: | |
| - conditions: "{{ args == [3329, 0] }}" | |
| sequence: !input button_left_long | |
| - conditions: "{{ args == [3328, 0] }}" | |
| sequence: !input button_right_long |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment