访问 iOS 设备和设备元数据

您可以通过适用于 iOS 的 Home API 访问设备 API。 将以下软件包导入您的应用:

import GoogleHomeSDK
import GoogleHomeTypes

如需了解详情,请参阅 iOS 上的数据模型

错误处理

Home API 中的某些方法会抛出 HomeError,因此我们建议您使用 do-catch 块来捕获这些调用中的 HomeError

处理 HomeError 时,请检查其 codemessage 字段,了解具体出错情况。

任何未处理的错误都会导致应用崩溃。

如需了解详情,请参阅 错误处理

如需查看示例,请参阅向设备发送命令

示例调用

获取设备列表

通过对 Home 对象的引用,调用 devices() 以获取可访问 设备的 Query。调用 Query's batched() 方法,该方法会在每次 设备元数据更改时发出一个反映 Home 当前状态的 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 版,您可以选择将 API 设置为将每个多部分设备表示为单个设备,方法是将 devices() 方法的 enableMultipartDevices 参数设置为 true。如需了解详情,请参阅 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() 以获取 PublisherDeviceTypeCollection。 借助此类,您可以检查设备是否具有特定设备类型:

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 中的端点)。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 设备, 为了通过 Cloud-to-cloud 设备标识您的 BasicInformation 特征,您可以在 其 SYNC 响应中添加以下字符串字段:

  • Connectivity Standards Alliance (Alliance) 发布的供应商 ID: "matterOriginalVendorId": "0xfff1",

  • 用于唯一标识供应商产品的商品标识符: "matterOriginalProductId": "0x1234",

  • 设备的唯一标识符,以 制造商特定的方式构建: "matterUniqueId": "matter-device-id",

输入这些字符串字段时,请使用您的 Matter 供应商 ID 和商品 ID(如果有)。如果您不是 Alliance成员且未获分配这些 ID,则可以 将 matterOriginalVendorIdmatterOriginalProductId 字段留空 并提供 matterUniqueId 作为标识符。

示例 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 文档

设备和特征元数据

Home API 中的设备和特征具有与其关联的元数据,这些元数据有助于管理应用中的用户体验。

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 地址,请使用 networkInterfaces 属性 的 GeneralDiagnosticsTrait。 地址以 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 指示特征是远程路由(通过云)、本地路由(通过本地 hub)还是对等路由(直接从设备到设备,没有 hub)。

未知本地性值 unspecified 是可能的,例如,当应用启动且尚未连接到 hub 或服务器以进行设备连接时。这些设备无法访问,并且来自命令或事件的互动请求将失败。客户端可以自行决定如何处理此类设备。

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 码位(字符)的限制,系统会截断名称,并且不会抛出任何错误。开发者负责处理长名称,例如,可以决定是否告知用户名称将被截断。