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 eine Struktur erfolgt, muss zuerst die Berechtigung für die Struktur erteilt werden, bevor sie verwendet werden können.

Sobald die Berechtigungen für eine Struktur erteilt wurden, importieren Sie diese Pakete in Ihre 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() Listet alle Automatisierungen auf, die zur Struktur gehören. Es werden nur Automatisierungen zurückgegeben, die Sie über die Home APIs erstellt haben.
createAutomation(automation) Erstellt eine Automatisierungsinstanz für eine Struktur.
deleteAutomation(automationId) Löscht eine Automatisierungsinstanz anhand ihrer ID.

Automatisierung erstellen

Erstellen Sie eine Instanz von „Home“ und erhalten Sie Berechtigungen vom Nutzer. Rufen Sie dann die Struktur und das Gerät bzw. 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-Schnittstelle 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.
  • Eine Liste von Knoten, die die Logik der Automatisierung enthalten, der sogenannte Automatisierungsgraph, dargestellt durch die automationGraph-Eigenschaft.

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

Weisen Sie 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 zunächst deaktiviert ist. In diesem Fall setzen Sie das Flag beim Erstellen auf false.

Die DraftAutomation-Schnittstelle wird zum Erstellen von Automatisierungen verwendet, und die Automation-Schnittstelle 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()) }
  }
}

Sobald die Automation DSL definiert ist, übergeben Sie sie an die createAutomation() Methode, um die DraftAutomation Instanz zu erstellen:

val createdAutomation = structure.createAutomation(automation)

Von hier aus 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, enthält eine Warnung oder Fehlermeldung Informationen zum Problem. Weitere Informationen finden Sie in der ValidationIssueType Referenz.

Codebeispiele

Hier sind einige Codebeispiele, die verwendet werden könnten, um Teile der hypothetischen Automatisierungen zu implementieren, die auf der Seite Automatisierung auf Android entwerfen beschrieben werden.

Einfache Automatisierung

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

Geräte dynamisch mit Entitätsfiltern auswählen

Beim Schreiben einer Automatisierung sind Sie nicht auf die Angabe bestimmter Geräte beschränkt. Mit einer Funktion namens Entitätsfilter kann Ihre Automatisierung Geräte zur Laufzeit anhand verschiedener Kriterien auswählen.

Mit Entitätsfiltern kann Ihre Automatisierung beispielsweise Folgendes anvisieren:

  • 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 Geräte, die eingeschaltet sind
  • alle Geräte, die in einem bestimmten Raum eingeschaltet sind

So verwenden Sie Entitätsfilter:

  1. Rufen Sie entweder für Structure oder Room die Methode 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.

Beispiel: Ein beliebiger Ein/Aus-Schalter soll eine Automatisierung über einen Auslöser auslösen:

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

So erfassen Sie den OnOff-Status aller Lampen in einer Struktur (insbesondere Ein/Aus-Lampen) in einem Statusleser:

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

So rufen Sie die Lampen in einem bestimmten Raum ab und verwenden 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 } }

Zur 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 in einer Aktion. Die Automatisierung wird gestartet, aber die Aktion bewirkt nichts.

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 execute() Methode aus:

createdAutomation.execute()

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

Wenn der Vorgang execute() fehlschlägt, kann eine HomeException ausgelöst werden. Weitere Informationen finden Sie unter Fehler behebung.

Automatisierung beenden

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


createdAutomation.stop()

Wenn der Vorgang stop() fehlschlägt, kann eine HomeException ausgelöst werden. Weitere Informationen finden Sie unter Fehler behebung.

Liste der Automatisierungen für eine Struktur abrufen

Automatisierungen werden auf Strukturebene definiert. Erfassen Sie die Struktur's automations() , 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

Rufen Sie die automations() Methode für die Struktur auf und suchen Sie nach der ID, um eine Automatisierung anhand der Automatisierungs-ID abzurufen:

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 nach Name abrufen

Die filter() Methode in Kotlin kann verwendet werden, um API-Aufrufe weiter zu verfeinern. Rufen Sie die Automatisierungen der Struktur ab und filtern Sie nach dem Namen der Automatisierung, um eine Automatisierung anhand des Namens abzurufen:

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 Automatisierungen für ein Gerät abrufen

Verwenden Sie eine verschachtelte Filterung, um den automationGraph jeder Automatisierung zu scannen, um alle Automatisierungen abzurufen, die auf ein bestimmtes Gerät verweisen:

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

Rufen Sie die update() Methode auf und übergeben Sie einen Lambda-Ausdruck, der die Metadaten festlegt, um die Metadaten einer Automatisierung zu aktualisieren:

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 update() Methode unterstützt das vollständige Ersetzen eines Automatisierungsgraphen, aber nicht die Bearbeitung des Graphen pro Knoten. Die Bearbeitung pro Knoten ist aufgrund von Abhängigkeiten zwischen Knoten fehleranfällig. Wenn Sie die Logik einer Automatisierung ändern möchten, generieren Sie einen neuen Graphen und ersetzen Sie den vorhandenen 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

Verwenden Sie die Methode der Struktur deleteAutomation(), um eine Automatisierung zu löschen. 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, kann eine HomeException ausgelöst werden. Weitere Informationen finden Sie unter Fehler behebung.

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 auslösen. Die Automatisierung kann keine Attribute daraus lesen oder Befehle an das Gerät senden. Wenn ein Nutzer beispielsweise ein OccupancySensorDevice aus seinem Zuhause löscht und eine Automatisierung einen Auslöser hat, der von dem OccupancySensorDeviceabhängt, kann dieser Auslöser die Automatisierung nicht mehr aktivieren.