Controlar dispositivos no Android

Verificar se uma característica é compatível com um comando

Também é possível verificar o suporte para um comando de característica. Use também a função supports no nível da característica para verificar se um comando é compatível com um dispositivo específico.

Por exemplo, para verificar se um dispositivo é compatível com o comando toggle da característica de ativar/desativar:

// 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 um comando para um dispositivo

Enviar um comando é semelhante a ler um atributo de estado de uma característica. Para ligar ou desligar o dispositivo, use oOnOff O comando Toggle da característica, que é definido no modelo de dados do ecossistema Google Home comotoggle(). Esse método muda onOff para false se for true ou para true se for false:

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

Todos os comandos de características são funções suspend e só são concluídos quando uma resposta é retornada pela API (como confirmar que o estado do dispositivo mudou). Os comandos podem retornar uma exceção se for detectado um problema no fluxo de execução. Como desenvolvedor, use um bloco try-catch para processar essas exceções corretamente e mostrar informações detalhadas aos usuários nos casos em que os erros podem ser corrigidos. Exceções não tratadas interromperão a execução do aplicativo e podem resultar em falhas no seu aplicativo.

Como alternativa, use os comandos off() ou on() para definir explicitamente o estado:

onOffTrait.off()
onOffTrait.on()

Após enviar um comando para alterar o estado, assim que ele for concluído, você pode ler o estado conforme descrito em Ler o estado de um dispositivo para manipulá-lo em seu aplicativo. Como alternativa, use fluxos conforme descrito em Observar o estado, que é o método preferido.

Enviar um comando com parâmetros

Alguns comandos podem usar parâmetros, como aqueles emOnOff ouLevelControl características:

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

Alguns comandos possuem argumentos opcionais, que vêm depois dos argumentos obrigatórios.

Por exemplo, o comando step para a característica FanControl possui dois argumentos opcionais:

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 }

Verifique se uma característica suporta um atributo.

Alguns dispositivos podem oferecer suporte a uma característica Matter, mas não a um atributo específico. Por exemplo, um dispositivo Cloud-to-cloud mapeado para Matter pode não ser compatível com todos os atributos de Matter. Para lidar com casos como esses, use a função de nível de traço supports e o enum Attribute do traço para verificar se o atributo é suportado para um determinado dispositivo.

Por exemplo, para verificar se um dispositivo é compatível com o atributo onOff da característica On/Off:

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

Alguns atributos podem ser nulos na especificação Matter ou no esquema Cloud-to-cloud smart home. Para esses atributos, você pode determinar se um null retornado pelo atributo se deve ao dispositivo não estar relatando esse valor, ou se o valor do atributo é realmente null, usando isNullable além 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!")
}

Atualizar atributos de traço

Se você quiser mudar o valor de um atributo específico, e nenhum dos comandos da característica fizer isso, o atributo poderá permitir que o valor seja definido explicitamente.

A possibilidade de mudar o valor de um atributo depende de dois fatores:

  • O atributo é gravável?
  • O valor do atributo pode mudar como um efeito colateral do envio de um comando de traço?

A documentação de referência para traços e atributos fornece essas informações.

Portanto, as combinações de propriedades que determinam como o valor de um atributo pode ser mudado são:

  • Somente leitura e não afetado por outros comandos. Isso significa que o valor do atributo não muda. Por exemplo, o atributo currentPosition do traço Switch.

  • Somente leitura e afetado por outros comandos. Isso significa que a única maneira de o valor do atributo mudar é como resultado do envio de um comando. Por exemplo, o atributo currentLevel do traço LevelControl Matter é somente leitura, mas o valor dele pode ser alterado por comandos como moveToLevel.

  • Gravável e não afetado por outros comandos. Isso significa que você pode mudar diretamente o valor do atributo usando a função update da característica, mas não há comandos que afetem o valor do atributo. Por exemplo, o atributo WrongCodeEntryLimit da característica DoorLock.

  • Gravável e afetado por outros comandos. Isso significa que você pode mudar diretamente o valor do atributo usando a função update da característica, e o valor do atributo pode mudar como resultado do envio de um comando. Por exemplo, o atributo speedSetting do FanControlTrait pode ser gravado diretamente, mas também é mutável usando o comando step.

Exemplo de uso da função de atualização para mudar o valor de um atributo

Este exemplo mostra como definir explicitamente o valor do DoorLockTrait.WrongCodeEntryLimit atributo.

Para definir um valor de atributo, chame a função update da característica e transmita a ela uma função mutadora que define o novo valor. É recomendável primeiro verificar se a característica aceita um atributo.

Exemplo:

    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 vários comandos de uma só vez

A API de Loteamento permite que um cliente envie vários comandos de dispositivos da API Home em uma única carga útil. Os comandos são agrupados em uma única carga útil e executados em paralelo, semelhante a como se pode construir uma API doméstica automation usando o nó parallel, como o exemplo Abrir persianas antes do nascer do sol. No entanto, a API Batching permite comportamentos mais complexos e sofisticados do que a API Automation, como a capacidade de selecionar dispositivos dinamicamente no tempo de execução de acordo com qualquer critério.

Os comandos em um lote podem ter como alvo múltiplas características em vários dispositivos, em várias salas e em várias estruturas.

O envio de comandos em lote permite que os dispositivos executem ações simultaneamente, o que não é realmente possível quando os comandos são enviados sequencialmente em solicitações separadas. O comportamento alcançado com o uso de comandos em lote permite que o desenvolvedor defina o estado de um grupo de dispositivos para corresponder a um estado agregado predeterminado.

Utilize a API de Loteamento

Há três etapas básicas envolvidas na invocação de comandos pela API Batching:

  1. Invoque o método Home.sendBatchedCommands().
  2. Dentro do corpo do bloco sendBatchedCommands(), especifique os comandos a serem incluídos no lote.
  3. Verifique os resultados dos comandos enviados para saber se eles foram concluídos ou falharam.

Invocar o método sendBatchedCommands()

Chame o método Home.sendBatchedCommands(). Nos bastidores, esse método configura uma expressão lambda em um contexto de lote especial.

home.sendBatchedCommands() {

Especificar comandos em lote

No corpo do bloco sendBatchedCommands(), preencha batchable commands. Os comandos em lote são versões "sombra" dos comandos atuais da API Device que podem ser usados em um contexto de lote e são nomeados com o sufixo Batchable. Por exemplo, o comando moveToLevel() do traço LevelControl tem uma contraparte chamada moveToLevelBatchable().

Exemplo:

  val response1 = add(command1)

  val response2 = add(command2)

O lote é enviado automaticamente quando todos os comandos são adicionados ao contexto de lote e a execução sai do contexto.

As respostas são registradas emDeferredResponse<T> objetos.

As instâncias DeferredResponse<T> podem ser reunidas em um objeto de qualquer tipo, como um Collection ou uma classe de dados definida por você. Qualquer tipo de objeto que você escolher para montar as respostas é o que será retornado por sendBatchedCommands(). Por exemplo, o contexto de lote pode retornar duas instâncias de DeferredResponse em um Pair:

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

Como alternativa, o contexto de lote pode retornar as instâncias DeferredResponse em uma classe de dados personalizada:

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

Verifique cada resposta

Fora do bloco sendBatchedCommands(), verifique as respostas para determinar se o comando correspondente foi bem-sucedido ou falhou. Isso é feito chamando DeferredResponse.getOrThrow(), que: - retorna o resultado do comando executado; - ou, se o escopo do lote não foi concluído ou o comando não foi bem-sucedido, gera um erro.

Verifique os resultados fora do escopo da função lambda sendBatchedCommands().

Exemplo

Digamos que você queira criar um app que use a API Batching para configurar uma cena de "boa noite" que configure todos os dispositivos da casa para a noite, quando todos estão dormindo. Esse app deve apagar as luzes e trancar as portas da frente e de trás.

Confira uma maneira de realizar essa tarefa:

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