Automating the IKEA BILRESA Matter Remote

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:

https://github.com/Amantux/HomeAssistantBluePrints/blob/main/IKEA%20BILRESA%20(Matter)%20Dual%20Button%20%E2%86%92%202%20Lights%20(Adaptive)%20+%20Long-Press%20Full%20White%20Toggle

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:

  1. Turn the light on with no attributes.
  2. Optionally wait a few milliseconds.
  3. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *