Créer une automatisation sur Android

Les API Automation sont accessibles via les API Home pour Android, mais comme leur point d'entrée se fait par le biais d'une structure, une autorisation doit d'abord être accordée sur la structure avant de pouvoir les utiliser.

Une fois les autorisations accordées pour une structure, importez ces packages dans votre application :


import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.Id
import com.google.home.Structure

Une structure contient une interface HasAutomations avec les méthodes spécifiques à l'automatisation suivantes :

API Description
automations() Répertorie toutes les automatisations appartenant à la structure. Seules les automatisations que vous avez créées à l'aide des API Home sont renvoyées.
createAutomation(automation) Créez une instance d'automatisation pour une structure.
deleteAutomation(automationId) Supprimez une instance d'automatisation par son ID.

Créer une automatisation

Après avoir créé une instance de Home et reçu les autorisations de l'utilisateur, récupérez la structure et les appareils :

val structure = homeManager.structures().list().single()
val device = homeManager.devices().get(Id("myDevice"))!!

Définissez ensuite la logique de votre automatisation à l'aide du DSL d'automatisation. Dans les API Home, une automatisation est représentée par l'interface Automation. Cette interface contient un ensemble de propriétés :

  • Métadonnées, comme le nom et la description.
  • Indicateurs qui indiquent, par exemple, si l'automatisation peut être exécutée ou non.
  • Liste des nœuds contenant la logique de l'automatisation, appelée graphique d'automatisation, représentée par la propriété automationGraph.

automationGraph est de type SequentialFlow par défaut. Il s'agit d'une classe qui contient une liste de nœuds qui s'exécutent dans l'ordre séquentiel. Chaque nœud représente un élément de l'automatisation, tel qu'un déclencheur, une condition ou une action.

Attribuez un name et un description à l'automatisation.

Lors de la création d'une automatisation, l'option isActive est définie par défaut sur true. Il n'est donc pas nécessaire de définir explicitement cette option, sauf si vous souhaitez que l'automatisation soit désactivée au départ. Dans ce cas, définissez l'indicateur sur false lors de la création.

L'interface DraftAutomation est utilisée pour créer des automatisations, et l'interface Automation est utilisée pour la récupération. Par exemple, voici le DSL d'automatisation pour une automatisation qui allume un appareil lorsqu'un autre appareil est allumé :

import com.google.home.automation.Action
import com.google.home.automation.Automation
import com.google.home.automation.Condition
import com.google.home.automation.DraftAutomation
import com.google.home.automation.Equals
import com.google.home.automation.Node
import com.google.home.automation.SequentialFlow
import com.google.home.automation.Starter
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.matter.standard.OnOff
import com.google.home.Structure

...

val automation: DraftAutomation = automation {
  name = "MyFirstAutomation"
  description = "Turn on a device when another device is turned on."
  sequential {
    val starterNode = starter<_>(device1, OnOffLightDevice, trait=OnOff)
    condition() { expression = stateReaderNode.onOff equals true }
    action(device2, OnOffLightDevice) { command(OnOff.on()) }
  }
}

Une fois le DSL d'automatisation défini, transmettez-le à la méthode createAutomation() pour créer l'instance DraftAutomation :

val createdAutomation = structure.createAutomation(automation)

À partir de là, vous pouvez utiliser toutes les autres méthodes d'automatisation sur l'automatisation, telles que execute(), stop() et update().

Erreurs de validation

Si la création d'une automatisation ne passe pas la validation, un message d'avertissement ou d'erreur fournit des informations sur le problème. Pour en savoir plus, consultez la documentation de référence sur ValidationIssueType.

Exemples de code

Nous présentons ici des exemples de code qui pourraient être utilisés pour implémenter des parties des automatisations hypothétiques décrites sur la page Concevoir une automatisation sur Android.

Automatisation simple

Une automatisation qui lève les stores à 8h00 peut être implémentée comme suit :

// get all the automation node candidates in the structure
val allCandidates = structure.allCandidates().first()
// determine whether a scheduled automation can be constructed
val isSchedulingSupported =
  allCandidates.any {
    it is EventCandidate &&
      it.eventFactory == Time.ScheduledTimeEvent &&
      it.unsupportedReasons.isEmpty()
  }
// get the blinds present in the structure
val blinds =
  allCandidates
    .filter {
      it is CommandCandidate &&
        it.commandDescriptor == WindowCoveringTrait.UpOrOpenCommand &&
        it.unsupportedReasons.isEmpty()
    }
    .map { it.entity }
    .filterIsInstance<HomeDevice>()
    .filter { it.has(WindowCoveringDevice) }
 if (isSchedulingSupported && blinds.isNotEmpty()) {
  // Proceed to create automation
  val automation: DraftAutomation = automation {
    name = "Day time open blinds"
    description = "Open all blinds at 8AM everyday"
    isActive = true
    sequential {
      // At 8:00am local time....
      val unused =
        starter(structure, Time.ScheduledTimeEvent) {
          parameter(Time.ScheduledTimeEvent.clockTime(LocalTime.of(8, 0, 0, 0)))
        }
        // ...open all the blinds
       parallel {
        for (blind in blinds) {
          action(blind, WindowCoveringDevice) { command(WindowCovering.upOrOpen()) }
        }
      }
    }
  }
   val createdAutomation = structure.createAutomation(automation)
} else if (!isSchedulingSupported) {
  // Cannot create automation.
  // Set up your address on the structure, then try again.
} else {
  // You don't have any WindowCoveringDevices.
  // Try again after adding some blinds to your structure.
}

Automatisation complexe

Une automatisation qui déclenche des lumières clignotantes lorsqu'un mouvement est détecté peut être implémentée comme suit :

import com.google.home.Home
import com.google.home.HomeClient
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure
import com.google.home.automation.action
import com.google.home.automation.automation
import com.google.home.automation.equals
import com.google.home.automation.parallel
import com.google.home.automation.starter
import com.google.home.google.AssistantBroadcast
import com.google.home.matter.standard.OnOff
import com.google.home.matter.standard.OnOff.Companion.toggle
import com.google.home.matter.standard.OnOffLightDevice
import java.time.Duration

// get all the automation node candidates in the structure
val allCandidates = structure.allCandidates().first()

// get the lights present in the structure
val availableLights = allCandidates.filter {
   it is CommandCandidate &&
   it.commandDescriptor == OnOffTrait.OnCommand
}.map { it.entity }
.filterIsInstance<HomeDevice>()
.filter {it.has(OnOffLightDevice) ||
         it.has(ColorTemperatureLightDevice) ||
         it.has(DimmableLightDevice) ||
         it.has(ExtendedColorLightDevice)}

val selectedLights = ... // user selects one or more lights from availableLights

automation {
isActive = true

sequential {
   // If the presence state changes...
   val starterNode = starter<_>(structure, AreaPresenceState)
   // ...and if the area is occupied...
   condition() {
      expression = starterNode.presenceState equals PresenceState.PresenceStateOccupied
   }
   // "blink" the light(s)
   parallel {
            for(light in selectedLights) {
            action(light, OnOffLightDevice) { command(OnOff.toggle()) }
            delayFor(Duration.ofSeconds(1))
            action(light, OnOffLightDevice) { command(OnOff.toggle()) }
            delayFor(Duration.ofSeconds(1))
            action(light, OnOffLightDevice) { command(OnOff.toggle()) }
            delayFor(Duration.ofSeconds(1))
            action(light, OnOffLightDevice) { command(OnOff.toggle())}
         }
      }
   }
}

Sélectionner dynamiquement des appareils avec des filtres d'entités

Lorsque vous écrivez une automatisation, vous n'êtes pas limité à la spécification d'appareils spécifiques. Une fonctionnalité appelée filtres d'entités permet à votre automatisation de sélectionner des appareils au moment de l'exécution en fonction de différents critères.

Par exemple, à l'aide de filtres d'entités, votre automatisation peut cibler :

  • tous les appareils d'un type d'appareil particulier.
  • tous les appareils d'une pièce spécifique
  • tous les appareils d'un type d'appareil spécifique dans une pièce donnée ;
  • tous les appareils allumés ;
  • tous les appareils allumés dans une pièce donnée.

Pour utiliser les filtres d'entités :

  1. Sur Structure ou Room, appelez atExecutionTime(). Cette opération renvoie un TypedExpression<TypedEntity<StructureType>>.
  2. Sur cet objet, appelez getDevicesOfType() en lui transmettant un DeviceType.

Les filtres d'entités peuvent être utilisés dans les déclencheurs, les lecteurs d'état et les actions.

Par exemple, pour qu'un déclencheur lance une automatisation à partir d'un déclencheur :

// If any light is turned on or off
val starter =
  starter(
    entityExpression = structure.atExecutionTime().getDevicesOfType(OnOffLightDevice),
    trait = OnOff,
  )

Pour capturer l'état OnOff de toutes les lumières d'une structure (plus précisément, les lumières allumées/éteintes) dans un lecteur d'état :

// Build a Map<Entity, OnOff>
val onOffStateOfAllLights =
  stateReader(
    entityExpression = structure.atExecutionTime().getDevicesOfType(OnOffLightDevice),
    trait = OnOff,
  )

Pour obtenir les lumières d'une pièce spécifique et les utiliser dans une condition :

val livingRoomLights =
  stateReader(
    entityExpression = livingRoom.atExecutionTime().getDevicesOfType(OnOffLightDevice),
    trait = OnOff,
  )
// Are any of the lights in the living room on?
condition { expression = livingRoomLights.values.any { it.onOff equals true } }

Lors de l'exécution :

Scénario Résultat
Aucun appareil ne répond aux critères d'un déclencheur. L'automatisation ne se déclenche pas.
Aucun appareil ne répond aux critères dans un lecteur d'état. L'automatisation démarre, mais ne fait rien.
Aucun appareil ne répond aux critères d'une action. L'automatisation démarre, mais l'action ne fait rien.

L'exemple suivant est une automatisation qui éteint toutes les lumières, sauf celle du couloir, chaque fois qu'une lumière individuelle est éteinte :

val unused = automation {
  sequential {
    // If any light is turned on or off
    val starter =
      starter(
        entityExpression = structure.atExecutionTime().getDevicesOfType(OnOffLightDevice),
        trait = OnOff,
      )
    condition {
      // Check to see if the triggering light was turned off
      expression = starter.onOff equals false
    }
    // Turn off all lights except the hall light
    action(
      entityExpression =
        structure.atExecutionTime().getDevicesOfType(OnOffLightDevice).filter {
          it notEquals entity(hallwayLight, OnOffLightDevice)
        }
    ) {
      command(OnOff.on())
    }
  }
}

Exécuter une automatisation

Exécutez une automatisation créée à l'aide de la méthode execute() :

createdAutomation.execute()

Si l'automatisation comporte un déclencheur manuel, execute() démarre l'automatisation à partir de ce point, en ignorant tous les nœuds qui précèdent le déclencheur manuel. Si l'automatisation ne dispose pas d'un déclencheur manuel, l'exécution commence à partir du nœud suivant le premier nœud de déclencheur.

Si l'opération execute() échoue, une HomeException peut être générée. Consultez la section Gestion des erreurs.

Arrêter une automatisation

Arrêtez une automatisation en cours d'exécution à l'aide de la méthode stop() :


createdAutomation.stop()

Si l'opération stop() échoue, une HomeException peut être générée. Consultez la section Gestion des erreurs.

Obtenir la liste des automatisations pour une structure

Les automatisations sont définies au niveau de la structure. Appuyez sur l'automations() de la structure pour accéder à un Flow d'automatisations :


import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
structure.automations().collect {
  println("Available automations:")
  for (automation in it) {
    println(String.format("%S %S", "$automation.id", "$automation.name"))
  }
}

Vous pouvez également l'attribuer à un Collection local :

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

var myAutomations: Collection<Automation> = emptyList()
myAutomations = structure.automations()

Obtenir une automatisation par ID

Pour obtenir une automatisation par ID d'automatisation, appelez la méthode automations() sur la structure et faites correspondre l'ID :

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
val automation: DraftAutomation = structure.automations().mapNotNull {
  it.firstOrNull
    { automation -> automation.id == Id("automation-id") }
  }.firstOrNull()

Response:

// Here's how the automation looks like in the get response.
// Here, it's represented as if calling a println(automation.toString())

Automation(
  name = "automation-name",
  description = "automation-description",
  isActive = true,
  id = Id("automation@automation-id"),
  automationGraph = SequentialFlow(
    nodes = [
      Starter(
        entity="device@test-device",
        type="home.matter.0000.types.0101",
        trait="OnOff@6789..."),
      Action(
        entity="device@test-device",
        type="home.matter.0000.types.0101",
        trait="OnOff@8765...",
        command="on")
    ]))

Obtenir une automatisation par son nom

La méthode filter() en Kotlin peut être utilisée pour affiner davantage les appels d'API. Pour obtenir une automatisation par son nom, récupérez les automatisations de la structure et filtrez-les sur le nom de l'automatisation :

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
val automation: DraftAutomation = structure.automations().filter {
  it.name.equals("Sunset Blinds") }

Obtenir toutes les automatisations d'un appareil

Pour obtenir toutes les automatisations qui font référence à un appareil donné, utilisez le filtrage imbriqué pour analyser le automationGraph de chaque automatisation :

import android.util.Log
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure
import com.google.home.automation.Action
import com.google.home.automation.Automation
import com.google.home.automation.Automation.automationGraph
import com.google.home.automation.Node
import com.google.home.automation.ParallelFlow
import com.google.home.automation.SelectFlow
import com.google.home.automation.SequentialFlow
import com.google.home.automation.Starter
import com.google.home.automation.StateReader

...

fun collectDescendants(node: Node): List<Node> {
  val d: MutableList<Node> = mutableListOf(node)

  val children: List<Node> =
    when (node) {
      is SequentialFlow -> node.nodes
      is ParallelFlow -> node.nodes
      is SelectFlow -> node.nodes
      else -> emptyList()
    }
  for (c in children) {
    d += collectDescendants(c)
  }
  return d
}

val myDeviceId = "device@452f78ce8-0143-84a-7e32-1d99ab54c83a"
val structure = homeManager.structures().list().single()
val automations =
  structure.automations().first().filter {
    automation: Automation ->
    collectDescendants(automation.automationGraph!!).any { node: Node ->
      when (node) {
        is Starter -> node.entity.id.id == myDeviceId
        is StateReader -> node.entity.id.id == myDeviceId
        is Action -> node.entity.id.id == myDeviceId
        else -> false
      }
    }
  }

Modifier une automatisation

Pour mettre à jour les métadonnées d'une automatisation, appelez sa méthode update() en lui transmettant une expression lambda qui définit les métadonnées :

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
val automation: DraftAutomation = structure.automations().mapNotNull {
  it.firstOrNull
    { automation -> automation.id == Id("automation-id") }
  }.firstOrNull()
automation.update { this.name = "Flashing lights 2" }

La méthode update() permet de remplacer complètement un graphique d'automatisation, mais pas de modifier chaque nœud du graphique. La modification par nœud est sujette aux erreurs en raison des interdépendances entre les nœuds. Si vous souhaitez modifier la logique d'une automatisation, générez un nouveau graphique et remplacez complètement celui existant.

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
val automation: Automation = structure.automations().mapNotNull {
  it.firstOrNull
    { automation -> automation.id == Id("automation-id") }
  }.firstOrNull()
automation.update {
  this.automationGraph = sequential {
    val laundryWasherCompletionEvent =
      starter<_>(laundryWasher, LaundryWasherDevice, OperationCompletionEvent)
    condition {
      expression =
        laundryWasherCompletionEvent.completionErrorCode equals
          // UByte 0x00u means NoError
          0x00u
    }
    action(speaker, SpeakerDevice) { command(AssistantBroadcast.broadcast("laundry is done")) }
    }
  }
}

Supprimer une automatisation

Pour supprimer une automatisation, utilisez la méthode deleteAutomation() de la structure. Une automatisation doit être supprimée à l'aide de son ID.

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
val automation: DraftAutomation = structure.automations().first()
structure.deleteAutomation(automation.id)

Si la suppression échoue, une HomeException peut être générée. Consultez la section Gestion des erreurs.

Impact de la suppression d'un appareil sur les automatisations

Si un utilisateur supprime un appareil utilisé dans une automatisation, l'appareil supprimé ne peut plus déclencher de démarreurs, et l'automatisation ne peut plus lire ses attributs ni lui envoyer de commandes. Par exemple, si un utilisateur supprime un OccupancySensorDevice de sa maison et qu'une automatisation comporte un déclencheur qui dépend du OccupancySensorDevice, ce déclencheur ne peut plus activer l'automatisation.