iOS 上的結構體 API

您可以透過 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)