A API Discovery foi desenvolvida para ser usada por apps que podem criar automações com base nos dispositivos presentes na casa do usuário. Ele pode revelar a um app em tempo de execução quais características e dispositivos estão presentes em uma determinada estrutura para uso em automações. Além disso, ele expõe os comandos, atributos e eventos associados, bem como o intervalo de valores permitidos para parâmetros e campos.
A API Discovery ignora todos os dispositivos ou atributos que existem em uma estrutura que
não é compatível com a API Automation, bem como todos os dispositivos ou atributos que
não foram registrados no
FactoryRegistry
. Consulte
Criar uma instância do Google Home para mais informações sobre como usar
FactoryRegistry
.
Usar a API
O núcleo da API Discovery é a interface
HasCandidates
,
que é a raiz de uma hierarquia de tipos que inclui Structure
,
Room
e HomeDevice
.
A interface
HasCandidates
define dois métodos, candidates()
e allCandidates()
, que
retornam objetos Flow
.
candidates()
produz uma lista de candidatos à automação para a entidade (Structure
,Room
,HomeDevice
).allCandidates()
produz uma lista de candidatos à automação para a entidade e todos os filhos dela. Esse método não é compatível comRoom
.
Ao contrário dos outros objetos Flow
retornados pelas APIs do Google Home, eles contêm um
instantâneo único.
Para conseguir a lista mais atualizada de candidatos disponíveis, o desenvolvedor
precisa chamar candidates()
ou allCandidates()
toda vez. Não é possível chamar
collect()
nos objetos Flow
. Além disso, como esses dois métodos são
especialmente intensivos em recursos, chamá-los mais de uma vez por minuto
resulta na devolução de dados armazenados em cache, que podem não refletir o estado
atual no momento.
A interface
NodeCandidate
representa um nó candidato encontrado por esses dois métodos e é a
raiz de uma hierarquia que inclui as seguintes interfaces:
e as seguintes classes:
Trabalhar com candidatos à automação
Digamos que você esteja escrevendo um app que cria uma automação para fechar um conjunto de
persianas inteligentes em um horário especificado pelo usuário. No entanto, você não sabe se o usuário tem
um dispositivo compatível com a característica WindowCovering
e se WindowCovering
ou qualquer um dos atributos ou comandos dela pode ser usado em automações.
O código a seguir ilustra como usar a API Discovery para filtrar a
saída do método candidates()
para restringir os resultados e receber o
tipo específico de elemento (estrutura, evento, comando) que está sendo procurado. No final,
ele cria uma automação com os elementos coletados.
import com.google.home.Structure
import com.google.home.automation.CommandCandidate
import com.google.home.automation.EventCandidate
import com.google.home.automation.Automation
import com.google.home.automation.DraftAutomation
import com.google.home.platform.Time
import java.time.LocalTime
import com.google.home.matter.standard.WindowCoveringTrait
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
fun createAutomationWithDiscoveryApiTimeStarter(
structureName: String,
scheduledTimeInSecond: Int,
): String = runBlocking {
// get the Structure
val structure = homeClient.structures().findStructureByName(structureName)
// get an event candidate
val clockTimeStarter =
structure
.allCandidates().first().firstOrNull {candidate ->
candidate is EventCandidate && candidate.eventFactory == Time.ScheduledTimeEvent
} as EventCandidate
// retrieve the first 'DownOrClose' command encountered
val downOrCloseCommand =
structure.allCandidates().first().firstOrNull {
candidate ->
candidate is CommandCandidate
&& candidate.commandDescriptor == WindowCoveringTrait.DownOrCloseCommand
} as CommandCandidate
val blinds = ...
// prompt user to select the WindowCoveringDevice
...
if (clockTimeStarter && downOrCloseCommand && blinds) {
// Create the draft automation
val draftAutomation: DraftAutomation = automation {
name = ""
description = ""
isActive = true
sequential {
val mainStarter = starter<_>(structure, Time.ScheduledTimeEvent) {
parameter(
Time.ScheduledTimeEvent.clockTime(
LocalTime.ofSecondOfDay(scheduledTimeInSecond.toLong())
)
)
}
action(blinds, WindowCoveringDevice) { command(WindowCoveringTrait.downOrClose())
}
}
// Create the automation in the structure
val automation = structure.createAutomation(draftAutomation)
return@runBlocking automation.id.id
} else ... //the automation cannot be created
O exemplo a seguir cria uma automação para definir o nível de brilho de uma luz quando ela é ligada.
import com.google.home.Structure
import com.google.home.automation.CommandCandidate
import com.google.home.automation.TraitAttributesCandidate
import com.google.home.automation.Automation
import com.google.home.automation.DraftAutomation
import com.google.home.matter.standard.LevelControl
import com.google.home.matter.standard.LevelControlTrait.MoveToLevelCommand
import com.google.home.matter.standard.OnOff
import com.google.home.matter.standard.OnOff.Companion.onOff
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import com.google.home.automation.equals
fun createAutomationWithDiscoveryApiDimLight(
structureName: String,
): String = runBlocking {
// get the Structure
val structure: Structure = homeClient.structures().findStructureByName(structureName)
/**
* When I turn on the light, move the brightness level to 55
*/
val allCandidates = structure.allCandidates().first()
val dimmableLightDevice = structure.devices().list().first {it.has(OnOff) && it.has(LevelControl)}
val starterCandidate =
allCandidates
.filterIsInstance<TraitAttributesCandidate>()
.first { it.entity == dimmableLightDevice && it.trait == OnOff }
val actionCandidate =
allCandidates
.filterIsInstance<CommandCandidate>()
.first {it.entity == dimmableLightDevice && it.commandDescriptor == MoveToLevelCommand }
if (starterCandidate && actionCandidate) {
// Create the draft automation
val draftAutomation: DraftAutomation = automation {
sequential {
val starter = starter<_>(dimmableLightDevice, OnOffLightDevice, OnOff)
condition { expression = starter.onOff equals true }
action(dimmableLightDevice,DimmableLightDevice) {
mapOf(MoveToLevelCommand.Request.CommandFields.level to 55u.toUByte())
)
}
}
// Create the automation in the structure
val automation = structure.createAutomation(draftAutomation)
return@runBlocking automation.id.id
}
} else ... //the automation cannot be created
Verificar os pré-requisitos
A API Discovery informa que uma característica não tem um pré-requisito para uso, como
uma assinatura ou um endereço de estrutura.
Isso é feito usando o atributo
unsupportedReasons
da classe Candidate
. Esse atributo é preenchido com um
UnsupportedCandidateReason
durante a chamada candidates()
. E as mesmas informações aparecem nas
mensagens de erro de validação quando createAutomation()
é chamado.
Exemplos de motivos:
MissingStructureAddressSetup
informa ao usuário que a configuração do endereço é necessária para usar o traçoTime
. Alterar o endereço residencial do Google explica como um usuário pode inserir o endereço da estrutura usando o Google Home app (GHA).MissingPresenceSensingSetup
informa ao usuário que a configuração de presença é necessária para usar os atributosAreaPresenceState
eAreaAttendanceState
.MissingSubscription
informa ao usuário que uma assinatura do Nest Aware é necessária para usar o atributoObjectDetection
.
Por exemplo, para processar o MissingStructureAddressSetup
UnsupportedCandidateReason
, você pode mostrar um
aviso
no app e abrir o GHA para permitir que o usuário forneça
o endereço da estrutura:
val structure = homeManager.structures().list().single()
val allCandidates = structure.allCandidates().list().single()
val scheduledStarterCandidate = allCandidates.first { it is EventCandidate && it.eventFactory == ScheduledTimeEvent }
if (scheduledStarterCandidate.unsupportedReasons.any { it is MissingStructureAddressSetup }) {
showToast("No Structure Address setup. Redirecting to GHA to set up an address.")
launchChangeAddress(...)
}
Validar parâmetros
A API Discovery retorna os valores permitidos para um campo de atributo,
parâmetro ou evento, na forma de uma
instância de
Constraint
. Essas informações permitem que o desenvolvedor do app impeça que os usuários
definam valores inválidos.
Cada uma das subclasses de Constraint
tem uma maneira própria de representar
os valores aceitos.
Classe de restrição | Propriedades que representam valores aceitos |
---|---|
BitmapConstraint |
combinedBits
|
BooleanConstraint | |
ByteConstraint |
maxLength e minLength
|
EnumConstraint |
allowedSet
|
NumberRangeConstraint |
lowerBound , upperBound , step
e unit
|
NumberSetConstraint |
allowedSet e unit
|
StringConstraint |
allowedSet , disallowedSet , isCaseSensitive ,
maxLength , minLength e
regex
|
StructConstraint |
fieldConstraints
|
ListConstraint |
elementConstraint
|
Usar restrições
Digamos que você esteja escrevendo um app que cria uma automação que define o nível de um
dispositivo com o
traço
LevelControl
. O exemplo a seguir mostra como garantir que o valor usado para
definir o atributo
currentLevel
do atributo LevelControl
esteja dentro dos limites aceitos.
import android.content.Context
import com.google.home.Home
import com.google.home.Structure
import com.google.home.automation.Action
import com.google.home.automation.Automation
import com.google.home.automation.CommandCandidate
import com.google.home.automation.Condition
import com.google.home.automation.Constraint
import com.google.home.automation.Equals
import com.google.home.automation.EventCandidate
import com.google.home.automation.HasCandidates
import com.google.home.automation.Node
import com.google.home.automation.NodeCandidate
import com.google.home.automation.SequentialFlow
import com.google.home.automation.Starter
import com.google.home.matter.standard.LevelControlTrait
// Filter the output of candidates() to find the TraitAttributesCandidate
// for the LevelControl trait.
val levelCommand =
structure
.allCandidates()
.first()
.firstOrNull { candidate ->
candidate is CommandCandidate && candidate.command == LevelControlTrait.MoveToLevelCommand
} as? CommandCandidate
var levelConstraint = null
// Get the NodeCandidate instance's fieldDetailsMap and
// retrieve the Constraint associated with the level parameter.
// In this case, it is a NumberRangeConstraint.
if (levelCommand != null) {
levelConstraint =
levelCommand.fieldDetailsMap[
LevelControlTrait.MoveToLevelCommand.Request.CommandFields.level
]!!.constraint
}
...
// Test the value against the Constraint (ignoring step and unit)
if ( value in levelConstraint.lowerBound..levelConstraint.upperBound) {
// ok to use the value
}
Comparar a API Device e a API Discovery
É possível descobrir tipos de dispositivo, características e atributos sem usar a API Discovery. Com a API Device, você pode descobrir:
- Os tipos de dispositivo principal que o usuário concedeu permissão ao desenvolvedor para
controlar, usando o método
DeviceType.Metadata.isPrimaryType()
. - Se cada dispositivo oferece suporte a todas as características necessárias para a automação, usando
o método
HasTraits.has()
. - Se cada trait oferece suporte a todos os
atributos e
comandos que a automação
requer, usando o método
supports()
.
É importante observar que, se você usar a API Device para fazer descobertas, não vai se beneficiar dos seguintes recursos da API Discovery:
- Filtragem automática de traços que não são compatíveis com a API Automation.
- A capacidade de oferecer aos usuários uma opção para selecionar um valor válido para esses atributos e parâmetros que usam restrições.