存取 iOS 裝置和裝置中繼資料

您可以透過 iOS 版的 Home API 存取裝置 API。將下列套件匯入應用程式:

import GoogleHomeSDK
import GoogleHomeTypes

詳情請參閱「iOS 資料模型」。

處理錯誤

Home API 中的部分方法會擲回 HomeError,因此建議您使用 do-catch 區塊,在這些呼叫中擷取 HomeError。

處理 HomeError 時,請檢查 code 和 message 欄位,瞭解發生了什麼錯誤。

任何未處理的錯誤都會導致應用程式當機。

詳情請參閱「錯誤處理」。

如需範例,請參閱「將指令傳送至裝置」。

範例呼叫

取得裝置清單

取得 Home 物件的參照後,請叫用 devices() 取得可存取裝置的 Query。呼叫 Query 的 batched() 方法,這個方法會在每次裝置中繼資料變更時,發出反映住家目前狀態的 Set。或者呼叫 Query.list() 取得可用裝置的快照。這是便利方法,可訂閱 batched() 串流並傳回第一個發出的值。Query.stream() 會產生串流,在裝置中繼資料 (例如名稱、房間或結構) 變更時發出新值。在內部,這會使用 batched(),且只會發出變更的屬性。

// Get a list of all devices accessible to the user
let homeDevices = try await self.home.devices().list()

接著,你就能存取各裝置的狀態,並向裝置傳送指令。

在 Home API 1.8 版中,您可以將 devices() 方法的 enableMultipartDevices 參數設為 true,讓 API 將每個多部分裝置視為單一裝置。詳情請參閱「iOS 上的多部分裝置」。

取得裝置類型

如要取得與裝置相關聯的裝置類型,請讀取裝置的 types 屬性,該屬性會傳回 DeviceTypeController。

呼叫 DeviceTypeController.subscribe(_:),訂閱特定裝置類型的更新:

let devices = try await self.home.devices().list()
if let device = devices.first(where: { $0.id == myDeviceId }) {
  var receivedUpdate1 = false
  var receivedUpdate2 = false
  device.types.subscribe(OnOffLightDeviceType.self)
    .assertNoFailure()
    .sink { device in
      if !receivedUpdate1 {
        receivedUpdate1 = true
        Task {
          try await device.matterTraits.onOffTrait?.on()
        }
        return
      }
      if !receivedUpdate2 {
        receivedUpdate2 = true
        return
      }
      fatalError("Received unexpected update")
    }
}

如果裝置不支援指定的裝置類型,系統會傳回立即完成的 Empty Publisher。

如果裝置支援特定裝置類型,您可以呼叫 get() 取得該類型的控制代碼:

if let device = devices.first(where: { $0.id == myDeviceId }) {
  let _ = await device.types.get(OnOffLightDeviceType.self)
}

如果裝置不支援指定的類型,則會傳回 nil。

呼叫 DeviceTypeController.subscribeAll() 即可取得 DeviceTypeCollection 的 Publisher。這個類別可讓您檢查裝置是否具有特定裝置類型:

if let device = devices.first(where: { $0.id == myDeviceId }) {
  device.types.subscribeAll()
    .assertNoFailure()
    .sink { types in
      let lightDeviceType = types[OnOffLightDeviceType.self]
      let fanDeviceType = types[FanDeviceType.self]
    }
}

取得裝置類型特徵

裝置類型是讀取特徵的進入點,因為裝置類型會將裝置分解為功能片段 (例如 Matter 中的端點)。

如果裝置有兩種裝置類型,且這兩種裝置類型可能具有相同特徵,這些特徵也會計入。舉例來說,如果裝置同時是音箱和可調光燈具,就會有兩個開/關和兩個亮度控制特徵。

如果裝置有兩個名稱相同的特徵,也可能發生特徵衝突。舉例來說,onOff 可能參照標準 OnOff 特徵的例項,也可能參照製造商定義的 OnOff 特徵例項。為避免可能出現的特徵不明確問題,請透過各裝置類型上的其中一個特徵集合參照特徵。

如為標準特徵 (即與 Matter 標準叢集類似的特徵),請使用 matterTraits。舉例來說,如要取得可調光燈具裝置類型的特定特徵:

if let dimmableLightDeviceType =
  await device.types.get(DimmableLightDeviceType.self)
{
  // Accessing standard trait on the type.
  let levelControlTrait =
    dimmableLightDeviceType.matterTraits.levelControlTrait.self
}

如要使用 Google 特徵,請使用 googleTraits:

if let doorbellDeviceType = await device.types.get(GoogleDoorbellDeviceType.self) {
  // Accessing Google trait on the type.
  let doorbellPressTrait =
    doorbellDeviceType.traits[Google.DoorbellPressTrait.self]
}

如要存取特定製造商的特徵,請透過 traits 屬性參照,但請先加上製造商的套件名稱:

let deviceType = await device1?.types.get(OnOffLightDeviceType.self)
// Accessing custom trait on the type.
if let spinnerTrait = deviceType?.traits[ExampleOrganization.SpinnerTrait.self] {
  let rpmVal = spinnerTrait.attributes.rpm
}

讀取裝置狀態

請參閱以下範例,瞭解如何從裝置的開/關特徵檢查 OnOff 屬性:

let lightDevices = devices.filter {
  $0.types.contains(OnOffLightDeviceType.self)
}
let light1 = lightDevices.first
let lightDeviceTypeOptional = await light1?.types.get(OnOffLightDeviceType.self)
if let onOffTrait = lightDeviceTypeOptional?.matterTraits.onOffTrait {
  let onOffVal = onOffTrait.attributes.onOff
}

取得具有特定特徵的裝置清單

如要取得具有特定特徵的裝置清單,您必須疊代裝置、每個裝置的裝置類型,以及每個裝置類型的特徵。舉例來說,如要取得住家中所有具有「開/關」特徵的裝置清單:

// Get all light devices that support levelControl
var levelControlDevices: [HomeDevice] = []
let allDevices = try await home.devices().list()
for device in allDevices {
  if let deviceType = await device.types.get(OnOffLightDeviceType.self) {
    if deviceType.traits.contains(Matter.LevelControlTrait.self) {
      levelControlDevices.append(device)
    }
  }
}

如需 Home API 提供的完整特徵清單,請參閱「iOS 上的特徵索引」。

取得裝置類型相似的裝置清單

如要取得代表住家所有燈具的裝置清單,請按照下列步驟操作:

// Get a list of devices with similar device types (lights)
let lightDevices =
  try await self.home.devices().list().compactMap {
    $0.types.contains(DimmableLightDeviceType.self)
      || $0.types.contains(OnOffLightDeviceType.self)
      || $0.types.contains(ColorTemperatureLightDeviceType.self)
      || $0.types.contains(ExtendedColorLightDeviceType.self)
  }

Home API 中有多種裝置類型,可代表核心裝置類型。舉例來說,沒有「燈」裝置類型。而是有四種不同的裝置類型可以代表燈具,如上例所示。因此,如要全面瞭解住家中較高層級的裝置類型,必須納入多種裝置類型。

如需 Home API 支援的裝置類型和特徵完整清單,請參閱「iOS 支援的裝置類型」。

取得裝置的供應商名稱、供應商 ID 或產品 ID

BasicInformationTrait 特徵包含裝置的供應商 ID、產品 ID、產品名稱和序號等資訊:

guard
  let vendorName =
    basicInfoTrait.attributes.vendorName
else {
  fatalError("Failed to get vendorName")
}
guard
  let vendorID =
    basicInfoTrait.attributes.vendorID
else {
  fatalError("Failed to get vendorID")
}
guard
  let productID =
    basicInfoTrait.attributes.productID
else {
  fatalError("Failed to get productID")
}

裝置製造商的雲端對雲端裝置識別

如果您是裝置製造商,並建構 Cloud-to-cloud 裝置,為了透過 BasicInformation 特徵識別 Cloud-to-cloud 裝置,可以在 SYNC 回應中加入下列字串欄位:

  • Connectivity Standards Alliance (Alliance)核發的供應商 ID: "matterOriginalVendorId": "0xfff1",

  • 可專屬識別供應商產品的產品 ID: "matterOriginalProductId": "0x1234",

  • 裝置的專屬 ID,建構方式因製造商而異: "matterUniqueId": "matter-device-id",

輸入這些字串欄位時,請使用Matter 供應商和產品 ID (如有)。如果你不是Alliance成員,且未獲派這些 ID,可以將 matterOriginalVendorId 和 matterOriginalProductId 欄位留空,並提供 matterUniqueId 做為 ID。

以下範例 SYNC 回應顯示這些欄位的使用方式:

{
  "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf",
  "payload": {
    "agentUserId": "1836.15267389",
    "devices": [
      {
        "id": "456",
        "type": "action.devices.types.LIGHT",
        "traits": [
          "action.devices.traits.OnOff",
          "action.devices.traits.Brightness",
          "action.devices.traits.ColorSetting",
        ],
        "willReportState": true,
        "deviceInfo": { ... },
        "matterOriginalVendorId": "0xfff1",
        "matterOriginalProductId": "0x1234",
        "matterUniqueId": "matter-device-id",
        "otherDeviceIds": [
          {
            "deviceId": "local-device-id",
          }
        ]
      }
    ]
  }
}

詳情請參閱Cloud-to-cloud SYNC 說明文件。

裝置和特徵中繼資料

Google Home API 中的裝置和特徵會與中繼資料建立關聯,有助於管理應用程式中的使用者體驗。

Google Home API 中的每個特徵都包含 sourceConnectivity 屬性,其中含有特徵的線上狀態和位置資訊 (本機或遠端路徑)。

取得裝置的主要類型

部分裝置可能會透過 Home API 呈現多種裝置類型。 為確保使用者在應用程式中看到裝置的適當選項 (例如裝置控制和建議的自動化動作),請檢查裝置類型是否為主要類型。

if let deviceType =
  await device?.types.get(HumiditySensorDeviceType.self)
{
  if deviceType.metadata.isPrimaryType {
    print("Humidity Sensor is the primary type on this device.")
  } else {
    print("Humidity Sensor isn't the primary type on this device.")
  }
}

檢查特徵是否已上線

請讀取 connectivityState 屬性,檢查特徵的連線狀態:

let levelControlConnectivity =
  levelControlTrait.metadata.sourceConnectivity
  .connectivityState

如果裝置沒有網路連線,部分特徵 (通常是 Google smart home 特徵) 可能會顯示為離線。這是因為這些特徵是以雲端為基礎,沒有本機路徑。

檢查裝置的連線狀態

裝置的連線狀態實際上是在裝置類型層級檢查,因為部分裝置支援多種裝置類型。傳回的狀態是該裝置上所有特徵的連線狀態組合。

let lightConnectivity =
  dimmableLightDeviceType.metadata.sourceConnectivity
  .connectivityState

如果沒有網際網路連線,混合裝置類型可能會處於 partiallyOnline 狀態。Matter標準特徵可能仍會透過本機路徑保持連線,但雲端特徵會離線。

取得裝置的 IP 位址

如要找出裝置的 IP 位址,請使用 GeneralDiagnosticsTrait 的 networkInterfaces 屬性。位址會以 Data 物件的形式傳回,您可以使用 Network 架構將位址格式設為標準 IPv4 或 IPv6 字串:

func getIpAddresses(trait: Matter.GeneralDiagnosticsTrait) -> [String] {
  let interfaces = trait.attributes.networkInterfaces ?? []
  var ipAddresses: [String] = []

  for interface in interfaces {
    for data in interface.iPv4Addresses {
      if let ipv4 = IPv4Address(data) {
        ipAddresses.append(String(describing: ipv4))
      }
    }
    for data in interface.iPv6Addresses {
      if let ipv6 = IPv6Address(data) {
        ipAddresses.append(String(describing: ipv6))
      }
    }
  }

  return ipAddresses
}

檢查特徵的網路路徑

特徵的地域性也可在 Home API 中使用。dataSourceLocality 表示特徵是透過雲端遠端傳送、透過本機中樞裝置在本機傳送,還是透過對等互連 (直接從裝置傳送至裝置,不透過中樞裝置) 傳送。

舉例來說,應用程式啟動時可能還未連上中樞或伺服器,因此無法取得裝置連線資訊,這時就會出現不明的地區值 unspecified。這些裝置無法連線,因此會導致指令或事件的互動要求失敗。用戶端可自行決定如何處理這類裝置。

let levelControlLocality =
  levelControlTrait.metadata.sourceConnectivity
  .dataSourceLocality

檢查裝置的網路路徑

與連線能力一樣,區域性是在裝置類型層級進行檢查。傳回的狀態是該裝置上所有特徵的地區設定組合。

let lightLocality =
  dimmableLightDeviceType.metadata.sourceConnectivity.dataSourceLocality

在類似 partiallyOnline 連線狀態的情況下,可能會出現 mixed 狀態:某些特徵以雲端為基礎,其他特徵則為本機特徵。

變更裝置名稱

呼叫 setName(_:) 方法,變更裝置名稱:

let updatedDevice = try await theDevice.setName("new device name")

變更裝置名稱時,原始 HomeDevice 結構體會保持不變,變更會反映在傳回的更新 HomeDevice 物件中。

如果名稱超過 60 個 Unicode 碼點 (字元) 的限制,系統會截斷名稱,但不會擲回任何錯誤。開發人員有責任處理長名稱,例如決定是否要通知使用者名稱會遭到截斷。

檢查裝置的群組成員資格

實體裝置可屬於一或多個裝置群組。如要檢查裝置所屬的群組,請查看裝置上的 GroupMembership 特徵:

let trait = try await device.traits.get(Google.GroupMembershipTrait.self)
for groupId in trait.groupObjectIDs {
  print("Device belongs to group: \(groupId)")
}