1. Antes de começar
Este é o segundo codelab da série sobre como criar um app Android usando as APIs do Google Home. Neste codelab, mostramos como criar automações residenciais e damos algumas dicas sobre as práticas recomendadas para usar as APIs. Se você ainda não concluiu o primeiro codelab, Criar um app para dispositivos móveis usando as APIs Home no Android, recomendamos que você o faça antes de iniciar este codelab.
As APIs do Google Home oferecem um conjunto de bibliotecas para que os desenvolvedores do Android controlem dispositivos de casa inteligente no ecossistema do Google Home. Com essas novas APIs, os desenvolvedores poderão definir automações para uma casa inteligente que podem controlar os recursos do dispositivo com base em condições predefinidas. O Google também oferece uma API Discovery que permite consultar dispositivos para descobrir quais atributos e comandos eles suportam.
Pré-requisitos
- Concluir o codelab Criar um app para dispositivos móveis usando as APIs Home no Android.
- Conhecimento do ecossistema do Google Home (nuvem a nuvem e Matter).
- Uma estação de trabalho com o Android Studio (2024.3.1 Ladybug ou mais recente) instalado.
- Um smartphone Android que atenda aos requisitos das APIs Home (consulte Pré-requisitos), com o Google Play Services e o app Google Home instalados.
- Um Google Home Hub compatível com as APIs do Google Home.
- Opcional: um dispositivo de casa inteligente compatível com as APIs do Google Home.
O que você vai aprender
- Como criar automações para dispositivos de casa inteligente usando as APIs Home.
- Como usar as APIs Discovery para conhecer os recursos de dispositivos compatíveis.
- Como usar as práticas recomendadas ao criar seus apps com as APIs Home.
2. Configurar o projeto
O diagrama a seguir ilustra a arquitetura de um app de APIs do Google Home:
- Código do app:o código principal em que os desenvolvedores trabalham para criar a interface do usuário do app e a lógica para interagir com o SDK das APIs Home.
- SDK das APIs Home:o SDK das APIs Home fornecido pelo Google funciona com o serviço das APIs Home no GMSCore para controlar dispositivos de casa inteligente. Os desenvolvedores criam apps que funcionam com as APIs Home agrupando-as com o SDK das APIs Home.
- GMSCore no Android:o GMSCore, também conhecido como Google Play Services, é uma plataforma do Google que oferece serviços principais do sistema, ativando a funcionalidade principal em todos os dispositivos Android certificados. O módulo principal do Google Play Services contém os serviços que interagem com as APIs Home.
Neste codelab, vamos desenvolver o que abordamos em Criar um app para dispositivos móveis usando as APIs Home no Android.
Verifique se você tem uma estrutura com pelo menos dois dispositivos compatíveis configurados e funcionando na conta. Como vamos configurar automações neste codelab (uma mudança no estado de um dispositivo aciona uma ação em outro), você vai precisar de dois dispositivos para conferir os resultados.
Fazer o download do app de exemplo
O código-fonte do app de amostra está disponível no GitHub no repositório google-home/google-home-api-sample-app-android.
Este codelab usa os exemplos da ramificação codelab-branch-2
do app de exemplo.
Navegue até o local em que você quer salvar o projeto e clone a ramificação codelab-branch-2
:
$ git clone -b codelab-branch-2 https://github.com/google-home/google-home-api-sample-app-android.git
Essa é uma ramificação diferente da usada em Criar um app para dispositivos móveis usando as APIs Home no Android. Esta ramificação da base de código é baseada no que foi feito no primeiro codelab. Desta vez, os exemplos mostram como criar automações. Se você concluiu o codelab anterior e conseguiu fazer com que todas as funcionalidades funcionassem, pode usar o mesmo projeto do Android Studio para concluir este codelab em vez de usar codelab-branch-2
.
Depois que o código-fonte for compilado e estiver pronto para ser executado no dispositivo móvel, continue para a próxima seção.
3. Saiba mais sobre as automações
As automações são um conjunto de instruções "se isso, então aquilo" que podem controlar os estados do dispositivo de forma automatizada com base em fatores selecionados. Os desenvolvedores podem usar automações para criar recursos interativos avançados nas APIs.
As automações são compostas por três tipos diferentes de componentes conhecidos como nodes: ativações, ações e condições. Esses nós trabalham juntos para automatizar comportamentos usando dispositivos de casa inteligente. Normalmente, elas são avaliadas na seguinte ordem:
- Starter: define as condições iniciais que ativam a automação, como uma mudança no valor de um atributo. Uma automação precisa ter uma Starter.
- Condição: qualquer restrição adicional a ser avaliada depois que uma automação for acionada. A expressão em uma Condição precisa ser avaliada como verdadeira para que as ações de uma automação sejam executadas.
- Ação: comandos ou atualizações de estado que são realizadas quando todas as condições são atendidas.
Por exemplo, você pode ter uma automação que diminua a intensidade das luzes de uma sala quando um interruptor é acionado, enquanto a TV dessa sala está ligada. Neste exemplo:
- Starter: o interruptor na sala é acionado.
- Condição: o estado "OnOff" da TV é avaliado como "On".
- Ação: as luzes no mesmo ambiente que o interruptor são atenuadas.
Esses nós são avaliados pelo Automation Engine de forma sequencial ou paralela.
Um fluxo sequencial contém nós que são executados em ordem sequencial. Normalmente, são ativação, condição e ação.
Um fluxo paralelo pode ter vários nós de ação sendo executados simultaneamente, como acender várias luzes ao mesmo tempo. Os nós que seguem um fluxo paralelo não são executados até que todos os ramos do fluxo paralelo terminem.
Há outros tipos de nós no esquema de automação. Saiba mais sobre eles na seção Nodes do guia para desenvolvedores das APIs Home. Além disso, os desenvolvedores podem combinar diferentes tipos de nós para criar automações complexas, como estas:
Os desenvolvedores fornecem esses nós ao Automation Engine usando uma linguagem específica do domínio (DSL) criada especificamente para automações do Google Home.
Conheça a DSL de automação
Uma linguagem específica de domínio (DSL) é usada para capturar o comportamento do sistema no código. O compilador gera classes de dados que são serializadas para JSON de buffer de protocolo e usadas para fazer chamadas aos Serviços de automação do Google.
O DSL procura o seguinte esquema:
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()) }
}
}
A automação no exemplo anterior sincroniza duas lâmpadas. Quando o estado OnOff
de device1
muda para On
(onOffTrait.onOff equals true
), o estado OnOff
de device2
muda para On
(command(OnOff.on()
).
Ao trabalhar com automações, saiba que há limites de recursos.
As automações são uma ferramenta muito útil para criar recursos automatizados em uma casa inteligente. No caso de uso mais básico, é possível programar explicitamente uma automação para usar dispositivos e características específicos. Mas um caso de uso mais prático é aquele em que o app permite que o usuário configure os dispositivos, comandos e parâmetros de uma automação. A próxima seção explica como criar um editor de automação que permite que o usuário faça exatamente isso.
4. Criar um editor de automação
No app de exemplo, vamos criar um editor de automação em que os usuários podem selecionar dispositivos, os recursos (ações) que querem usar e como as automações são acionadas usando ativações.
Configurar inicializadores
A ativação da automação é o ponto de entrada para a automação. Uma ativação aciona uma automação quando um evento específico ocorre. No app de exemplo, capturamos os iniciadores de automação usando a classe StarterViewModel
, encontrada no arquivo de origem StarterViewModel.kt
, e mostramos a visualização do editor usando o StarterView
(StarterView.kt
).
Um nó inicial precisa dos seguintes elementos:
- Dispositivo
- Traço
- Operação
- Valor
O dispositivo e o atributo podem ser selecionados nos objetos retornados pela API Devices. Os comandos e parâmetros de cada dispositivo compatível são uma questão mais complexa e precisam ser tratados separadamente.
O app define uma lista predefinida de operações:
// 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
}
Em seguida, para cada atributo compatível, é possível acompanhar as operações compatíveis:
// 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
))
De forma semelhante, o app de exemplo rastreia os valores atribuíveis a traits:
enum class OnOffValue {
On,
Off,
}
enum class ThermostatValue {
Heat,
Cool,
Off,
}
E mantém um mapeamento entre os valores definidos pelo app e os definidos pelas APIs:
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,
)
Em seguida, o app mostra um conjunto de elementos de visualização que os usuários podem usar para selecionar os campos obrigatórios.
Remova a marca de comentário da etapa 4.1.1 no arquivo StarterView.kt
para renderizar todos os dispositivos de inicialização e implementar o callback de clique em um 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
// }
// )
// }
}
Remova a marca de comentário da etapa 4.1.2 no arquivo StarterView.kt
para renderizar todos os recursos do dispositivo inicial e implementar o callback de clique em um 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
// }
// )
}
}
Remova a marca de comentário da etapa 4.1.3 no arquivo StarterView.kt
para renderizar todas as operações do atributo selecionado e implementar o callback de clique em um 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
// }
// )
// }
}
Remova a marca de comentário da etapa 4.1.4 no arquivo StarterView.kt
para renderizar todos os valores do atributo selecionado e implementar o callback de clique em um 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 -> {
...
}
}
Remova a marca de comentário da etapa 4.1.5 no arquivo StarterView.kt
para armazenar todas as variáveis ViewModel
de ativação no ViewModel
de ativação da automação de rascunho (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)) }
A execução do app e a seleção de uma nova automação e um iniciador vão mostrar uma visualização como esta:
O app de exemplo só oferece suporte a iniciadores com base em características do dispositivo.
Configurar ações
A ação de automação reflete o objetivo central de uma automação, como ela afeta uma mudança no mundo físico. No app de exemplo, capturamos as ações de automação usando a classe ActionViewModel
e mostramos a visualização do editor usando a classe ActionView
.
O app de exemplo usa as seguintes entidades das APIs Home para definir os nós de ação de automação:
- Dispositivo
- Traço
- Comando
- Valor (opcional)
Cada ação de comando do dispositivo usa um comando, mas algumas também exigem um valor de parâmetro associado, como MoveToLevel()
e uma porcentagem de destino.
O dispositivo e o atributo podem ser selecionados nos objetos retornados pela API Devices.
O app define uma lista predefinida de comandos:
// List of operations available when creating automation starters:
enum class Action {
ON,
OFF,
MOVE_TO_LEVEL,
MODE_HEAT,
MODE_COOL,
MODE_OFF,
}
O app rastreia as operações compatíveis para cada atributo:
// 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,
)
Para comandos que usam um ou mais parâmetros, também há uma variável:
val valueLevel: MutableStateFlow<UByte?>
A API mostra um conjunto de elementos de visualização que os usuários podem usar para selecionar os campos obrigatórios.
Remova o comentário da etapa 4.2.1 no arquivo ActionView.kt
para renderizar todos os dispositivos de ação e implementar o callback de clique em um DropdownMenu
para definir 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
// }
// )
// }
}
Remova o comentário da etapa 4.2.2 no arquivo ActionView.kt
para renderizar todos os atributos de actionDeviceVM
e implementar o callback de clique em um DropdownMenu
para definir o actionTrait
, representando o atributo ao qual o comando pertence.
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
// }
// )
// }
}
Remova o comentário da etapa 4.2.3 no arquivo ActionView.kt
para renderizar todas as ações disponíveis de actionTrait
e implementar o callback de clique em um DropdownMenu
para definir o actionAction
, que representa a ação de automação selecionada.
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
// }
// )
// }
}
Remova a marca de comentário da etapa 4.2.4 no arquivo ActionView.kt
para renderizar os valores disponíveis da ação de atributo (comando) e armazenar o valor em actionValueLevel
no callback de mudança de valor:
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
// )
// }
...
}
Remova a marca de comentário da etapa 4.2.5 no arquivo ActionView.kt
para armazenar todas as variáveis da ação ViewModel
na ação ViewModel
da automação de rascunho (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)) }
A execução do app e a seleção de uma nova automação e ação vão resultar em uma visualização como esta:
No app de exemplo, só oferecemos suporte a ações com base em características do dispositivo.
Renderizar uma automação de rascunho
Quando o DraftViewModel
for concluído, ele poderá ser renderizado por HomeAppView.kt
:
fun HomeAppView (homeAppVM: HomeAppViewModel) {
...
// If a draft automation is selected, show the draft editor:
if (selectedDraftVM != null) {
DraftView(homeAppVM)
}
...
}
Em DraftView.kt
:
fun DraftView (homeAppVM: HomeAppViewModel) {
val draftVM: DraftViewModel = homeAppVM.selectedDraftVM.collectAsState().value!!
...
// Draft Starters:
DraftStarterList(draftVM)
// Draft Actions:
DraftActionList(draftVM)
}
Criar uma automação
Agora que você aprendeu a criar ativações e ações, está tudo pronto para criar um rascunho de automação e enviá-lo à API Automation. A API tem uma função createAutomation()
que usa um rascunho de automação como argumento e retorna uma nova instância de automação.
A preparação do rascunho da automação acontece na classe DraftViewModel
do app de exemplo. Confira a função getDraftAutomation()
para saber mais sobre como estruturamos o rascunho da automação usando as variáveis de inicialização e ação na seção anterior.
Remova a anotação de comentário da etapa 4.4.1 no arquivo DraftViewModel.kt
para criar as expressões "select" necessárias para criar o gráfico de automação quando o atributo inicializador for 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()
...
}
Remova a anotação de comentário da etapa 4.4.2 no arquivo DraftViewModel.kt
para criar as expressões paralelas necessárias para criar o gráfico de automação quando o atributo de ação selecionado for LevelControl
e a ação selecionada for 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) }
...
}
A última etapa para concluir uma automação é implementar a função getDraftAutomation
para criar um AutomationDraft.
Remova a marca de comentário da etapa 4.4.3 no arquivo HomeAppViewModel.kt
para criar a automação chamando as APIs Home e processando exceções:
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
}
}
Agora, execute o app e confira as mudanças no seu dispositivo.
Depois de selecionar um inicializador e uma ação, você pode criar a automação:
Dê um nome exclusivo à automação e toque no botão Criar automação, que vai chamar as APIs e retornar à visualização de lista de automações com a automação criada:
Toque na automação que você acabou de criar e confira como ela é retornada pelas APIs.
A API retorna um valor indicando se uma automação é válida e está ativa no momento. É possível criar automações que não passam na validação quando são analisadas no lado do servidor. Se a análise de automação falhar na validação, isValid
será definido como false
, indicando que a automação é inválida e inativa. Se a automação for inválida, verifique o campo automation.validationIssues
para ver os detalhes.
Confira se a automação está definida como válida e ativa. Depois, teste a automação.
Testar a automação
As automações podem ser executadas de duas maneiras:
- Com um evento inicial. Se as condições forem atendidas, isso aciona a ação definida na automação.
- Com uma chamada de API de execução manual.
Se uma automação de rascunho tiver um manualStarter()
definido no bloco DSL de automação de rascunho, o mecanismo de automação vai oferecer suporte à execução manual dessa automação. Isso já está presente nos exemplos de código no app de exemplo.
Como você ainda está na tela de visualização da automação no seu dispositivo móvel, toque no botão Executar manualmente. Isso vai chamar automation.execute()
, que executa o comando de ação no dispositivo selecionado ao configurar a automação.
Depois de validar o comando de ação com a execução manual usando a API, é hora de verificar se ele também está sendo executado usando o iniciador definido.
Acesse a guia "Devices", selecione o dispositivo de ação e o atributo e defina um valor diferente. Por exemplo, defina o LevelControl
(brilho) de light2
como 50%, conforme ilustrado na captura de tela a seguir:
Agora vamos tentar acionar a automação usando o dispositivo de ativação. Escolha o dispositivo inicial que você selecionou ao criar a automação. Alterne a característica escolhida. Por exemplo, defina OnOff
de starter outlet1
como On
:
Isso também executa a automação e define a característica LevelControl
do dispositivo de ação light2
como o valor original, 100%:
Parabéns! Você usou as APIs Home para criar automações.
Para saber mais sobre a API Automation, consulte API Android Automation.
5. Descobrir recursos
As APIs Home incluem uma API dedicada chamada API Discovery, que os desenvolvedores podem usar para consultar quais características com capacidade de automação são compatíveis com um determinado dispositivo. O app de exemplo mostra como usar essa API para descobrir quais comandos estão disponíveis.
Comandos do Google Assistente
Nesta seção, vamos discutir como descobrir CommandCandidates
com suporte e como criar uma automação com base nos nós candidatos descobertos.
No app de exemplo, chamamos device.candidates()
para receber uma lista de candidatos, que pode incluir instâncias de CommandCandidate
, EventCandidate
ou TraitAttributesCandidate
.
Acesse o arquivo HomeAppViewModel.kt
e remova a marca de comentário da etapa 5.1.1 para recuperar a lista de candidatos e filtrar com o 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)
}
Veja como ele filtra para o CommandCandidate.
. Os candidatos retornados pela API pertencem a tipos diferentes. O app de exemplo é compatível com CommandCandidate
. Remova a marca de comentário da etapa 5.1.2 no commandMap
definido em ActionViewModel.kt
para definir os seguintes atributos compatíveis:
// 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
)
Agora que podemos chamar a API Discovery e filtrar os resultados compatíveis no app de exemplo, vamos discutir como integrar isso ao nosso editor.
Para saber mais sobre a API Discovery, acesse Usar a descoberta de dispositivos no Android.
Integrar o editor
A maneira mais comum de usar as ações descobertas é apresentá-las a um usuário final para que ele faça a seleção. Antes que o usuário selecione os campos de automação do rascunho, podemos mostrar a lista de ações descobertas e, dependendo do valor selecionado, podemos pré-preencher o nó de ação no rascunho da automação.
O arquivo CandidatesView.kt
contém a classe de visualização que mostra os candidatos encontrados. Remova o comentário da etapa 5.2.1 para ativar a função .clickable{}
do CandidateListItem
, que define homeAppVM.selectedDraftVM
como 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)) }
}) {
...
}
}
}
Semelhante à etapa 4.3 em HomeAppView.kt
, quando o selectedDraftVM
é definido, ele renderiza o 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)
}
...
}
Tente de novo tocando em light2 - MOVE_TO_LEVEL, mostrado na seção anterior, que solicita a criação de uma nova automação com base no comando do candidato:
Agora que você já sabe como criar automações no app de exemplo, é possível integrar as automações nos seus apps.
6. Exemplos de automação avançada
Antes de encerrarmos, vamos discutir alguns outros exemplos de DSL de automação. Eles ilustram alguns dos recursos avançados que você pode alcançar com as APIs.
Horário como ativação
Além dos atributos do dispositivo, as APIs do Google Home oferecem atributos estruturados, como Time
. Você pode criar uma automação com um acionador baseado em tempo, como este:
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
...
}
}
Transmissão do Google Assistente como ação
O atributo AssistantBroadcast
está disponível como um atributo no nível do dispositivo em um SpeakerDevice
(se o alto-falante for compatível) ou como um atributo no nível da estrutura (porque alto-falantes do Google e dispositivos móveis Android podem transmitir transmissões do Google Assistente). Exemplo:
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!!")
)
}
}
}
Usar DelayFor
e suppressFor
A API Automation também oferece operadores avançados, como delayFor, que serve para atrasar comandos, e suppressFor, que pode impedir que uma automação seja acionada pelos mesmos eventos em um determinado período. Confira alguns exemplos de uso desses operadores:
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!"))
}
...
}
Usar AreaPresenceState
em um starter
AreaPresenceState
é um atributo no nível da estrutura que detecta se há alguém em casa.
Por exemplo, o exemplo a seguir demonstra o trancamento automático das portas quando alguém está em casa depois das 22h:
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()))
}
}
}
Agora que você já conhece esses recursos avançados de automação, crie apps incríveis.
7. Parabéns!
Parabéns! Você concluiu a segunda parte do desenvolvimento de um app Android usando as APIs do Google Home. Neste codelab, você conheceu as APIs Automation e Discovery.
Esperamos que você goste de criar apps que controlam dispositivos de forma criativa no ecossistema do Google Home e crie cenários de automação incríveis usando as APIs do Home.
Próximas etapas
- Leia Solução de problemas para aprender a depurar apps e resolver problemas envolvendo as APIs Home.
- Você pode entrar em contato com recomendações ou informar problemas no tópico de suporte da Issue Tracker, Smart Home.