Skip to content

Instantly share code, notes, and snippets.

@robojerk
Last active March 9, 2026 20:48
Show Gist options
  • Select an option

  • Save robojerk/b4a66e2769079a29f81d4c89ea883c95 to your computer and use it in GitHub Desktop.

Select an option

Save robojerk/b4a66e2769079a29f81d4c89ea883c95 to your computer and use it in GitHub Desktop.
blueprint:
name: "IKEA BILRESA - Light Controller (v1.1)"
description: >
Light control for IKEA BILRESA (Matter). Supports dynamic scrolling, scene cycling, and multi-click actions for up to three channels.
domain: automation
input:
remote_device:
name: "Remote Control"
description: "The IKEA BILRESA scroll wheel (Matter) used as the trigger."
selector:
device:
filter:
- manufacturer: "IKEA of Sweden"
model: "BILRESA scroll wheel"
override_scroll_entities:
name: "Fix scroll (Matter) – pick event entities manually"
description: "If scroll does nothing, expand this. Pick the remote's event entities for Channel 1: scroll-left, scroll-right, and click. Find them under Settings → Devices → [your Bilresa] → Entities (event.*)."
icon: "mdi:hand-back-right"
collapsed: true
input:
scroll_left_1:
name: "Channel 1 – scroll left / down (event entity)"
description: "Event entity that fires when you turn the wheel one way."
default: ""
selector:
entity:
filter:
- domain: event
scroll_right_1:
name: "Channel 1 – scroll right / up (event entity)"
description: "Event entity that fires when you turn the wheel the other way."
default: ""
selector:
entity:
filter:
- domain: event
click_1:
name: "Channel 1 – press/click (event entity)"
description: "Event entity for the center button press (single click)."
default: ""
selector:
entity:
filter:
- domain: event
# --- CHANNEL 1 ---
header_1:
name: "Channel 1"
default: ""
selector:
constant:
value: ""
group_scroll_1:
name: "Scroll Wheel Settings"
icon: "mdi:rotate-right"
collapsed: true
input:
input_light_1:
name: "Light / Light Group"
description: "Select the light or group to be controlled."
default: {}
selector: { target: { entity: { domain: light } } }
input_mode_1:
name: "Scroll Function"
description: "Define which value is changed when turning the wheel."
default: "brightness"
selector:
select:
mode: dropdown
options:
- { label: "Brightness", value: "brightness" }
- { label: "Color", value: "hs_color" }
- { label: "Color temperature", value: "temp_kelvin" }
input_sens_1:
name: "Multiplier"
description: "Define how much the value should change with each scroll step."
default: 5
selector:
number:
min: 1
max: 50
unit_of_measurement: "x"
mode: slider
input_transition_1:
name: "Transition duration"
description: "Define how long the transition between changes should take."
default: 200
selector:
number:
min: 0
max: 5000
unit_of_measurement: "ms"
mode: slider
group_single_click_1:
name: "Single Click"
icon: "mdi:gesture-tap"
collapsed: true
input:
input_click_mode_1:
name: "Click Mode"
description: "Choose between a single action or cycling through scenes."
default: "action"
selector:
select:
mode: dropdown
options:
- { label: "Perform single action", value: "action" }
- { label: "Scene cycler", value: "scene_cycle" }
input_scenes_1:
name: "Scene Selection"
description: "Select the scenes to cycle through sequentially."
default: []
selector: { entity: { domain: scene, multiple: true } }
input_click_1:
name: "Action on Click"
description: "Action to perform when in 'Single Action' mode."
default: []
selector: { action: {} }
group_multi_click_1:
name: "Additional Button Functions"
icon: "mdi:plus-circle-outline"
collapsed: true
input:
input_double_1: { name: "Double Click", description: "Action to perform on double press.", default: [], selector: { action: {} } }
input_triple_1: { name: "Triple Click", description: "Action to perform on triple press.", default: [], selector: { action: {} } }
input_long_1: { name: "Hold", description: "Action to perform when holding the button.", default: [], selector: { action: {} } }
input_release_1: { name: "Release", description: "Action to perform when releasing the button after a long press.", default: [], selector: { action: {} } }
# --- CHANNEL 2 ---
header_2:
name: "Channel 2"
default: ""
selector:
constant:
value: ""
group_scroll_2:
name: "Scroll Wheel Settings"
icon: "mdi:rotate-right"
collapsed: true
input:
input_light_2:
name: "Light / Light Group"
description: "Select the light or group to be controlled."
default: {}
selector: { target: { entity: { domain: light } } }
input_mode_2:
name: "Scroll Function"
description: "Define which value is changed when turning the wheel."
default: "brightness"
selector:
select:
mode: dropdown
options:
- { label: "Brightness", value: "brightness" }
- { label: "Color", value: "hs_color" }
- { label: "Color temperature", value: "temp_kelvin" }
input_sens_2:
name: "Multiplier"
description: "Define how much the value should change with each scroll step."
default: 5
selector:
number:
min: 1
max: 50
unit_of_measurement: "x"
mode: slider
input_transition_2:
name: "Transition duration"
description: "Define how long the transition between changes should take."
default: 200
selector:
number:
min: 0
max: 5000
unit_of_measurement: "ms"
mode: slider
group_single_click_2:
name: "Single Click"
icon: "mdi:gesture-tap"
collapsed: true
input:
input_click_mode_2: { name: "Click Mode", description: "Choose between a single action or cycling through scenes.", default: "action", selector: { select: { mode: dropdown, options: [{ label: "Perform single action", value: "action" }, { label: "Scene cycler", value: "scene_cycle" }] } } }
input_scenes_2: { name: "Scene Selection", description: "Select the scenes to cycle through sequentially.", default: [], selector: { entity: { domain: scene, multiple: true } } }
input_click_2: { name: "Action on Click", description: "Action to perform when in 'Single Action' mode.", default: [], selector: { action: {} } }
group_multi_click_2:
name: "Additional Button Functions"
icon: "mdi:plus-circle-outline"
collapsed: true
input:
input_double_2: { name: "Double Click", description: "Action to perform on double press.", default: [], selector: { action: {} } }
input_triple_2: { name: "Triple Click", description: "Action to perform on triple press.", default: [], selector: { action: {} } }
input_long_2: { name: "Hold", description: "Action to perform when holding the button.", default: [], selector: { action: {} } }
input_release_2: { name: "Release", description: "Action to perform when releasing the button after a long press.", default: [], selector: { action: {} } }
# --- CHANNEL 3 ---
header_3:
name: "Channel 3"
default: ""
selector:
constant:
value: ""
group_scroll_3:
name: "Scroll Wheel Settings"
icon: "mdi:rotate-right"
collapsed: true
input:
input_light_3:
name: "Light / Light Group"
description: "Select the light or group to be controlled."
default: {}
selector: { target: { entity: { domain: light } } }
input_mode_3:
name: "Scroll Function"
description: "Define which value is changed when turning the wheel."
default: "brightness"
selector:
select:
mode: dropdown
options:
- { label: "Brightness", value: "brightness" }
- { label: "Color", value: "hs_color" }
- { label: "Color temperature", value: "temp_kelvin" }
input_sens_3:
name: "Multiplier"
description: "Define how much the value should change with each scroll step."
default: 5
selector:
number:
min: 1
max: 50
unit_of_measurement: "x"
mode: slider
input_transition_3:
name: "Transition duration"
description: "Define how long the transition between changes should take."
default: 200
selector:
number:
min: 0
max: 5000
unit_of_measurement: "ms"
mode: slider
group_single_click_3:
name: "Single Click"
icon: "mdi:gesture-tap"
collapsed: true
input:
input_click_mode_3: { name: "Click Mode", description: "Choose between a single action or cycling through scenes.", default: "action", selector: { select: { mode: dropdown, options: [{ label: "Perform single action", value: "action" }, { label: "Scene cycler", value: "scene_cycle" }] } } }
input_scenes_3: { name: "Scene Selection", description: "Select the scenes to cycle through sequentially.", default: [], selector: { entity: { domain: scene, multiple: true } } }
input_click_3: { name: "Action on Click", description: "Action to perform when in 'Single Action' mode.", default: [], selector: { action: {} } }
group_multi_click_3:
name: "Additional Button Functions"
icon: "mdi:plus-circle-outline"
collapsed: true
input:
input_double_3: { name: "Double Click", description: "Action to perform on double press.", default: [], selector: { action: {} } }
input_triple_3: { name: "Triple Click", description: "Action to perform on triple press.", default: [], selector: { action: {} } }
input_long_3: { name: "Hold", description: "Action to perform when holding the button.", default: [], selector: { action: {} } }
input_release_3: { name: "Release", description: "Action to perform when releasing the button after a long press.", default: [], selector: { action: {} } }
mode: single
max_exceeded: silent
trigger_variables:
var_remote: !input remote_device
override_left_1: !input scroll_left_1
override_right_1: !input scroll_right_1
override_click_1: !input click_1
var_events: >
{{ device_entities(var_remote) | select('match', 'event\.') | sort | list }}
temp_kanal1_rechts: "{{ override_right_1 if override_right_1 else (var_events[0] if var_events | length > 0 else 'none') }}"
temp_kanal1_links: "{{ override_left_1 if override_left_1 else (var_events[1] if var_events | length > 1 else 'none') }}"
temp_kanal1_klick: "{{ override_click_1 if override_click_1 else (var_events[2] if var_events | length > 2 else 'none') }}"
temp_kanal2_rechts: "{{ var_events[3] if var_events | length > 3 else 'none' }}"
temp_kanal2_links: "{{ var_events[4] if var_events | length > 4 else 'none' }}"
temp_kanal2_klick: "{{ var_events[5] if var_events | length > 5 else 'none' }}"
temp_kanal3_rechts: "{{ var_events[6] if var_events | length > 6 else 'none' }}"
temp_kanal3_links: "{{ var_events[7] if var_events | length > 7 else 'none' }}"
temp_kanal3_klick: "{{ var_events[8] if var_events | length > 8 else 'none' }}"
trigger:
- { trigger: event, event_type: state_changed, event_data: { entity_id: "{{ temp_kanal1_links }}" }, id: trigger_kanal1_links }
- { trigger: event, event_type: state_changed, event_data: { entity_id: "{{ temp_kanal1_rechts }}" }, id: trigger_kanal1_rechts }
- { trigger: event, event_type: state_changed, event_data: { entity_id: "{{ temp_kanal1_klick }}" }, id: trigger_kanal1_klick }
- { trigger: event, event_type: state_changed, event_data: { entity_id: "{{ temp_kanal2_links }}" }, id: trigger_kanal2_links }
- { trigger: event, event_type: state_changed, event_data: { entity_id: "{{ temp_kanal2_rechts }}" }, id: trigger_kanal2_rechts }
- { trigger: event, event_type: state_changed, event_data: { entity_id: "{{ temp_kanal2_klick }}" }, id: trigger_kanal2_klick }
- { trigger: event, event_type: state_changed, event_data: { entity_id: "{{ temp_kanal3_links }}" }, id: trigger_kanal3_links }
- { trigger: event, event_type: state_changed, event_data: { entity_id: "{{ temp_kanal3_rechts }}" }, id: trigger_kanal3_rechts }
- { trigger: event, event_type: state_changed, event_data: { entity_id: "{{ temp_kanal3_klick }}" }, id: trigger_kanal3_klick }
action:
- variables:
m_1: !input input_mode_1
m_2: !input input_mode_2
m_3: !input input_mode_3
s_1: !input input_sens_1
s_2: !input input_sens_2
s_3: !input input_sens_3
l_1: !input input_light_1
l_2: !input input_light_2
l_3: !input input_light_3
tr_1: !input input_transition_1
tr_2: !input input_transition_2
tr_3: !input input_transition_3
action_kanal_id: "{{ trigger.id.split('_')[1] }}"
event_attr: "{{ trigger.event.data.new_state.attributes }}"
# Step count: Matter may use totalNumberOfPressesCounted, step, or rotation; others use totalNumberOfPressesCounted
ticks: "{{ event_attr.totalNumberOfPressesCounted | default(event_attr.step) | default(event_attr.rotation) | default(1) | int }}"
action_event_type: "{{ event_attr.event_type | default('none') }}"
curr_mode: "{% if action_kanal_id == 'kanal1' %}{{ m_1 }}{% elif action_kanal_id == 'kanal2' %}{{ m_2 }}{% else %}{{ m_3 }}{% endif %}"
base_sens: "{% if action_kanal_id == 'kanal1' %}{{ s_1 }}{% elif action_kanal_id == 'kanal2' %}{{ s_2 }}{% else %}{{ s_3 }}{% endif %}"
step_val: "{% set val = base_sens * ticks %}{{ val if trigger.id.endswith('_rechts') else val * -1 }}"
curr_target_raw: "{% if action_kanal_id == 'kanal1' %}{{ l_1 }}{% elif action_kanal_id == 'kanal2' %}{{ l_2 }}{% else %}{{ l_3 }}{% endif %}"
curr_target: "{{ curr_target_raw.entity_id if curr_target_raw.entity_id is defined else curr_target_raw }}"
transition_ms: "{% if action_kanal_id == 'kanal1' %}{{ tr_1 }}{% elif action_kanal_id == 'kanal2' %}{{ tr_2 }}{% else %}{{ tr_3 }}{% endif %}"
transition_s: "{{ transition_ms / 1000 }}"
- choose:
- conditions: "{{ trigger.id.endswith('_rechts') or trigger.id.endswith('_links') }}"
sequence:
- choose:
- conditions: "{{ curr_mode == 'brightness' }}"
sequence:
- variables:
target_value: >
{% set current = state_attr(curr_target, 'brightness') | int(128) %}
{{ [([current + (step_val | int * 2.55 | int), 0] | max), 255] | min }}
- service: light.turn_on
target:
entity_id: "{{ curr_target }}"
data:
transition: "{{ transition_s }}"
brightness: "{{ target_value }}"
- conditions: "{{ curr_mode == 'temp_kelvin' }}"
sequence:
- variables:
target_value: >
{% set curr = state_attr(curr_target, 'color_temp_kelvin') | int(3000) %}
{% set min_v = state_attr(curr_target, 'min_color_temp_kelvin') | int(2000) %}
{% set max_v = state_attr(curr_target, 'max_color_temp_kelvin') | int(6535) %}
{{ [([curr + (step_val * 10) | int, min_v] | max), max_v] | min }}
- service: light.turn_on
target:
entity_id: "{{ curr_target }}"
data:
transition: "{{ transition_s }}"
color_temp_kelvin: "{{ target_value }}"
- conditions: "{{ curr_mode == 'hs_color' }}"
sequence:
- variables:
target_value: >
{% set hs = state_attr(curr_target, 'hs_color') | default([0, 100], true) %}
{{ [((hs[0] | float(0) + step_val) % 360) | round(2), hs[1] | float(100)] }}
- service: light.turn_on
target:
entity_id: "{{ curr_target }}"
data:
transition: "{{ transition_s }}"
hs_color: "{{ target_value }}"
- conditions: "{{ trigger.id.endswith('_klick') }}"
sequence:
- variables:
cm_1: !input input_click_mode_1
cm_2: !input input_click_mode_2
cm_3: !input input_click_mode_3
cs_1: !input input_scenes_1
cs_2: !input input_scenes_2
cs_3: !input input_scenes_3
c_mode: "{% if action_kanal_id == 'kanal1' %}{{ cm_1 }}{% elif action_kanal_id == 'kanal2' %}{{ cm_2 }}{% else %}{{ cm_3 }}{% endif %}"
c_scenes: "{% if action_kanal_id == 'kanal1' %}{{ cs_1 }}{% elif action_kanal_id == 'kanal2' %}{{ cs_2 }}{% else %}{{ cs_3 }}{% endif %}"
- choose:
- conditions: "{{ action_event_type == 'multi_press_1' }}"
sequence:
- choose:
- conditions: "{{ c_mode == 'scene_cycle' and c_scenes | count > 0 }}"
sequence:
- service: scene.turn_on
target:
entity_id: "{% set count = c_scenes | count %}{% set idx = (as_timestamp(now()) | int // 2) % count %}{{ c_scenes[idx] }}"
- conditions: "{{ c_mode == 'action' }}"
sequence:
- choose:
- { conditions: "{{ action_kanal_id == 'kanal1' }}", sequence: !input input_click_1 }
- { conditions: "{{ action_kanal_id == 'kanal2' }}", sequence: !input input_click_2 }
- { conditions: "{{ action_kanal_id == 'kanal3' }}", sequence: !input input_click_3 }
- conditions: "{{ action_event_type == 'multi_press_2' }}"
sequence:
- choose:
- { conditions: "{{ action_kanal_id == 'kanal1' }}", sequence: !input input_double_1 }
- { conditions: "{{ action_kanal_id == 'kanal2' }}", sequence: !input input_double_2 }
- { conditions: "{{ action_kanal_id == 'kanal3' }}", sequence: !input input_double_3 }
- conditions: "{{ action_event_type == 'multi_press_3' }}"
sequence:
- choose:
- { conditions: "{{ action_kanal_id == 'kanal1' }}", sequence: !input input_triple_1 }
- { conditions: "{{ action_kanal_id == 'kanal2' }}", sequence: !input input_triple_2 }
- { conditions: "{{ action_kanal_id == 'kanal3' }}", sequence: !input input_triple_3 }
- conditions: "{{ action_event_type == 'long_press' }}"
sequence:
- choose:
- { conditions: "{{ action_kanal_id == 'kanal1' }}", sequence: !input input_long_1 }
- { conditions: "{{ action_kanal_id == 'kanal2' }}", sequence: !input input_long_2 }
- { conditions: "{{ action_kanal_id == 'kanal3' }}", sequence: !input input_long_3 }
- conditions: "{{ action_event_type == 'long_release' }}"
sequence:
- choose:
- { conditions: "{{ action_kanal_id == 'kanal1' }}", sequence: !input input_release_1 }
- { conditions: "{{ action_kanal_id == 'kanal2' }}", sequence: !input input_release_2 }
- { conditions: "{{ action_kanal_id == 'kanal3' }}", sequence: !input input_release_3 }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment