1. Прежде чем начать
Это вторая практическая работа из серии, посвящённой созданию приложения для Android с использованием API Google Home. В ней мы рассмотрим создание домашней автоматизации и дадим несколько советов по передовому опыту использования API. Если вы ещё не выполнили первую практическую работу — «Создание мобильного приложения с использованием API Google Home на Android» , — рекомендуем выполнить её перед началом работы.
API Google Home предоставляют разработчикам Android набор библиотек для управления устройствами умного дома в экосистеме Google Home. С помощью этих новых API разработчики смогут настраивать автоматизацию для умного дома, которая будет управлять функциями устройств на основе предопределенных условий. Google также предоставляет API Discovery , который позволяет запрашивать у устройств информацию о поддерживаемых ими атрибутах и командах.
Предпосылки
- Выполните практическую работу по созданию мобильного приложения с использованием Home API на Android .
- Знание экосистемы Google Home ( Cloud-to-cloud и Matter ).
- Рабочая станция с установленной Android Studio (2024.3.1 Ladybug или более поздняя версия).
- Телефон Android, соответствующий требованиям Home API (см. раздел «Предварительные требования» ), с установленными сервисами Google Play и приложением Google Home . Эмулятор не работает, для примера приложения поддерживаются только физические телефоны Android.
- Совместимый Google Home Hub , поддерживающий API Google Home .
- Дополнительно — устройство «умного дома», совместимое с API Google Home.
Чему вы научитесь
- Как создать автоматизацию для устройств умного дома с помощью Home API.
- Как использовать API-интерфейсы Discovery для изучения поддерживаемых возможностей устройств.
- Как использовать лучшие практики при создании приложений с использованием Home API.
2. Настройка вашего проекта
На следующей диаграмме показана архитектура приложения Home API:
- Код приложения: основной код, над которым работают разработчики для создания пользовательского интерфейса приложения и логики взаимодействия с Home API SDK.
- Home API SDK: Home API SDK, предоставляемый Google, работает со службой Home API в GMSCore для управления устройствами умного дома. Разработчики создают приложения, работающие с Home API, объединяя их с Home API SDK.
- GMSCore на Android: GMSCore, также известный как сервисы Google Play, — это платформа Google, предоставляющая основные системные сервисы, обеспечивающие ключевые функции на всех сертифицированных устройствах Android. Модуль Home сервисов Google Play содержит сервисы, взаимодействующие с API Home.
В этой лабораторной работе мы будем опираться на то, что мы рассмотрели в разделе Создание мобильного приложения с использованием API Home на Android .
Убедитесь, что у вас есть структура, включающая как минимум два поддерживаемых устройства, настроенных и работающих в учётной записи. Поскольку в этой лабораторной работе мы будем настраивать автоматизацию (изменение состояния одного устройства запускает действие на другом), вам понадобятся два устройства для просмотра результатов.
Получить образец приложения
Исходный код примера приложения доступен на GitHub в репозитории google-home/google-home-api-sample-app-android .
В этой лабораторной работе используются примеры из ветки codelab-branch-2
приложения Sample App.
Перейдите туда, где вы хотите сохранить проект, и клонируйте ветку codelab-branch-2
:
$ git clone -b codelab-branch-2 https://github.com/google-home/google-home-api-sample-app-android.git
Обратите внимание, что это другая ветка, нежели та, что используется в разделе «Создание мобильного приложения с использованием Home API на Android» . Эта ветвь кодовой базы основана на том, на чём остановилась первая лабораторная работа. На этот раз примеры покажут вам, как создавать автоматизации. Если вы выполнили предыдущую лабораторную работу и смогли реализовать весь функционал, вы можете использовать тот же проект Android Studio для выполнения этой лабораторной работы вместо codelab-branch-2
.
Как только исходный код будет скомпилирован и готов к запуску на мобильном устройстве, переходите к следующему разделу.
3. Узнайте об автоматизации
Автоматизации представляют собой набор операторов типа «если это, то то», которые позволяют автоматически управлять состояниями устройств на основе выбранных факторов. Разработчики могут использовать автоматизации для создания расширенных интерактивных функций в своих API.
Автоматизация состоит из трёх различных типов компонентов, известных как узлы : стартёры, действия и условия. Эти узлы работают вместе для автоматизации действий с использованием устройств умного дома. Обычно они оцениваются в следующем порядке:
- Starter — определяет начальные условия, активирующие автоматизацию, например, изменение значения признака. Автоматизация должна иметь Starter .
- Условие — любые дополнительные ограничения, которые необходимо оценить после срабатывания автоматизации. Выражение в условии должно быть истинным, чтобы действия автоматизации были выполнены.
- Действие — команды или обновления состояния, которые выполняются при выполнении всех условий.
Например, можно настроить автоматику, которая приглушает свет в комнате при переключении выключателя, при этом телевизор в этой комнате включен. В этом примере:
- Стартер — Переключатель в комнате переключен.
- Состояние — Состояние телевизора OnOff оценивается как Включено.
- Действие — Свет в той же комнате, где находится Switch, приглушается.
Эти узлы оцениваются Automation Engine последовательно или параллельно.
Последовательный поток содержит узлы, которые выполняются последовательно. Обычно это: стартер, условие и действие.
Параллельный поток может включать несколько узлов действий, выполняемых одновременно, например, включение нескольких лампочек одновременно. Узлы, следующие за параллельным потоком, не будут выполняться, пока не завершатся все его ветви.
В схеме автоматизации есть и другие типы узлов. Подробнее о них можно узнать в разделе «Узлы» Руководства разработчика Home API. Кроме того, разработчики могут комбинировать различные типы узлов для создания сложных схем автоматизации, например:
Разработчики предоставляют эти узлы Automation Engine, используя доменно-ориентированный язык (DSL), созданный специально для автоматизации Google Home.
Изучите DSL автоматизации
Предметно-ориентированный язык (DSL) — это язык, используемый для описания поведения системы в коде. Компилятор генерирует классы данных, которые сериализуются в буфер протокола JSON и используются для вызовов служб автоматизации Google.
DSL ищет следующую схему:
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()) }
}
}
Автоматизация в предыдущем примере синхронизирует две лампочки. Когда состояние OnOff
device1
меняется на On
( onOffTrait.onOff equals true
), состояние OnOff
устройства device2
меняется на On
( command(OnOff.on()
).
При работе с автоматизацией помните, что существуют ограничения по ресурсам .
Автоматизация — очень полезный инструмент для создания автоматизированных функций в умном доме. В самом простом случае вы можете явно запрограммировать автоматизацию для использования определённых устройств и характеристик. Но более практичный вариант — когда приложение позволяет пользователю настраивать устройства, команды и параметры автоматизации. В следующем разделе объясняется, как создать редактор автоматизации, который позволит пользователю делать именно это.
4. Создайте редактор автоматизации
В приложении-примере мы создадим редактор автоматизации, с помощью которого пользователи смогут выбирать устройства, возможности (действия), которые они хотят использовать, и способы запуска автоматизации с помощью стартеров.
Настройка стартеров
Стартер автоматизации — это точка входа для автоматизации. Стартер запускает автоматизацию при наступлении заданного события. В примере приложения мы получаем стартеры автоматизации с помощью класса StarterViewModel
, находящегося в исходном файле StarterViewModel.kt
, и отображаем представление редактора с помощью StarterView
( StarterView.kt
).
Для стартового узла необходимы следующие элементы:
- Устройство
- Черта
- Операция
- Ценить
Устройство и характеристику можно выбрать из объектов, возвращаемых API устройств. Команды и параметры для каждого поддерживаемого устройства — более сложная задача, требующая отдельной обработки.
Приложение определяет предустановленный список операций:
// 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
}
Затем для каждого поддерживаемого признака отслеживаются поддерживаемые операции:
// 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
))
Аналогичным образом приложение Sample App отслеживает значения, присваиваемые признакам:
enum class OnOffValue {
On,
Off,
}
enum class ThermostatValue {
Heat,
Cool,
Off,
}
И отслеживает соответствие между значениями, определенными приложением, и значениями, определенными 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,
)
Затем приложение отображает набор элементов представления, которые пользователи могут использовать для выбора необходимых полей.
Раскомментируйте шаг 4.1.1 в файле StarterView.kt
, чтобы отобразить все стартовые устройства и реализовать обратный вызов нажатия в 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
// }
// )
// }
}
Раскомментируйте шаг 4.1.2 в файле StarterView.kt
, чтобы отобразить все характеристики начального устройства и реализовать обратный вызов нажатия в 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
// }
// )
}
}
Раскомментируйте шаг 4.1.3 в файле StarterView.kt
, чтобы отобразить все операции выбранного признака и реализовать обратный вызов щелчка в 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
// }
// )
// }
}
Раскомментируйте шаг 4.1.4 в файле StarterView.kt
, чтобы отобразить все значения выбранного признака и реализовать обратный вызов щелчка в 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 -> {
...
}
}
Раскомментируйте шаг 4.1.5 в файле StarterView.kt
, чтобы сохранить все переменные ViewModel
стартера в ViewModel
стартера автоматизации проекта ( 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)) }
Запуск приложения и выбор новой автоматизации и стартера должны отобразить следующее представление:
Приложение Sample App поддерживает только стартовые версии, основанные на характеристиках устройства.
Настройка действий
Действие автоматизации отражает основную цель автоматизации — то, как она влияет на изменение физического мира. В примере приложения мы фиксируем действия автоматизации с помощью класса ActionViewModel
и отображаем представление редактора с помощью класса ActionView
.
В примере приложения используются следующие сущности Home API для определения узлов действий автоматизации:
- Устройство
- Черта
- Команда
- Значение (необязательно)
Каждое действие команды устройства использует команду, но некоторые также требуют связанного с ней значения параметра, например MoveToLevel()
и целевого процента.
Устройство и характеристику можно выбрать из объектов, возвращаемых API устройств.
Приложение определяет предопределенный список команд:
// List of operations available when creating automation starters:
enum class Action {
ON,
OFF,
MOVE_TO_LEVEL,
MODE_HEAT,
MODE_COOL,
MODE_OFF,
}
Приложение отслеживает поддерживаемые операции для каждого поддерживаемого признака:
// 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,
)
Для команд, принимающих один или несколько параметров, также существует переменная:
val valueLevel: MutableStateFlow<UByte?>
API отображает набор элементов представления, которые пользователи могут использовать для выбора необходимых полей.
Раскомментируйте шаг 4.2.1 в файле ActionView.kt
, чтобы отобразить все устройства действий и реализовать обратный вызов щелчка в DropdownMenu
для установки 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
// }
// )
// }
}
Раскомментируйте шаг 4.2.2 в файле ActionView.kt
, чтобы отобразить все черты actionDeviceVM
и реализовать обратный вызов щелчка в DropdownMenu
для установки actionTrait
, представляющего черту, к которой принадлежит команда.
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
// }
// )
// }
}
Раскомментируйте шаг 4.2.3 в файле ActionView.kt
, чтобы отобразить все доступные действия actionTrait
и реализовать обратный вызов щелчка в DropdownMenu
для установки actionAction
, представляющего выбранное действие автоматизации.
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
// }
// )
// }
}
Раскомментируйте шаг 4.2.4 в файле ActionView.kt
, чтобы отобразить доступные значения действия признака (команды) и сохранить значение в actionValueLevel
в обратном вызове изменения значения:
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
// )
// }
...
}
Раскомментируйте шаг 4.2.5 в файле ActionView.kt
, чтобы сохранить все переменные действия ViewModel
в действии ViewModel
проекта автоматизации ( 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)) }
Запуск приложения и выбор новой автоматизации и действия должны привести к следующему виду:
В примере приложения мы поддерживаем только действия, основанные на характеристиках устройства.
Визуализация чернового варианта автоматизации
Когда DraftViewModel
будет готов, его можно отобразить с помощью HomeAppView.kt
:
fun HomeAppView (homeAppVM: HomeAppViewModel) {
...
// If a draft automation is selected, show the draft editor:
if (selectedDraftVM != null) {
DraftView(homeAppVM)
}
...
}
В DraftView.kt
:
fun DraftView (homeAppVM: HomeAppViewModel) {
val draftVM: DraftViewModel = homeAppVM.selectedDraftVM.collectAsState().value!!
...
// Draft Starters:
DraftStarterList(draftVM)
// Draft Actions:
DraftActionList(draftVM)
}
Создать автоматизацию
Теперь, когда вы научились создавать стартеры и действия, вы готовы создать черновик автоматизации и отправить его в API автоматизации. В API есть функция createAutomation()
, которая принимает черновик автоматизации в качестве аргумента и возвращает новый экземпляр автоматизации.
Подготовка черновика автоматизации выполняется в классе DraftViewModel
в примере приложения. Подробнее о том, как структурировать черновик автоматизации с помощью переменных starter и action из предыдущего раздела, читайте в функции getDraftAutomation()
.
Раскомментируйте шаг 4.4.1 в файле DraftViewModel.kt
, чтобы создать выражения «select», необходимые для создания графика автоматизации, когда начальный признак — 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()
...
}
Раскомментируйте шаг 4.4.2 в файле DraftViewModel.kt
, чтобы создать параллельные выражения, необходимые для создания графика автоматизации, когда выбранным признаком действия является LevelControl
, а выбранным действием — 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) }
...
}
Последним шагом в завершении автоматизации является реализация функции getDraftAutomation
для создания AutomationDraft.
Раскомментируйте шаг 4.4.3 в файле HomeAppViewModel.kt
, чтобы создать автоматизацию путем вызова API Home и обработки исключений:
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
}
}
Теперь запустите приложение и посмотрите изменения на вашем устройстве!
После выбора стартера и действия вы готовы создать автоматизацию:
Обязательно дайте своей автоматизации уникальное имя, затем нажмите кнопку «Создать автоматизацию» , которая должна вызвать API и вернуть вас к списку автоматизаций с вашей автоматизацией:
Нажмите на только что созданную вами автоматизацию и посмотрите, как она отображается в API.
Обратите внимание, что API возвращает значение, указывающее, является ли автоматизация допустимой и активной в данный момент . Можно создавать автоматизации, которые не проходят проверку при анализе на стороне сервера. Если автоматизация не проходит проверку, isValid
устанавливается в false
, что означает, что автоматизация недопустима и неактивна. Если ваша автоматизация недопустима, проверьте поле automation.validationIssues
для получения подробной информации.
Убедитесь, что ваша автоматизация установлена как действующая и активная, а затем вы можете опробовать ее.
Попробуйте свою автоматизацию
Автоматизацию можно реализовать двумя способами:
- С начальным событием. Если условия совпадают, запускается действие, заданное в автоматизации.
- С вызовом API ручного выполнения.
Если в DSL-блоке черновика автоматизации определён метод manualStarter()
, движок автоматизации будет поддерживать ручное выполнение этой автоматизации. Это уже реализовано в примерах кода в приложении-примере.
Поскольку вы всё ещё находитесь на экране просмотра автоматизации на мобильном устройстве, нажмите кнопку «Выполнить вручную» . Это должно вызвать метод automation.execute()
, который выполнит команду действия на устройстве, выбранном при настройке автоматизации.
После проверки команды действия посредством ручного выполнения с использованием API теперь пора проверить, выполняется ли она с использованием определенного вами стартера.
Перейдите на вкладку «Устройства», выберите устройство действия и характеристику, а затем задайте другое значение (например, задайте LevelControl
(яркость) light2
на 50%, как показано на следующем снимке экрана):
Теперь попробуем запустить автоматизацию с помощью пускового устройства. Выберите пусковое устройство, выбранное при создании автоматизации. Измените выбранный вами признак (например, установите starter outlet1
значение OnOff
на On
):
Вы увидите, что это также выполняет автоматизацию и устанавливает свойство LevelControl
устройства действия light2
на исходное значение 100%:
Поздравляем, вы успешно использовали Home API для создания автоматизации!
Дополнительную информацию об API автоматизации см. в разделе API автоматизации Android .
5. Откройте для себя возможности
В состав Home API входит специальный API, называемый Discovery API, который разработчики могут использовать для запроса поддерживаемых устройством функций автоматизации. В приложении Sample App представлен пример использования этого API для определения доступных команд.
Откройте для себя команды
В этом разделе мы обсудим, как обнаружить поддерживаемые CommandCandidates
и как создать автоматизацию на основе обнаруженных узлов-кандидатов.
В примере приложения мы вызываем device.candidates()
, чтобы получить список кандидатов, который может включать экземпляры CommandCandidate
, EventCandidate
или TraitAttributesCandidate
.
Перейдите в файл HomeAppViewModel.kt
и раскомментируйте шаг 5.1.1, чтобы получить список кандидатов и отфильтровать его по типу 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)
}
Посмотрите, как он фильтрует CommandCandidate.
Кандидаты, возвращаемые API, относятся к разным типам. Пример приложения поддерживает CommandCandidate
. Раскомментируйте шаг 5.1.2 в commandMap
, определённом в ActionViewModel.kt
, чтобы задать следующие поддерживаемые характеристики:
// 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
)
Теперь, когда мы можем вызывать API Discovery и фильтровать результаты, которые мы поддерживаем в примере приложения, мы обсудим, как мы можем интегрировать это в наш редактор.
Дополнительную информацию об API обнаружения см. на странице Использование обнаружения устройств на Android .
Интегрировать редактор
Самый распространённый способ использования обнаруженных действий — предоставить их конечному пользователю для выбора. Непосредственно перед тем, как пользователь выберет поля автоматизации в черновике, мы можем показать ему список обнаруженных действий и, в зависимости от выбранного значения, предварительно заполнить узел действия в черновике автоматизации.
Файл CandidatesView.kt
содержит класс представления, отображающий найденных кандидатов. Раскомментируйте шаг 5.2.1, чтобы включить функцию .clickable{}
объекта CandidateListItem
, которая устанавливает homeAppVM.selectedDraftVM
в качестве 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)) }
}) {
...
}
}
}
Аналогично шагу 4.3 в HomeAppView.kt
, когда установлен selectedDraftVM
, он отображает 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)
}
...
}
Попробуйте еще раз, нажав light2 - MOVE_TO_LEVEL , как показано в предыдущем разделе, что предложит вам создать новую автоматизацию на основе команды-кандидата:
Теперь, когда вы знакомы с созданием автоматизации в примере приложения, вы можете интегрировать автоматизацию в свои приложения.
6. Примеры расширенной автоматизации
Прежде чем завершить, мы рассмотрим несколько дополнительных примеров автоматизации DSL. Они иллюстрируют некоторые расширенные возможности, которые можно реализовать с помощью API.
Время суток как начало
Помимо характеристик устройства, API Google Home предлагают структурные характеристики, такие как Time
. Вы можете создать автоматизацию с запуском по времени, например:
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
...
}
}
Помощник Трансляция как Действие
Функция AssistantBroadcast
доступна либо как функция уровня устройства в SpeakerDevice
(если динамик поддерживает её), либо как функция уровня структуры (поскольку динамики Google и мобильные устройства Android могут воспроизводить трансляции Ассистента). Например:
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
и suppressFor
API автоматизации также предоставляет расширенные операторы, такие как delayFor для задержки команд и suppressFor , который позволяет предотвратить запуск автоматизации теми же событиями в течение заданного периода времени. Вот несколько примеров использования этих операторов:
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
в стартере
AreaPresenceState
— это признак уровня структуры, который определяет, находится ли кто-нибудь дома.
Например, следующий пример демонстрирует автоматическую блокировку дверей, когда кто-то находится дома после 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()))
}
}
}
Теперь, когда вы знакомы с этими передовыми возможностями автоматизации, идите и создавайте потрясающие приложения!
7. Поздравляем!
Поздравляем! Вы успешно завершили вторую часть разработки приложения для Android с использованием API Google Home. В ходе этой лабораторной работы вы изучили API автоматизации и обнаружения.
Мы надеемся, что вам понравится создавать приложения, которые творчески управляют устройствами в экосистеме Google Home, и реализовывать захватывающие сценарии автоматизации с использованием API Home!
Следующие шаги
- Ознакомьтесь с разделом Устранение неполадок , чтобы узнать, как эффективно отлаживать приложения и устранять неполадки, связанные с API Home.
- Вы можете обратиться к нам с любыми рекомендациями или сообщить о любых проблемах через раздел поддержки Smart Home в системе отслеживания проблем .