在 Android 设备上控制设备

检查特性是否支持命令

您还可以检查特性命令是否受支持。此外,您还可以使用特性级 supports 函数检查特定设备是否支持命令。

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

// Check if the OnOff trait supports the toggle command.
if (onOffTrait.supports(OnOff.Command.Toggle)) {
  println("onOffTrait supports toggle command")
} else {
  println("onOffTrait does not support stateful toggle command")
}

向设备发送命令

发送命令类似于从特性读取状态属性。如需开启或关闭设备,请使用 OnOff 特性的 Toggle 命令,该命令在 Google Home 生态系统数据 模型中定义为 toggle()。如果 onOfftrue,此方法会将其更改为 false;如果 onOfffalse,此方法会将其更改为 true

// Calling a command on a trait.
try {
  onOffTrait.toggle()
} catch (e: HomeException) {
  // Code for handling the exception
}

所有特性命令都是 suspend 函数,并且仅在 API 返回响应 (例如确认设备状态已更改)时才会完成。 如果执行流程中检测到问题,命令可能会返回异常。作为开发者,您应使用 try-catch 块来正确处理这些异常,并在错误可操作的情况下向用户显示详细信息。未处理的异常会停止应用运行时,并可能导致应用崩溃。

或者,您也可以使用 off()on() 命令来明确设置状态:

onOffTrait.off()
onOffTrait.on()

发送用于更改状态的命令后,您可以在命令完成后读取状态,如读取设备状态中所述,以便在应用中处理该状态。或者,您也可以使用流,如观察状态中所述,这是首选方法。

发送带参数的命令

某些命令可能会使用参数,例如 OnOffLevelControl 特性中的参数:

offWithEffect

// Turn off the light using the DyingLight effect.
onOffTrait.offWithEffect(
  effectIdentifier = OnOffTrait.EffectIdentifierEnum.DyingLight,
  effectVariant = 0u,
)

moveToLevel

// Change the brightness of the light to 50%
levelControlTrait.moveToLevel(
  level = 127u.toUByte(),
  transitionTime = null,
  optionsMask = LevelControlTrait.OptionsBitmap(),
  optionsOverride = LevelControlTrait.OptionsBitmap(),
)

某些命令具有可选实参,这些实参位于必需实参之后。

例如,step 命令(适用于 FanControl 特性 )具有两个可选实参:

val fanControlTraitFlow: Flow<FanControl?> =
  device.type(FanDevice).map { it.standardTraits.fanControl }.distinctUntilChanged()

val fanControl = fanControlTraitFlow.firstOrNull()

// Calling a command with optional parameters not set.
fanControl?.step(direction = FanControlTrait.StepDirectionEnum.Increase)

// Calling a command with optional parameters.
fanControl?.step(direction = FanControlTrait.StepDirectionEnum.Increase) { wrap = true }

检查特性是否支持属性

某些设备可能支持 Matter 特性,但不支持 特定属性。例如,映射到 MatterCloud-to-cloud 设备可能不支持每个 Matter 属性。如需处理此类情况,请使用特性级 supports 函数和特性的 Attribute 枚举来检查特定设备是否支持属性。

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

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

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

// Check if a nullable attribute is set or is not supported.
if (onOffTrait.supports(OnOff.Attribute.startUpOnOff)) {
  // The device supports startupOnOff, it is safe to expect this value in the trait.
  if (OnOff.Attribute.startUpOnOff.isNullable && onOffTrait.startUpOnOff == null) {
    // This value is nullable and set to null. Check the specification as to
    // what null in this case means
    println("onOffTrait supports startUpOnOff and it is null")
  } else {
    // This value is nullable and set to a value.
    println("onOffTrait supports startUpOnOff and it is set to ${onOffTrait.startUpOnOff}")
  }
} else {
  println("onOffTrait does not support startUpOnOff!")
}

更新特性属性

如果您想更改给定属性的值,但特性的任何命令都无法实现此目的,则该属性可能支持明确设置其值。

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

  • 该属性是否可写?
  • 发送特性命令后,属性的值是否会作为副作用而更改?

特性及其属性的参考文档提供了此信息。

因此,决定属性值更改方式的属性组合包括:

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

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

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

例如:

    val doorLockDevice = home.devices().list().first { device -> device.has(DoorLock) }

    val traitFlow: Flow<DoorLock?> =
      doorLockDevice.type(DoorLockDevice).map { it.standardTraits.doorLock }.distinctUntilChanged()

    val doorLockTrait: DoorLock = traitFlow.first()!!

    if (doorLockTrait.supports(DoorLock.Attribute.wrongCodeEntryLimit)) {
      val unused = doorLockTrait.update { setWrongCodeEntryLimit(3u) }
    }

一次发送多个命令

Batching API 允许客户端在单个载荷中发送多个 Home API 设备命令。这些命令会批处理到单个载荷中并 并行执行,类似于使用 Home API 自动化操作的方式,例如使用 并行节点(例如 日出前打开百叶窗 示例)。不过,Batching API 允许实现比 Automation API 更复杂、更精细的行为,例如能够根据任何条件在运行时动态选择设备。

一个批次中的命令可以定位多个设备、多个房间、多个结构中的多个特性。

以批处理方式发送命令可让设备同时执行操作,这在单独的请求中按顺序发送命令时是不可能实现的。使用批处理命令实现的行为可让开发者将一组设备的状态设置为与预定的聚合状态相匹配。

使用 Batching API

通过 Batching API 调用命令涉及三个基本步骤:

  1. 调用 Home.sendBatchedCommands() 方法。
  2. sendBatchedCommands() 块的正文内,指定要包含在批处理中的命令。
  3. 检查发送的命令的结果,以查看它们是成功还是失败。

调用 sendBatchedCommands() 方法

调用 Home.sendBatchedCommands() 方法。在后台,此方法会在特殊的批处理上下文中设置 lambda 表达式。

home.sendBatchedCommands() {

指定批处理命令

sendBatchedCommands() 块的正文内,填充可批处理的命令。 可批处理的命令是现有设备 API 命令的“影子”版本,可以在批处理上下文中使用,并且名称中添加了后缀 Batchable例如, LevelControl 特性's moveToLevel() 命令有一个名为 moveToLevelBatchable()的对应命令。

示例:

  val response1 = add(command1)

  val response2 = add(command2)

将所有命令添加到 批处理上下文且执行离开该上下文后,系统会自动发送批处理。

响应会捕获到 DeferredResponse<T> 对象中。

DeferredResponse<T> 实例可以收集到任何类型的对象中,例如 Collection 或您定义的数据类。无论您选择哪种类型的对象来组装响应, sendBatchedCommands() 都会返回该类型的对象。例如,批处理上下文可以在 Pair 中返回两个 DeferredResponse 实例:

  val (response1, response2) = homeClient.sendBatchedComamnds {
    val response1 = add(someCommandBatched(...))
    val response2 = add(someOtherCommandBatched(...))
    Pair(response1, response2)
  }

或者,批处理上下文可以在自定义数据类中返回 DeferredResponse 实例:

  // Custom data class
  data class SpecialResponseHolder(
    val response1: DeferredResponse<String>,
    val response2: DeferredResponse<Int>,
    val other: OtherResponses
  )
  data class OtherResponses(...)

检查每个响应

sendBatchedCommands() 块之外,检查响应以确定相应命令是成功还是失败。这是通过调用 DeferredResponse.getOrThrow()来完成的,该方法要么: - 返回执行的命令的结果, - 要么在批处理范围尚未完成或命令不 成功时抛出错误。

您应该仅在 sendBatchedCommands() lambda 范围之外检查结果。

示例

假设您想构建一个应用,该应用使用 Batching API 设置“晚安”场景,以便在每个人都睡觉时为家中的所有设备配置夜间模式。此应用应关闭灯并锁定前门和后门。

以下是一种解决此任务的方法:

val lightDevices: List<OnOffLightDevice>
val doorlockDevices: List<DoorLockDevice>

// Send all the commands
val responses: List<DeferredResponse<Unit>> = home.sendBatchedCommands {
  // For each light device, send a Batchable command to turn it on
  val lightResponses: List<DeferredResponse<Unit>> = lightDevices.map { lightDevice ->
    add(lightDevice.standardTraits.onOff.onBatchable())
  }

  // For each doorlock device, send a Batchable command to lock it
  val doorLockResponse: List<DeferredResponse<Unit>> = doorlockDevices.map { doorlockDevice ->
    add(doorlockDevice.standardTraits.doorLock.lockDoorBatchable())
  }

  lightResponses + doorLockResponses
}

// Check that all responses were successful
for (response in responses) {
  response.getOrThrow()
}