1. Prima di iniziare
Questo è il secondo codelab della serie sulla creazione di un'app per Android utilizzando le API Google Home. In questo codelab, vedremo come creare automazioni per la casa e forniremo alcuni suggerimenti sulle best practice per l'utilizzo delle API. Se non hai ancora completato il primo codelab, Crea un'app mobile utilizzando le API Home su Android, ti consigliamo di farlo prima di iniziare questo codelab.
Le API Google Home forniscono un insieme di librerie per consentire agli sviluppatori Android di controllare i dispositivi per la smart home all'interno dell'ecosistema Google Home. Con queste nuove API, gli sviluppatori potranno impostare automazioni per una smart home in grado di controllare le funzionalità dei dispositivi in base a condizioni predefinite. Google fornisce anche un'API Discovery che ti consente di eseguire query sui dispositivi per scoprire quali attributi e comandi supportano.
Prerequisiti
- Completa il codelab Crea un'app mobile utilizzando le API Home su Android.
- Conoscenza dell'ecosistema Google Home (cloud-to-cloud e Matter).
- Una workstation con Android Studio (2024.3.1 Ladybug o versioni successive) installato.
- Uno smartphone Android che soddisfi i requisiti delle API Home (vedi Prerequisiti), con Google Play Services e l'app Google Home installati. Un emulatore non funzionerà, solo gli smartphone Android fisici sono supportati per l'app di esempio.
- Un Google Nest Hub compatibile che supporti le API Google Home.
- Facoltativo: un dispositivo per la smart home compatibile con le API Google Home.
Obiettivi didattici
- Come creare automazioni per i dispositivi per la smart home utilizzando le API Home.
- Come utilizzare le API Discovery per esplorare le funzionalità dei dispositivi supportate.
- Come utilizzare le best practice durante la creazione delle app con le API per la casa.
2. Configurazione del progetto in corso…
Il seguente diagramma illustra l'architettura di un'app Home APIs:
- Codice app:il codice principale su cui gli sviluppatori lavorano per creare l'interfaccia utente dell'app e la logica per interagire con l'SDK Home APIs.
- SDK Home APIs:l'SDK Home APIs fornito da Google funziona con il servizio Home APIs in GMSCore per controllare i dispositivi per la smart home. Gli sviluppatori creano app che funzionano con le API Home raggruppandole con l'SDK Home APIs.
- GMSCore su Android: GMSCore, noto anche come Google Play Services, è una piattaforma Google che fornisce servizi di sistema di base, consentendo funzionalità chiave su tutti i dispositivi Android certificati. Il modulo Home di Google Play Services contiene i servizi che interagiscono con le API Home.
In questo codelab riprenderemo i concetti trattati in Crea un'app mobile utilizzando le API Home su Android.
Assicurati di avere una casa con almeno due dispositivi supportati configurati e funzionanti sull'account. Poiché in questo codelab configureremo le automazioni (una modifica dello stato di un dispositivo attiva un'azione su un altro), avrai bisogno di due dispositivi per visualizzare i risultati.
Ottieni l'app di esempio
Il codice sorgente dell'app di esempio è disponibile su GitHub nel repository google-home/google-home-api-sample-app-android.
Questo codelab utilizza gli esempi del ramo codelab-branch-2
dell'app di esempio.
Vai alla posizione in cui vuoi salvare il progetto e clona il ramo codelab-branch-2
:
$ git clone -b codelab-branch-2 https://github.com/google-home/google-home-api-sample-app-android.git
Tieni presente che questo è un ramo diverso da quello utilizzato in Creare un'app mobile utilizzando le API Home su Android. Questo ramo del codebase si basa su quanto appreso nel primo codelab. Questa volta, gli esempi ti mostrano come creare automazioni. Se hai completato il codelab precedente e sei riuscito a far funzionare tutte le funzionalità, puoi scegliere di utilizzare lo stesso progetto Android Studio per completare questo codelab anziché utilizzare codelab-branch-2
.
Una volta compilato il codice sorgente e pronto per l'esecuzione sul dispositivo mobile, continua con la sezione successiva.
3. Scopri di più sulle automazioni
Le automazioni sono un insieme di istruzioni "se succede questo, allora fai quest'altro" che possono controllare gli stati dei dispositivi in base a fattori selezionati, in modo automatico. Gli sviluppatori possono utilizzare le automazioni per creare funzionalità interattive avanzate nelle loro API.
Le automazioni sono costituite da tre diversi tipi di componenti noti come nodi: comandi iniziali, azioni e condizioni. Questi nodi collaborano per automatizzare i comportamenti utilizzando i dispositivi per la smart home. In genere, vengono valutati nel seguente ordine:
- Comando iniziale: definisce le condizioni iniziali che attivano l'automazione, ad esempio una modifica a un valore di un tratto. Un'automazione deve avere un comando iniziale.
- Condizione: eventuali vincoli aggiuntivi da valutare dopo l'attivazione di un'automazione. L'espressione in una condizione deve essere valutata come vera affinché le azioni di un'automazione vengano eseguite.
- Azione: comandi o aggiornamenti di stato eseguiti quando tutte le condizioni sono state soddisfatte.
Ad esempio, puoi impostare un'automazione che abbassa l'intensità delle luci in una stanza quando viene attivato un interruttore, mentre la TV nella stanza è accesa. In questo esempio:
- Starter: l'interruttore nella stanza è attivato.
- Condizione: lo stato di accensione/spegnimento della TV è impostato su On.
- Azione: le luci nella stessa stanza dell'interruttore vengono attenuate.
Questi nodi vengono valutati da Automation Engine in modo seriale o parallelo.
Un flusso sequenziale contiene nodi che vengono eseguiti in ordine sequenziale. In genere, si tratta di un comando iniziale, una condizione e un'azione.
Un flusso parallelo può avere più nodi di azione eseguiti contemporaneamente, ad esempio l'accensione di più luci contemporaneamente. I nodi che seguono un flusso parallelo non vengono eseguiti finché non vengono completati tutti i rami del flusso parallelo.
Esistono altri tipi di nodi nello schema di automazione. Puoi scoprire di più su questi nodi nella sezione Nodi della Guida per gli sviluppatori delle API Home. Inoltre, gli sviluppatori possono combinare diversi tipi di nodi per creare automazioni complesse, ad esempio:
Gli sviluppatori forniscono questi nodi all'Automation Engine utilizzando un linguaggio specifico del dominio (DSL) creato appositamente per le automazioni di Google Home.
Esplora il DSL di automazione
Un linguaggio specifico del dominio (DSL) è un linguaggio utilizzato per acquisire il comportamento del sistema nel codice. Il compilatore genera classi di dati serializzate in JSON di protocol buffer e utilizzate per effettuare chiamate ai servizi di automazione di Google.
Il DSL cerca lo schema seguente:
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()) }
}
}
L'automazione nell'esempio precedente sincronizza due lampadine. Quando lo stato OnOff
di device1
cambia in On
(onOffTrait.onOff equals true
), lo stato OnOff
di device2
cambia in On
(command(OnOff.on()
).
Quando lavori con le automazioni, tieni presente che esistono limiti delle risorse.
Le automazioni sono uno strumento molto utile per creare funzionalità automatizzate in una smart home. Nello scenario di utilizzo più semplice, puoi codificare esplicitamente un'automazione per utilizzare dispositivi e caratteristiche specifici. Un caso d'uso più pratico è quello in cui l'app consente all'utente di configurare i dispositivi, i comandi e i parametri di un'automazione. La sezione successiva spiega come creare un editor di automazione che consenta all'utente di fare esattamente questo.
4. Creare un editor delle automazioni
All'interno dell'app di esempio, creeremo un editor di automazioni con cui gli utenti potranno selezionare i dispositivi, le funzionalità (azioni) che vogliono utilizzare e il modo in cui le automazioni vengono attivate utilizzando gli starter.
Configurare i comandi iniziali
Il comando iniziale per l'automazione è il punto di ingresso per l'automazione. Un comando iniziale attiva un'automazione quando si verifica un determinato evento. Nell'app di esempio, acquisisci gli starter di automazione utilizzando la classe StarterViewModel
, che si trova nel file sorgente StarterViewModel.kt
, e visualizza la visualizzazione dell'editor utilizzando StarterView
(StarterView.kt
).
Un nodo iniziale richiede i seguenti elementi:
- Dispositivo
- Tratto
- Operazione
- Valore
Il dispositivo e il tratto possono essere selezionati dagli oggetti restituiti dall'API Devices. I comandi e i parametri per ogni dispositivo supportato sono una questione più complessa e devono essere gestiti separatamente.
L'app definisce un elenco preimpostato di operazioni:
// 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
}
Poi, per ogni tratto supportato, tiene traccia delle operazioni supportate:
// 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 modo simile, l'app di esempio tiene traccia dei valori assegnabili ai tratti:
enum class OnOffValue {
On,
Off,
}
enum class ThermostatValue {
Heat,
Cool,
Off,
}
e tiene traccia di una mappatura tra i valori definiti dall'app e quelli definiti dalle API:
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,
)
L'app mostra quindi un insieme di elementi di visualizzazione che gli utenti possono utilizzare per selezionare i campi obbligatori.
Rimuovi il commento dal passaggio 4.1.1 nel file StarterView.kt
per eseguire il rendering di tutti i dispositivi iniziali e implementare il callback dei clic in un DropdownMenu
:
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
// }
// )
// }
}
Rimuovi il commento dal passaggio 4.1.2 nel file StarterView.kt
per eseguire il rendering di tutte le caratteristiche del dispositivo iniziale e implementare il callback dei clic in un DropdownMenu
:
// 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
// }
// )
}
}
Rimuovi il commento dal passaggio 4.1.3 nel file StarterView.kt
per eseguire il rendering di tutte le operazioni del tratto selezionato e implementare il callback dei clic in un DropdownMenu
:
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
// }
// )
// }
}
Rimuovi il commento dal passaggio 4.1.4 nel file StarterView.kt
per visualizzare tutti i valori del tratto selezionato e implementare il callback dei clic in un 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 -> {
...
}
}
Rimuovi il commento dal passaggio 4.1.5 nel file StarterView.kt
per memorizzare tutte le variabili di avvio ViewModel
nell'avvio ViewModel
dell'automazione bozza (draftVM.starterVMs
).
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)) }
L'esecuzione dell'app e la selezione di una nuova automazione e di un nuovo starter dovrebbero mostrare una visualizzazione simile alla seguente:
L'app di esempio supporta solo gli starter basati sulle caratteristiche del dispositivo.
Configurare le azioni
L'azione di automazione riflette lo scopo principale di un'automazione, ovvero come influisce su un cambiamento nel mondo fisico. Nell'app di esempio, acquisisce le azioni di automazione utilizzando la classe ActionViewModel
e visualizza la visualizzazione dell'editor utilizzando la classe ActionView
.
L'app di esempio utilizza le seguenti entità delle API Home per definire i nodi di azione dell'automazione:
- Dispositivo
- Tratto
- Comando
- Valore (facoltativo)
Ogni azione di comando del dispositivo utilizza un comando, ma alcune richiedono anche un valore del parametro associato, ad esempio MoveToLevel()
e una percentuale target.
Il dispositivo e il tratto possono essere selezionati dagli oggetti restituiti dall'API Devices.
L'app definisce un elenco predefinito di comandi:
// List of operations available when creating automation starters:
enum class Action {
ON,
OFF,
MOVE_TO_LEVEL,
MODE_HEAT,
MODE_COOL,
MODE_OFF,
}
L'app tiene traccia delle operazioni supportate per ogni caratteristica supportata:
// 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,
)
Per i comandi che accettano uno o più parametri, è disponibile anche una variabile:
val valueLevel: MutableStateFlow<UByte?>
L'API mostra un insieme di elementi della visualizzazione che gli utenti possono utilizzare per selezionare i campi obbligatori.
Rimuovi il commento dal passaggio 4.2.1 nel file ActionView.kt
per eseguire il rendering di tutti i dispositivi di azione e implementare il callback dei clic in un DropdownMenu
per impostare actionDeviceVM
.
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
// }
// )
// }
}
Rimuovi il commento dal passaggio 4.2.2 nel file ActionView.kt
per eseguire il rendering di tutti i tratti di actionDeviceVM
e implementa il callback dei clic in un DropdownMenu
per impostare actionTrait
, che rappresenta il tratto a cui appartiene il comando.
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
// }
// )
// }
}
Rimuovi il commento dal passaggio 4.2.3 nel file ActionView.kt
per eseguire il rendering di tutte le azioni disponibili di actionTrait
e implementare il callback dei clic in un DropdownMenu
per impostare actionAction
, che rappresenta l'azione di automazione selezionata.
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
// }
// )
// }
}
Rimuovi il commento dal passaggio 4.2.4 nel file ActionView.kt
per eseguire il rendering dei valori disponibili dell'azione (comando) del tratto e memorizzare il valore in actionValueLevel
nel callback di modifica del valore:
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
// )
// }
...
}
Rimuovi il commento dal passaggio 4.2.5 nel file ActionView.kt
per memorizzare tutte le variabili dell'azione ViewModel
nell'azione ViewModel
della bozza di automazione (draftVM.actionVMs
):
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)) }
L'esecuzione dell'app e la selezione di una nuova automazione e azione dovrebbero generare una visualizzazione simile alla seguente:
Supportiamo solo le azioni basate sulle caratteristiche del dispositivo nell'app di esempio.
Rendering di un'automazione bozza
Al termine dell'DraftViewModel
, è possibile eseguire il rendering da parte di HomeAppView.kt
:
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)
}
Crea un'automazione
Ora che hai imparato a creare comandi iniziali e azioni, puoi creare una bozza di automazione e inviarla all'API Automation. L'API ha una funzione createAutomation()
che accetta una bozza di automazione come argomento e restituisce una nuova istanza di automazione.
La preparazione della bozza di automazione avviene nella classe DraftViewModel
dell'app di esempio. Consulta la funzione getDraftAutomation()
per scoprire di più su come strutturiamo la bozza di automazione utilizzando le variabili di avvio e di azione nella sezione precedente.
Rimuovi il commento dal passaggio 4.4.1 nel file DraftViewModel.kt
per creare le espressioni "select" necessarie per creare il grafico di automazione quando la caratteristica iniziale è OnOff
:
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()
...
}
Rimuovi il commento dal passaggio 4.4.2 nel file DraftViewModel.kt
per creare le espressioni parallele necessarie per creare il grafico di automazione quando l'attributo di azione selezionato è LevelControl
e l'azione selezionata è MOVE_TO_LEVEL
:
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) }
...
}
L'ultimo passaggio per completare un'automazione consiste nell'implementare la funzione getDraftAutomation
per creare un AutomationDraft.
Rimuovi il commento dal passaggio 4.4.3 nel file HomeAppViewModel.kt
per creare l'automazione chiamando le API Home e gestendo le eccezioni:
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
}
}
Ora esegui l'app e visualizza le modifiche sul tuo dispositivo.
Dopo aver selezionato un comando iniziale e un'azione, puoi creare l'automazione:
Assicurati di dare all'automazione un nome univoco, quindi tocca il pulsante Crea automazione, che dovrebbe chiamare le API e riportarti alla visualizzazione elenco delle automazioni con la tua automazione:
Tocca l'automazione che hai appena creato e vedi come viene restituita dalle API.
Tieni presente che l'API restituisce un valore che indica se un'automazione è valida e attualmente attiva. È possibile creare automazioni che non superano la convalida quando vengono analizzate sul lato server. Se l'analisi di un'automazione non supera la convalida, isValid
viene impostato su false
, a indicare che l'automazione non è valida e inattiva. Se l'automazione non è valida, controlla il campo automation.validationIssues
per i dettagli.
Assicurati che l'automazione sia impostata come valida e attiva, quindi puoi provarla.
Prova l'automazione
Le automazioni possono essere eseguite in due modi:
- Con un evento iniziale. Se le condizioni corrispondono, viene attivata l'azione impostata nell'automazione.
- Con una chiamata API di esecuzione manuale.
Se una bozza di automazione ha un manualStarter()
definito nel blocco DSL della bozza di automazione, il motore di automazione supporterà l'esecuzione manuale per l'automazione. Questo è già presente negli esempi di codice nell'app di esempio.
Poiché ti trovi ancora nella schermata della visualizzazione dell'automazione sul tuo dispositivo mobile, tocca il pulsante Esegui manualmente. Dovrebbe chiamare automation.execute()
, che esegue il comando di azione sul dispositivo selezionato durante la configurazione dell'automazione.
Dopo aver convalidato il comando di azione tramite l'esecuzione manuale utilizzando l'API, è il momento di verificare se viene eseguito anche utilizzando l'intent predefinito che hai definito.
Vai alla scheda Dispositivi, seleziona il dispositivo di azione e la caratteristica e impostala su un valore diverso (ad esempio, imposta la LevelControl
(luminosità) di light2
al 50%, come illustrato nello screenshot seguente:
Ora proveremo ad attivare l'automazione utilizzando il dispositivo iniziale. Scegli il dispositivo di avvio selezionato durante la creazione dell'automazione. Attiva/disattiva il tratto che hai scelto (ad esempio, imposta OnOff
di starter outlet1
su On
):
Vedrai che viene eseguita anche l'automazione e che il tratto LevelControl
del dispositivo di azione light2
viene impostato sul valore originale, 100%:
Congratulazioni, hai utilizzato correttamente le API Home per creare automazioni.
Per saperne di più sull'API Automation, consulta API Android Automation.
5. Scopri le funzionalità
Le API Home includono un'API dedicata chiamata API Discovery, che gli sviluppatori possono utilizzare per eseguire query sulle caratteristiche compatibili con l'automazione supportate in un determinato dispositivo. L'app di esempio fornisce un esempio in cui puoi utilizzare questa API per scoprire quali comandi sono disponibili.
Scoprire i comandi
In questa sezione, vedremo come scoprire i nodi supportati CommandCandidates
e come creare un'automazione in base ai nodi candidati rilevati.
Nell'app di esempio, chiamiamo device.candidates()
per ottenere un elenco di candidati, che possono includere istanze di CommandCandidate
, EventCandidate
o TraitAttributesCandidate
.
Vai al file HomeAppViewModel.kt
e rimuovi il commento dal passaggio 5.1.1 per recuperare l'elenco dei candidati e filtrare in base al tipo Candidate
:
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)
}
Scopri come filtra i CommandCandidate.
I candidati restituiti dall'API appartengono a tipi diversi. L'app di esempio supporta CommandCandidate
. Rimuovi il commento dal passaggio 5.1.2 in commandMap
definito in ActionViewModel.kt
per impostare questi tratti supportati:
// 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
)
Ora che possiamo chiamare l'API Discovery e filtrare i risultati supportati nell'app di esempio, vedremo come integrarli nel nostro editor.
Per saperne di più sull'API Discovery, visita la pagina Sfruttare la scoperta dei dispositivi su Android.
Integrare l'editor
Il modo più comune per utilizzare le azioni rilevate è presentarle a un utente finale affinché le selezioni. Poco prima che l'utente selezioni i campi di automazione della bozza, possiamo mostrargli l'elenco delle azioni rilevate e, a seconda del valore selezionato, possiamo precompilare il nodo dell'azione nella bozza di automazione.
Il file CandidatesView.kt
contiene la classe di visualizzazione che mostra i candidati scoperti. Rimuovi il commento dal passaggio 5.2.1 per attivare la funzione .clickable{}
di CandidateListItem
che imposta homeAppVM.selectedDraftVM
come candidateVM
:
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)) }
}) {
...
}
}
}
Analogamente al passaggio 4.3 in HomeAppView.kt
, quando viene impostato selectedDraftVM
, viene visualizzato DraftView(...) in
DraftView.kt`:
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)
}
...
}
Riprova toccando light2 - MOVE_TO_LEVEL, mostrato nella sezione precedente, che ti chiede di creare una nuova automazione in base al comando del candidato:
Ora che hai familiarità con la creazione di automazioni nell'app di esempio, puoi integrarle nelle tue app.
6. Esempi di automazione avanzata
Prima di concludere, esamineremo alcuni esempi aggiuntivi di DSL di automazione. Questi esempi illustrano alcune delle funzionalità avanzate che puoi ottenere con le API.
Ora del giorno come comando iniziale
Oltre alle caratteristiche del dispositivo, le API Google Home offrono caratteristiche basate sulla struttura, come Time
. Puoi creare un'automazione con un trigger basato sul tempo, ad esempio:
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
...
}
}
Trasmissione dell'assistente come azione
La caratteristica AssistantBroadcast
è disponibile come caratteristica a livello di dispositivo in un SpeakerDevice
(se lo speaker la supporta) o come caratteristica a livello di struttura (perché gli speaker Google e i dispositivi mobili Android possono riprodurre le trasmissioni dell'assistente). Ad esempio:
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!!")
)
}
}
}
Utilizza DelayFor
e suppressFor
L'API Automation fornisce anche operatori avanzati come delayFor, che serve per ritardare i comandi, e suppressFor, che può impedire l'attivazione di un'automazione da parte degli stessi eventi in un determinato periodo di tempo. Ecco alcuni esempi di utilizzo di questi operatori:
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!"))
}
...
}
Utilizzare AreaPresenceState
in un comando iniziale
AreaPresenceState
è una caratteristica a livello di struttura che rileva se c'è qualcuno in casa.
Ad esempio, il seguente esempio mostra la chiusura automatica delle porte quando qualcuno è in casa dopo le 22:00:
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()))
}
}
}
Ora che hai familiarità con queste funzionalità di automazione avanzate, crea app fantastiche.
7. Complimenti!
Complimenti! Hai completato correttamente la seconda parte dello sviluppo di un'app per Android utilizzando le API Google Home. In questo codelab, hai esplorato le API Automation e Discovery.
Ci auguriamo che ti divertirai a creare app che controllano in modo creativo i dispositivi all'interno dell'ecosistema Google Home e a creare scenari di automazione entusiasmanti utilizzando le API Home.
Passaggi successivi
- Leggi la sezione Risoluzione dei problemi per scoprire come eseguire il debug delle app e risolvere i problemi relativi alle API Home in modo efficace.
- Puoi contattarci per eventuali consigli o segnalare problemi tramite Issue Tracker, l'argomento di assistenza per la smart home.