[Bug] Wrong profile matching for Matter buttons with battery

I was playing with the IKEA DIRIGERA bridging some remotes, just for fun since I use them via Zigbee. Model doesn’t matter, happens with all remotes.

Only one of the buttons in the remotes is available since it uses the default button-battery profile instead of, let’s say, 2-button-battery, 5-button-battery, etc.

Looks like a recent bug that could affect any Matter button not in the list of manufacturer-specific fingerprints since the way the driver matches the profile is prone to this behaviour. Interestingly, if you switch to another driver and go back to the stock one, the profile is updated correctly, we’ll see why in a moment!

The problem is the battery profiling logic. The driver detects correctly the number of buttons (endpoints) but it doesn’t change the profile when it detects the number of buttons (maybe it should to prevent these issues). Instead, it sends a read request for some power attributes and only continues when it receives the response, that might never arrive…

If the response isn’t received the button is stuck with the defaut profile forever, like happens always to me when I add the device to the bridge. Forever or until you switch drivers, which calls driver_switched and match_profile and sends the read request again, but this time the response arrives and the profile is updated as expected.

2 Likes

This is hilarious.

Suggested change:

local function build_button_profile(device, main_endpoint, num_button_eps)
  local profile_name = string.gsub(num_button_eps .. "-button", "1%-", "") -- remove the "1-" in a device with 1 button ep
  if device_type_supports_button_switch_combination(device, main_endpoint) then
    profile_name = "light-level-" .. profile_name
  end

  device:try_update_metadata({profile = profile_name})

-- Why are we doing this here in the build_button_profile() function?
  local battery_supported = #device:get_endpoints(clusters.PowerSource.ID, {feature_bitmap = clusters.PowerSource.types.PowerSourceFeature.BATTERY}) > 0
  if battery_supported then -- battery profiles may be overridden later, in power_source_attribute_list_handler
    device:send(clusters.PowerSource.attributes.AttributeList:read(device))
  end
end
2 Likes

Fixing it for good is going to be tricky and probably requires a rethinking of the driver. With the incoming modular profiles it will hopefully be more manageable.

Changing the profile first and then immediately again after the read request/response could lead to other issues like the button having a wrong profile with no battery information if the response doesn’t arrive or in case a race condition is possible in try_update_metadataby calling it twice in a row.

Maybe the read request should be delayed one second or more like they do in other drivers to avoid race conditions when requesting features from a device. Delays are ugly though and the driver would still be betting everything to one response in just one try.

Modular profiles can’t come soon enough!

2 Likes

… especially when it takes a while until the new profile is finally reflected in the app.

Somewhat related: why hide this in the hub logs? It’s not like they are top secret informations and it would make it easier for us to help others.

Back on topic: try_update_metadata should be called once and only once.

2 Likes

The actual root of the issue is capabilities that ultimately makes everything more complex than it should in SmartThings :face_with_steam_from_nose: . Had to say it, they seem to be written in stone and can’t adapt when needed. I still don’t understand why there’s not a “long press release” event for buttons, or a “initial press”, maybe they cannot modify capabilities?

The driver needs to ask for the PowerSource reporting attributes because SmartThings has TWO capabilities to report battery state and needs to decide which one to use in the profile.

Why, oh why, is there abattery and a batteryLevel capability which are basically identical, with duplicated attributes, except one uses a numeric level and another a qualitative level?

It would have been so much easier to just have one with two attributes: a level and aqualitativeLevel. The app could display the information available, the percentage with a number and the normal/warning/critical with colours for instance. No profile change would be needed at all since the capability would always be the same and this post would not exist :sweat_smile: .

End of rant :rofl:

1 Like

Anyway…

Trigger warning: AI

I just gave grok the init.lua and asked it if there are any issues with the code. It explained in detail (a dozen pages) what’s wrong with the profile matching and it even mentioned the timing. Try it yourself!

Maybe AI should do the code review…

Adding a delay seems to work for the bridged buttons but, again, feels ugly and it’s not that I did extensive testing :upside_down_face:

  if battery_supported then -- battery profiles are configured later, in power_source_attribute_list_handler
      device.thread:call_with_delay(3, function(d)
        device:send(clusters.PowerSource.attributes.AttributeList:read(device))
      end)
  else

Although the problem here was not receiving the response in the driver, that could as well be an issue with the Dirigera not sending the response, the beta firmware of the hub not sending the request, etc.

1 Like

I’ve tried to replicate the issue now, but now the response is received correctly and the profile updated, at least the three times I tried.

Assuming there’s been no changes since the first post -ST hub fw is the same, driver is the same, Dirigera fw is the same- it makes the bug even more strange.

Caught it! Hub firmware is now 58.9, DIRIGERA fw is 2.815.2, and production Matter Switch driver 2025-09-10. I’ve been able to reproduce it. I sent the hub logs too @nayelyz.

This is the read request:

2025-09-16T08:43:56.954166441Z INFO Matter Switch <MatterDevice: 1e037c8e-50c2-4bce-9e98-918d4b341d03 [AEEECEB804603F1B-BD943F0B98D2B7AB-79] (Matter Button)> sending InteractionRequest: <InteractionRequest || type: READ, info_blocks: [<InteractionInfoBlock || cluster: PowerSource, attribute: AttributeList>]>

But there’s no response so it ends up with the default profile for just 1 button when it was a TRADRFI 5-button.

Even if it’s DIRIGERA’s fault not replying (I only know the driver handler isn’t called, but hopefully you can get more insights in the hub logs), the driver could be modified to be more fail-proof as discussed in previous comments, maybe retrying or at least falling back to a profile with all the buttons even if the battery information is unknown.

1 Like

I’d like to be wrong, but it seems a lot of the mucking around with profiles has keeping the mobile app happy as its goal.

Along with its belief that the online/offline status of a device is a complete absolute, an opinion seeming not shared elsewhere in the platform, the mobile app seems to place great import on attributes having been assigned a value when actually it doesn’t really matter.

So we get synthetic events being generated when a N/A would make it clear that the device hasn’t provided any data yet and something might be broken. We also get a desperate need to avoid unsupported capabilities in the profiles. Apart from making the UI look messy, should it really matter?

There really should be a fallback profile that results in a fully functioning device, even if there is extraneous stuff in it, rather than a minimal profile that results in a partially functioning device. However I get the feeling the mobile app prefers the latter.

I am sure the mobile app wasn’t always so fussy. It would let us know which attributes hadn’t been initialised but let us get on with using the device. That was far more sensible.

2 Likes
1 Like