Matter - Attribute Subscriptions

This is a bit of a "watch out" that I've come across for Matter device driver development that might be useful to you.

In implementing a Matter driver, your driver will (in almost all case), need to subscribe to device attribute changes. You should be aware that the number of subscriptions supported by a device may be very low (3 per fabric is typical). This is explained in Section 11.1.4.4 ("CapabiltiyMinimaStructType") of the Matter 1.2 Core Specification. This CapabiltiyMinimaStructType structure isn't (yet) decoded on Hubitat, so I don't think drivers can make use of it yet, but I also use HomeAssistant which does decode this and I was unable to find any device where the manufacturer supported more than 3 subscriptions (RGB bulbs, Eve devices, and a few others were checked). My guess is most device manufacturers just don't think about increasing this!

So, if you need more subscriptions than 3, what can you do ...

Fortunately, Matter does provide for a "wildcard" subscription allowing you to subscribe to things like "all endpoints, all clusters, all attribute" in a single subscription. See example below

The wildcard for endpoints is "0xFFFF", for clusters is "0xFFFF_FFFF, and for attributes is also "0xFFFF_FFFF".

    if (txtEnable) log.info "Sending command to Subscribe to all device attribute reports with a 1 second minimum report delay, refresh at least every 30 minutes."
    String cmd = 'he subscribe 0x0001 0x0700 [{"ep":"0xFFFF","cluster":"0xFFFFFFFF","attr":"0xFFFFFFFF"}]'
    sendHubCommand(new hubitat.device.HubAction(cmd, hubitat.device.Protocol.MATTER))

You an also intermix wildcards and non-wildcards to "save" on subscriptions. So, for example, if you want to subscribe to attribute 0x0000 on multiple endpoints, you could use:

    String cmd = 'he subscribe 0x0001 0x0700 [{"ep":"0xFFFF","cluster":"0xFFFFFFFF","attr":"0x00000000"}]'
    sendHubCommand(new hubitat.device.HubAction(cmd, hubitat.device.Protocol.MATTER))

Wildcards can, of course, cause an increase in the amount of data that gets reported, but many attributes are read-only so they only get reported on a refresh or when the subscription is first reported, and many change slowly. For faster changing attributes, like the on/off state, you likely want very short reporting periods (longer minimum reporting periods can delay triggering Hubitat rules or cause triggers to be missed)

2 Likes

And to further clarify, a single "subscription" can have multiple attribute paths, so something like this:

    List<Map<String, String>> attributePaths = []
    attributePaths.add(matter.attributePath(0x01, 0x0006, 0x00))
    attributePaths.add(matter.attributePath(0x01, 0x0008, 0x00))
    attributePaths.add(matter.attributePath(0x01, 0x0300, 0x00))
    attributePaths.add(matter.attributePath(0x01, 0x0300, 0x01))
    attributePaths.add(matter.attributePath(0x01, 0x0300, 0x07))
    attributePaths.add(matter.attributePath(0x01, 0x0300, 0x08))


    attributePaths.add(matter.attributePath(0x02, 0x0400, 0x0000)) //illuminance
    attributePaths.add(matter.attributePath(0x03, 0x0406, 0x0000)) //occupancy
    //standard 0 reporting interval is way too busy for bulbs
    String cmd = matter.subscribe(5,0xFFFF,attributePaths)

Counts as 1. The time when you need to be most careful is if you send separate request - for example, if you want different report timings!

1 Like

A caution must be taken when subscribing to matter attributes - not all devices support all the attributes. As an example, there are color bulbs that will not allow subscribing for the ColorCluster attribute 00 (CurrentHue) or 01 (CurrentSaturation). An attempt to subscribe for an unsupported attribute results in automatic call of the initialize() method on the next command sent to the Matter device.

Before reading or subscribing for an attribute, the driver must check whether this particular attribute number is present in the GlobalElements AttributeList (0xFFFB) for the same endpoint and cluster.

1 Like

I'm curious - which color bulbs don't support CurrentHue, CurrentSaturation. If the bulb accepts Hue / Saturation commands, these attributes are mandatory (Cluster Spec. Section 3.2.5, if FeatureMap bit 1 is on, meaning the bulb accepts Hue / Sat commands, then these are mandatory). Another way to tell which commands are accepted, and what reports are generated, is by the FeatureMap bit setting.

2 Likes

You are right - I have been checking another cluster's FeatureMap (0xFFFC), not the ColorCluster.
Thank you! : )

1 Like

I haven't documented it yet (other than by inline comments), but the library linked below may be of help to you for accessing feature Maps. In a driver's parse routine, you put the 3 lines of code shown in the comments at lines 61-63 of the library into your driver, i.e.,:

        descMap = matter.parseDescriptionAsMap(description)
        descMap.put("endpointInt", (Integer.parseInt(descMap.endpoint, 16)))
        storeRetrievedData(descMap)

and it records all attributes as they are parsed. If you do a get attributes with endpoint, cluster, and attribute wildcards at startup, you'll then have a record of every attribute for every cluster for every endpoint. When you need the featureMap, for example, you just read it from that stored data with getStoredAttributeData(ep:1, clusterInt:0x0300, attrInt:0xFFFC)

(Note the second line adds endpoint in integer form into the Map. I don't like working with Hex strings - I find they add errors and confusion, so all my libraries use integer. Bryan is going to eventually include endpointInt by default into the descMap map that is produced by matter.parseDescriptionAsMap, but for now, I add it manually).

1 Like

That's an elegant and efficient way of storing operative data! :+1:

I am currently storing this information in state variables, which has its performance penalty... : (

Have you thought of a way to persist the @Field static variable, so that it survives after HE hub reboot / power off?

I did that in my old Z-Wave drivers where I wrote everything to state as a giant block and the re-parsed it on startup so as to avoid a large number of reads. I decided I did not want to do that for Matter because with the multi-fabric / multi-controller architecture, another device on another fabric could make a change, even go so far as to update the firmware, and, therefore, invalidate information you store. I also don't like using "state" for storage of temporary information because it can make a very messy driver UI for the user.

I came up with this particular solution because, at one point in developing drivers (before atomicState was added), I was having some driver problems and I realized it was because calls to the driver that were occurring very close in time were interfering with storing correct values of state. Thus, I implemented this method which uses Java concurrent libraries so it is concurrency safe.

PS - it may also be very useful for your work on developing a Hue bridge since it allows you to capture all data coming from the bridge, giving your driver a full picture of bridge state at any point in time.

3 Likes