您可以透過 Home API 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() 訂閱所有結構變更來驅動 UI,則最終應會傳回有效的結構清單。在其他情況下,系統也可能傳回空白的結構清單,例如使用者的手機失去連線,或使用者撤銷應用程式的權限。請務必在應用程式中處理這些情況。
@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(),因為 Home Graph 變更時,這些呼叫會自動產生資料。
取得結構體屬性
取得結構清單後,即可存取其屬性:
// 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 struct 會保持不變,變更會反映在傳回的更新 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)