Last active
March 9, 2026 20:48
-
-
Save robojerk/b4a66e2769079a29f81d4c89ea883c95 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: "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