访问 iOS 设备和设备元数据

您可以通过 Home API for iOS 访问设备 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",

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

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

输入这些字符串字段时,请使用您的 Matter 供应商和产品 ID(如有)。如果您不是 Alliance 成员,并且未被分配这些 ID,则可以将 matterOriginalVendorId 和 matterOriginalProductId 字段留空,并提供 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 中的设备和 trait 具有关联的元数据,这些元数据有助于管理应用中的用户体验。

Home API 中的每个 trait 都包含一个 sourceConnectivity 属性,其中包含有关 trait 的在线状态和位置(本地或远程路由)的信息。

获取设备的主要类型

某些设备可能会通过 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 状态:某些 trait 基于云,而其他 trait 则是本地的。

更改设备名称

调用 setName(_:) 方法以更改设备名称:

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

更改设备名称时,原始 HomeDevice 结构体保持不变,更改会反映在返回的更新后的 HomeDevice 对象中。

如果名称超过 60 个 Unicode 码位(字符)限制,则会被截断,并且不会抛出任何错误。开发者负责处理长名称,例如,可以决定是否要告知用户名称将被截断。

查看设备的群组成员资格

一部实体设备可以属于一个或多个设备组。如需检查设备所属的群组,请检查设备上的 GroupMembershipTrait:

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

如需检查 DeviceGroup 支持的设备类型,并以单个实体形式向该组发送命令,请参阅控制设备组。