Créer une automatisation sur Android

Vous pouvez accéder aux API Automation via les API Home pour Android, mais comme leur point d'entrée se fait par le biais d'une structure, vous devez d'abord accorder l'autorisation 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 HasAutomations interface avec les méthodes spécifiques à l'automatisation suivantes :

API Description
automations() Répertorie toutes les automatisations qui appartiennent à la structure. Seules les automatisations que vous avez créées via les API Home sont renvoyées.
createAutomation(automation) Crée une instance d'automatisation pour une structure.
deleteAutomation(automationId) Supprime 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 l'appareil ou 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, telles que le nom et la description.
  • Indicateurs qui indiquent, par exemple, si l'automatisation peut être exécutée ou non.
  • Liste de 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 un 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 une description à l'automatisation.

La création d'une automatisation définit par défaut l'indicateur isActive sur true. Il n'est donc pas nécessaire de définir explicitement cet indicateur, 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. Voici, par exemple, le DSL d'automatisation pour une automatisation qui allume un appareil lorsqu'un autre 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 createAutomation() méthode pour créer l'instance DraftAutomation :

val createdAutomation = structure.createAutomation(automation)

Vous pouvez ensuite 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 réussit pas la validation, un message d'avertissement ou d'erreur fournit des informations sur le problème. Pour en savoir plus, consultez la ValidationIssueType référence.

Exemples de code

Nous présentons ici quelques 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é

Lorsque vous écrivez une automatisation, vous n'êtes pas limité à la spécification d'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 différents 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 particulière
  • tous les appareils d'un type d'appareil particulier dans une pièce particulière
  • tous les appareils allumés
  • tous les appareils allumés dans une pièce particulière

Pour utiliser des filtres d'entité :

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

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

Par exemple, pour qu'un déclencheur d'automatisation soit déclenché par n'importe quelle lumière marche/arrêt :

// 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 (en particulier, les lumières marche/arrêt) 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 particulière 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 } }

Au moment de l'exécution :

Scénario Résultat
Aucun appareil ne répond aux critères d'un déclencheur. L'automatisation n'est pas déclenchée.
Aucun appareil ne répond aux critères d'un lecteur d'état. L'automatisation démarre, mais se poursuit en fonction du nœud de condition.
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, à l'exception de 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 execute() méthode :

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 qui suit 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. Collectez les 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 à une Collection locale :

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 automations() méthode 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 en Kotlin peut être utilisée pour affiner davantage les appels d'API.filter() Pour obtenir une automatisation par nom, obtenez 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 pour un appareil

Pour obtenir toutes les automatisations qui font référence à un appareil donné, utilisez un 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 update() méthode, 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 update() méthode permet de remplacer complètement un graphique d'automatisation, mais pas de modifier le graphique par nœud. La modification par nœud est sujette aux erreurs en raison des interdépendances des 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. 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éclencheurs, et l'automatisation ne pourra 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 de the OccupancySensorDevice, ce déclencheur ne peut plus activer l'automatisation.