Controlar dispositivos no Android

Verificar se uma característica oferece suporte a um comando

O suporte também pode ser verificado 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 o suporte de um dispositivo ao comando da característica "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")
}

Enviar um comando para um dispositivo

O envio de um comando é semelhante à leitura de um atributo de estado de uma característica. Para ativar ou desativar o dispositivo, use o OnOff comando "Toggle" da característica, que é definido no modelo de dados do ecossistema do Google Home como toggle(). 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ística 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 um problema for detectado no fluxo de execução. Como desenvolvedor, você precisa usar um bloco try-catch para processar essas exceções corretamente e mostrar informações detalhadas aos usuários nos casos em que os erros são acionáveis. Exceções não processadas interrompem o tempo de execução do app e podem resultar em falhas.

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

onOffTrait.off()
onOffTrait.on()

Depois de enviar um comando para mudar o estado, quando ele for concluído, você poderá ler o estado conforme descrito em Ler o estado de um dispositivo para processá-lo no app. 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 nas OnOff ou LevelControl 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 têm argumentos opcionais, que vêm depois dos obrigatórios.

Por exemplo, o comando step da característica FanControl trait tem 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 }

Verificar se uma característica oferece suporte a um atributo

Alguns dispositivos podem oferecer suporte a uma Matter característica, mas não a um atributo específico. Por exemplo, um dispositivo Cloud-to-cloud que foi mapeado para Matter pode não oferecer suporte a todos os atributos Matter. Para processar casos como esses, use a função supports no nível da característica e a enumeração Attribute da característica para verificar se o atributo é compatível com um dispositivo específico.

Por exemplo, para verificar o suporte de um dispositivo ao atributo da característica "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!")
}

Alguns atributos podem ser anuláveis na especificação Matter ou no esquema Cloud-to-cloud smart home. Para esses atributos, é possível determinar se um nulo retornado pelo atributo é devido ao dispositivo não informar 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 característica

Se você quiser mudar o valor de um determinado atributo e nenhum dos comandos da característica fizer isso, o atributo poderá oferecer suporte à definição explícita do valor.

Se o valor de um atributo pode ser alterado depende de dois fatores:

  • O atributo é gravável?
  • O valor do atributo pode mudar como um efeito colateral do envio de um comando de característica?

A documentação de referência para características e atributos fornece essas informações.

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

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 uma função mutadora que define o novo valor. É recomendável primeiro verificar se a característica oferece suporte a um atributo.

Por 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 Batching permite que um cliente envie vários comandos de dispositivo das APIs Home em um único payload. Os comandos são agrupados em um único payload e executados em paralelo, de maneira semelhante à construção de uma automação da API Home usando o nó paralelo, 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 segmentar várias 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 é possível quando os comandos são enviados sequencialmente em solicitações separadas. O comportamento alcançado usando comandos em lote permite que o desenvolvedor defina o estado de um grupo de dispositivos para corresponder a um estado agregado predeterminado.

Usar a API Batching

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

  1. Invocar o Home.sendBatchedCommands() método.
  2. No 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 bem-sucedidos ou falharam.

Invocar o método sendBatchedCommands()

Chame o Home.sendBatchedCommands() método. 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 comandos em lote. Os comandos em lote são versões "sombra" dos comandos da API Device que podem ser usados em um contexto de lote e são nomeados com o sufixo Batchable. Por exemplo, o LevelControl da característica moveToLevel() comando 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 do lote e a execução sai do contexto.

As respostas são capturadas em DeferredResponse<T> objetos.

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

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

Como alternativa, o contexto do lote pode retornar as DeferredResponse instâncias 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(...)

Verificar 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 tiver sido concluído ou o comando não tiver sido bem-sucedido, gera um erro.

Você só precisa verificar os resultados fora do escopo 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 estiverem dormindo. Esse app precisa apagar as luzes e trancar as portas da frente e de trás.

Confira uma maneira de abordar a 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()
}