Cómo controlar dispositivos en Android

Cómo verificar si un rasgo admite un comando

También se puede verificar la compatibilidad de un comando de rasgo. Además, usa la función supports a nivel del rasgo para verificar si un comando es compatible con un dispositivo en particular.

Por ejemplo, para verificar la compatibilidad de un dispositivo con el rasgo On/Off, haz lo siguiente: toggle comando:

// 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")
}

Enviar un comando a un dispositivo

Enviar un comando es similar a leer un atributo de estado de un rasgo. Para encender o apagar el dispositivo, usa el OnOff comando Toggle del rasgo, que se define en el modelo de datos del ecosistema de Google Home como toggle(). Este método cambia onOff a false si es true, o a true si es false:

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

Todos los comandos de rasgo son funciones suspend y solo se completan cuando la API muestra una respuesta (por ejemplo, para confirmar que cambió el estado del dispositivo). Los comandos pueden mostrar una excepción si se detecta un problema con el flujo de ejecución. Como desarrollador, debes usar un bloque try-catch para controlar correctamente estas excepciones y mostrar información detallada a los usuarios en los casos en los que los errores sean procesables. Las excepciones no controladas detendrán el tiempo de ejecución de la app y pueden provocar fallas en ella.

Como alternativa, usa los comandos off() o on() para establecer el estado de forma explícita:

onOffTrait.off()
onOffTrait.on()

Después de enviar un comando para cambiar el estado, una vez que se complete, puedes leer el estado como se describe en Leer el estado de un dispositivo para controlarlo en tu app. Como alternativa, usa los flujos como se describe en Observar el estado, que es el método preferido.

Enviar un comando con parámetros

Algunos comandos pueden usar parámetros, como los de los OnOff o LevelControl rasgos:

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(),
)

Algunos comandos tienen argumentos opcionales, que aparecen después de los argumentos obligatorios.

Por ejemplo, el step comando para el FanControl rasgo tiene dos argumentos opcionales:

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 }

Cómo verificar si un rasgo admite un atributo

Es posible que algunos dispositivos admitan un Matter rasgo, pero no un atributo específico. Por ejemplo, es posible que un dispositivo Cloud-to-cloud que se asignó a Matter no admita todos los atributos de Matter. Para controlar casos como estos, usa la función supports a nivel del rasgo y la enumeración Attribute del rasgo para verificar si el atributo es compatible con un dispositivo en particular.

Por ejemplo, para verificar la compatibilidad de un dispositivo con el rasgo On/Off onOff atributo:

// 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!")
}

Algunos atributos pueden aceptar valores nulos en la Matter especificación o el Cloud-to-cloud smart home esquema. Para estos atributos, puedes determinar si un valor nulo que muestra el atributo se debe a que el dispositivo no informa ese valor o si el valor del atributo es realmente null. Para ello, usa isNullable además de supports:

// 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!")
}

Actualizar atributos de rasgos

Si deseas cambiar el valor de un atributo determinado y ninguno de los comandos del rasgo lo hace, es posible que el atributo admita que su valor se establezca de forma explícita.

El hecho de que se pueda cambiar el valor de un atributo depende de dos factores:

  • ¿Se puede escribir el atributo?
  • ¿Puede cambiar el valor del atributo como efecto secundario del envío de un comando de rasgo?

La documentación de referencia de los rasgos y sus atributos proporciona esta información.

Por lo tanto, las combinaciones de propiedades que determinan cómo se puede cambiar el valor de un atributo son las siguientes:

  • Solo lectura y no afectado por otros comandos. Esto significa que el valor del atributo no cambia. Por ejemplo, el currentPosition atributo del Switch rasgo.

  • Solo lectura y afectado por otros comandos. Esto significa que la única forma en que puede cambiar el valor del atributo es como resultado del envío de un comando. Por ejemplo, el currentLevel atributo del LevelControl Matter rasgo es de solo lectura, pero su valor puede mutar con comandos como moveToLevel.

  • Se puede escribir y no se ve afectado por otros comandos. Esto significa que puedes cambiar directamente el valor del atributo con la función update del rasgo, pero no hay comandos que afecten el valor del atributo. Por ejemplo, el WrongCodeEntryLimit atributo del DoorLock rasgo.

  • Se puede escribir y se ve afectado por otros comandos. Esto significa que puedes cambiar directamente el valor del atributo con la función update del rasgo, y el valor del atributo puede cambiar como resultado del envío de un comando. Por ejemplo, el speedSetting atributo de la FanControlTrait se puede escribir directamente, pero también se puede mutar con el comando step.

Ejemplo de uso de la función de actualización para cambiar el valor de un atributo

En este ejemplo, se muestra cómo establecer de forma explícita el valor del DoorLockTrait.WrongCodeEntryLimit atributo.

Para establecer un valor de atributo, llama a la función update del rasgo y pásale una función de mutador que establezca el valor nuevo. Se recomienda verificar primero que el rasgo admita un atributo.

Por ejemplo:

    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) }
    }

Enviar varios comandos a la vez

La API de Batching permite que un cliente envíe varios comandos de dispositivo de las APIs de Home en una sola carga útil. Los comandos se agrupan en una sola carga útil y se ejecutan en paralelo, de forma similar a la forma en que se puede construir una automatización de la API de Home con el nodo paralelo, como el ejemplo de abrir las persianas antes del amanecer. Sin embargo, la API de Batching permite comportamientos más complejos y sofisticados que la API de Automation, como la capacidad de seleccionar dispositivos de forma dinámica en el tiempo de ejecución según cualquier criterio.

Los comandos de un lote pueden orientarse a varios rasgos en varios dispositivos, en varias habitaciones y en varias estructuras.

El envío de comandos en un lote permite que los dispositivos realicen acciones de forma simultánea, lo que no es posible cuando los comandos se envían de forma secuencial en solicitudes separadas. El comportamiento que se logra con los comandos por lotes permite que el desarrollador establezca el estado de un grupo de dispositivos para que coincida con un estado agregado predeterminado.

Usar la API de Batching

Existen tres pasos básicos para invocar comandos a través de la API de Batching:

  1. Invoca el Home.sendBatchedCommands() método.
  2. Dentro del cuerpo del bloque sendBatchedCommands(), especifica los comandos que se incluirán en el lote.
  3. Verifica los resultados de los comandos enviados para ver si se realizaron correctamente o no.

Invocar el método sendBatchedCommands()

Llama al Home.sendBatchedCommands() método. Tras bambalinas, este método configura una expresión lambda en un contexto de lote especial.

home.sendBatchedCommands() {

Especificar comandos por lotes

Dentro del cuerpo del bloque sendBatchedCommands(), propaga comandos por lotes. Los comandos por lotes son versiones "sombreadas" de los comandos existentes de la API de Device que se pueden usar en un contexto de lote y se nombran con el sufijo agregado Batchable. Por ejemplo, el LevelControl rasgo's moveToLevel() comando tiene una contraparte llamada moveToLevelBatchable().

Ejemplo:

  val response1 = add(command1)

  val response2 = add(command2)

El lote se envía automáticamente una vez que se agregan todos los comandos al contexto del lote y la ejecución abandona el contexto.

Las respuestas se capturan en DeferredResponse<T> objetos.

Las instancias DeferredResponse<T> se pueden recopilar en un objeto de cualquier tipo, como un Collection o una clase de datos que definas. Cualquier tipo de objeto que elijas para ensamblar las respuestas es lo que muestra sendBatchedCommands(). Por ejemplo, el contexto del lote puede mostrar dos DeferredResponse instancias en un Pair:

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

Como alternativa, el contexto del lote puede mostrar las instancias DeferredResponse en una clase de datos personalizada:

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

Verificar cada respuesta

Fuera del bloque sendBatchedCommands(), verifica las respuestas para determinar si el comando correspondiente se realizó correctamente o no. Para ello, llama a DeferredResponse.getOrThrow(), que: - muestra el resultado del comando ejecutado - o, si el alcance del lote no se completó o el comando no se realizó correctamente, arroja un error.

Solo debes verificar los resultados fuera del alcance lambda de sendBatchedCommands().

Ejemplo

Supongamos que quieres compilar una app que use la API de Batching para configurar una escena de "buenas noches" que configure todos los dispositivos de la casa para la noche, cuando todos estén dormidos. Esta app debe apagar las luces y cerrar las puertas delanteras y traseras.

Una forma de abordar la tarea es la siguiente:

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()
}