L'API Discovery est destinée aux applications pouvant créer des automatisations en fonction des appareils présents dans la maison de l'utilisateur. Il peut révéler à une application au moment de l'exécution les traits et les appareils présents dans une structure donnée à utiliser dans les automatisations. De plus, il expose les commandes, attributs et événements associés, ainsi que la plage de valeurs autorisées pour les paramètres et les champs.
L'API Discovery ignore tous les appareils ou traits qui existent dans une structure qui ne sont pas compatibles avec l'API Automation, ainsi que tous les appareils ou traits qui n'ont pas été enregistrés dans FactoryRegistry
. Pour en savoir plus sur l'utilisation de FactoryRegistry
, consultez la section Créer une instance Home.
Utiliser l'API
L'interface HasCandidates
est au cœur de l'API Discovery. Elle est la racine d'une hiérarchie de types qui inclut Structure
, Room
et HomeDevice
.
L'interface HasCandidates
définit deux méthodes, candidates()
et allCandidates()
, qui renvoient toutes les deux des objets Flow
.
candidates()
génère une liste de candidats d'automatisation pour l'entité (Structure
,Room
,HomeDevice
).allCandidates()
génère une liste de candidats d'automatisation pour l'entité et tous ses enfants. Cette méthode n'est pas compatible avecRoom
.
Contrairement aux autres objets Flow
renvoyés par les API Home, ceux-ci contiennent un instantané unique.
Pour obtenir la liste la plus à jour des candidats disponibles, le développeur doit appeler candidates()
ou allCandidates()
à chaque fois et ne peut pas simplement appeler collect()
sur les objets Flow
. De plus, étant donné que ces deux méthodes sont particulièrement gourmandes en ressources, les appeler plus d'une fois par minute entraînera le retour de données mises en cache, qui ne reflètent pas nécessairement l'état actuel à ce moment-là.
L'interface NodeCandidate
représente un nœud candidat trouvé par ces deux méthodes. Elle est la racine d'une hiérarchie qui inclut les interfaces suivantes:
et les classes suivantes:
Travailler avec des candidats à l'automatisation
Supposons que vous écriviez une application qui crée une automatisation pour fermer un ensemble de volets intelligents à une heure spécifiée par l'utilisateur. Toutefois, vous ne savez pas si l'utilisateur possède un appareil compatible avec la caractéristique WindowCovering
, ni si WindowCovering
ou l'un de ses attributs ou de ses commandes peut être utilisé dans des automatisations.
Le code suivant montre comment utiliser l'API Discovery pour filtrer la sortie de la méthode candidates()
afin d'affiner les résultats et d'obtenir le type d'élément spécifique (structure, événement, commande) recherché. À la fin, il crée une automatisation à partir des éléments collectés.
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
L'exemple suivant crée une automatisation pour définir le niveau de luminosité d'une ampoule lorsqu'elle est allumée.
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
Vérifier les prérequis
L'API Discovery vous indique si une caractéristique ne dispose pas d'un prérequis à son utilisation, comme un abonnement ou une adresse de structure.
Pour ce faire, elle utilise l'attribut unsupportedReasons
de la classe Candidate
. Cet attribut est renseigné avec un UnsupportedCandidateReason
lors de l'appel candidates()
. Les mêmes informations s'affichent dans les messages d'erreur de validation lorsque createAutomation()
est appelé.
Exemples de raisons:
MissingStructureAddressSetup
indique à l'utilisateur que la configuration de l'adresse est requise pour utiliser le traitTime
. Modifier l'adresse Google de la maison explique comment un utilisateur peut saisir l'adresse de la structure à l'aide de Google Home app (GHA).MissingPresenceSensingSetup
indique à l'utilisateur que la configuration de la présence est requise pour utiliser les traitsAreaPresenceState
etAreaAttendanceState
.MissingSubscription
indique à l'utilisateur qu'un abonnement Nest Aware est nécessaire pour utiliser la caractéristiqueObjectDetection
.
Par exemple, pour gérer le UnsupportedCandidateReason
MissingStructureAddressSetup
, vous pouvez afficher un toast dans votre application et ouvrir le GHA pour permettre à l'utilisateur de fournir l'adresse de la structure:
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(...)
}
Vérifier les paramètres
L'API Discovery renvoie les valeurs autorisées pour un attribut, un paramètre ou un champ d'événement, sous la forme d'une instance Constraint
. Ces informations permettent au développeur de l'application d'empêcher les utilisateurs de définir des valeurs non valides.
Chacune des sous-classes de Constraint
a sa propre façon de représenter les valeurs acceptées.
Classe de contrainte | Propriétés représentant les valeurs acceptées |
---|---|
BitmapConstraint |
combinedBits
|
BooleanConstraint | |
ByteConstraint |
maxLength et minLength
|
EnumConstraint |
allowedSet
|
NumberRangeConstraint |
lowerBound , upperBound , step et unit
|
NumberSetConstraint |
allowedSet et unit
|
StringConstraint |
allowedSet , disallowedSet , isCaseSensitive , maxLength , minLength et regex
|
StructConstraint |
fieldConstraints
|
ListConstraint |
elementConstraint
|
Utiliser des contraintes
Imaginons que vous écriviez une application qui crée une automatisation qui définit le niveau d'un appareil avec la caractéristique LevelControl
. L'exemple suivant montre comment vous assurer que la valeur utilisée pour définir l'attribut currentLevel
du trait LevelControl
se situe dans les limites acceptées.
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
}
Comparer l'API Device et l'API Discovery
Vous pouvez découvrir les types d'appareils, les caractéristiques et leurs attributs sans utiliser l'API Discovery. L'API Device vous permet de découvrir les éléments suivants:
- Types d'appareils principaux pour lesquels l'utilisateur a accordé au développeur l'autorisation de contrôle, à l'aide de la méthode
DeviceType.Metadata.isPrimaryType()
. - Indique si chaque appareil est compatible avec toutes les caractéristiques requises par l'automatisation à l'aide de la méthode
HasTraits.has()
. - Indique si chaque trait est compatible avec tous les attributs et les commandes requis par l'automatisation, à l'aide de la méthode
supports()
.
Il est important de noter que si vous utilisez l'API Device pour effectuer la découverte, vous ne bénéficiez pas des fonctionnalités suivantes de l'API Discovery:
- Filtrage automatique des caractéristiques non compatibles avec l'API Automation.
- Possibilité de fournir aux utilisateurs la possibilité de sélectionner une valeur valide pour les attributs et paramètres qui utilisent des contraintes.