在 iOS 设备上控制设备

本指南是在 iOS 上访问设备和设备元数据的后续内容,介绍了设备控制和访问的其他示例。

如需使用特定设备类型或特征(例如此处许多示例中使用的 Matter OnOffTrait),必须导入它们:

import GoogleHomeSDK
import GoogleHomeTypes

检查特征是否支持命令

使用 trait 级 supports 函数检查特定设备是否支持某个命令。

例如,如需检查设备是否支持 On/Off 特性的 toggle 命令,请运行以下命令:

// Check if the OnOff trait supports the toggle command.
if onOffTraitTest.supportsToggleCommand {
  print("onOffTrait supports toggle command")
} else {
  print("onOffTrait does not support stateful toggle command")
}

向设备发送命令

发送命令与从 trait 读取状态属性类似。如需开启或关闭设备,请使用 OnOffTrait 切换命令,该命令在 Google Home 生态系统数据模型中定义为 toggle()。此方法会将 onOff 更改为 false(如果其值为 true),或更改为 true(如果其值为 false):

// Calling a command on a trait.
do {
  try await onOffTraitTest.toggle()
} catch let _ as HomeError {
  // Code for handling the exception
}

如果检测到执行流程存在问题,命令可能会返回异常。作为开发者,您应使用 do-catch 代码块来妥善处理这些异常,并在错误可操作的情况下向用户显示详细信息。未处理的异常会停止应用运行时,并可能导致应用崩溃。

或者,使用 off() 或 on() 命令明确设置状态:

do {
  try await onOffTraitTest.off()
  try await onOffTraitTest.on()
} catch let _ as HomeError {
  // Code for handling the exception
}

发送更改状态的命令后,一旦命令完成,您就可以按照读取设备状态中所述读取状态,以便在应用中进行处理。

发送带参数的命令

某些命令可能会使用参数,例如 OnOffTrait 或 LevelControlTrait 上的参数:

offWithEffect

// Turn off the light using the DyingLight effect.
do {
  try await onOffTraitTest.offWithEffect(
    effectIdentifier: Matter.OnOffTrait.EffectIdentifierEnum.dyingLight,
    effectVariant: 0
  )
} catch let _ as HomeError {
  // Code for handling the exception
}

moveToLevel

// Change the brightness of the light to 50%
do {
  try await levelControlTraitTest.moveToLevel(
    level: UInt8(127),
    transitionTime: 0,
    optionsMask: Matter.LevelControlTrait.OptionsBitmap(),
    optionsOverride: Matter.LevelControlTrait.OptionsBitmap()
  )
} catch let _ as HomeError {
  // Code for handling the exception
}

检查特征是否支持属性

某些设备可能支持 Matter 特征,但不支持特定属性。例如,映射到 Matter 的 Cloud-to-cloud 设备可能不支持每个 Matter 属性。如需处理此类情况,请使用特征级 isSupported 属性来检查特定设备是否支持该属性。

例如,如需检查设备是否支持 On/Off 特性的 onOff 属性,请执行以下操作:

// Check if the OnOff trait supports the onOff attribute.
if onOffTrait.attributes.$onOff.isSupported {
  print("onOffTrait supports onOff state")
} else {
  print("onOffTrait is for a command only device!")
}

某些属性在 Matter 规范或 Cloud-to-cloud smart home 架构中可为 null。对于这些属性,除了 isSupported 之外,您还可以使用 isNullable 来确定属性返回的 nil 是由于设备未报告该值,还是由于属性的值实际上是 nil:

// Check if a nullable attribute is set or is not supported.
if let deviceType = await device.types.get(OnOffLightDeviceType.self) {
  if let onOffTrait = deviceType.traits[Matter.OnOffTrait.self] {
    if onOffTrait.attributes.startUpOnOff == nil {
      if onOffTrait.attributes.$startUpOnOff.isSupported {
        print(
          "onOffTrait supports startUpOnOff and it is nil. Check the spec for the contextual meaning."
        )
      } else {
        print("onOffTrait does not support startUpOnOff!")
      }
    } else {
      print(
        "onOffTrait supports startUpOnOff and it is set to \(String(describing: onOffTrait.attributes.startUpOnOff))"
      )
    }
  }
}

更新特征属性

如果您想更改给定属性的值,但 trait 的命令都无法做到这一点,则该属性可能支持显式设置其值。

属性的值是否可以更改取决于两个因素:

  • 属性是否可写?
  • 属性值是否会因发送特征命令而产生副作用?

有关特征及其属性的参考文档提供了这些信息。

因此,决定属性值如何更改的属性组合如下:

  • 只读,不受其他命令的影响。这意味着该属性的值不会发生变化。例如, currentPosition 的 SwitchTrait 属性。

  • 只读,且受其他命令影响。这意味着,更改属性值的唯一方法是发送命令。 例如, LevelControlTrait 的 currentLevel 属性 是只读的,但其值可以通过 moveToLevel 等命令进行更改。

  • 可写入,且不受其他命令影响。这意味着,您可以使用 trait 的 update 函数直接更改属性的值,但没有任何命令会影响属性的值。例如, WrongCodeEntryLimit 的 DoorLockTrait 属性。

  • 可写入,且受其他命令影响。这意味着,您可以使用 trait 的 update 函数直接更改属性的值,并且属性的值可能会因发送命令而发生变化。例如,ThermostatTrait 的 occupiedCoolingSetpoint 属性可以写入,但也可以使用 setpointRaiseLower 命令进行更新。

使用更新函数更改属性值的示例

此示例展示了如何明确设置 DoorLockTrait.wrongCodeEntryLimit 属性的值。

如需设置属性值,请调用 trait 的 update 函数,并向其传递一个用于设置新值的更新函数。最好先验证 trait 是否支持某个属性。

例如:

if doorLockTraitTest.attributes.$wrongCodeEntryLimit.isSupported {
  let _ = try await doorLockTraitTest.update {
    $0.setWrongCodeEntryLimit(3)
  }
}

控制设备组

除了通过遍历其成员设备来精细控制组中的每个成员之外,您还可以将 DeviceGroup 作为一个实体进行控制。DeviceGroup 符合 HasParts 协议,并公开组中设备支持的 DeviceType 部分:

  • 如果实例组支持,await group.part(_:) 会返回指定 DeviceType 的第一个实例,否则会返回 nil。
  • try await group.parts(_:) 会返回一个数组,其中包含与指定 DeviceType 匹配的所有顶级部分。

当您对 DeviceGroup 中的设备类型调用 trait 命令时,SDK 会将该命令中继到组中支持该功能的所有关联成员设备,无论其底层连接或传输方式(例如 Matter 或 Cloud-to-cloud)如何。

组部分特征属性和错误处理

定位到群组部分可简化您想要在群组中发送幂等命令(例如开灯或关灯,或设置亮度级别)的常见情况。在 DeviceGroup 部分使用 trait 和命令时,请注意以下行为:

  • 动态状态属性(例如 OnOffTrait 的 onOff 属性或 LevelControlTrait 的 currentLevel 属性)通常不会填充到群组部分的 trait 中,因为群组中的成员设备可能处于不同的状态,无法合并为单个值。不过,系统会填充支持的命令(通过 supports(_:) 检查,例如可调光灯上的 .moveToLevelWithOnOff)和部分功能属性(例如彩色灯上 ColorControlTrait 的 colorTempPhysicalMinMireds 和 colorTempPhysicalMaxMireds),以便您的应用可以适当地配置界面控件。
  • 并非群组成员特征的所有命令都适用于群组部分。 尤其是,返回值的命令不可用。
  • 对群组部分执行的命令不会返回每个底层成员设备命令的单独结果。如需检查单个命令结果或调用返回值的命令,请直接遍历群组的成员设备。
  • 发送到 DeviceGroup 上的某个部分的命令会在跨成员设备的整个扇出完成后完成,并且系统会收集所有底层命令的成功或失败情况。只有在命令针对每个关联的成员设备都失败或所有目标设备都不可用时,命令才会失败(抛出 HomeError),并显示为与单命令调用语法匹配的单命令失败。如果至少有一个成员设备执行了该命令,则组命令会完成,而不会抛出错误。

将命令发送至群组部分

如需开启设备组中的所有 OnOffLightDeviceType 灯,请执行以下操作:

if let lightPart = await group.part(OnOffLightDeviceType.self),
   let onOffTrait = lightPart.matterTraits.onOffTrait,
   onOffTrait.supports(.on) {
  try await onOffTrait.on()
}

您还可以检查多种相关设备类型(例如,关闭群组中支持 OnOffTrait 的任何灯具设备类型):

func onOffTrait(in group: DeviceGroup) async -> Matter.OnOffTrait? {
  if let light = await group.part(OnOffLightDeviceType.self) {
    return light.matterTraits.onOffTrait
  }
  if let light = await group.part(DimmableLightDeviceType.self) {
    return light.matterTraits.onOffTrait
  }
  if let light = await group.part(ColorTemperatureLightDeviceType.self) {
    return light.matterTraits.onOffTrait
  }
  if let light = await group.part(ExtendedColorLightDeviceType.self) {
    return light.matterTraits.onOffTrait
  }
  return nil
}

if let trait = await onOffTrait(in: group) {
  try await trait.off()
}

您可以在发送命令之前检查命令支持情况或读取群组部件的功能边界(例如,检查可调光灯部件上的 .moveToLevelWithOnOff,或读取色温灯部件上的 colorTempPhysicalMinMireds 和 colorTempPhysicalMaxMireds):

if let dimmableLight = await group.part(DimmableLightDeviceType.self),
   let levelTrait = dimmableLight.matterTraits.levelControlTrait,
   levelTrait.supports(.moveToLevelWithOnOff) {
  try await levelTrait.moveToLevelWithOnOff(
    level: 127,
    transitionTime: nil,
    optionsMask: [],
    optionsOverride: []
  )
}

if let colorLight = await group.part(ColorTemperatureLightDeviceType.self),
   let colorTrait = colorLight.matterTraits.colorControlTrait,
   let minMireds = colorTrait.attributes.colorTempPhysicalMinMireds,
   let maxMireds = colorTrait.attributes.colorTempPhysicalMaxMireds {
  let midMireds = UInt16((UInt32(minMireds) + UInt32(maxMireds)) / 2)
  try await colorTrait.moveToColorTemperature(
    colorTemperatureMireds: midMireds,
    transitionTime: 0,
    optionsMask: [],
    optionsOverride: []
  )
}