1. Hinweis
Dies ist das zweite Codelab in einer Reihe zum Erstellen einer Android-App mit den Google Home-APIs. In diesem Codelab erfahren Sie, wie Sie automatisierte Abläufe für Ihr Zuhause erstellen, und erhalten einige Tipps zu den Best Practices für die Verwendung der APIs. Wenn Sie das erste Codelab Build a mobile app using the Home APIs on Android noch nicht durchgearbeitet haben, empfehlen wir Ihnen, dies zu tun, bevor Sie mit diesem Codelab beginnen.
Die Google Home-APIs bieten eine Reihe von Bibliotheken für Android-Entwickler, mit denen Smart-Home-Geräte im Google Home-Ökosystem gesteuert werden können. Mit diesen neuen APIs können Entwickler Automatisierungen für ein Smart Home einrichten, mit denen Gerätefunktionen basierend auf vordefinierten 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
- Arbeite das Codelab zum Erstellen einer mobilen App mit den Home-APIs unter Android durch.
- Kenntnisse des Google Home-Ökosystems (Cloud-to-Cloud und Matter).
- Eine Workstation, auf der Android Studio (2024.3.1 Ladybug oder höher) installiert ist.
- 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. Ein Emulator funktioniert nicht. Für die Beispiel-App werden nur physische Android-Smartphones unterstützt.
- Ein kompatibler 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
- Hier erfahren Sie, wie Sie mit den Home-APIs automatisierte Abläufe für Smart-Home-Geräte erstellen.
- So verwenden Sie die Discovery APIs, um die unterstützten Gerätefunktionen zu ermitteln.
- So wenden Sie die Best Practices beim Erstellen von Apps mit den Home-APIs an.
2. Projekt wird eingerichtet
Das folgende Diagramm veranschaulicht die Architektur einer Home APIs-App:
- App-Code:Der Hauptcode, an dem Entwickler arbeiten, um die Benutzeroberfläche der App und die Logik für die Interaktion mit dem Home APIs SDK zu 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 unter Android:GMSCore, auch bekannt als Google Play-Dienste, ist eine Google-Plattform, die wichtige Systemdienste bereitstellt und wichtige Funktionen auf allen zertifizierten Android-Geräten ermöglicht. Das Home-Modul der Google Play-Dienste enthält die Dienste, die mit den Home APIs interagieren.
In diesem Codelab bauen wir auf den Inhalten des Codelabs Build a mobile app using the Home APIs on Android auf.
Du musst eine Struktur mit mindestens zwei unterstützten Geräten eingerichtet haben, die mit dem Konto funktionieren. Da wir in diesem Codelab Automatisierungen einrichten (eine Änderung des Gerätestatus löst eine Aktion auf einem anderen Gerät aus), 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 codelab-branch-2
-Branch:
$ git clone -b codelab-branch-2 https://github.com/google-home/google-home-api-sample-app-android.git
Dieser Branch unterscheidet sich von dem, der in Build a mobile app using the Home APIs on Android verwendet wird. Dieser Zweig der Codebasis baut auf dem ersten Codelab auf. Dieses Mal wird anhand der Beispiele gezeigt, wie Sie Automatisierungen erstellen. Wenn Sie das vorherige Codelab abgeschlossen haben und alle Funktionen funktionieren, können Sie für dieses Codelab dasselbe Android Studio-Projekt verwenden, anstatt codelab-branch-2
zu nutzen.
Wenn Sie den Quellcode kompiliert haben und er auf Ihrem Mobilgerät ausgeführt werden kann, fahren Sie mit dem nächsten Abschnitt fort.
3. Weitere Informationen zu Automatisierungen
Automatisierte Abläufe sind eine Reihe von „Wenn X, dann Y“-Anweisungen, mit denen Gerätestatus basierend auf ausgewählten Faktoren automatisch gesteuert werden können. Entwickler können Automatisierungen verwenden, um erweiterte interaktive Funktionen in ihre APIs einzubauen.
Automatisierungen bestehen aus drei verschiedenen Arten von Komponenten, die als Knoten bezeichnet werden: Auslöser, Aktionen und Bedingungen. Diese Knoten arbeiten zusammen, um Verhaltensweisen mithilfe von Smart-Home-Geräten zu automatisieren. In der Regel werden sie in der folgenden Reihenfolge ausgewertet:
- Auslöser: Definiert die Anfangsbedingungen, die die Automatisierung aktivieren, z. B. eine Änderung des Attributwerts. Eine Automatisierung muss einen Auslöser haben.
- Bedingung: Alle zusätzlichen Einschränkungen, die nach dem Auslösen einer Automatisierung ausgewertet werden sollen. Der Ausdruck in einer Condition muss „true“ ergeben, 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 erstellen, die das Licht in einem Raum dimmt, wenn ein Schalter umgelegt wird, während der Fernseher in diesem Raum eingeschaltet ist. In diesem Fall gilt Folgendes:
- Starter: Der Schalter im Raum wird umgelegt.
- Bedingung: Der Ein/Aus-Status des Fernsehers wird als „Ein“ ausgewertet.
- Aktion: Die Lampen im selben Raum wie der Schalter werden gedimmt.
Diese Knoten werden von der Automation 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, Bedingungen und Aktionen.
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 Zweige des parallelen Ablaufs abgeschlossen sind.
Es gibt auch andere Arten von Knoten im Automatisierungsschema. Weitere Informationen dazu finden Sie im Abschnitt Knoten im Entwicklerleitfaden für Home-APIs. Außerdem können Entwickler verschiedene Arten von Knoten kombinieren, um komplexe Automatisierungen zu erstellen, z. B.:
Entwickler stellen diese Knoten der Automation Engine über eine domänenspezifische Sprache (DSL) zur Verfügung, die speziell für Google Home-Automatisierungen entwickelt wurde.
Automatisierungs-DSL kennenlernen
Eine domänenspezifische Sprache (Domain-Specific Language, DSL) ist eine Sprache, mit der das Systemverhalten im Code erfasst wird. Der Compiler generiert Datenklassen, die in Protocol Buffer-JSON serialisiert und für Aufrufe 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 sind Ressourcenlimits zu beachten.
Automatisierungen sind ein sehr nützliches Tool, um automatisierte Funktionen in einem Smart Home zu erstellen. Im einfachsten Anwendungsfall können Sie eine Automatisierung explizit so programmieren, dass bestimmte Geräte und Merkmale verwendet werden. Ein praktischerer Anwendungsfall ist jedoch, wenn die Nutzer die Geräte, Befehle und Parameter einer Automatisierung über die App konfigurieren können. Im nächsten Abschnitt wird beschrieben, wie Sie einen Automatisierungseditor erstellen, mit dem der Nutzer genau das tun kann.
4. Automatisierungs-Editor erstellen
In der Beispiel-App erstellen wir einen Automatisierungseditor, mit dem Nutzer Geräte und die Funktionen (Aktionen) auswählen können, die sie verwenden möchten. Außerdem können sie festlegen, wie die Automatisierungen über Starter ausgelöst werden.
Auslöser einrichten
Der Automatisierungsauslöser ist der Einstiegspunkt für die Automatisierung. Ein Auslöser startet einen automatisierten Ablauf, wenn ein bestimmtes Ereignis eintritt. In der Beispiel-App erfassen wir die Automatisierungsstarter mit der Klasse StarterViewModel
, die sich in der Quelldatei StarterViewModel.kt
befindet, und zeigen die Editoransicht mit StarterView
(StarterView.kt
) an.
Für einen Startknoten sind die folgenden Elemente erforderlich:
- Gerät
- Attribut
- 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 voreingestellte 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 jedes unterstützte Attribut 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
))
Auf ähnliche Weise werden in der Beispiel-App Werte für Merkmale erfasst:
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<OnOffValue, Boolean> = mapOf(
OnOffValue.On to true,
OnOffValue.Off to false,
)
val valuesThermostat: Map<ThermostatValue, ThermostatTrait.SystemModeEnum> = mapOf(
ThermostatValue.Heat to ThermostatTrait.SystemModeEnum.Heat,
ThermostatValue.Cool to ThermostatTrait.SystemModeEnum.Cool,
ThermostatValue.Off to ThermostatTrait.SystemModeEnum.Off,
)
In der App werden dann eine Reihe von Ansichtselementen angezeigt, mit denen Nutzer die erforderlichen Felder auswählen können.
Entfernen Sie die Auskommentierung von Schritt 4.1.1 in der Datei StarterView.kt
, um alle Startergeräte zu rendern und den Klick-Callback in einem DropdownMenu
zu implementieren:
val deviceVMs: List<DeviceViewModel> = 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 die Auskommentierung von Schritt 4.1.2 in der Datei StarterView.kt
, um alle Attribute des Startergeräts zu rendern und den Klick-Callback in einem DropdownMenu
zu implementieren:
// Selected starter attributes for StarterView on screen:
val starterDeviceVM: MutableState<DeviceViewModel?> = 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
// }
// )
}
}
Entfernen Sie die Kommentarzeichen für Schritt 4.1.3 in der Datei StarterView.kt
, um alle Vorgänge des ausgewählten Attributs zu rendern und den Klick-Callback in einem DropdownMenu
zu implementieren:
val starterOperation: MutableState<StarterViewModel.Operation?> = 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.Operation> = StarterViewModel.starterOperations.get(starterTrait.value ?: OnOff)?.operations!!
// for (operation in operations) {
// DropdownMenuItem(
// text = { Text(operation.toString()) },
// onClick = {
// scope.launch {
// starterOperation.value = operation
// }
// expandedOperationSelection = false
// }
// )
// }
}
Entfernen Sie die Kommentarzeichen für Schritt 4.1.4 in der Datei StarterView.kt
, um alle Werte des ausgewählten Attributs zu rendern und den Klick-Callback in einem DropdownMenu
zu implementieren:
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 die Kommentarzeichen für Schritt 4.1.5 in der Datei StarterView.kt
, um alle Startervariablen ViewModel
im Starter ViewModel
(draftVM.starterVMs
) des Entwurfs für die Automatisierung 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 Starter auswählen, sollte eine Ansicht wie die folgende angezeigt werden:
Die Beispiel-App unterstützt nur Starter, die auf Geräte-Traits basieren.
Aktionen einrichten
Die Automatisierungsaktion spiegelt den zentralen Zweck einer Automatisierung wider, also wie sie eine Änderung in der physischen 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
- Attribut
- Befehl
- Wert (optional)
Für jede Gerätebefehlsaktion wird ein Befehl verwendet. Für einige 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 verfolgt unterstützte Vorgänge für jedes unterstützte Attribut:
// 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<TraitFactory<out Trait>, Actions> = mapOf(
OnOff to OnOffActions,
LevelControl to LevelActions,
// BooleanState - No Actions
// OccupancySensing - No Actions
Thermostat to ThermostatActions,
)
Für Befehle, die einen oder mehrere Parameter verwenden, gibt es auch eine Variable:
val valueLevel: MutableStateFlow<UByte?>
Die API zeigt eine Reihe von Ansichtselementen an, mit denen Nutzer die erforderlichen Felder auswählen können.
Entfernen Sie die Kommentarzeichen für Schritt 4.2.1 in der Datei ActionView.kt
, um alle Aktionsgeräte zu rendern und den Klick-Callback in einem DropdownMenu
zu implementieren, 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
// }
// )
// }
}
Entfernen Sie die Auskommentierung von Schritt 4.2.2 in der Datei ActionView.kt
, um alle Merkmale von actionDeviceVM
zu rendern, und implementieren Sie den Klick-Callback in einem DropdownMenu
, um actionTrait
festzulegen. actionTrait
steht für das Merkmal, zu dem der Befehl gehört.
val actionDeviceVM: MutableState<DeviceViewModel?> = remember {
mutableStateOf(actionVM.deviceVM.value) }
...
DropdownMenu(expanded = expandedTraitSelection, onDismissRequest = { expandedTraitSelection = false }) {
// TODO: 4.2.2 - Action device traits selection dropdown
// val deviceTraits: List<Trait> = 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 die Kommentarzeichen für Schritt 4.2.3 in der Datei ActionView.kt
, um alle verfügbaren Aktionen von actionTrait
zu rendern, und implementieren Sie den Klick-Callback in einem DropdownMenu
, um actionAction
festzulegen. actionAction
steht für die ausgewählte Automatisierungsaktion.
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.Action> = 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 die Kommentarzeichen in Schritt 4.2.4 in der Datei ActionView.kt
, um die verfügbaren Werte der Trait-Aktion (Befehl) zu rendern und den Wert im Callback für Wertänderungen 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 die Auskommentierung von Schritt 4.2.5 in der Datei ActionView.kt
, um alle Variablen der Aktion ViewModel
in der Aktion ViewModel
(draftVM.actionVMs
) des Automatisierungsentwurfs 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äteattributen basieren.
Automatisierungsentwurf rendern
Wenn die DraftViewModel
abgeschlossen ist, kann sie von 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 jetzt einen Automatisierungsentwurf erstellen und an die Automation API senden. Die API hat eine createAutomation()
-Funktion, die einen Automatisierungsentwurf als Argument akzeptiert und eine neue Automatisierungsinstanz zurückgibt.
Die Vorbereitung des automatisierten Ablaufs für den Entwurf erfolgt in der Klasse DraftViewModel
in der Beispiel-App. In der Funktion getDraftAutomation()
erfahren Sie mehr darüber, wie wir den automatisierten Ablaufentwurf mit den Starter- und Aktionsvariablen aus dem vorherigen Abschnitt strukturieren.
Entfernen Sie die Kommentarzeichen für Schritt 4.4.1 in der Datei DraftViewModel.kt
, um die „select“-Ausdrücke zu erstellen, die zum Erstellen des Automatisierungsdiagramms erforderlich sind, wenn das Starter-Trait OnOff
ist:
val starterVMs: List<StarterViewModel> = starterVMs.value
val actionVMs: List<ActionViewModel> = actionVMs.value
...
fun getDraftAutomation() : DraftAutomation {
...
val starterVMs: List<StarterViewModel> = 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<out 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<out OnOff> =
// starterExpression as TypedExpression<out 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 die Kommentarzeichen für Schritt 4.4.2 in der Datei DraftViewModel.kt
, um die parallelen Ausdrücke zu erstellen, die zum Erstellen des Automatisierungsdiagramms erforderlich sind, wenn das ausgewählte Aktionstrait LevelControl
und die ausgewählte Aktion MOVE_TO_LEVEL
ist:
val starterVMs: List<StarterViewModel> = starterVMs.value
val actionVMs: List<ActionViewModel> = 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) }
...
}
Der letzte Schritt zum Abschließen einer Automatisierung ist die Implementierung der Funktion getDraftAutomation
, um ein AutomationDraft.
zu erstellen.
Entfernen Sie die Auskommentierung von Schritt 4.4.3 in der Datei HomeAppViewModel.kt
, um die Automatisierung durch Aufrufen der Home APIs und Behandeln von Ausnahmen zu erstellen:
fun createAutomation(isPending: MutableState<Boolean>) {
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 die App nun 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:
Gib deiner Automatisierung einen eindeutigen Namen und tippe dann auf die Schaltfläche Automatisierung erstellen. Dadurch sollten die APIs aufgerufen werden und du solltest zur Liste der Automatisierungen zurückkehren, in der deine Automatisierung angezeigt wird:
Tippe auf die gerade erstellte Automatisierung und sieh dir 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 serverseitig geparst werden. Wenn die Validierung einer Automatisierung fehlschlägt, wird isValid
auf false
gesetzt. Das bedeutet, dass die Automatisierung ungültig und inaktiv ist. Wenn Ihre Automatisierung ungültig ist, sehen Sie sich das Feld automation.validationIssues
an, um weitere Informationen zu erhalten.
Prüfen Sie, ob Ihre Automatisierung als gültig und aktiv festgelegt ist. Anschließend können Sie sie ausprobieren.
Automatisierung ausprobieren
Automatisierungen können auf zwei Arten ausgeführt werden:
- Mit einem Auslöser-Ereignis. Wenn die Bedingungen erfüllt sind, wird die Aktion ausgelöst, die Sie in der Automatisierung festgelegt haben.
- Mit einem API-Aufruf für die manuelle Ausführung.
Wenn für eine automatisierte Kampagne im DSL-Block des Kampagnenentwurfs ein manualStarter()
definiert ist, unterstützt die Automatisierungs-Engine die manuelle Ausführung dieser automatisierten Kampagne. Dies ist in den Codebeispielen in der Beispiel-App bereits vorhanden.
Da Sie sich auf Ihrem Mobilgerät noch auf dem Bildschirm mit der Ablauf-Ansicht befinden, tippen Sie auf die Schaltfläche Manuell ausführen. Dadurch sollte automation.execute()
aufgerufen werden, wodurch der Aktionsbefehl auf dem Gerät ausgeführt wird, das Sie beim Einrichten der Automatisierung ausgewählt haben.
Nachdem Sie den Aktionsbefehl durch manuelle Ausführung über die API validiert haben, ist es an der Zeit, zu prüfen, ob er auch über den von Ihnen definierten Starter ausgeführt wird.
Gehe zum Tab „Geräte“, wähle das Aktionsgerät und das Attribut aus und lege einen anderen Wert fest (z. B. LevelControl
(Helligkeit) von light2
auf 50%, wie im folgenden Screenshot dargestellt):
Wir versuchen jetzt, die Automatisierung mit dem Auslösergerät auszulösen. Wähle das Startergerät aus, das du beim Erstellen der Automatisierung ausgewählt hast. Aktiviere das Attribut, das du ausgewählt hast (z. B. setze starter outlet1
s OnOff
auf On
):
Dadurch wird auch die Automatisierung ausgeführt und das light2
-AktionsgerätLevelControl
wird auf den ursprünglichen Wert von 100 % zurückgesetzt:
Herzlichen Glückwunsch! Sie haben die Home-APIs erfolgreich verwendet, um Automatisierungen zu erstellen.
Weitere Informationen zur Automation API finden Sie unter Android Automation API.
5. Funktionen entdecken
Die Home-APIs enthalten eine spezielle API namens Discovery API, mit der Entwickler abfragen können, welche Automatisierungsmerkmale auf einem bestimmten Gerät unterstützt werden. Die Beispiel-App enthält ein Beispiel dafür, wie Sie mit dieser API herausfinden können, welche Befehle verfügbar sind.
Befehle entdecken
In diesem Abschnitt wird beschrieben, wie Sie unterstützte CommandCandidates
ermitteln und eine Automatisierung basierend auf ermittelten Kandidatenknoten erstellen.
In der Beispiel-App rufen wir device.candidates()
auf, um eine Liste von Kandidaten abzurufen, die Instanzen von CommandCandidate
, EventCandidate
oder TraitAttributesCandidate
enthalten können.
Rufen Sie die Datei HomeAppViewModel.kt
auf und entfernen Sie die Kommentarzeichen für 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<NodeCandidate> = 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 die API nach CommandCandidate.
filtert. Die von der API zurückgegebenen Kandidaten gehören zu verschiedenen Typen. Die Beispiel-App unterstützt CommandCandidate
. Entfernen Sie die Auskommentierung von Schritt 5.1.2 in der commandMap
, die in ActionViewModel.kt
definiert ist, um diese unterstützten Attribute festzulegen:
// Map of supported commands from Discovery API:
val commandMap: Map<CommandDescriptor, 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
)
Nachdem wir die Discovery API aufrufen und die Ergebnisse filtern können, die in der Beispiel-App unterstützt werden, sehen wir uns an, wie wir das in unseren Editor einbinden können.
Weitere Informationen zur Discovery API
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 Datei CandidatesView.kt
enthält die Ansichtsklasse, in der die gefundenen Kandidaten angezeigt werden. Entfernen Sie die Auskommentierung von Schritt 5.2.1, um die .clickable{}
-Funktion von CandidateListItem
zu aktivieren, mit der homeAppVM.selectedDraftVM
als candidateVM
festgelegt wird:
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 in Schritt 4.3 in HomeAppView.kt
wird beim Festlegen von selectedDraftVM
die DraftView(...) in
-Datei „DraftView.kt“ gerendert:
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)
}
...
}
Versuche es noch einmal, indem du auf light2 – MOVE_TO_LEVEL tippst (siehe vorheriger Abschnitt). Daraufhin wirst du aufgefordert, eine neue Automatisierung basierend auf dem Befehl des Kandidaten zu erstellen:
Nachdem Sie nun wissen, wie Sie Automatisierungen in der Beispiel-App erstellen, können Sie Automatisierungen in Ihre Apps einbinden.
6. Beispiele für erweiterte Automatisierung
Bevor wir zum Schluss kommen, sehen wir uns noch einige zusätzliche Beispiele für die Automatisierungs-DSL an. Sie veranschaulichen einige der erweiterten Funktionen, die Sie mit den APIs erreichen können.
Tageszeit als Auslöser
Zusätzlich zu den Geräte-Traits bieten Google Home APIs auch strukturbezogene Traits 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
(wenn der Lautsprecher es unterstützt) oder als Attribut auf Strukturebene verfügbar, da Google-Lautsprecher und Android-Mobilgeräte Assistant-Broadcasts abspielen 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 Automation API bietet auch erweiterte Operatoren wie delayFor zum Verzögern von Befehlen und suppressFor, mit dem verhindert werden kann, 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 Starter verwenden
AreaPresenceState
ist ein Merkmal auf Strukturebene, mit dem erkannt wird, ob jemand zu Hause ist.
Im folgenden Beispiel werden die Türen automatisch verriegelt, 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 loslegen und tolle Apps erstellen.
7. Glückwunsch!
Glückwunsch! Sie haben den zweiten Teil der Entwicklung einer Android-App mit den Google Home APIs abgeschlossen. In diesem Codelab haben Sie die Automation and Discovery APIs kennengelernt.
Wir hoffen, dass Sie viel Spaß beim Entwickeln von Apps haben, mit denen Geräte im Google Home-Ökosystem kreativ gesteuert werden können, und dass Sie mit den Home-APIs spannende Automatisierungsszenarien erstellen.
Nächste Schritte
- Im Abschnitt Fehlerbehebung erfahren Sie, wie Sie Apps effektiv debuggen und Probleme mit den Home-APIs beheben können.
- Sie können uns jederzeit Empfehlungen senden oder Probleme über die Problemverfolgung im Supportthema „Smart Home“ melden.