1. Hinweis
Dies ist das zweite Codelab in der Reihe zum Erstellen einer Android-App mit den Google Home APIs. In diesem Codelab zeigen wir Ihnen, wie Sie automatisierte Abläufe für Ihr Zuhause erstellen, und geben Ihnen einige Tipps zu den Best Practices für die Verwendung der APIs. Wenn Sie das erste Codelab Eine mobile App mit den Home APIs auf Android erstellen noch nicht abgeschlossen haben, empfehlen wir Ihnen, dies vor Beginn dieses Codelabs zu tun.
Die Google Home APIs bieten Android-Entwicklern eine Reihe von Bibliotheken, mit denen sie Smart-Home-Geräte im Google Home-System steuern können. Mit diesen neuen APIs können Entwickler Automatisierungen für ein Smart Home einrichten, mit denen Gerätefunktionen anhand vordefinierter Bedingungen gesteuert werden können. Google bietet außerdem eine Discovery API, mit der Sie Geräte abfragen können, um herauszufinden, welche Attribute und Befehle sie unterstützen.
Vorbereitung
- Schließen Sie das Codelab Eine mobile App mit den Home APIs auf Android erstellen ab.
- Kenntnisse über das Google Home-System (Cloud-to-Cloud und Matter)
- Eine Workstation mit installiertem Android Studio (2024.3.1 Ladybug oder höher)
- Ein Android-Smartphone, das die Anforderungen der Home APIs erfüllt (siehe Voraussetzungen), auf dem die Google Play-Dienste und die Google Home App installiert sind.
- Einen kompatiblen Google Home Hub, der die Google Home APIs unterstützt.
- Optional: Ein Smart-Home-Gerät, das mit den Google Home APIs kompatibel ist.
Lerninhalte
- So erstellst du mit den Home APIs automatisierte Abläufe für Smart-Home-Geräte.
- So kannst du mit den Discovery APIs die unterstützten Gerätefunktionen ermitteln.
- Best Practices für die Entwicklung von Apps mit den Home APIs
2. Projekt wird eingerichtet
Das folgende Diagramm zeigt die Architektur einer Home APIs-Anwendung:
- App-Code:Der Hauptcode, mit dem Entwickler die Benutzeroberfläche der App und die Logik für die Interaktion mit dem Home APIs SDK erstellen.
- Home APIs SDK:Das von Google bereitgestellte Home APIs SDK funktioniert mit dem Home APIs-Dienst in GMSCore, um Smart-Home-Geräte zu steuern. Entwickler erstellen Apps, die mit den Home APIs funktionieren, indem sie sie mit dem Home APIs SDK bündeln.
- GMSCore auf Android:GMSCore, auch als Google Play-Dienste bezeichnet, ist eine Google-Plattform, die wichtige Systemdienste bereitstellt und wichtige Funktionen auf allen zertifizierten Android-Geräten ermöglicht. Das Smart-Home-Modul der Google Play-Dienste enthält die Dienste, die mit den Smart-Home APIs interagieren.
In diesem Codelab bauen wir auf dem auf, was wir im Artikel Eine mobile App mit den Home APIs auf Android erstellen behandelt haben.
Sie benötigen ein Gebäude mit mindestens zwei unterstützten Geräten, die im Konto eingerichtet und funktionsfähig sind. Da wir in diesem Codelab Automatisierungen einrichten, bei denen eine Änderung des Gerätestatus eine Aktion auf einem anderen Gerät auslöst, benötigen Sie zwei Geräte, um die Ergebnisse zu sehen.
Beispiel-App abrufen
Der Quellcode für die Beispiel-App ist auf GitHub im Repository google-home/google-home-api-sample-app-android verfügbar.
In diesem Codelab werden die Beispiele aus dem codelab-branch-2
-Branch der Beispiel-App verwendet.
Gehen Sie zu dem Speicherort, an dem Sie das Projekt speichern möchten, und klonen Sie den Branch codelab-branch-2
:
$ git clone -b codelab-branch-2 https://github.com/google-home/google-home-api-sample-app-android.git
Hinweis: Dies ist ein anderer Branch als der, der in Eine mobile App mit den Home APIs auf Android erstellen verwendet wird. Dieser Codebasis-Branch baut auf dem ersten Codelab auf. Dieses Mal werden Sie anhand von Beispielen durch die Erstellung von Automatisierungen geführt. Wenn Sie das vorherige Codelab abgeschlossen und alle Funktionen zum Laufen gebracht haben, können Sie dasselbe Android Studio-Projekt verwenden, um dieses Codelab abzuschließen, anstatt codelab-branch-2
zu verwenden.
Sobald der Quellcode kompiliert und auf Ihrem Mobilgerät ausführbar ist, fahren Sie mit dem nächsten Abschnitt fort.
3. Automatisierungen
Automatisierte Abläufe bestehen aus einer Reihe von „Wenn-dann-Bedingungen“, mit denen Gerätestatus basierend auf ausgewählten Faktoren automatisch gesteuert werden können. Entwickler können mithilfe von Automatisierungen erweiterte interaktive Funktionen in ihren APIs erstellen.
Automatisierungen bestehen aus drei verschiedenen Arten von Komponenten, die als nodes bezeichnet werden: Auslöser, Aktionen und Bedingungen. Diese Knoten arbeiten zusammen, um Abläufe mit Smart-Home-Geräten zu automatisieren. Normalerweise werden sie in der folgenden Reihenfolge ausgewertet:
- Starter: Hier werden die Anfangsbedingungen definiert, die die Automatisierung aktivieren, z. B. eine Änderung des Attributwerts. Eine Automatisierung muss einen Starter haben.
- Bedingung: Zusätzliche Einschränkungen, die nach dem Auslösen einer Automatisierung ausgewertet werden. Der Ausdruck in einer Bedingung muss als wahr ausgewertet werden, damit die Aktionen einer Automatisierung ausgeführt werden.
- Aktion: Befehle oder Statusaktualisierungen, die ausgeführt werden, wenn alle Bedingungen erfüllt sind.
Sie können beispielsweise eine Automatisierung einrichten, die die Lampen in einem Raum gedimmt, wenn ein Schalter betätigt wird, während der Fernseher in diesem Raum eingeschaltet ist. In diesem Fall gilt Folgendes:
- Starter: Der Schalter im Raum wird betätigt.
- Bedingung: Der Ein-/Aus-Status des Fernsehers wird als „An“ ausgewertet.
- Aktion: Die Lampen im selben Raum wie der Schalter werden gedimmt.
Diese Knoten werden von der Automatisierungs-Engine entweder seriell oder parallel ausgewertet.
Ein sequenzieller Ablauf enthält Knoten, die in sequenzieller Reihenfolge ausgeführt werden. In der Regel sind das Auslöser, Bedingung und Aktion.
Ein paralleler Ablauf kann mehrere Aktionsknoten haben, die gleichzeitig ausgeführt werden, z. B. das gleichzeitige Einschalten mehrerer Lampen. Knoten, die einem parallelen Ablauf folgen, werden erst ausgeführt, wenn alle Verzweigungen des parallelen Ablaufs abgeschlossen sind.
Es gibt noch andere Knotentypen im Automatisierungsschema. Weitere Informationen finden Sie im Abschnitt Knoten des Entwicklerhandbuchs für Home APIs. Außerdem können Entwickler verschiedene Knotentypen kombinieren, um komplexe Automatisierungen zu erstellen, z. B.:
Entwickler stellen diese Knoten der Automatisierungs-Engine mit einer domänenspezifischen Sprache (DSL) zur Verfügung, die speziell für Google Home-Automatisierungen entwickelt wurde.
Automation DSL kennenlernen
Eine domainspezifische Sprache (DSL) ist eine Sprache, mit der das Systemverhalten in Code erfasst wird. Der Compiler generiert Datenklassen, die in JSON-Protokollpuffer serialisiert und zum Aufrufen der Automatisierungsdienste von Google verwendet werden.
Die DSL sucht nach dem folgenden Schema:
automation {
name = "AutomationName"
description = "An example automation description."
isActive = true
sequential {
val onOffTrait = starter<_>(device1, OnOffLightDevice, OnOff)
condition() { expression = onOffTrait.onOff equals true }
action(device2, OnOffLightDevice) { command(OnOff.on()) }
}
}
Die Automatisierung im vorherigen Beispiel synchronisiert zwei Glühbirnen. Wenn sich der OnOff
-Status von device1
in On
(onOffTrait.onOff equals true
) ändert, wird der OnOff
-Status von device2
in On
(command(OnOff.on()
) geändert.
Bei der Arbeit mit Automatisierungen gibt es Ressourcenlimits.
Automatisierungen sind ein sehr nützliches Tool, um automatisierte Funktionen in einem Smart Home zu erstellen. Im einfachsten Fall können Sie eine Automatisierung explizit so programmieren, dass bestimmte Geräte und Eigenschaften verwendet werden. Ein praktischerer Anwendungsfall ist jedoch der, bei dem der Nutzer die Geräte, Befehle und Parameter einer Automatisierung in der App konfigurieren kann. Im nächsten Abschnitt wird beschrieben, wie Sie einen Automatisierungseditor erstellen, mit dem Nutzer genau das tun können.
4. Automatisierungs-Editor erstellen
In der Beispiel-App erstellen wir einen Automatisierungseditor, mit dem Nutzer Geräte, die gewünschten Funktionen (Aktionen) und die Auslöser für die Automatisierungen auswählen können.
Auslöser einrichten
Der Automatisierungsauslöser ist der Ausgangspunkt für die Automatisierung. Ein Auslöser löst eine Automatisierung aus, wenn ein bestimmtes Ereignis eintritt. In der Beispiel-App erfassen wir die Automatisierungsauslöser mit der Klasse StarterViewModel
in der Quelldatei StarterViewModel.kt
und zeigen die Editoransicht mit der StarterView
(StarterView.kt
) an.
Für einen Auslöserknoten sind die folgenden Elemente erforderlich:
- Gerät
- Merkmal
- Vorgang
- Wert
Das Gerät und das Merkmal können aus den von der Devices API zurückgegebenen Objekten ausgewählt werden. Die Befehle und Parameter für jedes unterstützte Gerät sind komplexer und müssen separat behandelt werden.
Die App definiert eine vordefinierte Liste von Vorgängen:
// List of operations available when creating automation starters:
enum class Operation {
EQUALS,
NOT_EQUALS,
GREATER_THAN,
GREATER_THAN_OR_EQUALS,
LESS_THAN,
LESS_THAN_OR_EQUALS
}
Anschließend werden für jede unterstützte Eigenschaft die unterstützten Vorgänge erfasst:
// List of operations available when comparing booleans:
object BooleanOperations : Operations(listOf(
Operation.EQUALS,
Operation.NOT_EQUALS
))
// List of operations available when comparing values:
object LevelOperations : Operations(listOf(
Operation.GREATER_THAN,
Operation.GREATER_THAN_OR_EQUALS,
Operation.LESS_THAN,
Operation.LESS_THAN_OR_EQUALS
))
In ähnlicher Weise werden in der Beispiel-App Werte erfasst, die Merkmalen zugewiesen werden können:
enum class OnOffValue {
On,
Off,
}
enum class ThermostatValue {
Heat,
Cool,
Off,
}
Außerdem wird eine Zuordnung zwischen den von der App und den von den APIs definierten Werten verwaltet:
val valuesOnOff: Map Boolean> = mapOf(
OnOffValue.On to true,
OnOffValue.Off to false,
)
val valuesThermostat: Map ThermostatTrait.SystemModeEnum> = mapOf(
ThermostatValue.Heat to ThermostatTrait.SystemModeEnum.Heat,
ThermostatValue.Cool to ThermostatTrait.SystemModeEnum.Cool,
ThermostatValue.Off to ThermostatTrait.SystemModeEnum.Off,
)
Die App zeigt dann eine Reihe von Ansichtselementen an, mit denen Nutzer die erforderlichen Felder auswählen können.
Entfernen Sie in der Datei StarterView.kt
den Kommentar zu Schritt 4.1.1, um alle Auslösergeräte zu rendern und einen Klick-Callback in einer DropdownMenu
-Datei zu implementieren:
val deviceVMs: List = structureVM.deviceVMs.collectAsState().value
...
DropdownMenu(expanded = expandedDeviceSelection, onDismissRequest = { expandedDeviceSelection = false }) {
// TODO: 4.1.1 - Starter device selection dropdown
// for (deviceVM in deviceVMs) {
// DropdownMenuItem(
// text = { Text(deviceVM.name) },
// onClick = {
// scope.launch {
// starterDeviceVM.value = deviceVM
// starterType.value = deviceVM.type.value
// starterTrait.value = null
// starterOperation.value = null
// }
// expandedDeviceSelection = false
// }
// )
// }
}
Entfernen Sie in der Datei StarterView.kt
den Kommentar zu Schritt 4.1.2, um alle Eigenschaften des Auslösegeräts zu rendern und einen Klick-Callback in einer DropdownMenu
-Datei zu implementieren:
// Selected starter attributes for StarterView on screen:
val starterDeviceVM: MutableState = remember {
mutableStateOf(starterVM.deviceVM.value) }
...
DropdownMenu(expanded = expandedTraitSelection, onDismissRequest = { expandedTraitSelection = false }) {
// TODO: 4.1.2 - Starter device traits selection dropdown
// val deviceTraits = starterDeviceVM.value?.traits?.collectAsState()?.value!!
// for (trait in deviceTraits) {
// DropdownMenuItem(
// text = { Text(trait.factory.toString()) },
// onClick = {
// scope.launch {
// starterTrait.value = trait.factory
// starterOperation.value = null
// }
// expandedTraitSelection = false
// }
// )
}
}
Entferne in der Datei StarterView.kt
den Kommentar zu Schritt 4.1.3, um alle Vorgänge der ausgewählten Eigenschaft zu rendern und einen Klick-Callback in einer DropdownMenu
-Datei zu implementieren:
val starterOperation: MutableState = remember {
mutableStateOf(starterVM.operation.value) }
...
DropdownMenu(expanded = expandedOperationSelection, onDismissRequest = { expandedOperationSelection = false }) {
// ...
if (!StarterViewModel.starterOperations.containsKey(starterTrait.value))
return@DropdownMenu
// TODO: 4.1.3 - Starter device trait operations selection dropdown
// val operations: List = StarterViewModel.starterOperations.get(starterTrait.value ?: OnOff)?.operations!!
// for (operation in operations) {
// DropdownMenuItem(
// text = { Text(operation.toString()) },
// onClick = {
// scope.launch {
// starterOperation.value = operation
// }
// expandedOperationSelection = false
// }
// )
// }
}
Entferne in der Datei StarterView.kt
den Kommentar zu Schritt 4.1.4, um alle Werte des ausgewählten Merkmals zu rendern, und implementiere einen Klick-Callback in einer DropdownMenu
:
when (starterTrait.value) {
OnOff -> {
...
DropdownMenu(expanded = expandedBooleanSelection, onDismissRequest = { expandedBooleanSelection = false }) {
// TODO: 4.1.4 - Starter device trait values selection dropdown
// for (value in StarterViewModel.valuesOnOff.keys) {
// DropdownMenuItem(
// text = { Text(value.toString()) },
// onClick = {
// scope.launch {
// starterValueOnOff.value = StarterViewModel.valuesOnOff.get(value)
// }
// expandedBooleanSelection = false
// }
// )
// }
}
...
}
LevelControl -> {
...
}
}
Entfernen Sie in der Datei StarterView.kt
den Kommentar zu Schritt 4.1.5, um alle Auslöservariablen ViewModel
in der Auslöservariablen ViewModel
(draftVM.starterVMs
) der automatisierten Vorlage zu speichern.
val draftVM: DraftViewModel = homeAppVM.selectedDraftVM.collectAsState().value!!
// Save starter button:
Button(
enabled = isOptionsSelected && isValueProvided,
onClick = {
scope.launch {
// TODO: 4.1.5 - store all starter ViewModel variables into draft ViewModel
// starterVM.deviceVM.emit(starterDeviceVM.value)
// starterVM.trait.emit(starterTrait.value)
// starterVM.operation.emit(starterOperation.value)
// starterVM.valueOnOff.emit(starterValueOnOff.value!!)
// starterVM.valueLevel.emit(starterValueLevel.value!!)
// starterVM.valueBooleanState.emit(starterValueBooleanState.value!!)
// starterVM.valueOccupancy.emit(starterValueOccupancy.value!!)
// starterVM.valueThermostat.emit(starterValueThermostat.value!!)
//
// draftVM.starterVMs.value.add(starterVM)
// draftVM.selectedStarterVM.emit(null)
}
})
{ Text(stringResource(R.string.starter_button_create)) }
Wenn Sie die App ausführen und eine neue Automatisierung und einen neuen Auslöser auswählen, sollte eine Ansicht wie die folgende angezeigt werden:
Die Beispiel-App unterstützt nur Auslöser, die auf Gerätemerkmalen basieren.
Aktionen einrichten
Die Automatisierungsaktion spiegelt den zentralen Zweck einer Automatisierung wider, also wie sie eine Änderung in der realen Welt bewirkt. In der Beispiel-App erfassen wir die Automatisierungsaktionen mit der Klasse ActionViewModel
und zeigen die Editoransicht mit der Klasse ActionView
an.
In der Beispiel-App werden die folgenden Home APIs-Entitäten verwendet, um die Automatisierungsaktionsknoten zu definieren:
- Gerät
- Merkmal
- Befehl
- Wert (optional)
Für jede Gerätebefehlsaktion wird ein Befehl verwendet. Für einige Aktionen ist jedoch auch ein zugehöriger Parameterwert erforderlich, z. B. MoveToLevel()
und ein Zielprozentsatz.
Das Gerät und das Merkmal können aus den von der Devices API zurückgegebenen Objekten ausgewählt werden.
Die App definiert eine vordefinierte Liste von Befehlen:
// List of operations available when creating automation starters:
enum class Action {
ON,
OFF,
MOVE_TO_LEVEL,
MODE_HEAT,
MODE_COOL,
MODE_OFF,
}
Die App überwacht die unterstützten Vorgänge für jedes unterstützte Merkmal:
// List of operations available when comparing booleans:
object OnOffActions : Actions(listOf(
Action.ON,
Action.OFF,
))
// List of operations available when comparing booleans:
object LevelActions : Actions(listOf(
Action.MOVE_TO_LEVEL
))
// List of operations available when comparing booleans:
object ThermostatActions : Actions(listOf(
Action.MODE_HEAT,
Action.MODE_COOL,
Action.MODE_OFF,
))
// Map traits and the comparison operations they support:
val actionActions: Map Trait>, Actions> = mapOf(
OnOff to OnOffActions,
LevelControl to LevelActions,
// BooleanState - No Actions
// OccupancySensing - No Actions
Thermostat to ThermostatActions,
)
Für Befehle mit einem oder mehreren Parametern gibt es auch eine Variable:
val valueLevel: MutableStateFlow
Die API zeigt eine Reihe von Ansichtselementen an, mit denen Nutzer die erforderlichen Felder auswählen können.
Entferne in der Datei ActionView.kt
den Kommentar zu Schritt 4.2.1, um alle Aktionsgeräte zu rendern, und implementiere einen Klick-Callback in einer DropdownMenu
, um actionDeviceVM
festzulegen.
val deviceVMs = structureVM.deviceVMs.collectAsState().value
...
DropdownMenu(expanded = expandedDeviceSelection, onDismissRequest = { expandedDeviceSelection = false }) {
// TODO: 4.2.1 - Action device selection dropdown
// for (deviceVM in deviceVMs) {
// DropdownMenuItem(
// text = { Text(deviceVM.name) },
// onClick = {
// scope.launch {
// actionDeviceVM.value = deviceVM
// actionTrait.value = null
// actionAction.value = null
// }
// expandedDeviceSelection = false
// }
// )
// }
}
Entferne in der Datei ActionView.kt
den Kommentar zu Schritt 4.2.2, um alle Merkmale von actionDeviceVM
zu rendern, und implementiere einen Klick-Callback in einer DropdownMenu
, um das actionTrait
festzulegen, das dem Attribut entspricht, zu dem der Befehl gehört.
val actionDeviceVM: MutableState = remember {
mutableStateOf(actionVM.deviceVM.value) }
...
DropdownMenu(expanded = expandedTraitSelection, onDismissRequest = { expandedTraitSelection = false }) {
// TODO: 4.2.2 - Action device traits selection dropdown
// val deviceTraits: List = actionDeviceVM.value?.traits?.collectAsState()?.value!!
// for (trait in deviceTraits) {
// DropdownMenuItem(
// text = { Text(trait.factory.toString()) },
// onClick = {
// scope.launch {
// actionTrait.value = trait
// actionAction.value = null
// }
// expandedTraitSelection = false
// }
// )
// }
}
Entfernen Sie in der Datei ActionView.kt
den Kommentar zu Schritt 4.2.3, um alle verfügbaren Aktionen von actionTrait
zu rendern, und implementieren Sie einen Klick-Callback in einer DropdownMenu
, um die actionAction
festzulegen, die die ausgewählte Automatisierungsaktion darstellt.
DropdownMenu(expanded = expandedActionSelection, onDismissRequest = { expandedActionSelection = false }) {
// ...
if (!ActionViewModel.actionActions.containsKey(actionTrait.value?.factory))
return@DropdownMenu
// TODO: 4.2.3 - Action device trait actions (commands) selection dropdown
// val actions: List = ActionViewModel.actionActions.get(actionTrait.value?.factory)?.actions!!
// for (action in actions) {
// DropdownMenuItem(
// text = { Text(action.toString()) },
// onClick = {
// scope.launch {
// actionAction.value = action
// }
// expandedActionSelection = false
// }
// )
// }
}
Entfernen Sie in der Datei ActionView.kt
die Kommentarzeichen bei Schritt 4.2.4, um die verfügbaren Werte der Merkmalaktion (Befehl) zu rendern und den Wert im Rückruf bei Wertänderung in actionValueLevel
zu speichern:
when (actionTrait.value?.factory) {
LevelControl -> {
// TODO: 4.2.4 - Action device trait action(command) values selection widget
// Column (Modifier.padding(horizontal = 16.dp, vertical = 8.dp).fillMaxWidth()) {
// Text(stringResource(R.string.action_title_value), fontSize = 16.sp, fontWeight = FontWeight.SemiBold)
// }
//
// Box (Modifier.padding(horizontal = 24.dp, vertical = 8.dp)) {
// LevelSlider(value = actionValueLevel.value?.toFloat()!!, low = 0f, high = 254f, steps = 0,
// modifier = Modifier.padding(top = 16.dp),
// onValueChange = { value : Float -> actionValueLevel.value = value.toUInt().toUByte() }
// isEnabled = true
// )
// }
...
}
Entfernen Sie in der Datei ActionView.kt
den Kommentar zu Schritt 4.2.5, um alle Variablen der Aktion ViewModel
in der Aktion ViewModel
(draftVM.actionVMs
) der Entwurfsautomatisierung zu speichern:
val draftVM: DraftViewModel = homeAppVM.selectedDraftVM.collectAsState().value!!
// Save action button:
Button(
enabled = isOptionsSelected,
onClick = {
scope.launch {
// TODO: 4.2.5 - store all action ViewModel variables into draft ViewModel
// actionVM.deviceVM.emit(actionDeviceVM.value)
// actionVM.trait.emit(actionTrait.value)
// actionVM.action.emit(actionAction.value)
// actionVM.valueLevel.emit(actionValueLevel.value)
//
// draftVM.actionVMs.value.add(actionVM)
// draftVM.selectedActionVM.emit(null)
}
})
{ Text(stringResource(R.string.action_button_create)) }
Wenn Sie die App ausführen und eine neue Automatisierung und Aktion auswählen, sollte eine Ansicht wie die folgende angezeigt werden:
In der Beispiel-App werden nur Aktionen unterstützt, die auf Gerätemerkmalen basieren.
Automatisierungsvorlage rendern
Wenn die DraftViewModel
fertig ist, kann sie mit HomeAppView.kt
gerendert werden:
fun HomeAppView (homeAppVM: HomeAppViewModel) {
...
// If a draft automation is selected, show the draft editor:
if (selectedDraftVM != null) {
DraftView(homeAppVM)
}
...
}
In DraftView.kt
:
fun DraftView (homeAppVM: HomeAppViewModel) {
val draftVM: DraftViewModel = homeAppVM.selectedDraftVM.collectAsState().value!!
...
// Draft Starters:
DraftStarterList(draftVM)
// Draft Actions:
DraftActionList(draftVM)
}
Automatisierung erstellen
Nachdem Sie gelernt haben, wie Sie Auslöser und Aktionen erstellen, können Sie einen Automatisierungsentwurf erstellen und an die Automatisierungs-API senden. Die API hat eine createAutomation()
-Funktion, die einen Automatisierungsentwurf als Argument annimmt und eine neue Automatisierungs-Instanz zurückgibt.
Die Vorbereitung des Automatisierungs-Entwurfs erfolgt in der Klasse DraftViewModel
in der Beispiel-App. Sehen Sie sich die Funktion getDraftAutomation()
an, um mehr darüber zu erfahren, wie wir den Automatisierungsentwurf mit den Auslöser- und Aktionsvariablen aus dem vorherigen Abschnitt strukturieren.
Entfernen Sie in der Datei DraftViewModel.kt
den Kommentar zu Schritt 4.4.1, um die „select“-Ausdrücke zu erstellen, die zum Erstellen des Automatisierungsdiagramms erforderlich sind, wenn das Auslöserattribut OnOff
ist:
val starterVMs: List = starterVMs.value
val actionVMs: List = actionVMs.value
...
fun getDraftAutomation() : DraftAutomation {
...
val starterVMs: List = starterVMs.value
...
return automation {
this.name = name
this.description = description
this.isActive = true
// The sequential block wrapping all nodes:
sequential {
// The select block wrapping all starters:
select {
// Iterate through the selected starters:
for (starterVM in starterVMs) {
// The sequential block for each starter (should wrap the Starter Expression!)
sequential {
...
val starterTrait: TraitFactory Trait> = starterVM.trait.value!!
...
when (starterTrait) {
OnOff -> {
// TODO: 4.4.1 - Set starter expressions according to trait type
// val onOffValue: Boolean = starterVM.valueOnOff.value
// val onOffExpression: TypedExpression OnOff> =
// starterExpression as TypedExpression OnOff>
// when (starterOperation) {
// StarterViewModel.Operation.EQUALS ->
// condition { expression = onOffExpression.onOff equals onOffValue }
// StarterViewModel.Operation.NOT_EQUALS ->
// condition { expression = onOffExpression.onOff notEquals onOffValue }
// else -> { MainActivity.showError(this, "Unexpected operation for OnOf
// }
}
LevelControl -> {
...
// Function to allow manual execution of the automation:
manualStarter()
...
}
Entfernen Sie in der Datei DraftViewModel.kt
den Kommentar zu Schritt 4.4.2, um die parallelen Ausdrücke zu erstellen, die zum Erstellen der Automatisierungsgrafik erforderlich sind, wenn die ausgewählte Aktionseigenschaft LevelControl
und die ausgewählte Aktion MOVE_TO_LEVEL
ist:
val starterVMs: List = starterVMs.value
val actionVMs: List = actionVMs.value
...
fun getDraftAutomation() : DraftAutomation {
...
return automation {
this.name = name
this.description = description
this.isActive = true
// The sequential block wrapping all nodes:
sequential {
...
// Parallel block wrapping all actions:
parallel {
// Iterate through the selected actions:
for (actionVM in actionVMs) {
val actionDeviceVM: DeviceViewModel = actionVM.deviceVM.value!!
// Action Expression that the DSL will check for:
action(actionDeviceVM.device, actionDeviceVM.type.value.factory) {
val actionCommand: Command = when (actionVM.action.value) {
ActionViewModel.Action.ON -> { OnOff.on() }
ActionViewModel.Action.OFF -> { OnOff.off() }
// TODO: 4.4.2 - Set starter expressions according to trait type
// ActionViewModel.Action.MOVE_TO_LEVEL -> {
// LevelControl.moveToLevelWithOnOff(
// actionVM.valueLevel.value!!,
// 0u,
// LevelControlTrait.OptionsBitmap(),
// LevelControlTrait.OptionsBitmap()
// )
// }
ActionViewModel.Action.MODE_HEAT -> { SimplifiedThermostat
.setSystemMode(SimplifiedThermostatTrait.SystemModeEnum.Heat) }
...
}
Im letzten Schritt einer Automatisierung implementieren Sie die getDraftAutomation
-Funktion, um einen AutomationDraft.
zu erstellen.
Entfernen Sie in der Datei HomeAppViewModel.kt
den Kommentar zu Schritt 4.4.3, um die Automatisierung zu erstellen, indem Sie die Home APIs aufrufen und Ausnahmen behandeln:
fun createAutomation(isPending: MutableState) {
viewModelScope.launch {
val structure : Structure = selectedStructureVM.value?.structure!!
val draft : DraftAutomation = selectedDraftVM.value?.getDraftAutomation()!!
isPending.value = true
// TODO: 4.4.3 - Call the Home API to create automation and handle exceptions
// // Call Automation API to create an automation from a draft:
// try {
// structure.createAutomation(draft)
// }
// catch (e: Exception) {
// MainActivity.showError(this, e.toString())
// isPending.value = false
// return@launch
// }
// Scrap the draft and automation candidates used in the process:
selectedCandidateVMs.emit(null)
selectedDraftVM.emit(null)
isPending.value = false
}
}
Führen Sie jetzt die App aus und sehen Sie sich die Änderungen auf Ihrem Gerät an.
Nachdem Sie einen Auslöser und eine Aktion ausgewählt haben, können Sie die Automatisierung erstellen:
Geben Sie einen eindeutigen Namen für die Automatisierung ein und tippen Sie dann auf die Schaltfläche Automatisierung erstellen. Dadurch werden die APIs aufgerufen und Sie werden zur Listenansicht der Automatisierungen mit Ihrer Automatisierung zurückgeleitet:
Tippen Sie auf die gerade erstellte Automatisierung und sehen Sie sich an, wie sie von den APIs zurückgegeben wird.
Die API gibt einen Wert zurück, der angibt, ob eine Automatisierung gültig und derzeit aktiv ist. Es ist möglich, Automatisierungen zu erstellen, die die Validierung nicht bestehen, wenn sie auf der Serverseite geparst werden. Wenn die Validierung beim Parsen einer Automatisierung fehlschlägt, wird isValid
auf false
gesetzt, was bedeutet, dass die Automatisierung ungültig und inaktiv ist. Wenn Ihre Automatisierung ungültig ist, finden Sie im Feld automation.validationIssues
weitere Informationen.
Achten Sie darauf, dass die Automatisierung als gültig und aktiv festgelegt ist.
Automatisierung testen
Automatisierungen können auf zwei Arten ausgeführt werden:
- Mit einem Auslöser-Ereignis Wenn die Bedingungen erfüllt sind, wird die in der Automatisierung festgelegte Aktion ausgelöst.
- Mit einem API-Aufruf zur manuellen Ausführung.
Wenn für einen Automatisierungsentwurf im DSL-Block „automation draft“ eine manualStarter()
definiert ist, unterstützt die Automatisierungs-Engine die manuelle Ausführung für diese Automatisierung. Dies ist bereits in den Codebeispielen in der Beispiel-App vorhanden.
Da Sie sich auf Ihrem Mobilgerät noch in der Ablauf-Ansicht befinden, tippen Sie auf die Schaltfläche Manuell ausführen. Dadurch wird automation.execute()
aufgerufen, wodurch der Aktionsbefehl auf dem Gerät ausgeführt wird, das Sie bei der Einrichtung der Automatisierung ausgewählt haben.
Nachdem Sie den Aktionsbefehl durch manuelle Ausführung über die API validiert haben, können Sie prüfen, ob er auch über den von Ihnen definierten Auslöser ausgeführt wird.
Rufen Sie den Tab „Geräte“ auf, wählen Sie das Aktionsgerät und das Merkmal aus und legen Sie einen anderen Wert fest. Legen Sie beispielsweise die LevelControl
(Helligkeit) von light2
auf 50 % fest, wie im folgenden Screenshot dargestellt:
Versuchen wir jetzt, die Automatisierung über das Auslösegerät zu starten. Wählen Sie das Auslösegerät aus, das Sie beim Erstellen der Automatisierung ausgewählt haben. Aktivieren oder deaktivieren Sie das ausgewählte Merkmal (z. B. OnOff
von starter outlet1
auf On
setzen):
Sie sehen, dass dadurch auch die Automatisierung ausgeführt und das Attribut LevelControl
des Aktionsgeräts light2
auf den ursprünglichen Wert 100 % gesetzt wird:
Herzlichen Glückwunsch! Sie haben mit den Home APIs erfolgreich automatisierte Abläufe erstellt.
Weitere Informationen zur Automatisierungen-API finden Sie unter Android Automation API.
5. Funktionen entdecken
Die Home APIs umfassen eine spezielle API namens Discovery API, mit der Entwickler abfragen können, welche automatisierbaren Eigenschaften auf einem bestimmten Gerät unterstützt werden. In der Beispielanwendung können Sie mit dieser API herausfinden, welche Befehle verfügbar sind.
Befehle entdecken
In diesem Abschnitt erfahren Sie, wie Sie unterstützte CommandCandidates
finden und wie Sie eine Automatisierung basierend auf den gefundenen Kandidatenknoten erstellen.
In der Beispiel-App wird device.candidates()
aufgerufen, um eine Liste von Kandidaten abzurufen, die Instanzen von CommandCandidate
, EventCandidate
oder TraitAttributesCandidate
enthalten können.
Entfernen Sie in der Datei HomeAppViewModel.kt
den Kommentar zu Schritt 5.1.1, um die Kandidatenliste abzurufen und nach dem Typ Candidate
zu filtern:
fun showCandidates() {
...
// TODO: 5.1.1 - Retrieve automation candidates, filtering to include CommandCandidate types only
// // Retrieve a set of initial automation candidates from the device:
// val candidates: Set = deviceVM.device.candidates().first()
//
// for (candidate in candidates) {
// // Check whether the candidate trait is supported:
// if(candidate.trait !in HomeApp.supportedTraits)
// continue
// // Check whether the candidate type is supported:
// when (candidate) {
// // Command candidate type:
// is CommandCandidate -> {
// // Check whether the command candidate has a supported command:
// if (candidate.commandDescriptor !in ActionViewModel.commandMap)
// continue
// }
// // Other candidate types are currently unsupported:
// else -> { continue }
// }
//
// candidateVMList.add(CandidateViewModel(candidate, deviceVM))
// }
...
// Store the ViewModels:
selectedCandidateVMs.emit(candidateVMList)
}
Sehen Sie sich an, wie nach CommandCandidate.
gefiltert wird. Die von der API zurückgegebenen Kandidaten gehören zu verschiedenen Typen. Die Beispiel-App unterstützt CommandCandidate
. Entfernen Sie den Kommentar zu Schritt 5.1.2 in der in ActionViewModel.kt
definierten commandMap
, um die folgenden unterstützten Merkmale festzulegen:
// Map of supported commands from Discovery API:
val commandMap: Map Action> = mapOf(
// TODO: 5.1.2 - Set current supported commands
// OnOffTrait.OnCommand to Action.ON,
// OnOffTrait.OffCommand to Action.OFF,
// LevelControlTrait.MoveToLevelWithOnOffCommand to Action.MOVE_TO_LEVEL
)
Jetzt, da wir die Discovery API aufrufen und die Ergebnisse filtern können, die wir in der Beispiel-App unterstützen, sehen wir uns an, wie wir sie in unseren Editor einbinden können.
Weitere Informationen zur Discovery API finden Sie unter Geräteerkennung auf Android-Geräten nutzen.
Editor einbinden
Am häufigsten werden erkannte Aktionen einem Endnutzer zur Auswahl präsentiert. Kurz bevor der Nutzer die Felder für den Automatisierungsentwurf auswählt, können wir ihm die Liste der erkannten Aktionen anzeigen. Je nach ausgewähltem Wert können wir den Aktionsknoten im Automatisierungsentwurf vorab ausfüllen.
Die CandidatesView.kt
-Datei enthält die Ansichtsklasse, in der die gefundenen Kandidaten angezeigt werden. Entfernen Sie den Kommentar zu Schritt 5.2.1, um die .clickable{}
-Funktion von CandidateListItem
zu aktivieren, die homeAppVM.selectedDraftVM
auf candidateVM
festlegt:
fun CandidateListItem (candidateVM: CandidateViewModel, homeAppVM: HomeAppViewModel) {
val scope: CoroutineScope = rememberCoroutineScope()
Box (Modifier.padding(horizontal = 24.dp, vertical = 8.dp)) {
Column (Modifier.fillMaxWidth().clickable {
// TODO: 5.2.1 - Set the selectedDraftVM to the selected candidate
// scope.launch { homeAppVM.selectedDraftVM.emit(DraftViewModel(candidateVM)) }
}) {
...
}
}
}
Ähnlich wie bei Schritt 4.3 in HomeAppView.kt
wird die DraftView(...) in
DraftView.kt gerendert, wenn selectedDraftVM
festgelegt ist:
fun HomeAppView (homeAppVM: HomeAppViewModel) {
...
val selectedDraftVM: DraftViewModel? by homeAppVM.selectedDraftVM.collectAsState()
...
// If a draft automation is selected, show the draft editor:
if (selectedDraftVM != null) {
DraftView(homeAppVM)
}
...
}
Versuchen Sie es noch einmal. Tippen Sie dazu auf Lampe2 – MOVE_TO_LEVEL, wie im vorherigen Abschnitt gezeigt. Daraufhin werden Sie aufgefordert, eine neue Automatisierung basierend auf dem Befehl des Kandidaten zu erstellen:
Da Sie nun mit dem Erstellen von Automatisierungen in der Beispiel-App vertraut sind, können Sie Automatisierungen in Ihre Apps einbinden.
6. Beispiele für erweiterte Automatisierung
Bevor wir zum Ende kommen, sehen wir uns noch einige weitere Beispiele für die Automatisierungs-DSL an. Diese veranschaulichen einige der erweiterten Funktionen, die Sie mit den APIs nutzen können.
Uhrzeit als Auslöser
Neben den Gerätemerkmalen bieten Google Home APIs auch gebäudebasierte Merkmale wie Time
. Sie können eine Automatisierung mit einem zeitbasierten Auslöser erstellen, z. B.:
automation {
name = "AutomationName"
description = "An example automation description."
isActive = true
description = "Do ... actions when time is up."
sequential {
// starter
val starter = starter<_>(structure, Time.ScheduledTimeEvent) {
parameter(
Time.ScheduledTimeEvent.clockTime(
LocalTime.of(hour, min, sec, 0)
)
)
}
// action
...
}
}
Assistant-Broadcast als Aktion
Das Attribut AssistantBroadcast
ist entweder als Attribut auf Geräteebene in einem SpeakerDevice
verfügbar (sofern der Lautsprecher dies unterstützt) oder als Attribut auf Gebäudeebene (da Google-Lautsprecher und Android-Mobilgeräte Assistant-Ansagen wiedergeben können). Beispiel:
automation {
name = "AutomationName"
description = "An example automation description."
isActive = true
description = "Broadcast in Speaker when ..."
sequential {
// starter
...
// action
action(structure) {
command(
AssistantBroadcast.broadcast("Time is up!!")
)
}
}
}
DelayFor
und suppressFor
verwenden
Die Automatisierungs-API bietet auch erweiterte Operatoren wie delayFor, mit dem sich Befehle verzögern lassen, und suppressFor, mit dem sich verhindern lässt, dass eine Automatisierung innerhalb eines bestimmten Zeitraums durch dieselben Ereignisse ausgelöst wird. Hier einige Beispiele für die Verwendung dieser Operatoren:
sequential {
val starterNode = starter<_>(device, OccupancySensorDevice, MotionDetection)
// only proceed if there is currently motion taking place
condition { starterNode.motionDetectionEventInProgress equals true }
// ignore the starter for one minute after it was last triggered
suppressFor(Duration.ofMinutes(1))
// make announcements three seconds apart
action(device, SpeakerDevice) {
command(AssistantBroadcast.broadcast("Intruder detected!"))
}
delayFor(Duration.ofSeconds(3))
action(device, SpeakerDevice) {
command(AssistantBroadcast.broadcast("Intruder detected!"))
}
...
}
AreaPresenceState
in einem Auslöser verwenden
AreaPresenceState
ist ein Attribut auf Gebäudeebene, mit dem erkannt wird, ob jemand zu Hause ist.
Im folgenden Beispiel wird gezeigt, wie die Türen automatisch verriegelt werden, wenn nach 22:00 Uhr jemand zu Hause ist:
automation {
name = "Lock the doors when someone is home after 10pm"
description = "1 starter, 2 actions"
sequential {
val unused =
starter(structure, event = Time.ScheduledTimeEvent) {
parameter(Time.ScheduledTimeEvent.clockTime(LocalTime.of(22, 0, 0, 0)))
}
val stateReaderNode = stateReader<_>(structure, AreaPresenceState)
condition {
expression =
stateReaderNode.presenceState equals
AreaPresenceStateTrait.PresenceState.PresenceStateOccupied
}
action(structure) { command(AssistantBroadcast.broadcast("Locks are being applied")) }
for (lockDevice in lockDevices) {
action(lockDevice, DoorLockDevice) {
command(Command(DoorLock, DoorLockTrait.LockDoorCommand.requestId.toString(), mapOf()))
}
}
}
Jetzt, da Sie mit diesen erweiterten Automatisierungsfunktionen vertraut sind, können Sie tolle Apps erstellen.
7. Glückwunsch!
Glückwunsch! Sie haben den zweiten Teil der Entwicklung einer Android-App mit den Google Home APIs erfolgreich abgeschlossen. In diesem Codelab haben Sie die Automation API und die Discovery API kennengelernt.
Wir wünschen Ihnen viel Spaß beim Erstellen von Apps, mit denen Sie Geräte im Google Home-System kreativ steuern und mithilfe der Home APIs spannende Automatisierungsszenarien erstellen können.
Nächste Schritte
- Im Hilfeartikel Fehlerbehebung erfahren Sie, wie Sie Apps effektiv debuggen und Probleme mit den Home APIs beheben.
- Sie können uns jederzeit über den Issue Tracker im Smart-Home-Supportthema mit Empfehlungen kontaktieren oder Probleme melden.