Créer une automatisation sur Android

Vous pouvez accéder aux API Automation via les API Home pour Android. Toutefois, comme leur point d'entrée passe par une structure, une autorisation doit d'abord être accordée à la structure avant qu'elles ne puissent être utilisées.

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 via les 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, obtenez 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 Automation. 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 indiquant, 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.

Par défaut, automationGraph est de type SequentialFlow, qui est une classe contenant 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'indicateur isActive est défini par défaut sur true. Il n'est donc pas nécessaire de définir explicitement cet indicateur, sauf si vous souhaitez initialement désactiver l'automatisation. Dans ce scénario, définissez l'indicateur sur false lors de la création.

L'interface DraftAutomation permet de créer et de compiler des automatisations, tandis que l'interface Automation permet de les récupérer. 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

Voici quelques exemples de code qui peuvent ê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 volets à 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é

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

Par exemple, à l'aide de filtres d'entité, 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 particulier dans une pièce spécifique ;
  • tous les appareils allumés ;
  • tous les appareils allumés dans une pièce donnée ;

Pour utiliser des filtres d'entité:

  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.

Par exemple, pour obtenir l'état OnOff de toutes les lumières d'une structure (en particulier, les lumières "Marche/Arrêt"):

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

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

Au moment 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 correspond aux critères d'une action. L'automatisation démarre, mais l'action ne fait rien.
Aucun appareil ne répond aux critères d'un lecteur d'état. L'automatisation démarre, mais 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 comporte pas de 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 Traiter les 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 Traiter les erreurs.

Obtenir la liste des automatisations d'une structure

Les automatisations sont définies au niveau de la structure. Collectez sur le 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 nom

La méthode filter() en Kotlin peut être utilisée pour affiner davantage les appels d'API. Pour obtenir une automatisation par nom, obtenez les automatisations de la structure et filtrez 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
      }
    }
  }

Mettre à jour 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 le modifier par nœud. La modification par nœud est sujette à des 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 l'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. Vous devez supprimer une automatisation à 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 exception HomeException peut être générée. Consultez la section Traiter les erreurs.

Impact de la suppression d'un appareil sur les automatisations

Si un utilisateur supprime un appareil utilisé dans une automatisation, celui-ci ne peut plus déclencher de déclencheurs, et l'automatisation ne pourra plus en lire les 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 de l'OccupancySensorDevice, ce déclencheur ne peut plus activer l'automatisation.