La API de Discovery está diseñada para que la usen las apps que pueden crear automatizaciones según los dispositivos presentes en la casa del usuario. Puede revelarle a una app en el tiempo de ejecución qué atributos y dispositivos están presentes en una estructura determinada para usar en automatizaciones. Además, expone los comandos, atributos y eventos asociados, así como el rango de valores permitidos para los parámetros y campos.
La API de Discovery ignora los dispositivos o atributos que existen en una estructura que no es compatible con la API de Automation, así como los dispositivos o atributos que no se registraron en FactoryRegistry
. Consulta Cómo crear una instancia de Home para obtener más información sobre el uso de FactoryRegistry
.
Cómo usar la API
En el centro de la API de Discovery, se encuentra la interfaz HasCandidates
, que es la raíz de una jerarquía de tipos que incluye Structure
, Room
y HomeDevice
.
La interfaz HasCandidates
define dos métodos, candidates()
y allCandidates()
, que muestran objetos Flow
.
candidates()
produce una lista de candidatos de automatización para la entidad (Structure
,Room
,HomeDevice
).allCandidates()
produce una lista de candidatos de automatización para la entidad y todos sus elementos secundarios.Room
no admite este método.
A diferencia de los otros objetos Flow
que muestran las APIs de Home, estos contienen una
instantánea única.
Para obtener la lista más actualizada de candidatos disponibles, el desarrollador debe llamar a candidates()
o allCandidates()
cada vez y no puede llamar solo a collect()
en los objetos Flow
. Además, como estos dos métodos son
especialmente intensivos en recursos, llamarlos más de una vez por minuto hará
que se muestren datos almacenados en caché, que pueden no reflejar el estado actual
real en ese momento.
La interfaz NodeCandidate
representa un nodo candidato que encuentran estos dos métodos y es la raíz de una jerarquía que incluye las siguientes interfaces:
y las siguientes clases:
Trabaja con candidatos de automatización
Supongamos que estás escribiendo una app que crea una automatización para cerrar un conjunto de cortinas inteligentes en un momento especificado por el usuario. Sin embargo, no sabes si el usuario tiene un dispositivo que admita la característica WindowCovering
ni si WindowCovering
o alguno de sus atributos o comandos se pueden usar en automatizaciones.
En el siguiente código, se muestra cómo usar la API de Discovery para filtrar el resultado del método candidates()
y acotar los resultados y obtener el tipo específico de elemento (estructura, evento o comando) que se busca. Al final, crea una automatización a partir de los elementos recopilados.
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
En el siguiente ejemplo, se crea una automatización para establecer el nivel de brillo de una luz cuando se enciende.
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
Verifica los requisitos previos
La API de Discovery te permite saber que a un atributo le falta un requisito previo para su uso, como una suscripción o una dirección de estructura.
Para ello, usa el atributo unsupportedReasons
de la clase Candidate
. Este atributo se propaga con un UnsupportedCandidateReason
durante la llamada a candidates()
. Y la misma información aparece en los mensajes de error de validación cuando se llama a createAutomation()
.
Motivos de ejemplo:
MissingStructureAddressSetup
le informa al usuario que se requiere la configuración de la dirección para usar el atributoTime
. En Cambia la dirección de la casa de Google, se explica cómo un usuario puede ingresar la dirección de la estructura con Google Home app (GHA).MissingPresenceSensingSetup
le informa al usuario que se requiere la configuración de presencia para usar los atributosAreaPresenceState
yAreaAttendanceState
.MissingSubscription
le informa al usuario que se requiere una suscripción a Nest Aware para usar el atributoObjectDetection
.
Por ejemplo, para controlar el MissingStructureAddressSetup
UnsupportedCandidateReason
, te recomendamos que muestres un aviso emergente en tu app y abras el GHA para permitir que el usuario proporcione la dirección de la estructura:
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(...)
}
Valida parámetros
La API de Discovery muestra los valores permitidos para un atributo, un parámetro o un campo de evento, en forma de una instancia de Constraint
. Esta información permite que el desarrollador de la app evite que los usuarios establezcan valores no válidos.
Cada una de las subclases de Constraint
tiene su propia forma de representar los valores aceptados.
Clase de restricción | Propiedades que representan valores aceptados |
---|---|
BitmapConstraint |
combinedBits
|
BooleanConstraint | |
ByteConstraint |
maxLength y minLength
|
EnumConstraint |
allowedSet
|
NumberRangeConstraint |
lowerBound , upperBound , step
y unit
|
NumberSetConstraint |
allowedSet y unit
|
StringConstraint |
allowedSet , disallowedSet , isCaseSensitive ,
maxLength , minLength y
regex
|
StructConstraint |
fieldConstraints
|
ListConstraint |
elementConstraint
|
Usa restricciones
Supongamos que estás escribiendo una app que crea una automatización que establece el nivel de un dispositivo con el atributo LevelControl
. En el siguiente ejemplo, se muestra cómo te asegurarías de que el valor que se usa para configurar el atributo currentLevel
del rasgo LevelControl
esté dentro de los límites aceptados.
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
}
Compara la API de dispositivos y la API de Discovery
Puedes descubrir los tipos de dispositivos, los atributos y sus atributos sin usar la API de Discovery. Con la API de dispositivos, puedes descubrir lo siguiente:
- Los tipos de dispositivos principales a los que el usuario le otorgó permiso al desarrollador para controlarlos con el método
DeviceType.Metadata.isPrimaryType()
- Si cada dispositivo admite todas las características que requiere la automatización, con el método
HasTraits.has()
- Si cada rasgo admite todos los atributos y comandos que requiere la automatización, con el método
supports()
.
Es importante tener en cuenta que, si usas la API de Device para realizar el descubrimiento, no podrás beneficiarte de las siguientes funciones de la API de Discovery:
- Filtrado automático de atributos que no son compatibles con la API de Automation
- La capacidad de proporcionar a los usuarios una opción para seleccionar un valor válido para aquellos atributos y parámetros que usan restricciones.