Some Matter devices are made up of multiple endpoints with the same device type. Other Matter devices are hierarchical, with endpoints nested within other endpoints. In the Home APIs, both kinds of devices are referred to as multipart devices.
Flat representation
Prior to release 1.8 of the Home APIs, multipart devices are represented by the Home APIs as a set of separate, unrelated devices. This is referred to as the flat representation.
For example, a single wall panel device with four switches appears in the Home APIs as four distinct and unrelated devices. And a hierarchical Matter device such as a refrigerator may be represented in the Home APIs as a set of devices, each corresponding to one of the endpoints.
Flat representation of a 4-switch wall panel.
Flat representation of a refrigerator
Multipart representation
Starting with release 1.8 of the Home APIs, a multipart device can be
represented in the API as a single device. To enable this behavior, call the
devices() method on
Structure, Room, or HomeManager
and set the enableMultipartDevices parameter to true:
let devices = try await self.home.devices(enableMultipartDevices: true).list() let device = try XCTUnwrap(devices.first { $0.id == powerstripID }) let outlets = try await device.types.getAll(of: OnOffPluginUnitDeviceType.self)
The following diagrams illustrate how the enableMultipartDevices option
affects the representation of a multipart device in the Home APIs:
Multipart representation of a wall panel.
Multipart representation of a refrigerator.
You always have the option to get the flat representation by either omitting
the enableMultipartDevices parameter, or by setting it to false.
Navigate a multipart device
Within a multipart device, each component instance of a device type is called a part.
Parts may be accessed directly on either the parent device or a part in a
hierarchical manner, using device types or Matter semantic
tags. Semantic tags are implemented in the Home APIs with
DescriptorTrait.SemanticTagStruct.
The DeviceType abstract class
implements the
HasParts interface,
allowing developers to navigate the device tree through the
parts property and
part()
method. Each part instance also implements the HasParts interface, so that
calling parts() on a part produces a list of zero or more sub-parts.
The following example shows how to access the parts of the multi-switch device:
val device = homeManager .devices(enableMultipartDevices = true) .itemFlow(Id(MULTI_SWITCH_DEVICE)) .first() // Here at top-level, we are using the homeDevice.parts() API to access flow of // all the switches. Then we get the part ids. val partIds = device .parts() .map { parts -> parts.filter { it.has(Switch) }.mapNotNull { it.metadata.partId } } .first() .toSet()
The next example shows how to access the parts of a refrigerator device:
val rootDevice = homeManager.devices(true).itemFlow(Id("device@uuid1")) // On the top level, HomeDevice provides both plural (parts) // and singular (part) APIs. // The parts() API returns all the parts accessible from the top level, // including Endpoint 0 and its children. val childParts = rootDevice.parts().first() // childParts contain (EP0 as RootNode, EP1 as Refrigerator) // The singular part() API accepts DeviceType and tags (optional). val refrigerator = rootDevice.part(Refrigerator).first() // Get the refrigerator device which in this case is just device@uuid1 val refrigeratorDevice = homeManager.devices(false).itemFlow(refrigerator.metadata.partId.deviceId) // DeviceType uses a synchronous API for providing access to parts val cabinets = refrigerator.parts // [EP2, EP3] // Get the HomeDevice for these cabinets (device@uuid2 and device@uuid3) val cabinetDeviceIds = cabinets.map { it.metadata.partId } // Now use the devices API with enableMultipartDevices = false. val cabinetDevices = homeManager.devices(false) .map { devices -> devices.filter { it.id in cabinetDeviceIds } }.first()