1. Прежде чем начать
Это вторая практическая работа в серии по созданию приложения Android с использованием API Google Home. В этой практической работе мы рассмотрим, как создавать домашнюю автоматизацию, и дадим несколько советов по лучшим практикам использования API. Если вы еще не завершили первую практическую работу, Создание мобильного приложения с использованием API Home на Android , мы рекомендуем вам завершить ее перед началом этой практической работы.
API Google Home предоставляют набор библиотек для разработчиков Android для управления устройствами умного дома в экосистеме Google Home. С помощью этих новых API разработчики смогут устанавливать автоматизацию для умного дома, которая может управлять возможностями устройств на основе предопределенных условий. Google также предоставляет API Discovery , который позволяет вам запрашивать устройства, чтобы узнать, какие атрибуты и команды они поддерживают.
Предпосылки
- Выполните практическую работу по созданию мобильного приложения с использованием API Home на Android .
- Знание экосистемы Google Home ( Cloud-to-cloud и Matter ).
- Рабочая станция с установленной Android Studio (2024.3.1 Ladybug или более поздней версии).
- Телефон Android, отвечающий требованиям Home API (см. Предварительные условия ), с установленными сервисами Google Play и приложением Google Home .
- Совместимый Google Home Hub , поддерживающий API Google Home .
- Дополнительно — устройство «умного дома», совместимое с API Google Home.
Чему вы научитесь
- Как создать автоматизацию для устройств умного дома с помощью Home API.
- Как использовать API Discovery для изучения поддерживаемых возможностей устройств.
- Как использовать лучшие практики при создании приложений с использованием Home API.
2. Настройка вашего проекта
Следующая диаграмма иллюстрирует архитектуру приложения Home API:
- Код приложения: основной код, над которым работают разработчики для создания пользовательского интерфейса приложения и логики взаимодействия с SDK Home API.
- Home APIs SDK: Home APIs SDK, предоставляемый Google, работает с Home APIs Service в GMSCore для управления умными домашними устройствами. Разработчики создают приложения, которые работают с Home APIs, объединяя их с Home APIs SDK.
- GMSCore на Android: GMSCore, также известный как Google Play services, — это платформа Google, которая предоставляет основные системные службы, обеспечивая ключевые функции на всех сертифицированных устройствах Android. Домашний модуль служб 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
Обратите внимание, что это другая ветвь, нежели та, что используется в Build a mobile app using the Home APIs on Android . Эта ветвь кодовой базы строится на том месте, где остановилась первая кодовая лаборатория. На этот раз примеры покажут вам, как создавать автоматизации. Если вы завершили предыдущую кодовую лабораторию и смогли заставить работать все функции, вы можете использовать тот же проект Android Studio для завершения этой кодовой лаборатории вместо codelab-branch-2
.
Как только исходный код будет скомпилирован и готов к запуску на мобильном устройстве, переходите к следующему разделу.
3. Узнайте больше об автоматизации
Автоматизации — это набор утверждений «если это, то то», которые могут управлять состояниями устройств на основе выбранных факторов в автоматическом режиме. Разработчики могут использовать автоматизации для создания расширенных интерактивных функций в своих API.
Автоматизация состоит из трех различных типов компонентов, известных как узлы : стартеры, действия и условия. Эти узлы работают вместе для автоматизации поведения с использованием устройств умного дома. Обычно они оцениваются в следующем порядке:
- Starter — определяет начальные условия, которые активируют автоматизацию, например, изменение значения признака. Автоматизация должна иметь Starter .
- Условие — любые дополнительные ограничения для оценки после срабатывания автоматизации. Выражение в условии должно быть истинным, чтобы действия автоматизации выполнялись.
- Действие — команды или обновления состояния, которые выполняются при выполнении всех условий.
Например, у вас может быть автоматизация, которая приглушает свет в комнате при переключении переключателя, в то время как телевизор в этой комнате включен. В этом примере:
- Стартер — Переключатель в комнате переключен.
- Условие — Состояние телевизора OnOff оценивается как On.
- Действие — Свет в той же комнате, где находится Switch, приглушается.
Эти узлы оцениваются модулем автоматизации последовательно или параллельно.
Последовательный поток содержит узлы, которые выполняются в последовательном порядке. Обычно это стартер, условие и действие.
Параллельный поток может иметь несколько узлов действий, выполняемых одновременно, например, включение нескольких лампочек одновременно. Узлы, следующие за параллельным потоком, не будут выполняться, пока не завершатся все ветви параллельного потока.
В схеме автоматизации есть и другие типы узлов. Подробнее о них можно узнать в разделе «Узлы» Руководства разработчика API Home. Кроме того, разработчики могут комбинировать различные типы узлов для создания сложных автоматизаций, например, следующих:
Разработчики предоставляют эти узлы движку автоматизации, используя доменно-ориентированный язык (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. Создайте редактор автоматизации
В приложении Sample мы создадим редактор автоматизации, с помощью которого пользователи смогут выбирать устройства, возможности (действия), которые они хотят использовать, и способы запуска автоматизации с помощью стартеров.
Настройка стартеров
Стартер автоматизации — это точка входа для автоматизации. Стартер запускает автоматизацию, когда происходит заданное событие. В примере приложения мы захватываем стартеры автоматизации с помощью класса 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 поддерживает только стартовые версии, основанные на характеристиках устройства.
Настройте действия
Действие автоматизации отражает центральную цель автоматизации, то, как она влияет на изменение в физическом мире. В примере приложения мы фиксируем действия автоматизации с помощью класса 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
в Sample App. Посмотрите на функцию getDraftAutomation()
чтобы узнать больше о том, как мы структурируем черновик автоматизации с помощью переменных starter и action в предыдущем разделе.
Раскомментируйте шаг 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 ручного выполнения.
Если черновик автоматизации имеет manualStarter()
определенный в блоке DSL черновика автоматизации, движок автоматизации будет поддерживать ручное выполнение для этой автоматизации. Это уже присутствует в примерах кода в Sample App.
Поскольку вы все еще находитесь на экране просмотра автоматизации на своем мобильном устройстве, нажмите кнопку Manual Execute . Это должно вызвать automation.execute()
, который запускает вашу команду действия на устройстве, выбранном вами при настройке автоматизации.
После проверки команды действия посредством ручного выполнения с использованием API пришло время проверить, выполняется ли она также с использованием определенного вами стартера.
Перейдите на вкладку «Устройства», выберите устройство действия и черту и установите для нее другое значение (например, установите LevelControl
(яркость) light2
на 50%, как показано на следующем снимке экрана):
Теперь мы попробуем запустить автоматизацию с помощью стартового устройства. Выберите стартовое устройство, которое вы выбрали при создании автоматизации. Переключите выбранную вами черту (например, установите OnOff
starter outlet1
на On
):
Вы увидите, что это также выполняет автоматизацию и устанавливает свойство LevelControl
устройства действия light2
на исходное значение 100%:
Поздравляем, вы успешно использовали Home API для создания автоматизаций!
Дополнительную информацию об API автоматизации см. в разделе API автоматизации Android .
5. Откройте для себя возможности
API Home включают в себя выделенный API, называемый Discovery API, который разработчики могут использовать для запроса того, какие черты, поддерживающие автоматизацию, поддерживаются в данном устройстве. Пример приложения представляет собой пример, в котором вы можете использовать этот 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, относятся к разным типам. Sample App поддерживает 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 Discovery, посетите страницу Использование обнаружения устройств на 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 могут воспроизводить трансляции Assistant). Например:
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 в системе отслеживания проблем .