Verificar se um trait oferece suporte a um comando
O suporte também pode ser verificado para um comando de atributo. Use também a função supports
no nível do atributo para verificar se um comando tem suporte para um dispositivo específico.
Por exemplo, para verificar se um dispositivo oferece suporte ao comando
toggle
da característica Ligado/Desligado:
// 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 um trait. Para
ligar ou desligar o dispositivo, use o comando de alternância do atributo
OnOff
, 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 atributo 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, use um bloco try-catch
para processar
corretamente essas exceções e mostrar informações detalhadas aos usuários nos casos em que
os erros podem ser úteis. Exceções não tratadas vão interromper o tempo de execução do app e
poderão causar falhas nele.
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 nos
atributos
OnOff
ou
LevelControl
:
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
para o atributo
FanControl
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 um atributo é compatível com 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 que foi
mapeado para Matter pode não oferecer suporte a todos os
atributos Matter. Para lidar com casos como esses, use a
função supports
no nível do atributo e o tipo enumerado Attribute
do atributo para verificar se
o atributo tem suporte para um dispositivo específico.
Por exemplo, para verificar se um dispositivo oferece suporte ao atributo
onOff
da característica Ligado/Desligado:
// 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 são anuláveis na especificação Matter ou
no esquema Cloud-to-cloud smart home. Para esses
atributos, é possível determinar se um null 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 traço
Se você quiser mudar o valor de um determinado atributo e nenhum dos comandos da traço fizer isso, o atributo poderá ter o valor definido explicitamente.
A possibilidade de mudar o valor de um atributo depende de dois fatores:
- O atributo pode ser gravado?
- O valor do atributo pode mudar como efeito colateral do envio de um comando de característica?
A documentação de referência de traços e atributos fornece essas informações.
Portanto, as combinações de propriedades que determinam como o valor de um atributo pode ser alterado 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 atributoSwitch
.Somente leitura e afetado por outros comandos. Isso significa que a única maneira de mudar o valor do atributo é como resultado do envio de um comando. Por exemplo, o atributo
currentLevel
do trait MatterLevelControl
é somente leitura, mas o valor dele pode ser modificado por comandos comomoveToLevel
.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
do atributo, mas não há comandos que afetem o valor do atributo. Por exemplo, o atributoWrongCodeEntryLimit
do atributoDoorLock
.Pode ser gravado e afetado por outros comandos. Isso significa que você pode mudar diretamente o valor do atributo usando a função
update
do atributo, e o valor do atributo pode mudar como resultado do envio de um comando. Por exemplo, o atributooccupiedCoolingSetpoint
do traçoThermostat
pode ser gravado, mas também atualizado com o comandosetpointRaiseLower
.
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
atributo DoorLockTrait.WrongCodeEntryLimit
.
Para definir um valor de atributo, chame a função update
do atributo e transmita uma função modificadora que defina o novo valor.
É recomendável primeiro
verificar se o atributo é compatível com o atributo.
Exemplo:
var 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 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 forma semelhante a como uma automação da API Home pode ser construída usando o nó paralelo, como no 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 dinamicamente dispositivos no momento de execução de acordo com qualquer critério.
Os comandos em um lote podem segmentar vários recursos em vários dispositivos, em várias salas e em várias estruturas.
O envio de comandos em lote permite que os dispositivos realizem 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 predefinido.
Usar a API Batching
Há três etapas básicas envolvidas na invocação de comandos pela API de lote:
- Invoque o método
Home.sendBatchedCommands()
. - No corpo do bloco
sendBatchedCommands()
, especifique os comandos a serem incluídos no lote. - Verifique os resultados dos comandos enviados para saber se eles foram bem-sucedidos ou não.
Invocar o método sendBatchedCommands()
Chame o método Home.sendBatchedCommands()
. Por trás dos 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 os comandos
em lote. Os comandos em lote são versões "sombra" de comandos da API Device
existentes que podem ser usados em um contexto de lote e são nomeados com o sufixo
adicionado Batchable
. Por exemplo, o comando
moveToLevel()
do atributo
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 do lote e a execução sai do contexto.
As respostas são capturadas em
objetos
DeferredResponse<T>
.
As instâncias de DeferredResponse<T>
podem ser reunidas em um objeto de qualquer tipo, como um
Collection
ou uma classe de dados definida por você. O 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 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(...)
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 foi concluído ou o comando não
foi concluído, gera um erro.
Verifique os resultados fora do escopo da 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 precisa apagar as luzes e trancar as portas da frente e de trás.
Veja 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()
}