Skip to content

Instantly share code, notes, and snippets.

@basnijholt
Last active January 3, 2026 20:01
Show Gist options
  • Select an option

  • Save basnijholt/39205a1082f2740a930b437b12db290b to your computer and use it in GitHub Desktop.

Select an option

Save basnijholt/39205a1082f2740a930b437b12db290b to your computer and use it in GitHub Desktop.
Home Assistant Blueprint: IKEA STYRBAR with color temperature cycling
blueprint:
name: ZHA – IKEA STYRBAR all controls & 6-temp 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 6 preset color temperatures (wrap-around; uses an input_number helper to track index)
• LEFT/RIGHT (hold): smooth color temperature stepping that stops immediately on release
Setup notes:
1) If you want LEFT/RIGHT short-press cycling, create an *input_number* helper with min=1, max=6, step=1.
Example entity id: input_number.styrbar_color_index (you can choose another; select it below).
2) Choose your 6 color temperature presets (in Kelvin) for short-press cycling.
3) Adjust hold speed with "Tick interval (ms)", brightness step, and color temp step.
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–6)
description: Create/select an input_number with min=1, max=6, step=1 to remember the active palette color.
default: input_number.styrbar_color_index
selector:
entity:
domain: input_number
# 6-color temperature palette (Kelvin)
palette_temp1: { name: Temp 1 (Kelvin), default: 2700, selector: { color_temp: { unit: kelvin, min: 2000, max: 6500 } } }
palette_temp2: { name: Temp 2 (Kelvin), default: 3000, selector: { color_temp: { unit: kelvin, min: 2000, max: 6500 } } }
palette_temp3: { name: Temp 3 (Kelvin), default: 4000, selector: { color_temp: { unit: kelvin, min: 2000, max: 6500 } } }
palette_temp4: { name: Temp 4 (Kelvin), default: 5000, selector: { color_temp: { unit: kelvin, min: 2000, max: 6500 } } }
palette_temp5: { name: Temp 5 (Kelvin), default: 5500, selector: { color_temp: { unit: kelvin, min: 2000, max: 6500 } } }
palette_temp6: { name: Temp 6 (Kelvin), default: 6500, selector: { color_temp: { unit: kelvin, min: 2000, max: 6500 } } }
# 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: Brightness step (% per tick)
description: Amount of brightness changed per tick while holding UP/DOWN.
default: 5
selector:
number:
min: 1
max: 20
step: 1
unit_of_measurement: '%'
mode: slider
# Color temperature hold behavior
temp_step_kelvin:
name: Color temp step (K per tick)
description: Amount of color temperature changed per tick while holding LEFT/RIGHT.
default: 50
selector:
number:
min: 10
max: 200
step: 10
unit_of_measurement: K
mode: slider
temp_min_kelvin:
name: Min color temp (K)
description: Minimum color temperature limit.
default: 2000
selector:
number:
min: 1500
max: 4000
step: 100
unit_of_measurement: K
mode: slider
temp_max_kelvin:
name: Max color temp (K)
description: Maximum color temperature limit.
default: 6500
selector:
number:
min: 4000
max: 10000
step: 100
unit_of_measurement: K
mode: slider
# 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
temp_step_input: !input temp_step_kelvin
temp_min_input: !input temp_min_kelvin
temp_max_input: !input temp_max_kelvin
light_target: !input light
tick_s: "{{ (tick_ms_input | float(10)) / 1000 }}"
step_up: "{{ step_pct_input | int(5) }}"
step_down: "{{ (step_pct_input | int(5)) * -1 }}"
temp_step: "{{ temp_step_input | int(50) }}"
temp_min: "{{ temp_min_input | int(2000) }}"
temp_max: "{{ temp_max_input | int(6500) }}"
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 → color temp 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 >= 6 else curr_idx + 1 }}
{% else %}
{{ 6 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 }
- conditions: "{{ next_idx == 4 }}"
sequence:
- service: light.turn_on
target: !input light
data: { color_temp_kelvin: !input palette_temp4, transition: 0 }
- conditions: "{{ next_idx == 5 }}"
sequence:
- service: light.turn_on
target: !input light
data: { color_temp_kelvin: !input palette_temp5, transition: 0 }
- conditions: "{{ next_idx == 6 }}"
sequence:
- service: light.turn_on
target: !input light
data: { color_temp_kelvin: !input palette_temp6, transition: 0 }
# --- HOLD LEFT (color temp warmer / lower K) — step on timeout, stop on release ---
- conditions: "{{ cluster == 5 and cmd == 'hold' and args == [3329, 0] }}"
sequence:
- variables:
_i: 0
# Get first light entity from target to read current color temp
first_light: >
{% set entities = light_target.entity_id if light_target.entity_id is defined else [] %}
{% if entities is string %}{{ entities }}{% elif entities | length > 0 %}{{ entities[0] }}{% else %}{% endif %}
- repeat:
sequence:
- wait_for_trigger:
- platform: event
event_type: zha_event
event_data: { device_id: !input remote, cluster_id: 5, command: "release" }
# Opposite hold also cancels
- platform: event
event_type: zha_event
event_data: { device_id: !input remote, cluster_id: 5, command: "hold", args: [3328, 0] }
timeout: "{{ tick_s }}"
continue_on_timeout: true
- choose:
- conditions: "{{ not wait.completed }}"
sequence:
- variables:
current_temp: "{{ state_attr(first_light, 'color_temp_kelvin') | int(4000) }}"
new_temp: "{{ [temp_min | int, current_temp - (temp_step | int)] | max }}"
- service: light.turn_on
target: !input light
data:
color_temp_kelvin: "{{ new_temp }}"
transition: 0
- variables: { _i: "{{ _i + 1 }}" }
until:
- condition: template
value_template: "{{ wait.completed or (_i | int) >= (cap_ticks | int) }}"
# --- HOLD RIGHT (color temp cooler / higher K) — step on timeout, stop on release ---
- conditions: "{{ cluster == 5 and cmd == 'hold' and args == [3328, 0] }}"
sequence:
- variables:
_i: 0
first_light: >
{% set entities = light_target.entity_id if light_target.entity_id is defined else [] %}
{% if entities is string %}{{ entities }}{% elif entities | length > 0 %}{{ entities[0] }}{% else %}{% endif %}
- repeat:
sequence:
- wait_for_trigger:
- platform: event
event_type: zha_event
event_data: { device_id: !input remote, cluster_id: 5, command: "release" }
# Opposite hold also cancels
- platform: event
event_type: zha_event
event_data: { device_id: !input remote, cluster_id: 5, command: "hold", args: [3329, 0] }
timeout: "{{ tick_s }}"
continue_on_timeout: true
- choose:
- conditions: "{{ not wait.completed }}"
sequence:
- variables:
current_temp: "{{ state_attr(first_light, 'color_temp_kelvin') | int(4000) }}"
new_temp: "{{ [temp_max | int, current_temp + (temp_step | int)] | min }}"
- service: light.turn_on
target: !input light
data:
color_temp_kelvin: "{{ new_temp }}"
transition: 0
- variables: { _i: "{{ _i + 1 }}" }
until:
- condition: template
value_template: "{{ wait.completed or (_i | int) >= (cap_ticks | int) }}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment