I am working on an app for a Home automation device built on Matter. There are both standard and custom features/services/clusters on this device. HomeKit seems to pick up the standard ones except for one (leak detector) that is part of the Matter 1.3 spec. Of course HomeKit doesn't know what to do with the custom ones so they aren't shown in the Home app. I am trying to access those directly via calls to the Matter APIs so I can present them in a custom app. To access Matter APIs I am using a process suggested by DTS in a forum post that can be found here.
The MTRBaseDevice I obtain in the suggested way responds appropriately to read and write commands, but subscribe doesn't work as expected. It returns an error code 48 which is described as "The operation couldn’t be completed." and the source that contains the error calls it "operationNotSupported". This is the subscribe call referenced in the DTS response to earlier post, however there are three more "subscribe" methods and I have tried them all. Two also return an error 48, and the last one never calls any of the callback handlers providing no data or errors.
Since a subscription is supposed to return data periodically even when there have been no changes it doesn't seem that different than polling so I have written code for that and it works. However I don't expect iOS will let me poll in the background for alarm conditions and I am hoping subscription updates will be sent to a background app so it can notify users of these conditions. Is that a reasonable assumption?
Here is the code I am using to test subscribe, perhaps I am doing something wrong? Originally I had shorter intervals but changing them made no difference. Do they need to be longer?
let subscribeParams = MTRSubscribeParams(minInterval: 15, maxInterval: 900)
if #available(iOS 17.6, *) {
subscribeParams.shouldAssumeUnknownAttributesReportable = true
}
subscribeParams.shouldFilterByFabric = false
subscribeParams.shouldReplaceExistingSubscriptions = true
subscribeParams.shouldReportEventsUrgently = true
subscribeParams.shouldResubscribeAutomatically = false
print("Attempting subscribe:with")
let localClusterStateCache = MTRClusterStateCacheContainer()
matterBaseDevice.subscribe(with: OS_dispatch_queue_global.global(), params: subscribeParams, clusterStateCacheContainer: localClusterStateCache) { (attributes: [Any]) in
print("subscribe:with attributeHandler: \(attributes)")
} eventReportHandler: { (events: [Any]) in
print("subscribe:with eventHandler: \(events)")
} errorHandler: { (error: any Error) in
let reportingError = error as NSError
if reportingError.domain == "HMErrorDomain" {
let hkError = HMError(HMError.Code(rawValue: reportingError.code)!)
print("subscribe:with errorHandler: \(hkError.localizedDescription); UserInfo: \(hkError.userInfo); ErrorUserInfo: \(hkError.errorUserInfo)")
} else {
print("subscribe:with errorHandler: \(reportingError)")
}
} subscriptionEstablished: {
print("subscribe:with Subscription established!!!")
} resubscriptionScheduled: { (error: any Error, numericParam: NSNumber) in
print("subscribe:with Resubscription scheduled; error: \(error); numericParam: \(numericParam)")
}
Hi,
First off, please file a bug about the issues you're having then post the bug back here. The underlying problem here is that because this was implemented directly "on" MTRBaseDevice, we don't have any good way to clearly explaining in the documentation exactly what will/will not work.
SO, starting with the code side, here is what I believe will work, based on my review of our code:
-
"subscribeWithQueue..." is implemented, but ONLY the "subscriptionEstablishedHandler" actually works. If you've tried this already, try passing in "NULL" for everything except the "subscriptionEstablishedHandler".
-
"subscribeToAttributesWithEndpointID..." appears to be fully supported, but I haven't drilled far enough into our implementation to be entirely confident that I haven't overlooked something.
In both cases, when you're testing this makes sure that you're only testing one at at time and experiment with the configuration options (particular the handlers you're registering) before you assume it simply doesn't work.
Since a subscription is supposed to return data periodically even when there have been no changes it doesn't seem that different than polling so I have written code for that and it works. However I don't expect iOS will let me poll in the background for alarm conditions and I am hoping subscription updates will be sent to a background app so it can notify users of these conditions. Is that a reasonable assumption?
No. HomeKit was intentionally designed* as a "foreground only" API. I don't actually know how these particular APIs will behave in a backgrounded app if you happened to be awake, but the system itself isn't going to wake you up for them and nothing in HomeKit will function.
Note that this is also true of the matter usage more broadly. While an ecosystem app can use matter in the background, the system doesn't provide any background category/service that directly supports matter. That second point is critical, because the primary way iOS limits background operation is by tightly controlling when apps are awake at ALL, not by constraining there API usage. The fact that an app could use matter in the background doesn't really matter when the system doesn't provide any way to for it to be awake.
*An Extended Sidebar on HomeKit Background Access:
I've been asked about background to HomeKit, so I think it's helpful to try and explain why we've blocked this. Architecturally, the HomeKit framework was designed around the idea that apps were manipulating a shared configuration that would then "execute somewhere", NOT directly controlling accessories. Users were expected to have lots of device, any one of which could issues commands through lots of possible "routes". Within that frameworks any change happens from one of two reasons:
-
A user performed some kind of "direct" interaction that directly triggered the change.
-
The shared "HomeKit Database" included some sort of automation which was executed "auto-magically".
The concern here is an issue I like to call "The Phantom Lightbulb™". That is, a light bulb keeps turning on, you don't know "why", and now you have to fix it. In the system above, either a "user" did it (which is not an Apple problem) or "something" was configured "wrong" in your HomeKit database so an automation didn't do what you wanted. In the second case, you can open any of your controllers and edit your HomeKit database to find the problem.
This all falls apart if you allow background access to HomeKit. Background access means that ANY controller that can run apps can issue commands, so fixing the issue could (potentially) mean interacting with EVERY iOS device you own or have shared your home configuration to. Preventing all background access might seem like an "extreme" solution but, in practice, I think it was the only option that was actually workable.
As another example of these kinds of issues that come up here, it's very common that the device that's most likely to configure/interact with an accessory is also the wrong device to be monitoring it. Speaking from my own experience:
-
Every accessory in my house was setup and configured with my iPhone.
-
My iPhone is the only device that has any manufacturer specific apps installed on it.
-
Of all my devices, my iPhone is the LEAST likely to be at home... since it's in my pocket at the office.
In other words, the only device I'm likely to install a manufacturer app on is also the device that's LEAST likely to be able to communicate with hat accessory.
Just to be clear here, I don't have any particular object about non-HomeKit or vendor specific commands. They're a critical part of many great accessories. My concern here is that any design needs to account for the practical differences between features that are available through the ecosystem and features that are not.
__
Kevin Elliott
DTS Engineer, CoreOS/Hardware