A Quick Update
I’ve been making a conscious effort to automate more of my Home Assistant setup, but with one constraint: anything I automate should still make sense six months from now. That means predictable behavior, clear state transitions, and avoiding fragile logic that only works because I remember why I wrote it.
That constraint surfaced quickly when I started using the IKEA BILRESA Matter dual-button remote. It’s a clean piece of hardware with just enough capability to be useful, but the default patterns for handling it in Home Assistant fall apart once you introduce Adaptive Lighting and long-press behavior.
This post walks through the reasoning behind a blueprint I’ve been iterating on and why it’s structured the way it is.
The Latest full blueprint is available here:
Current V1.0 Attempt:
blueprint:
name: "IKEA BILRESA Matter Dual Button - 2 Lights - Option A Turn On Then Adaptive Apply - Long Press Full White Toggle (2x Adaptive)"
description: >
Option A to reduce flicker: on single-press ON, turn the light on without attributes first,
then apply Adaptive Lighting (optionally after a small delay). Long press toggles full white <-> adaptive.
Designed for IKEA BILRESA dual-button Matter remote (IKEA 806.178.76).
This variant supports TWO Adaptive Lighting instances: one per light.
domain: automation
input:
button1_event:
name: "Button 1 event entity"
description: "Matter event entity for Button 1 (event.*)"
selector:
entity:
domain: event
button2_event:
name: "Button 2 event entity"
description: "Matter event entity for Button 2 (event.*)"
selector:
entity:
domain: event
light_a:
name: "Light A (Button 1)"
selector:
entity:
domain: light
light_b:
name: "Light B (Button 2)"
selector:
entity:
domain: light
adaptive_switch_a:
name: "Adaptive Lighting switch for Light A"
description: "Adaptive Lighting instance that manages Light A (switch.adaptive_lighting_*)"
selector:
entity:
domain: switch
adaptive_switch_b:
name: "Adaptive Lighting switch for Light B"
description: "Adaptive Lighting instance that manages Light B (switch.adaptive_lighting_*)"
selector:
entity:
domain: switch
apply_transition:
name: "Adaptive apply transition (seconds)"
default: 0.1
selector:
number:
min: 0
max: 2
step: 0.1
unit_of_measurement: s
apply_delay_ms:
name: "Delay before adaptive apply (ms)"
default: 150
selector:
number:
min: 0
max: 1000
step: 50
unit_of_measurement: ms
full_white_kelvin:
name: "Full power white Kelvin (long press)"
default: 4000
selector:
number:
min: 2700
max: 6500
step: 100
unit_of_measurement: K
mode: restart
trigger:
- platform: state
id: button1
entity_id: !input button1_event
- platform: state
id: button2
entity_id: !input button2_event
# Guard: only act on real state changes, ignore unknown/unavailable
condition:
- condition: template
value_template: "{{ trigger.from_state is not none and trigger.to_state is not none }}"
- condition: template
value_template: "{{ trigger.from_state.state not in ['unknown', 'unavailable'] }}"
- condition: template
value_template: "{{ trigger.to_state.state not in ['unknown', 'unavailable'] }}"
- condition: template
value_template: "{{ trigger.from_state.state != trigger.to_state.state }}"
variables:
adaptive_a: !input adaptive_switch_a
adaptive_b: !input adaptive_switch_b
tr: !input apply_transition
delay_ms: !input apply_delay_ms
kelvin: !input full_white_kelvin
trigger_id: "{{ trigger.id }}"
press_type: "{{ trigger.to_state.attributes.event_type | default('') }}"
# Assumed Matter event_type mapping (adjust if your device differs)
single_press_type: "multi_press_1"
long_press_type: "long_press"
# Full-white detection thresholds
full_white_bri_min: 250
full_white_kelvin_tol: 250
action:
- choose:
# Button 1: SINGLE PRESS -> toggle Light A (turn on first, then adaptive apply)
- conditions: "{{ trigger_id == 'button1' and press_type == single_press_type }}"
sequence:
- variables:
L: !input light_a
adaptive: "{{ adaptive_a }}"
- choose:
- conditions: "{{ is_state(L, 'on') }}"
sequence:
- service: light.turn_off
target:
entity_id: "{{ L }}"
default:
- service: light.turn_on
target:
entity_id: "{{ L }}"
- service: adaptive_lighting.set_manual_control
data:
entity_id: "{{ adaptive }}"
lights: ["{{ L }}"]
manual_control: false
- choose:
- conditions: "{{ (delay_ms | int) > 0 }}"
sequence:
- delay:
milliseconds: "{{ delay_ms | int }}"
- service: adaptive_lighting.apply
data:
entity_id: "{{ adaptive }}"
lights: ["{{ L }}"]
transition: "{{ tr | float }}"
# Button 2: SINGLE PRESS -> toggle Light B (turn on first, then adaptive apply)
- conditions: "{{ trigger_id == 'button2' and press_type == single_press_type }}"
sequence:
- variables:
L: !input light_b
adaptive: "{{ adaptive_b }}"
- choose:
- conditions: "{{ is_state(L, 'on') }}"
sequence:
- service: light.turn_off
target:
entity_id: "{{ L }}"
default:
- service: light.turn_on
target:
entity_id: "{{ L }}"
- service: adaptive_lighting.set_manual_control
data:
entity_id: "{{ adaptive }}"
lights: ["{{ L }}"]
manual_control: false
- choose:
- conditions: "{{ (delay_ms | int) > 0 }}"
sequence:
- delay:
milliseconds: "{{ delay_ms | int }}"
- service: adaptive_lighting.apply
data:
entity_id: "{{ adaptive }}"
lights: ["{{ L }}"]
transition: "{{ tr | float }}"
# Button 1: LONG PRESS -> toggle Full White <-> Adaptive (Light A)
- conditions: "{{ trigger_id == 'button1' and press_type == long_press_type }}"
sequence:
- variables:
L: !input light_a
adaptive: "{{ adaptive_a }}"
bri: "{{ state_attr(L, 'brightness') | int(0) }}"
ct: "{{ state_attr(L, 'color_temp_kelvin') | int(0) }}"
is_full_white_now: >
{{ is_state(L,'on')
and bri >= full_white_bri_min
and ct != 0
and ((ct - (kelvin | int)) | abs) <= full_white_kelvin_tol }}
- choose:
- conditions: "{{ is_full_white_now }}"
sequence:
- service: adaptive_lighting.set_manual_control
data:
entity_id: "{{ adaptive }}"
lights: ["{{ L }}"]
manual_control: false
- service: adaptive_lighting.apply
data:
entity_id: "{{ adaptive }}"
lights: ["{{ L }}"]
transition: "{{ tr | float }}"
default:
- service: adaptive_lighting.set_manual_control
data:
entity_id: "{{ adaptive }}"
lights: ["{{ L }}"]
manual_control: true
- service: light.turn_on
target:
entity_id: "{{ L }}"
data:
brightness: 255
color_temp_kelvin: "{{ kelvin | int }}"
# Button 2: LONG PRESS -> toggle Full White <-> Adaptive (Light B)
- conditions: "{{ trigger_id == 'button2' and press_type == long_press_type }}"
sequence:
- variables:
L: !input light_b
adaptive: "{{ adaptive_b }}"
bri: "{{ state_attr(L, 'brightness') | int(0) }}"
ct: "{{ state_attr(L, 'color_temp_kelvin') | int(0) }}"
is_full_white_now: >
{{ is_state(L,'on')
and bri >= full_white_bri_min
and ct != 0
and ((ct - (kelvin | int)) | abs) <= full_white_kelvin_tol }}
- choose:
- conditions: "{{ is_full_white_now }}"
sequence:
- service: adaptive_lighting.set_manual_control
data:
entity_id: "{{ adaptive }}"
lights: ["{{ L }}"]
manual_control: false
- service: adaptive_lighting.apply
data:
entity_id: "{{ adaptive }}"
lights: ["{{ L }}"]
transition: "{{ tr | float }}"
default:
- service: adaptive_lighting.set_manual_control
data:
entity_id: "{{ adaptive }}"
lights: ["{{ L }}"]
manual_control: true
- service: light.turn_on
target:
entity_id: "{{ L }}"
data:
brightness: 255
color_temp_kelvin: "{{ kelvin | int }}"
What I Wanted the Remote to Do
Before writing any YAML, I wrote down the behavior I actually wanted:
- A single press should toggle a light on or off.
- A long press should toggle modes, not just adjust brightness or color:
- Adaptive Lighting ↔ full white.
- Each light should be managed independently, including its Adaptive Lighting configuration.
- Turning a light on under Adaptive Lighting should not cause visible flicker.
- The solution should work with Matter’s event model without relying on helpers or stateful hacks.
In other words: the remote should feel like a well-designed physical interface, not a demo device wired to a collection of automations.
Matter Button Events: Embrace the Model
Matter remotes don’t expose traditional “button triggers” in Home Assistant. Instead, they emit events via an event.* entity whose state changes on every interaction. The meaningful signal lives in the event_type attribute.
Rather than fighting that, the blueprint leans into it:
- Trigger on any state change of the event entity.
- Extract
event_type. - Route logic explicitly based on that value.
This approach avoids timing assumptions and keeps the automation resilient to how Matter devices actually behave.
Reducing Flicker When Using Adaptive Lighting
One of the more subtle problems with Adaptive Lighting is flicker when turning a light on. If you apply brightness and color temperature immediately, many bulbs visibly jump.
The blueprint uses a simple sequencing strategy:
- Turn the light on with no attributes.
- Optionally wait a few milliseconds.
- Apply Adaptive Lighting with a short transition.
This small change makes the interaction feel intentional rather than reactive, and it’s one of those details that matters more the longer you live with the automation.
Long Press as a Mode Toggle
The long-press behavior is intentionally binary and reversible:
- If the light is currently under Adaptive Lighting, a long press disables adaptive control and forces a known “full white” state.
- If the light is already in that full white state, a long press restores Adaptive Lighting.
There’s no cycling, no guesswork, and no hidden state. The current mode is inferred directly from the light’s attributes and the Adaptive Lighting instance that manages it.
Crucially, each light has its own Adaptive Lighting switch, which allows different schedules, delays, or behaviors per fixture. This avoids the common pitfall of tying unrelated lights to a single adaptive controller.
I’m hoping this helps anyone working through similar tradeoffs around Matter remotes, Adaptive Lighting, or Home Assistant automatons that need to stay understandable over time. I’m still actively experimenting with this setup trying different lights, rooms, and interaction patterns to see where the model holds up and where it needs refinement. One thing that noticeably sped up this work was using AI as a practical development aid: quickly pulling in context from existing documentation, cross-referencing similar blueprints, and reducing the overhead of jumping between sources while iterating. That kind of context compression and fast feedback made it easier to reason about changes, test alternatives, and converge on something cleaner without spending disproportionate time on discovery and recall.