Automatisierung auf Android-Geräten erstellen

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

Sobald die Berechtigungen für ein Gebäude erteilt wurden, importiere die folgenden Pakete in deine App:


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

Eine Struktur enthält eine HasAutomations-Schnittstelle mit den folgenden automatisierungsspezifischen Methoden:

API Beschreibung
automations() Alle automatisierten Abläufe auflisten, die zum Gebäude gehören. Es werden nur Automatisierungen zurückgegeben, die du über die Home APIs erstellt hast.
createAutomation(automation) Automatisierungsinstanz für ein Gebäude erstellen
deleteAutomation(automationId) Löscht eine Automatisierungsinstanz anhand ihrer ID.

Automatisierung erstellen

Nachdem du eine Instanz von „Home“ erstellt und die Berechtigungen vom Nutzer erhalten hast, rufe das Gebäude und die Geräte ab:

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

Definiere dann die Logik deiner Automatisierung mit der Automation DSL. In den Home APIs wird eine Automatisierung durch die Schnittstelle Automation dargestellt. Diese Schnittstelle enthält eine Reihe von Attributen:

  • Metadaten wie Name und Beschreibung.
  • Flags, die beispielsweise angeben, ob die Automatisierung ausgeführt werden kann.
  • Eine Liste von Knoten, die die Logik der Automatisierung enthalten, wird als Automatisierungsgrafik bezeichnet und durch das Attribut automationGraph dargestellt.

automationGraph ist standardmäßig vom Typ SequentialFlow. Dies ist eine Klasse, die eine Liste von Knoten enthält, die nacheinander ausgeführt werden. Jeder Knoten stellt ein Element der Automatisierung dar, z. B. einen Auslöser, eine Bedingung oder eine Aktion.

Weise der Automatisierung einen name und eine description zu.

Beim Erstellen einer Automatisierung wird das Flag isActive standardmäßig auf true gesetzt. Daher ist es nicht erforderlich, dieses Flag explizit festzulegen, es sei denn, Sie möchten, dass die Automatisierung anfangs deaktiviert ist. In diesem Szenario setzen Sie das Flag beim Erstellen auf false.

Die DraftAutomation-Schnittstelle wird zum Erstellen von Automatisierungen verwendet, die Automation-Schnittstelle zum Abrufen. Hier ist beispielsweise die Automatisierungs-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)

Von hier aus kannst du alle anderen Automatisierungsmethoden für die Automatisierung verwenden, z. B. execute(), stop() und update().

Validierungsfehler

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

Auch wenn createAutomation() ohne Ausnahme abgeschlossen wird, ist die erstellte Automatisierung möglicherweise nicht gültig oder ausführbar. Im Backend können ungültige Automatisierungsentwürfe gespeichert werden, z. B. wenn ein Nutzer die erforderlichen Einwilligungen wie die Gesichtswiedererkennung nicht erteilt hat oder wenn Gerätefunktionen fehlen.

Sie sollten immer isValid und validationIssues für die zurückgegebene Automation-Instanz prüfen:

val createdAutomation = structure.createAutomation(automation)

if (!createdAutomation.isValid) {
  // Iterate through validation issues to identify errors and warnings
  for (issue in createdAutomation.validationIssues) {
    when (issue.severity) {
      ValidationIssueSeverity.ERROR -> {
        Log.e(
          "Automation",
          "Validation error on node ${issue.node}: ${issue.issueType}"
        )
        // Handle error (for example, prompt the user to enable missing
        // consents or device features)
      }
      ValidationIssueSeverity.WARNING -> {
        Log.w(
          "Automation",
          "Validation warning on node ${issue.node}: ${issue.issueType}"
        )
      }
      else -> {}
    }
  }
}

Codebeispiele

Hier findest du Beispielcode, mit dem du Teile der hypothetischen Automatisierungen implementieren kannst, die auf der Seite Automatisierung für Android entwerfen beschrieben werden.

Einfache Automatisierung

Eine Automatisierung, die die Jalousien um 8:00 Uhr 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 Lichter auslöst, wenn 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())}
         }
      }
   }
}

Geräte mit Entitätsfiltern dynamisch auswählen

Beim Erstellen einer Automatisierung bist du nicht darauf beschränkt, bestimmte Geräte anzugeben. Mit der Funktion Entitätsfilter kann deine Automatisierung Geräte zur Laufzeit anhand verschiedener Kriterien auswählen.

Mit Entitätsfiltern können Sie Ihre Automatisierung beispielsweise auf Folgendes ausrichten:

  • alle Geräte eines bestimmten Gerätetyps
  • alle Geräte in einem bestimmten Raum
  • Alle Geräte eines bestimmten Gerätetyps in einem bestimmten Raum
  • alle eingeschalteten Geräte
  • alle Geräte, die in einem bestimmten Raum eingeschaltet sind

So verwenden Sie Entitätsfilter:

  1. Rufen Sie entweder Structure oder Room auf und rufen Sie atExecutionTime() auf. Dadurch wird ein TypedExpression<TypedEntity<StructureType>> zurückgegeben.
  2. Rufen Sie für dieses Objekt getDevicesOfType() auf und übergeben Sie ein DeviceType.

Entitätsfilter können in Auslösern, Statuslesern und Aktionen verwendet werden.

Wenn du beispielsweise möchtest, dass eine beliebige ein- oder ausschaltbare Lampe eine Automatisierung über einen Auslöser startet, gehe so vor:

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

So erfasst du den OnOff-Status aller Lampen in einem Gebäude (insbesondere Lampen, die ein- und ausgeschaltet werden können) in einem Statusleser:

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

So rufst du die Lampen in einem bestimmten Raum ab und verwendest sie in einer Bedingung:

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

Während der Laufzeit:

Szenario Ergebnis
Keine Geräte erfüllen die Kriterien in einem Auslöser. Die Automatisierung wird nicht ausgelöst.
Keine Geräte erfüllen die Kriterien in einem Statusleser. Die Automatisierung wird gestartet, aber basierend auf dem Bedingungsknoten fortgesetzt.
Keine Geräte erfüllen die Kriterien einer Aktion. Die Automatisierung wird gestartet, aber die Aktion hat keine Auswirkungen.

Das folgende Beispiel ist eine Automatisierung, die alle Lampen außer der Flurlampe ausschaltet, wenn eine einzelne Lampe ausgeschaltet wird:

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

Automatisierung ausführen

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 ab diesem Punkt und ignoriert alle Knoten, die dem manuellen Auslöser vorangehen. Wenn die Automatisierung keinen manuellen Auslöser hat, beginnt die Ausführung mit dem Knoten, der auf den ersten Auslöserknoten folgt.

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

Automatisierung beenden

Beenden Sie eine laufende Automatisierung mit der Methode stop():


createdAutomation.stop()

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

Liste der automatisierten Abläufe für ein Gebäude abrufen

Automatisierungen werden auf Gebäudeebene definiert. Rufe die automations() des Gebäudes auf, 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 einer 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 du eine Automatisierung anhand der Automatisierungs-ID abrufen möchtest, rufe die Methode automations() für das Gebäude auf und suche nach der 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()

Antwort:

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

Automatisierung anhand des Namens abrufen

Mit der Methode filter() in Kotlin können API-Aufrufe weiter verfeinert werden. Wenn du einen automatisierten Ablauf anhand des Namens abrufen möchtest, rufe die automatisierten Abläufe des Gebäudes ab und filtere 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 du alle automatisierten Abläufe abrufen möchtest, die auf ein bestimmtes Gerät verweisen, kannst du die verschachtelte Filterung verwenden, um die automationGraph der einzelnen automatisierten Abläufe zu scannen:

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 du die Metadaten einer automatisierten Abläufe aktualisieren möchtest, rufe die Methode update() auf und übergib ihr einen Lambda-Ausdruck, der die Metadaten festlegt:

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

Die Methode update() unterstützt das vollständige Ersetzen eines Automatisierungsdiagramms, aber nicht das Bearbeiten des Diagramms pro Knoten. Die Bearbeitung einzelner Knoten ist aufgrund von Knotenabhängigkeiten fehleranfällig. Wenn du die Logik einer Automatisierung ändern möchtest, generiere ein neues Diagramm und ersetze das vorhandene vollständig.

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

Automatisierung löschen

Wenn du eine Automatisierung löschen möchtest, verwende 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 ein HomeException-Fehler ausgegeben. Weitere Informationen finden Sie unter Fehlerbehandlung.

Auswirkungen des Löschens von Geräten auf automatisierte Abläufe

Wenn ein Nutzer ein Gerät löscht, das in einer Automatisierung verwendet wird, kann das gelöschte Gerät keine Auslöser mehr auslösen und die Automatisierung kann keine Attribute mehr von ihm lesen oder Befehle an es senden. Wenn ein Nutzer beispielsweise ein 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.