Создавайте средства автоматизации с помощью Home API на Android.

1. Прежде чем начать

Это вторая практическая работа в серии по созданию приложения Android с использованием API Google Home. В этой практической работе мы рассмотрим, как создавать домашнюю автоматизацию, и дадим несколько советов по лучшим практикам использования API. Если вы еще не завершили первую практическую работу, Создание мобильного приложения с использованием API Home на Android , мы рекомендуем вам завершить ее перед началом этой практической работы.

API Google Home предоставляют набор библиотек для разработчиков Android для управления устройствами умного дома в экосистеме Google Home. С помощью этих новых API разработчики смогут устанавливать автоматизацию для умного дома, которая может управлять возможностями устройств на основе предопределенных условий. Google также предоставляет API Discovery , который позволяет вам запрашивать устройства, чтобы узнать, какие атрибуты и команды они поддерживают.

Предпосылки

Чему вы научитесь

  • Как создать автоматизацию для устройств умного дома с помощью Home API.
  • Как использовать API Discovery для изучения поддерживаемых возможностей устройств.
  • Как использовать лучшие практики при создании приложений с использованием Home API.

2. Настройка вашего проекта

Следующая диаграмма иллюстрирует архитектуру приложения Home API:

Архитектура API Home для приложения Android

  • Код приложения: основной код, над которым работают разработчики для создания пользовательского интерфейса приложения и логики взаимодействия с 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.

Автоматизация состоит из трех различных типов компонентов, известных как узлы : стартеры, действия и условия. Эти узлы работают вместе для автоматизации поведения с использованием устройств умного дома. Обычно они оцениваются в следующем порядке:

  1. Starter — определяет начальные условия, которые активируют автоматизацию, например, изменение значения признака. Автоматизация должна иметь Starter .
  2. Условие — любые дополнительные ограничения для оценки после срабатывания автоматизации. Выражение в условии должно быть истинным, чтобы действия автоматизации выполнялись.
  3. Действие — команды или обновления состояния, которые выполняются при выполнении всех условий.

Например, у вас может быть автоматизация, которая приглушает свет в комнате при переключении переключателя, в то время как телевизор в этой комнате включен. В этом примере:

  • Стартер — Переключатель в комнате переключен.
  • Условие — Состояние телевизора OnOff оценивается как On.
  • Действие — Свет в той же комнате, где находится Switch, приглушается.

Эти узлы оцениваются модулем автоматизации последовательно или параллельно.

изображение5.png

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

изображение6.png

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

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

изображение13.png

Разработчики предоставляют эти узлы движку автоматизации, используя доменно-ориентированный язык (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 мы создадим редактор автоматизации, с помощью которого пользователи смогут выбирать устройства, возможности (действия), которые они хотят использовать, и способы запуска автоматизации с помощью стартеров.

img11-01.pngimg11-02.pngimg11-03.pngimg11-04.png

Настройка стартеров

Стартер автоматизации — это точка входа для автоматизации. Стартер запускает автоматизацию, когда происходит заданное событие. В примере приложения мы захватываем стартеры автоматизации с помощью класса 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)) }

Запуск приложения и выбор новой автоматизации и стартера должны отобразить следующее представление:

79beb3b581ec71ec.png

Приложение 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)) }

Запуск приложения и выбор новой автоматизации и действия должны привести к следующему виду:

6efa3c7cafd3e595.png

В примере приложения мы поддерживаем только действия, основанные на характеристиках устройства.

Сделать проект автоматизации

После завершения 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
  }
}

Теперь запустите приложение и посмотрите изменения на вашем устройстве!

После выбора стартера и действия вы готовы создать автоматизацию:

ec551405f8b07b8e.png

Обязательно дайте своей автоматизации уникальное имя, затем нажмите кнопку «Создать автоматизацию» , которая должна вызвать API и вернуть вас к списку автоматизаций с вашей автоматизацией:

8eebc32cd3755618.png

Нажмите на только что созданную вами автоматизацию и посмотрите, как ее возвращают API.

931dba7c325d6ef7.png

Имейте в виду, что API возвращает значение, указывающее, является ли автоматизация допустимой и активной в данный момент . Можно создавать автоматизацию, которая не проходит проверку при анализе на стороне сервера. Если анализ автоматизации не проходит проверку, isValid устанавливается в false , что указывает на то, что автоматизация недопустима и неактивна. Если ваша автоматизация недопустима, проверьте поле automation.validationIssues для получения подробной информации.

Убедитесь, что ваша автоматизация настроена как действительная и активная, а затем вы можете опробовать ее.

Попробуйте свою автоматизацию

Автоматизацию можно реализовать двумя способами:

  1. С начальным событием. Если условия совпадают, это запускает действие, которое вы установили в автоматизации.
  2. С помощью вызова API ручного выполнения.

Если черновик автоматизации имеет manualStarter() определенный в блоке DSL черновика автоматизации, движок автоматизации будет поддерживать ручное выполнение для этой автоматизации. Это уже присутствует в примерах кода в Sample App.

Поскольку вы все еще находитесь на экране просмотра автоматизации на своем мобильном устройстве, нажмите кнопку Manual Execute . Это должно вызвать automation.execute() , который запускает вашу команду действия на устройстве, выбранном вами при настройке автоматизации.

После проверки команды действия посредством ручного выполнения с использованием API пришло время проверить, выполняется ли она также с использованием определенного вами стартера.

Перейдите на вкладку «Устройства», выберите устройство действия и черту и установите для нее другое значение (например, установите LevelControl (яркость) light2 на 50%, как показано на следующем снимке экрана):

d0357ec71325d1a8.png

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

230c78cd71c95564.png

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

1f00292128bde1c2.png

Поздравляем, вы успешно использовали 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 и фильтровать результаты, которые мы поддерживаем в примере приложения, мы обсудим, как интегрировать это в наш редактор.

8a2f0e8940f7056a.png

Чтобы узнать больше об 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 , как показано в предыдущем разделе, что предложит вам создать новую автоматизацию на основе команды-кандидата:

15e67763a9241000.png

Теперь, когда вы знакомы с созданием автоматизации в примере приложения, вы можете интегрировать автоматизацию в свои приложения.

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!

Следующие шаги