Automatisierung erstellen

Auf die Automatisierungs-APIs kann über die Home APIs zugegriffen werden. Da ihr Einstiegspunkt jedoch über ein Gebäude erfolgt, muss zuerst die Berechtigung für das Gebäude erteilt werden, bevor sie verwendet werden können.

Nachdem Berechtigungen für ein Gebäude erteilt wurden, importieren Sie die folgenden Pakete in Ihre App:


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

Ein Gebäude enthält eine HasAutomations-Benutzeroberfläche mit den folgenden automatisierungsspezifischen Methoden:

API Beschreibung
automations() Listet alle Automatisierungen auf, die zum Gebäude gehören. Es werden nur automatisierte Abläufe zurückgegeben, die Sie über die Home APIs erstellt haben.
createAutomation(automation) Erstellen Sie eine Automatisierungs-Instanz für ein Gebäude.
deleteAutomation(automationId) Automatisierungsinstanzen anhand ihrer ID löschen

Automatisierung erstellen

Nachdem Sie eine Instanz von Home erstellt und Berechtigungen vom Nutzer erhalten haben, rufen Sie das Gebäude und die Geräte ab:

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

Definieren Sie dann die Logik Ihrer Automatisierung mit Automation DSL. In den Home APIs wird eine Automatisierung durch die Automation-Oberfläche dargestellt. Diese Schnittstelle enthält eine Reihe von Eigenschaften:

  • Metadaten wie Name und Beschreibung
  • Flags, die beispielsweise angeben, ob die Automatisierung ausgeführt werden kann oder nicht.
  • Eine Liste von Knoten, die die Logik der Automatisierung enthalten, der sogenannte Automatisierungsgraph, dargestellt durch die Property automationGraph.

automationGraph hat standardmäßig den Typ SequentialFlow. Das ist eine Klasse, die eine Liste von Knoten enthält, die in sequenzieller Reihenfolge ausgeführt werden. Jeder Knoten steht für ein Element der Automatisierung, z. B. einen Auslöser, eine Bedingung oder eine Aktion.

Weisen Sie der Automatisierung eine name und eine description zu.

Beim Erstellen einer Automatisierung wird das Flag isActive standardmäßig auf true gesetzt. Es ist daher nicht erforderlich, dieses Flag explizit festzulegen, es sei denn, Sie möchten die Automatisierung anfangs deaktivieren. Legen Sie in diesem Fall beim Erstellen das Flag auf false fest.

Die DraftAutomation-Oberfläche wird zum Erstellen von Automatisierungen verwendet und die Automation-Oberfläche zum Abrufen. Hier ist beispielsweise die Automation DSL für eine Automatisierung, die ein Gerät einschaltet, wenn ein anderes Gerät eingeschaltet wird:

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

Nachdem die Automatisierungs-DSL definiert wurde, übergeben Sie sie an die Methode createAutomation(), um die DraftAutomation-Instanz zu erstellen:

val createdAutomation = structure.createAutomation(automation)

Hier können Sie alle anderen Automatisierungsmethoden für die Automatisierung verwenden, z. B. execute(), stop() und update().

Validierungsfehler

Wenn die Erstellung der Automatisierung die Validierung nicht besteht, erhalten Sie eine Warn- oder Fehlermeldung mit Informationen zum Problem. Weitere Informationen finden Sie in der Referenz zu ValidationIssueType.

Codebeispiele

Hier finden Sie Beispielcode, mit dem Sie Teile der hypothetischen Automatisierungen implementieren können, die auf der Seite Automatisierung entwerfen beschrieben werden.

Einfache Automatisierung

Eine Automatisierung, die die Jalousien um 8:00 Uhr morgens hochfährt, könnte so implementiert werden:

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

Komplexe Automatisierung

Eine Automatisierung, die blinkende Lampen auslöst, wenn eine Bewegung erkannt wird, könnte so implementiert werden:

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

Allgemeine Automatisierung

Im folgenden Code wird gezeigt, wie mithilfe der Structure API, der Device API und der Discovery API Daten zum Zuhause des Nutzers erfasst werden können, um eine App zu implementieren, die den Nutzer durch die Erstellung einer eigenen generischen Automatisierung führt (wie unter Automatisierung entwerfen: Generische Automatisierung beschrieben).

val structureFlow: Flow<Structure> = home.structures().itemFlow(myStructureId)

// Get a snapshot of the structure.
val structure: Structure = structureFlow.first()

   // map of devices where key is device id
val devicesById = mutableMapOf<Id,HomeDevice>()

home.devices().list().map { devicesById.put(it.id, it) }

// map of rooms where key is room id
val roomsByName: MutableMap<String,Room?> = mutableMapOf<String,Room?>()

structure.rooms().list().map { roomsByName.put(it.name, it) }

// map of commands where key is trait id
val commandsByTrait: MutableMap<String,MutableList<CommandCandidate>?> = mutableMapOf<String,MutableList<CommandCandidate>?>()

// map of commands where key is DeviceType.Metadata.hashcode()
val commandsByDeviceType: MutableMap<Id,MutableList<CommandCandidate>?> = mutableMapOf<Id,MutableList<CommandCandidate>?>()

// map of commands where key is entity id
val commandsByEntity: MutableMap<Id,MutableList<CommandCandidate>?> = mutableMapOf<Id,MutableList<CommandCandidate>?>()


// map of stateReaders where key is trait id
val stateReadersByTrait: MutableMap<Int,MutableList<StateReaderCandidate>?> = mutableMapOf<Int,MutableList<StateReaderCandidate>?>()

// map of stateReaders where key is DeviceType.Metadata.hashcode()
val stateReadersByDeviceType: MutableMap<Int,MutableList<StateReaderCandidate>?> = mutableMapOf<Int,MutableList<StateReaderCandidate>?>()

// map of stateReaders where key is  entity id
val stateReadersByEntity: MutableMap<Int,MutableList<StateReaderCandidate>?> = mutableMapOf<Int,MutableList<StateReaderCandidate>?>()

// map of starters where key is trait id
val startersByTrait: MutableMap<Int,MutableList<StarterCandidate>?> = mutableMapOf<Int,MutableList<StarterCandidate>?>()

// map of starters where key is DeviceType.Metadata.hashcode()
val startersByDeviceType: MutableMap<Int,MutableList<CommandCandidate>?> = mutableMapOf<Int,MutableList<CommandCandidate>?>()

// map of starters where key is entity id
val startersByEntity: MutableMap<Int,MutableList<StarterCandidate>?> = mutableMapOf<Int,MutableList<StarterCandidate>?>()

// populate candidate maps
structure.allCandidates().first().map {
  when (it) {
   is CommandCandidate -> {

      // update commandsByTrait
      // TODO refactor into generic function to eliminate duplicate code
      var commandsByTraitList: MutableList<CommandCandidate>? = commandsByTrait.get(it.trait.factory.traitId: String)
      if(commandsByTraitList == null) { commandsByTraitList = arrayListOf<CommandCandidate>() }

      commandsByTraitList.add(it)
      commandsByTrait.put(it.trait.factory.traitId, commandsByTraitList)

      // update commandsByDeviceType
      // TODO refactor into generic function to eliminate duplicate code
      for (t in it.types) {

      //TODO filter out device types not present in the home

      //TODO how to get a reference to device type id? 

      var commandsByDeviceTypeList: MutableList<CommandCandidate>? = commandsByDeviceType.get(t.factory.typeId: Id)

      if (commandsByDeviceTypeList == null)  { commandsByDeviceTypeList = arrayListOf<CommandCandidate>() }

      commandsByDeviceTypeList.add(it)

      commandsByDeviceType.put(t.factory.typeId, commandsByDeviceTypeList)
      }

      // update commandsByEntity
      // TODO refactor into generic function to eliminate duplicate code
     var commandsByEntityList: MutableList<CommandCandidate>? = commandsByEntity.get(it.entity.id: Id)
      if ( commandsByEntityList == null ) {commandsByEntityList = arrayListOf<CommandCandidate>()}
      commandsByEntityList.add(it)
      commandsByEntity.put(it.entity.id, commandsByEntityList)
   }

/*
   is StateReaderCandidate -> {

      // update stateReadersByTrait

      var stateReadersList: MutableList<StateReaderCandidate>? = stateReadersByTrait.get(it.trait.factory.traitId)
      if(stateReadersList == null) { stateReadersList = arrayListOf<StateReaderCandidate>() }

      stateReadersList.add(it)
      stateReadersByTrait.put(it.trait.factory.traitId, stateReadersList)

   // update stateReadersByDeviceType

      for (t in it.types) {
      //TODO filter out device types not present in the home
      var stateReadersList: MutableList<StateReaderCandidate>? = stateReadersByDeviceType.get(t.metadata.hashcode())

      if (stateReadersList == null)  { stateReadersList = arrayListOf<StateReaderCandidate>() }

      stateReadersList.put(it)

      stateReadersByDeviceType.put(t.metadata.hashCode(),deviceTypeStateReaderList)
      }

      // update stateReadersByEntity

      MutableList<StateReaderCandidate> entityStateReaderList? = stateReadersByEntity.get(it.entity.id)
      if  entityStateReaderList  is null  {entityStateReaderList = arrayListOf<StateReaderCandidate>()}
      entityStateReaderList.add(it)
      stateReadersByEntity.put(it.entity.id, entityStateReaderList)
   }

   */

   /*
   is StarterCandidate  -> {

      // update startersByTrait

      var startersList: MutableList<StateReaderCandidate>? = startersByTrait.get(it.trait.factory.traitId)
      if(startersList == null) { startersList = arrayListOf<StateReaderCandidate>() }

      startersList.add(it)
      startersByTrait.put(it.trait.factory.traitId, startersList)

   // update startersByDeviceType

      for (t in it.types) {
      //TODO filter out device types not present in the home
      var startersList: MutableList<StateReaderCandidate>? = startersByDeviceType.get(t.metadata.hashcode())

      if (startersList == null)  { startersList = arrayListOf<StateReaderCandidate>() }

      startersList.put(it)

      startersByDeviceType.put(t.metadata.hashCode(),deviceTypeStateReaderList)
      }

      // update startersByEntity

      MutableList<StateReaderCandidate> entityStateReaderList? = startersByEntity.get(it.entity.id)
      if  entityStateReaderList  is null  {entityStateReaderList = arrayListOf<StateReaderCandidate>()}
      entityStateReaderList.add(it)
      stateReadersByEntity.put(it.entity.id, entityStateReaderList)

      }
*/
 else -> println("unknown type encountered: " + it)

  }
}

Automatisierung ausführen

So führen Sie eine erstellte Automatisierung mit der Methode execute() aus:

createdAutomation.execute()

Wenn die Automatisierung einen manuellen Auslöser hat, startet execute() die Automatisierung an diesem Punkt und ignoriert alle Knoten, die dem manuellen Auslöser vorausgehen. Wenn die Automatisierung keinen manuellen Auslöser hat, beginnt die Ausführung am Knoten nach dem ersten Auslöserknoten.

Wenn der execute()-Vorgang fehlschlägt, wird möglicherweise eine HomeException geworfen. Weitere Informationen finden Sie unter Fehlerbehandlung.

Automatisierung beenden

So beenden Sie eine laufende Automatisierung mit der Methode stop():


createdAutomation.stop()

Wenn der stop()-Vorgang fehlschlägt, wird möglicherweise eine HomeException geworfen. Weitere Informationen finden Sie unter Fehlerbehandlung.

Liste der Automatisierungen für ein Gebäude abrufen

Automatisierungen werden auf Gebäudeebene definiert. Erfassen Sie die automations() des Gebäudes, um auf eine Flow von Automatisierungen zuzugreifen:


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

Alternativ können Sie sie einem lokalen Collection zuweisen:

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

Automatisierung nach ID abrufen

Wenn Sie eine Automatisierung anhand der Automatisierungs-ID abrufen möchten, rufen Sie die Methode automations() für die Struktur auf und gleichen Sie die ID ab:

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")
    ]))

Automatisierungen anhand des Namens abrufen

Mit der Methode filter() in Kotlin können API-Aufrufe weiter optimiert werden. Wenn Sie einen automatisierten Ablauf nach Namen abrufen möchten, rufen Sie die automatisierten Abläufe des Gebäudes ab und filtern Sie nach dem Namen des automatisierten Ablaufs:

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

Alle automatisierten Abläufe für ein Gerät abrufen

Wenn Sie alle Automatisierungen abrufen möchten, die auf ein bestimmtes Gerät verweisen, verwenden Sie verschachtelte Filter, um die automationGraph der einzelnen Automatisierungen zu prüfen:

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

Automatisierung aktualisieren

Wenn Sie die Metadaten einer Automatisierung aktualisieren möchten, rufen Sie die Methode update() auf und übergeben Sie einen Lambda-Ausdruck, mit dem die Metadaten festgelegt werden:

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

Mit der Methode update() kann ein Automatisierungsdiagramm vollständig ersetzt werden, aber nicht Knoten für Knoten bearbeitet werden. Die Bearbeitung pro Knoten ist aufgrund von Knotenabhängigkeiten fehleranfällig. Wenn Sie die Logik einer Automatisierung ändern möchten, erstellen Sie ein neues Diagramm und ersetzen Sie das vorhandene vollständig.

Automatisierung löschen

Wenn Sie eine Automatisierung löschen möchten, verwenden Sie die Methode deleteAutomation() des Gebäudes. Eine Automatisierung muss anhand ihrer ID gelöscht werden.

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)

Wenn das Löschen fehlschlägt, wird möglicherweise eine HomeException ausgelöst. Weitere Informationen finden Sie unter Fehlerbehandlung.

Auswirkungen des Löschens von Geräten auf Automatisierungen

Wenn ein Nutzer ein Gerät löscht, das in einer Automatisierung verwendet wird, kann das gelöschte Gerät keine Auslöser mehr aktivieren und die Automatisierung kann keine Attribute daraus lesen oder ihm Befehle senden. Wenn ein Nutzer beispielsweise einen OccupancySensorDevice aus seinem Zuhause löscht und eine Automatisierung einen Auslöser hat, der vom OccupancySensorDevice abhängt, kann dieser Auslöser die Automatisierung nicht mehr aktivieren.