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-смартфон, соответствующий требованиям API Home (см. раздел «Предварительные условия» ), с установленными сервисами Google Play и приложением Google Home . Эмулятор не подойдёт , для демонстрации приложения поддерживаются только физические Android-смартфоны.
- Совместимый концентратор Google Home , поддерживающий API Google Home .
- Дополнительно — устройство для умного дома, совместимое с API Google Home.
Что вы узнаете
- Как создавать сценарии автоматизации для устройств умного дома с помощью Home API.
- Как использовать API Discovery для изучения возможностей поддерживаемых устройств.
- Как применять лучшие практики при разработке приложений с использованием API Home.
2. Настройка проекта
На следующей диаграмме представлена архитектура приложения Home APIs:

- Код приложения: Основной код, над которым работают разработчики для создания пользовательского интерфейса приложения и логики взаимодействия с SDK Home APIs.
- Home APIs SDK: Предоставляемый Google Home APIs SDK работает с сервисом Home APIs в GMSCore для управления устройствами умного дома. Разработчики создают приложения, работающие с Home APIs, путем включения их в состав Home APIs SDK.
- GMSCore на Android: GMSCore, также известный как Google Play Services, — это платформа Google, предоставляющая основные системные сервисы, обеспечивающие ключевую функциональность на всех сертифицированных устройствах Android. Модуль Home в Google Play Services содержит сервисы, взаимодействующие с API Home.
В этом практическом занятии мы продолжим изучение материала, рассмотренного в разделе «Создание мобильного приложения с использованием Home API на Android» .
Убедитесь, что в вашей учетной записи настроено и работает как минимум два поддерживаемых устройства. Поскольку в этом практическом задании мы будем настраивать автоматизацию (изменение состояния одного устройства запускает действие на другом), вам потребуется два устройства, чтобы увидеть результаты.
Скачайте пробную версию приложения.
Исходный код демонстрационного приложения доступен на GitHub в репозитории google-home/google-home-api-sample-app-android .
В этом практическом занятии используются примеры из ветки codelab-branch-2 демонстрационного приложения.
Перейдите в папку, где вы хотите сохранить проект, и клонируйте ветку codelab-branch-2 :
$ git clone -b codelab-branch-2 https://github.com/google-home/google-home-api-sample-app-android.git
Обратите внимание, что это другая ветка, нежели та, что используется в примере «Создание мобильного приложения с использованием API Home на Android» . Эта ветка кода продолжает работу, начатую в первом практическом занятии. На этот раз примеры покажут вам, как создавать автоматизации. Если вы завершили предыдущее практическое занятие и смогли заставить работать весь функционал, вы можете использовать тот же проект Android Studio для выполнения этого практического занятия вместо ветки codelab-branch-2 .
После того как исходный код будет скомпилирован и готов к запуску на вашем мобильном устройстве, переходите к следующему разделу.
3. Изучите основы автоматизации.
Автоматизация представляет собой набор операторов типа «если это, то то», позволяющих управлять состоянием устройства на основе выбранных факторов в автоматическом режиме. Разработчики могут использовать автоматизацию для создания расширенных интерактивных функций в своих API.
Автоматизация состоит из трех различных типов компонентов, известных как узлы : инициаторы, действия и условия. Эти узлы работают вместе, чтобы автоматизировать поведение с помощью устройств умного дома. Как правило, они оцениваются в следующем порядке:
- Запуск — определяет начальные условия, которые активируют автоматизацию, например, изменение значения признака. Автоматизация должна иметь Запуск .
- Условие — любые дополнительные ограничения, которые необходимо оценить после запуска автоматизации. Выражение в условии должно быть истинным, чтобы действия автоматизации могли быть выполнены.
- Действие — команды или обновления состояния, выполняемые при соблюдении всех условий.
Например, можно настроить автоматизацию, которая приглушает свет в комнате при нажатии выключателя, при этом телевизор в этой комнате должен быть включен. В этом примере:
- Начало — выключатель в комнате включен.
- Состояние — состояние «Вкл/Выкл» телевизора оценивается как «Вкл».
- Действие — Свет в комнате, где находится Switch, приглушен.
Эти узлы обрабатываются модулем автоматизации либо последовательно, либо параллельно.

Последовательный поток содержит узлы, которые выполняются в последовательном порядке. Как правило, это узел запуска, узел условия и узел действия.

В параллельном потоке может одновременно выполняться несколько узлов действий, например, включение нескольких источников света одновременно. Узлы, следующие за параллельным потоком, не начнут выполняться до тех пор, пока не завершатся все ветви параллельного потока.
В схеме автоматизации есть и другие типы узлов. Подробнее о них можно узнать в разделе «Узлы» руководства разработчика по API на главной странице. Кроме того, разработчики могут комбинировать различные типы узлов для создания сложных сценариев автоматизации, например, следующих:

Разработчики предоставляют эти узлы механизму автоматизации, используя предметно-ориентированный язык (DSL), созданный специально для автоматизации Google Home.
Изучите язык автоматизации DSL.
Предметно-ориентированный язык (DSL) — это язык, используемый для описания поведения системы в коде. Компилятор генерирует классы данных, которые сериализуются в формат JSON протокола Protocol Buffer и используются для вызовов к сервисам автоматизации 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
))
Аналогичным образом, демонстрационное приложение отслеживает значения, присваиваемые характеристикам:
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)) }
При запуске приложения и выборе новой автоматизации и запускающего скрипта должно отобразиться примерно следующее:

Демонстрационное приложение поддерживает только стартовые версии, основанные на характеристиках устройства.
Настройка действий
Действие автоматизации отражает основную цель автоматизации — то, как она влияет на физический мир. В примере приложения мы отслеживаем действия автоматизации с помощью класса 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 , чтобы отобразить доступные значения свойства action (command) и сохранить значение в 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%), как показано на следующем снимке экрана:

Теперь попробуем запустить автоматизацию, используя стартовое устройство. Выберите стартовое устройство, которое вы выбрали при создании автоматизации. Измените выбранную вами характеристику (например, установите OnOff для starter outlet1 в значение On ):

Вы увидите, что это также запускает автоматизацию и устанавливает свойство LevelControl устройства действия light2 на исходное значение, 100%:

Поздравляем, вы успешно использовали API Home для создания автоматизаций!
Чтобы узнать больше об API автоматизации, см. раздел «API автоматизации Android» .
5. Выявление возможностей
В состав Home API входит специальный 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, относятся к разным типам. Пример приложения поддерживает 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 могут воспроизводить трансляции 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.
- Вы можете связаться с нами с любыми рекомендациями или сообщить о любых проблемах через систему отслеживания проблем , в разделе поддержки умного дома.