Contrôler des appareils sur Android

Vérifier si un trait est compatible avec une commande

Vous pouvez également vérifier la compatibilité d'une commande de caractéristique. Utilisez également la fonction supports au niveau du trait pour vérifier si une commande est compatible avec un appareil spécifique.

Par exemple, pour vérifier si un appareil est compatible avec la commande toggle du trait On/Off :

// 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")
}

Envoyer une commande à un appareil

L'envoi d'une commande est semblable à la lecture d'un attribut d'état à partir d'un trait. Pour allumer ou éteindre l'appareil, utilisez la commande Toggle du trait OnOff, qui est définie dans le modèle de données de l'écosystème Google Home comme toggle(). Cette méthode remplace onOff par false si la valeur est true, ou par true si la valeur est false :

// Calling a command on a trait.
try {
  onOffTrait.toggle()
} catch (e: HomeException) {
  // Code for handling the exception
}

Toutes les commandes de traits sont des fonctions suspend et ne se terminent que lorsqu'une réponse est renvoyée par l'API (par exemple, pour confirmer que l'état de l'appareil a changé). Les commandes peuvent renvoyer une exception si un problème est détecté dans le flux d'exécution. En tant que développeur, vous devez utiliser un bloc try-catch pour gérer correctement ces exceptions et fournir des informations détaillées aux utilisateurs dans les cas où les erreurs sont exploitables. Les exceptions non gérées arrêteront l'exécution de l'application et peuvent entraîner des plantages dans votre application.

Vous pouvez également utiliser les commandes off() ou on() pour définir explicitement l'état :

onOffTrait.off()
onOffTrait.on()

Après avoir envoyé une commande pour modifier l'état, une fois qu'elle est terminée, vous pouvez lire l'état comme décrit dans Lire l'état d'un appareil pour le gérer dans votre application. Vous pouvez également utiliser des flux comme décrit dans Observer l'état, qui est la méthode recommandée.

Envoyer une commande avec des paramètres

Certaines commandes peuvent utiliser des paramètres, comme ceux des traits 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(),
)

Certaines commandes comportent des arguments facultatifs, qui suivent les arguments obligatoires.

Par exemple, la commande step pour le trait FanControl comporte deux arguments facultatifs :

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 }

Vérifier si un trait est compatible avec un attribut

Il est possible que certains appareils soient compatibles avec un trait Matter, mais pas avec un attribut spécifique. Par exemple, un appareil Cloud-to-cloud mappé sur Matter n'est pas forcément compatible avec tous les attributs Matter. Pour gérer ce type de cas, utilisez la fonction supports au niveau du trait et l'énumération Attribute du trait pour vérifier si l'attribut est compatible avec un appareil spécifique.

Par exemple, pour vérifier si un appareil est compatible avec l'attribut onOff du trait On/Off :

// 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!")
}

Certains attributs peuvent être définis sur "null" dans la spécification Matter ou dans le schéma smart home de Cloud-to-cloud. Pour ces attributs, vous pouvez déterminer si la valeur null renvoyée par l'attribut est due au fait que l'appareil ne signale pas cette valeur ou si la valeur de l'attribut est réellement null, en utilisant isNullable en plus 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!")
}

Mettre à jour les attributs de caractéristiques

Si vous souhaitez modifier la valeur d'un attribut donné et qu'aucune des commandes du trait ne le fait, l'attribut peut permettre de définir explicitement sa valeur.

La possibilité de modifier la valeur d'un attribut dépend de deux facteurs :

  • L'attribut est-il modifiable ?
  • La valeur de l'attribut peut-elle changer en tant qu'effet secondaire de l'envoi d'une commande de caractéristique ?

La documentation de référence sur les traits et leurs attributs fournit ces informations.

Par conséquent, les combinaisons de propriétés qui déterminent comment la valeur d'un attribut peut être modifiée sont les suivantes :

  • Lecture seule et non affecté par les autres commandes. Cela signifie que la valeur de l'attribut ne change pas. Par exemple, l'attribut currentPosition du trait Switch.

  • Lecture seule et affecté par d'autres commandes. Cela signifie que la valeur de l'attribut ne peut changer que suite à l'envoi d'une commande. Par exemple, l'attribut currentLevel du trait LevelControl Matter est en lecture seule, mais sa valeur peut être modifiée par des commandes telles que moveToLevel.

  • Écriture possible et non affecté par d'autres commandes. Cela signifie que vous pouvez modifier directement la valeur de l'attribut à l'aide de la fonction update du trait, mais qu'aucune commande n'affectera la valeur de l'attribut. Par exemple, l'attribut WrongCodeEntryLimit du trait DoorLock.

  • Écriture possible et affectée par d'autres commandes. Cela signifie que vous pouvez modifier directement la valeur de l'attribut à l'aide de la fonction update du trait, et que la valeur de l'attribut peut changer à la suite de l'envoi d'une commande. Par exemple, l'attribut speedSetting de FanControlTrait peut être écrit directement, mais il est également modifiable à l'aide de la commande step.

Exemple d'utilisation de la fonction de mise à jour pour modifier la valeur d'un attribut

Cet exemple montre comment définir explicitement la valeur de l'attribut DoorLockTrait.WrongCodeEntryLimit.

Pour définir la valeur d'un attribut, appelez la fonction update du trait et transmettez-lui une fonction de mutation qui définit la nouvelle valeur. Nous vous recommandons de vérifier d'abord que le trait accepte un attribut.

Exemple :

    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) }
    }

Envoyer plusieurs commandes à la fois

L'API Batching permet à un client d'envoyer plusieurs commandes d'appareils Home APIs dans une seule charge utile. Les commandes sont regroupées dans une seule charge utile et exécutées en parallèle, de la même manière que l'on peut créer une automatisation de l'API Home à l'aide du nœud parallèle, comme dans l'exemple Ouvrir les stores avant le lever du soleil. Toutefois, l'API Batching permet des comportements plus complexes et sophistiqués que l'API Automation, comme la possibilité de sélectionner dynamiquement des appareils au moment de l'exécution selon n'importe quel critère.

Les commandes d'un même lot peuvent cibler plusieurs caractéristiques sur plusieurs appareils, dans plusieurs pièces et dans plusieurs structures.

L'envoi de commandes par lot permet aux appareils d'effectuer des actions simultanément, ce qui n'est pas vraiment possible lorsque les commandes sont envoyées de manière séquentielle dans des requêtes distinctes. Le comportement obtenu à l'aide de commandes par lot permet au développeur de définir l'état d'un groupe d'appareils pour qu'il corresponde à un état agrégé prédéterminé.

Utiliser l'API Batch

L'appel de commandes via l'API Batching comporte trois étapes de base :

  1. Appelez la méthode Home.sendBatchedCommands().
  2. Dans le corps du bloc sendBatchedCommands(), spécifiez les commandes à inclure dans le lot.
  3. Vérifiez les résultats des commandes envoyées pour savoir si elles ont réussi ou échoué.

Appeler la méthode sendBatchedCommands()

Appelez la méthode Home.sendBatchedCommands(). En arrière-plan, cette méthode configure une expression lambda dans un contexte de lot spécial.

home.sendBatchedCommands() {

Spécifier des commandes par lot

Dans le corps du bloc sendBatchedCommands(), remplissez les commandes par lot. Les commandes par lot sont des versions "fantômes" des commandes existantes de l'API Device. Elles peuvent être utilisées dans un contexte de lot et sont nommées avec le suffixe Batchable. Par exemple, la commande moveToLevel() du trait LevelControl a une contrepartie nommée moveToLevelBatchable().

Exemple :

  val response1 = add(command1)

  val response2 = add(command2)

Le lot est automatiquement envoyé une fois que toutes les commandes ont été ajoutées au contexte du lot et que l'exécution a quitté le contexte.

Les réponses sont capturées dans des objets DeferredResponse<T>.

Les instances DeferredResponse<T> peuvent être regroupées dans un objet de n'importe quel type, tel qu'un Collection ou une classe de données que vous définissez. Quel que soit le type d'objet que vous choisissez pour assembler les réponses, c'est ce qui est renvoyé par sendBatchedCommands(). Par exemple, le contexte de lot peut renvoyer deux instances DeferredResponse dans un Pair :

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

Le contexte de lot peut également renvoyer les instances DeferredResponse dans une classe de données personnalisée :

  // Custom data class
  data class SpecialResponseHolder(
    val response1: DeferredResponse<String>,
    val response2: DeferredResponse<Int>,
    val other: OtherResponses
  )
  data class OtherResponses(...)

Vérifier chaque réponse

En dehors du bloc sendBatchedCommands(), vérifiez les réponses pour déterminer si la commande correspondante a réussi ou échoué. Pour ce faire, appelez DeferredResponse.getOrThrow(), qui : - renvoie le résultat de la commande exécutée ; - ou, si le champ d'application du lot n'est pas terminé ou si la commande a échoué, génère une erreur.

Vous ne devez vérifier les résultats qu'en dehors du champ d'application lambda sendBatchedCommands().

Exemple

Imaginons que vous souhaitiez créer une application qui utilise l'API Batching pour configurer une scène "Bonne nuit" qui configure tous les appareils de la maison pour la nuit, lorsque tout le monde dort. Cette application devrait éteindre les lumières et verrouiller les portes d'entrée et de derrière.

Voici une façon d'aborder la tâche :

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()
}