Skip to content

Instantly share code, notes, and snippets.

@jhol-byte
Last active January 1, 2026 17:26
Show Gist options
  • Select an option

  • Save jhol-byte/b2731a4d2476f530d76b9ff409f7f3a4 to your computer and use it in GitHub Desktop.

Select an option

Save jhol-byte/b2731a4d2476f530d76b9ff409f7f3a4 to your computer and use it in GitHub Desktop.
Home Assistent Blueprint "Ikea_bilresa_scroll_wheel"
blueprint:
name: Ikea_bilresa_scroll_wheel
description: |
Blueprint for Ikea Bilresa (button and scroll wheel for 3 different channels)
Version: 2025-01-01
domain: automation
# input selectors
input:
remote:
name: Remote
description: "Ikea Bilresa scroll wheel remote control"
selector:
device:
# input selectors: channel 1
channel1_section:
name: Channel 1
collapsed: true
input:
click_action_ch1:
name: "Channel 1: <click> action"
default: []
selector:
action: {}
double_click_action_ch1:
name: "Channel 1: <double-click> action"
default: []
selector:
action: {}
long_click_action_ch1:
name: "Channel 1: <long-click> action"
description: ""
default: []
selector:
action: {}
on_hold_action_ch1:
name: "Channel 1: <on-hold> action"
description: "repeat action until button is released, see also on_hold_delay"
default: []
selector:
action: {}
scroll_wheel_target_ch1:
name: "Channel 1: Scroll wheel target entity"
description: Light or media player to be controlled by scroll wheel for channel 1
selector:
entity:
multiple: true
filter:
domain:
- light
- media_player
default: []
scroll_wheel_mode_ch1:
selector:
select:
options:
- "lights: dim"
- "lights: color temp and color hue"
- "lights: color temp only"
- "lights: color hue only"
- "media: volume control"
default: dim_only
name: "Channel 1: Scroll wheel mode"
description: "Select attribute of target entity controlled by scroll wheel"
# input selectors: channel 1
channel2_section:
name: Channel 2
collapsed: true
input:
click_action_ch2:
name: "Channel 2: <click> action"
default: []
selector:
action: {}
double_click_action_ch2:
name: "Channel 2: <double-click> action"
default: []
selector:
action: {}
long_click_action_ch2:
name: "Channel 2: <long-click> action"
description: ""
default: []
selector:
action: {}
on_hold_action_ch2:
name: "Channel 2: <on-hold> action"
description: "repeat action until button is released, see also on_hold_delay"
default: []
selector:
action: {}
scroll_wheel_target_ch2:
name: "Channel 2: Scroll wheel target entity"
description: Light or media player to be controlled by scroll wheel for channel 2
selector:
entity:
multiple: true
filter:
domain:
- light
- media_player
default: []
scroll_wheel_mode_ch2:
selector:
select:
options:
- "lights: dim"
- "lights: color temp and color hue"
- "lights: color temp only"
- "lights: color hue only"
- "media: volume control"
default: dim_only
name: "Channel 2: Scroll wheel mode"
description: "Select attribute of target entity controlled by scroll wheel"
# input selectors: channel 3
channel3_section:
name: Channel 3
collapsed: true
input:
click_action_ch3:
name: "Channel 3: <click> action"
default: []
selector:
action: {}
double_click_action_ch3:
name: "Channel 3: <double-click> action"
default: []
selector:
action: {}
long_click_action_ch3:
name: "Channel 3: <long-click> action"
description: ""
default: []
selector:
action: {}
on_hold_action_ch3:
name: "Channel 3: <on-hold> action"
description: "repeat action until button is released, see also on_hold_delay"
default: []
selector:
action: {}
scroll_wheel_target_ch3:
name: "Channel 3: Scroll wheel target entity"
description: Light or media player to be controlled by scroll wheel for channel 3
selector:
entity:
multiple: true
filter:
domain:
- light
- media_player
default: []
scroll_wheel_mode_ch3:
selector:
select:
options:
- "lights: dim"
- "lights: color temp and color hue"
- "lights: color temp only"
- "lights: color hue only"
- "media: volume control"
default: dim_only
name: "Channel 3: Scroll wheel mode"
description: "Select attribute of target entity controlled by scroll wheel"
# input selectors: settings for all channels
global_section:
name: Global settings
collapsed: true
input:
dim_step_pct:
name: Dimmer step percentage
description: "change in brightness in %"
default: 5
selector:
number:
mode: box
dim_min_pct:
name: Minimum brightness
description: "minimum brightness in %"
default: 5
selector:
number:
mode: box
min_color_temp:
name: Minimum color temperature
description: "minimum color temperature in K (for color_mode = color_temp)"
default: 2200
selector:
number:
mode: box
max_color_temp:
name: Maximum color temperature
description: "maximum color temperature in K (for color_mode = color_temp)"
default: 4000
selector:
number:
mode: box
color_temp_step:
name: Color temperature step
description: "change in color temperature in K (for color_mode = color_temp)"
default: 100
selector:
number:
mode: box
color_hue_step:
name: Color hue step
description: "change in color hue in degree (0..360) (for color_mode = color_hue)"
default: 4
selector:
number:
mode: box
color_saturation:
name: Color saturation
description: "color saturation in % (for color_mode = color_hue)"
default: 100
selector:
number:
mode: box
on_hold_delay:
name: On hold delay
description: delay between separate action calls while on hold button
default: 0.1
selector:
number:
mode: box
step: 0.1
volume_step_pct:
name: Media player volume step percentage
description: "change in volume in %"
default: 2
selector:
number:
mode: box
volume_max_pct:
name: Media player max volume percentage
description: "max volume in %"
default: 50
selector:
number:
mode: box
# automation logic
alias: Ikea_bilresa_scroll_wheel
trigger:
- event_type: state_changed
event_data:
entity_id: "{{ var_button_ch1 }}"
trigger: event
id: button_ch1
- event_type: state_changed
event_data:
entity_id: "{{ var_scroll_left_ch1 }}"
trigger: event
id: scroll_left_ch1
- event_type: state_changed
event_data:
entity_id: "{{ var_scroll_right_ch1 }}"
trigger: event
id: scroll_right_ch1
- event_type: state_changed
event_data:
entity_id: "{{ var_button_ch2 }}"
trigger: event
id: button_ch2
- event_type: state_changed
event_data:
entity_id: "{{ var_scroll_left_ch2 }}"
trigger: event
id: scroll_left_ch2
- event_type: state_changed
event_data:
entity_id: "{{ var_scroll_right_ch2 }}"
trigger: event
id: scroll_right_ch2
- event_type: state_changed
event_data:
entity_id: "{{ var_button_ch3 }}"
trigger: event
id: button_ch3
- event_type: state_changed
event_data:
entity_id: "{{ var_scroll_left_ch3 }}"
trigger: event
id: scroll_left_ch3
- event_type: state_changed
event_data:
entity_id: "{{ var_scroll_right_ch3 }}"
trigger: event
id: scroll_right_ch3
condition:
- condition: template
value_template: >-
{{ trigger.event.data.old_state.state != 'unavailable'
and trigger.event.data.new_state.state != 'unavailable'
}}
action:
- variables:
channel: >-
{% if trigger.id in ['button_ch1', 'scroll_left_ch1', 'scroll_right_ch1'] %}
ch1
{% elif trigger.id in ['button_ch2', 'scroll_left_ch2', 'scroll_right_ch2'] %}
ch2
{% elif trigger.id in ['button_ch3', 'scroll_left_ch3', 'scroll_right_ch3'] %}
ch3
{% endif %}
- choose:
# button click
- conditions:
- condition: template
value_template: >
{{ trigger.id == ('button_' ~ channel) and trigger.event.data.new_state.attributes.event_type ==
'multi_press_1' }}
sequence:
- if: "{{ channel == 'ch1'}}"
then:
!input click_action_ch1
- if: "{{ channel == 'ch2'}}"
then:
!input click_action_ch2
- if: "{{ channel == 'ch3'}}"
then:
!input click_action_ch3
# button double-click
- conditions:
- condition: template
value_template: >
{{ trigger.id == ('button_' ~ channel) and trigger.event.data.new_state.attributes.event_type ==
'multi_press_2' }}
sequence:
- if: "{{ channel == 'ch1'}}"
then:
!input double_click_action_ch1
- if: "{{ channel == 'ch2'}}"
then:
!input double_click_action_ch2
- if: "{{ channel == 'ch3'}}"
then:
!input double_click_action_ch3
# button long-press
- conditions:
- condition: template
value_template: >
{{ trigger.id == ('button_' ~ channel) and trigger.event.data.new_state.attributes.event_type ==
'long_press' }}
# button on-hold action
sequence:
- if: "{{ channel == 'ch1'}}"
then:
sequence:
- sequence: !input long_click_action_ch1
- repeat:
while: "{{ repeat.index < 100 and state_attr(var_button_ch1, 'event_type') == 'long_press' }}"
sequence:
- sequence: !input on_hold_action_ch1
- delay: "{{ var_on_hold_delay }}"
- if: "{{ channel == 'ch2'}}"
then:
sequence:
- sequence: !input long_click_action_ch2
- repeat:
while: "{{ repeat.index < 100 and state_attr(var_button_ch2, 'event_type') == 'long_press' }}"
sequence:
- sequence: !input on_hold_action_ch2
- delay: "{{ var_on_hold_delay }}"
- if: "{{ channel == 'ch3'}}"
then:
sequence:
- sequence: !input long_click_action_ch3
- repeat:
while: "{{ repeat.index < 100 and state_attr(var_button_ch3, 'event_type') == 'long_press' }}"
sequence:
- sequence: !input on_hold_action_ch3
- delay: "{{ var_on_hold_delay }}"
# scroll wheel actions
- conditions:
- condition: template
value_template: >
{{ trigger.id == ('scroll_left_' ~ channel) or trigger.id == ('scroll_right_' ~ channel) }}
sequence:
- repeat:
for_each: "{{ vars['targets_entities_' ~ channel] }}"
sequence:
- choose:
- conditions:
- condition: template
value_template: "{{ vars['scroll_wheel_mode_' ~ channel] == 'lights: dim'}}"
sequence:
# scroll wheel mode: lights: dim
- if:
- condition: template
value_template: >-
{{ is_state(repeat.item, 'on')
}}
then:
- action: light.turn_on
target:
entity_id: "{{ repeat.item }}"
data:
brightness_step_pct: >-
{{ iif(trigger.id == 'scroll_right_' ~ channel, trigger.event.data.new_state.attributes.totalNumberOfPressesCounted * var_dim_step_pct,
-1*([trigger.event.data.new_state.attributes.totalNumberOfPressesCounted * var_dim_step_pct, state_attr(repeat.item, 'brightness')*100/254
- var_min_pct]|min))
}}
transition: 0.1
# scroll wheel mode: lights: color temp
- conditions:
- condition: template
value_template: "{{ vars['scroll_wheel_mode_' ~ channel] == 'lights: color temp only' or (vars['scroll_wheel_mode_' ~ channel] == 'lights: color temp and color hue' and state_attr(repeat.item, 'color_temp_kelvin') is not none ) }}"
sequence:
# scroll wheel mode: lights: color temp
- action: script.light_color_temp_helper
data:
mode: >-
{{ iif(trigger.id == 'scroll_left_' ~ channel, 'down', 'up')
}}
min_color_temp: "{{ var_min_color_temp }}"
max_color_temp: "{{ var_max_color_temp }}"
color_temp_step: "{{ var_color_temp_step * trigger.event.data.new_state.attributes.totalNumberOfPressesCounted }}"
target_light: "{{ repeat.item }}"
# scroll wheel mode: lights: color hue
- conditions:
- condition: template
value_template: "{{ vars['scroll_wheel_mode_' ~ channel] == 'lights: color hue only' or (vars['scroll_wheel_mode_' ~ channel] == 'lights: color temp and color hue' and state_attr(repeat.item, 'color_temp_kelvin') is none ) }}"
sequence:
- action: script.light_color_hs_helper
data:
target_light: "{{ repeat.item }}"
color_hue_step: "{{ var_color_hue_step * trigger.event.data.new_state.attributes.totalNumberOfPressesCounted }}"
color_saturation: "{{ var_color_saturation }}"
mode: >-
{{ iif(trigger.id == 'scroll_left_' ~ channel, 'down', 'up')
}}
# scroll wheel mode: media: volume
- conditions:
- condition: template
value_template: "{{ vars['scroll_wheel_mode_' ~ channel] == 'media: volume control' and is_state(repeat.item, 'playing')}}"
sequence:
- action: media_player.volume_set
target:
entity_id: "{{ repeat.item }}"
data:
volume_level: >-
{{ [[state_attr(repeat.item, 'volume_level')*100 +
iif(trigger.id == 'scroll_right_' ~ channel, +1, -1)* trigger.event.data.new_state.attributes.totalNumberOfPressesCounted * var_volume_step_pct, var_volume_max_pct] | min, 0] | max / 100
}}
mode: single
max_exceeded: silent
variables:
vars: {'targets_entities_ch1': !input scroll_wheel_target_ch1, 'targets_entities_ch2': !input scroll_wheel_target_ch2, 'targets_entities_ch3': !input scroll_wheel_target_ch3,
'scroll_wheel_mode_ch1': !input scroll_wheel_mode_ch1, 'scroll_wheel_mode_ch2': !input scroll_wheel_mode_ch2, 'scroll_wheel_mode_ch3': !input scroll_wheel_mode_ch3
}
var_min_pct: !input dim_min_pct
var_dim_step_pct: !input dim_step_pct
var_color_hue_step: !input color_hue_step
var_color_saturation: !input color_saturation
var_min_color_temp: !input min_color_temp
var_max_color_temp: !input max_color_temp
var_color_temp_step: !input color_temp_step
var_on_hold_delay: !input on_hold_delay
var_volume_step_pct: !input volume_step_pct
var_volume_max_pct: !input volume_max_pct
trigger_variables:
var_remote: !input remote
# get all event names from device_id only
var_button_ch1: >-
{% for item in device_entities(var_remote) %}
{% set match = item | regex_findall(find='^event\..+[a-zA-Z]_3.*$') %}
{% if (match | length) > 0 %}
{{ match[0] }}
{% endif %}
{% endfor %}
var_scroll_left_ch1: >-
{% for item in device_entities(var_remote) %}
{% set match = item | regex_findall(find='^event\..+[a-zA-Z]_2.*$') %}
{% if (match | length) > 0 %}
{{ match[0] }}
{% endif %}
{% endfor %}
var_scroll_right_ch1: >-
{% for item in device_entities(var_remote) %}
{% set match = item | regex_findall(find='^event\..+[a-zA-Z]_1.*$') %}
{% if (match | length) > 0 %}
{{ match[0] }}
{% endif %}
{% endfor %}
var_button_ch2: >-
{% for item in device_entities(var_remote) %}
{% set match = item | regex_findall(find='^event\..+[a-zA-Z]_6.*$') %}
{% if (match | length) > 0 %}
{{ match[0] }}
{% endif %}
{% endfor %}
var_scroll_left_ch2: >-
{% for item in device_entities(var_remote) %}
{% set match = item | regex_findall(find='^event\..+[a-zA-Z]_5.*$') %}
{% if (match | length) > 0 %}
{{ match[0] }}
{% endif %}
{% endfor %}
var_scroll_right_ch2: >-
{% for item in device_entities(var_remote) %}
{% set match = item | regex_findall(find='^event\..+[a-zA-Z]_4.*$') %}
{% if (match | length) > 0 %}
{{ match[0] }}
{% endif %}
{% endfor %}
var_button_ch3: >-
{% for item in device_entities(var_remote) %}
{% set match = item | regex_findall(find='^event\..+[a-zA-Z]_9.*$') %}
{% if (match | length) > 0 %}
{{ match[0] }}
{% endif %}
{% endfor %}
var_scroll_left_ch3: >-
{% for item in device_entities(var_remote) %}
{% set match = item | regex_findall(find='^event\..+[a-zA-Z]_8.*$') %}
{% if (match | length) > 0 %}
{{ match[0] }}
{% endif %}
{% endfor %}
var_scroll_right_ch3: >-
{% for item in device_entities(var_remote) %}
{% set match = item | regex_findall(find='^event\..+[a-zA-Z]_7.*$') %}
{% if (match | length) > 0 %}
{{ match[0] }}
{% endif %}
{% endfor %}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment