您可以通过 Home APIs for iOS 访问 Structure API。
如需使用 Structure API,请先将 GoogleHomeSDK 软件包导入您的应用:
import GoogleHomeSDK
错误处理
Home API 中的某些方法会抛出 HomeError,因此我们建议您使用 do-catch 块来捕获这些调用中的 HomeError。
处理 HomeError 时,请检查其 code 和 message 字段,了解具体出错情况。
任何未处理的错误都会导致应用崩溃。
如需了解详情,请参阅错误处理。
Structure API
Home 表示 Home Graph,是 Structure API 的入口点。它提供了对结构体、房间和设备的引用。
Structure 表示 Home Graph 中的结构。它提供对结构体元数据(例如 id 和 name)的访问权限。
使用 structures() 获取您账号中的所有结构体。这些结构以 Query 的形式返回,它提供了多种使用其数据的方式:
| API | 说明 |
|---|---|
stream() |
返回一个 Publisher,它会在发生更改时单独发出每个对象。 |
batched() |
返回一个 Publisher,它以对象 Set 的形式发出当前结果。每个发出的 Set 都代表对象图的当前状态。 |
list() |
以对象 Set 的形式返回当前结果。 |
structures().list() 调用可能不会立即返回一组有效的结构。如果您的应用是响应式的,并且会调用 stream() 来订阅所有结构体更改以驱动界面,则最终应返回有效的结构体列表。在其他情况下,也可能会返回空结构列表,例如,用户的手机失去连接,或者用户撤消了对应用的权限。您应确保在应用中处理这些情况。
@Published public private(set) var structures: [Structure] = []
private var structuresCancellable: AnyCancellable?
self.structuresCancellable = home
.structures()
.batched()
.receive(on: DispatchQueue.main)
.map { Array($0) }
.catch {
// Failed to load structures
return Just([Structure]())
}
.assign(to: \.structures, on: self)
示例结构调用
获取一组结构体
对 Query<Structure> 调用 list() 会返回最新的元素 Set:
// Get a stream of all structures accessible to the user let allStructuresChanges = self.home.structures() let allStructures = try? await allStructuresChanges.list()
在设计响应式应用时,您需要使用 batched() 和 stream() 调用,而不是 list(),因为当住宅图谱发生变化时,这些调用会自动生成数据。
获取结构体属性
有了结构列表后,您就可以访问其属性:
// Get a stream of changes taking place on a structure. let structureChanges = try await home.structures().list().filter { $0.id == structureID } // Get a snapshot of the structure. let structure = structureChanges.first! // Get structure properties print("id \(structure.id) ") print("name \(structure.name) ")
按名称查找结构体
如果您知道结构的名称,也可以使用 name 属性访问该结构:
do { structure1 = try await home.structures().list().first(where: { $0.name == "Main House" }) } catch let _ as HomeError { // Code for handling the exception }
从这里,您可以访问每个结构的属性、房间和设备。
使用多个结构
如需使用多个结构,请分别获取对每个结构的引用:
var structure1: Structure! var structure2: Structure! do { structure1 = try await home.structures().list().first(where: { $0.name == "Main House" }) } catch let _ as HomeError { // Code for handling the exception } do { structure2 = try await home.structures().list().first(where: { $0.name == "Guest Cottage" }) } catch let _ as HomeError { // Code for handling the exception }
Rooms
一个房间包含一组设备。房间始终是结构的一部分,一个结构可能有多个房间。从结构中移除房间不会将该房间中的设备从结构中移除。不过,如果删除房间,该房间中的设备将变为未分配状态。
使用 Home.rooms() 检索账号中的所有房间,然后使用 roomID = device.roomID 显示每个房间中的相应设备。
self.home.rooms().batched()
.combineLatest(self.home.devices().batched())
.receive(on: DispatchQueue.main)
.catch { error in
// Failed to load rooms and devices
return Just((Set<Room>(), Set<HomeDevice>()))
}
.map { rooms, devices in
var devicesByRoom = [Room: [HomeDevice]]()
for room in rooms where room.structureID == currentStructureID {
devicesByRoom[room] = devices.filter { $0.roomID == room.id }
}
return devicesByRoom
}.assign(to: &self.$devicesByRoom)
示例 Room 调用
获取聊天室列表
使用 Home 类,您可以获取会议室列表并访问其属性:
let allRoomsChanges = self.home.rooms() let allRooms = try await allRoomsChanges.list() let room = allRooms.first! XCTAssertTrue(allRooms.contains(room)) print("id \(room.id) ") print("name \(room.name) ")
创建聊天室
如需在Structure中创建新房间,请执行以下操作:
let testName = "Test Room Name" var newRoom: Room! do { newRoom = try await structure.createRoom(name: testName) XCTAssertNotNil(newRoom) } catch let _ as HomeError { // Code for handling the exception }
删除房间
或者,您也可以删除房间:
val roomToDelete = structure.rooms().list().filter { it.name == "room_id1" }.firstOrNull() structure.deleteRoom(roomToDelete!!)
您也可以使用聊天室 ID 删除聊天室:
let roomToDelete = allRooms.first(where: { $0.id == room.id }) if let roomToDelete1 = roomToDelete { do { try await structure.deleteRoom(roomToDelete1) } catch let _ as HomeError { // Code for handling the exception } }
如果删除包含设备的房间,这些设备仍会保留在结构中,但不再分配给任何房间。
将设备移至其他房间
借助 Structure,您还可以将设备移至其他房间:
do { try await structure.move(device: light, to: room) } catch let _ as HomeError { // Code for handling the exception }
更改房间名称
调用 setName(_:) 方法以更改聊天室的名称:
let updatedRoom = try await theRoom.setName("new room name")
更改房间名称时,原始 Room 结构体保持不变,更改会反映在返回的更新后的 Room 对象中。
如果名称超过 60 个 Unicode 码位(字符)限制,则会被截断,并且不会抛出任何错误。开发者负责处理长名称,例如,可以决定是否要告知用户名称将被截断。
设备组
设备组表示单个结构中由用户定义的一组设备。群组成员资格会保留在 Google 服务中,并且对住宅的其他成员以及有权访问住宅的其他应用可见。
与实体房间一样,设备组也属于单个结构。但与房间不同,设备组是逻辑分组。一个设备可以同时属于多个设备组。设备组最多可包含 100 台设备。
设备组由DeviceGroup 实体表示,该实体不同于 HomeDevice。如需与设备组互动,请在 Home.configure 中配置 ReferencedAutomationTypes 时添加相关设备类型和特征,以及 GroupTrait、GroupManagementTrait 和 GroupMembershipTrait 特征。
您可以通过直接遍历群组中的成员设备(请参阅与群组成员设备互动)或定位到 DeviceGroup 上的设备类型部分(请参阅控制设备组)来与群组中的设备互动。
获取设备组列表
如需查询住宅中的所有设备组,请执行以下操作:
let groups = try await home.entities(DeviceGroup.self).list()
for group in groups {
let groupTrait = await group.traits.get(Google.GroupTrait.self)
let name = groupTrait?.attributes.name ?? "Device Group"
print("Found group: \(name) with ID: \(group.id)")
}
与群组成员设备互动
通过遍历群组的成员设备,您可以精细控制各个成员,在单个请求中批量处理命令,以及跨成员汇总或写入属性。
首先,通过检查每个设备的 GroupMembershipTrait 是否包含目标群组 ID,过滤住宅中的设备:
let allDevices = try await home.devices().list()
var members: [HomeDevice] = []
for device in allDevices {
if let membership = await device.traits.get(
Google.GroupMembershipTrait.self
),
membership.attributes.groupObjectIdsArray.contains(group.id) {
members.append(device)
}
}
如需发送单个批量请求,其中包含针对群组中所有兼容成员的特征的命令,请执行以下操作:
var onOffTraits: [Matter.OnOffTrait] = []
for device in members {
if let light = await device.types.get(OnOffLightDeviceType.self),
let onOff = light.matterTraits.onOffTrait {
onOffTraits.append(onOff)
}
}
let traitsToUse = onOffTraits
let responses = try await home.sendBatchedCommands { batcher in
return try traitsToUse.map { batcher.add(try $0.onBatchable()) }
}
for response in responses {
try response.getOrThrow()
}
您还可以同时更新群组中所有匹配的成员设备上的属性(例如,在所有 DimmableLightDeviceType 成员上将 onTransitionTime 设置为 1),并在截止日期前等待完成:
try await withThrowingTaskGroup(of: Void.self) { taskGroup in
taskGroup.addTask {
try await Task.sleep(for: .seconds(5))
throw CancellationError()
}
for device in members {
taskGroup.addTask {
guard let light = await device.types.get(DimmableLightDeviceType.self),
let levelControl = light.matterTraits.levelControlTrait,
levelControl.attributes.$onTransitionTime.isSupported
else { return }
_ = try await levelControl.update {
$0.setOnTransitionTime(1)
}
}
}
for _ in 0..<members.count {
try await taskGroup.next()
}
taskGroup.cancelAll()
}
如需汇总群组中各成员的属性,例如查找群组中所有色温灯的最小 colorTempPhysicalMaxMireds,请执行以下操作:
var maxMiredsValues: [UInt16] = []
for device in members {
if let light = await device.types.get(ColorTemperatureLightDeviceType.self),
let mireds = light.matterTraits.colorControlTrait?.attributes
.colorTempPhysicalMaxMireds {
maxMiredsValues.append(mireds)
}
}
let minMaxMireds = maxMiredsValues.min()
创建设备组
对结构使用 GroupManagementTrait 可创建设备组:
let groupsManagementTrait =
try await structure.traits(Google.GroupManagementTrait)
let _ = try await groupsManagementTrait.createUserDefinedGroup(
withName: "Kitchen Lights",
withDevices: [light1, light2]
)
管理群组成员资格
如需向现有群组添加成员,请执行以下操作:
let groupManagementTrait =
try await structure.traits(Google.GroupManagementTrait.self)
groupManagementTrait.addGroupMembers(
groupObjectId: group.id,
memberDeviceObjectIds = [light3.id],
)
如需从群组中移除成员,请执行以下操作:
let groupManagementTrait =
try await structure.traits(Google.GroupManagementTrait.self)
_ = try await groupManagementTrait.removeGroupMembers(
groupObjectId: group.id,
memberDeviceObjectIdsArray: Array(removedIDs)
)
更改设备组的名称
如需重命名设备组,请更新 GroupTrait 的 name 字段:
let groupTrait = group.traits.get(Google.GroupTrait.self)
_ = try await groupTrait.update { $0.name = "Movie Corner" }
删除设备组
如需从住宅中删除设备组,请执行以下操作:
let groupManagementTrait =
try await structure.traits.get(Google.GroupManagementTrait.self)
_ = try await groupManagementTrait.deleteGroup(groupObjectId: group.id)