1. 始める前に
これは、Google Home API を使用して Android アプリを構築するシリーズの 2 つ目の Codelab です。この Codelab では、ホーム オートメーションを作成する方法について説明し、API を使用する際のベスト プラクティスに関するヒントを紹介します。まだ最初の Codelab「Android で Home API を使用してモバイルアプリを構築する」を完了していない場合は、この Codelab を始める前に完了することをおすすめします。
Google Home API は、Android デベロッパーが Google Home エコシステム内のスマートホーム デバイスを制御するためのライブラリのセットを提供します。これらの新しい API を使用すると、デベロッパーは、事前定義された条件に基づいてデバイスの機能を制御できるスマートホームの自動化を設定できます。Google は、デバイスにクエリを実行して、サポートされている属性とコマンドを確認できる Discovery API も提供しています。
前提条件
- Android で Home API を使用してモバイルアプリを作成するの Codelab を完了している
- Google Home エコシステム(Cloud-to-cloud、Matter)に関する知識。
- Android Studio(2024.3.1 Ladybug 以降)がインストールされたワークステーション。
- Google Play 開発者サービスと Google Home アプリがインストールされ、Home API の要件(前提条件を参照)を満たしている Android スマートフォン。エミュレータは動作しません。サンプルアプリでは、実機の Android スマートフォンのみがサポートされます。
- Google Home APIs をサポートする互換性のある Google Home Hub。
- 省略可 - Google Home API と互換性のあるスマートホーム デバイス。
学習内容
- Home API を使用してスマートホーム デバイスの自動化を作成する方法。
- Discovery API を使用して、サポートされているデバイスの機能を調べる方法。
- Home API を使用してアプリを構築する際にベスト プラクティスを適用する方法。
2. プロジェクトを設定する
次の図は、Home APIs アプリのアーキテクチャを示しています。

- アプリコード: デベロッパーがアプリのユーザー インターフェースと Home APIs SDK とのやり取りのロジックを構築するために使用するコアコード。
- Home APIs SDK: Google が提供する Home APIs SDK は、GMSCore の Home APIs Service と連携してスマートホーム デバイスを制御します。デベロッパーは、Home APIs SDK とバンドルすることで、Home APIs と連携するアプリを構築します。
- Android の GMSCore: GMSCore(Google Play 開発者サービスとも呼ばれます)は、コア システム サービスを提供する Google プラットフォームであり、すべての認定済み Android デバイスで主要な機能を有効にします。Google Play 開発者サービスのホーム モジュールには、Home API とやり取りするサービスが含まれています。
この Codelab では、Android で Home API を使用してモバイルアプリを構築するで説明した内容を基に構築します。
アカウントに、サポートされているデバイスが 2 台以上設定され、動作している構造があることを確認します。この Codelab ではオートメーション(デバイスの状態の変化が別のデバイスのアクションをトリガーする)を設定するため、結果を確認するには 2 つのデバイスが必要です。
サンプルアプリを入手する
サンプルアプリのソースコードは、GitHub の google-home/google-home-api-sample-app-android リポジトリで入手できます。
この Codelab では、サンプルアプリの codelab-branch-2 ブランチの例を使用します。
プロジェクトを保存する場所に移動し、codelab-branch-2 ブランチのクローンを作成します。
$ git clone -b codelab-branch-2 https://github.com/google-home/google-home-api-sample-app-android.git
これは、Android で Home API を使用してモバイルアプリをビルドするで使用されているブランチとは異なります。このコードベースのブランチは、最初の Codelab の続きです。今回は、自動化の作成方法を例で説明します。前の Codelab を完了し、すべての機能を動作させることができた場合は、codelab-branch-2 を使用する代わりに、同じ Android Studio プロジェクトを使用してこの Codelab を完了することもできます。
ソースコードをコンパイルしてモバイル デバイスで実行する準備ができたら、次のセクションに進みます。
3. 自動化について
自動化は、選択した要素に基づいてデバイスの状態を自動的に制御できる一連の「if this, then that」ステートメントです。デベロッパーは自動化を使用して、API に高度なインタラクティブ機能を構築できます。
自動化は、開始条件、アクション、条件という 3 種類のコンポーネント(ノード)で構成されます。これらのノードは連携して動作し、スマートホーム デバイスを使用して動作を自動化します。通常、これらは次の順序で評価されます。
- 開始条件 - 特性の値の変更など、自動化を有効にする初期条件を定義します。自動化には 開始条件が必要です。
- 条件 - 自動化がトリガーされた後に評価する追加の制約。自動化のアクションを実行するには、条件の式が true と評価される必要があります。
- アクション - すべての条件が満たされたときに実行されるコマンドまたは状態の更新。
たとえば、部屋のテレビがオンになっているときにスイッチを切り替えると、部屋の照明が暗くなる自動化を設定できます。この例では、次のようになります。
- スターター - チャットルームのスイッチが切り替わります。
- 条件 - テレビの OnOff 状態がオンと評価されます。
- アクション - Switch と同じ部屋の照明が暗くなります。
これらのノードは、Automation Engine によってシリアルまたは並列で評価されます。

シーケンシャル フローには、順番に実行されるノードが含まれています。通常、これらは開始条件、条件、アクションです。

並列フローでは、複数のアクション ノードを同時に実行できます(複数の照明を同時に点灯するなど)。並列フローに続くノードは、並列フローのすべてのブランチが完了するまで実行されません。
自動化スキーマには他のタイプのノードもあります。詳しくは、Home APIs デベロッパー ガイドのノードのセクションをご覧ください。また、デベロッパーはさまざまなタイプのノードを組み合わせて、次のような複雑な自動化を作成できます。

デベロッパーは、Google Home の自動化専用に作成されたドメイン固有言語(DSL)を使用して、これらのノードを Automation Engine に提供します。
自動化 DSL を調べる
ドメイン固有言語(DSL)は、コードでシステム動作をキャプチャするために使用される言語です。コンパイラは、プロトコル バッファ JSON にシリアル化され、Google の Automation Services への呼び出しに使用されるデータクラスを生成します。
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()) }
  }
}
上記の例の自動化では、2 つの電球が同期されます。device1 の OnOff 状態が On(onOffTrait.onOff equals true)に変わると、device2 の OnOff 状態が On(command(OnOff.on())に変わります。
自動化を使用する場合は、リソースの上限があることに注意してください。
自動化は、スマートホームで自動化された機能を作成するのに非常に便利なツールです。最も基本的なユースケースでは、特定のデバイスと特性を使用するように自動化を明示的にコーディングできます。より実用的なユースケースは、アプリで自動化のデバイス、コマンド、パラメータをユーザーが構成できるようにする場合です。次のセクションでは、ユーザーがまさにそれを実行できる自動化エディタを作成する方法について説明します。
4. 自動化エディタを構築する
サンプルアプリ内で、ユーザーがデバイス、使用する機能(アクション)、自動化のトリガー方法(開始条件)を選択できる自動化エディタを作成します。
 
   
   
  
開始条件を設定する
自動化の開始条件は、自動化のエントリ ポイントです。スターターは、特定のイベントが発生したときに自動化をトリガーします。サンプルアプリでは、StarterViewModel.kt ソースファイルにある StarterViewModel クラスを使用して自動化スターターを取得し、StarterView(StarterView.kt)を使用してエディタビューを表示します。
スターター ノードには次の要素が必要です。
- デバイス
- トレイト
- オペレーション
- 値
デバイスとトレイトは、Devices 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,
)
アプリには、ユーザーが必要なフィールドを選択するために使用できる一連のビュー要素が表示されます。
StarterView.kt ファイルのステップ 4.1.1 のコメントを解除して、すべてのスターター デバイスをレンダリングし、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
//         }
//     )
// }
}
StarterView.kt ファイルのステップ 4.1.2 のコメントを解除して、スターター デバイスのすべてのトレイトをレンダリングし、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
//         }
//     )
}
}
StarterView.kt ファイルのステップ 4.1.3 のコメントを解除して、選択したトレイトのすべてのオペレーションをレンダリングし、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
    //          }
    //      )
    //  }
}
StarterView.kt ファイルのステップ 4.1.4 のコメントを解除して、選択した特性のすべての値をレンダリングし、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 -> {
              ...
      }
   }
StarterView.kt ファイルのステップ 4.1.5 のコメントを解除して、すべてのスターター 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() や目標パーセンテージなど、関連付けられたパラメータ値も必要になる場合があります。
デバイスとトレイトは、Devices 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,
)
1 つ以上のパラメータを受け取るコマンドには、次の変数もあります。
   val valueLevel: MutableStateFlow<UByte?>
API は、ユーザーが必要なフィールドを選択するために使用できる一連のビュー要素を表示します。
ActionView.kt ファイルのステップ 4.2.1 のコメントを解除して、すべてのアクション デバイスをレンダリングし、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
//         }
//     )
// }
}
ActionView.kt ファイルのステップ 4.2.2 のコメントを解除して、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
//         }
//     )
// }
}
ActionView.kt ファイルのステップ 4.2.3 のコメントを解除して、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
//         }
//     )
// }
}
ActionView.kt ファイルのステップ 4.2.4 のコメントを解除して、トレイト アクション(コマンド)の利用可能な値をレンダリングし、値変更コールバックの 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
//      )
//  }
...
}
ActionView.kt ファイルのステップ 4.2.5 のコメントを解除して、すべてのアクション 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)
}
自動化の作成
開始条件とアクションの作成方法を学習したので、自動化の下書きを作成して Automation API に送信する準備が整いました。この API には、自動化の下書きを引数として受け取り、新しい自動化インスタンスを返す createAutomation() 関数があります。
下書きの自動化の準備は、サンプルアプリの DraftViewModel クラスで行われます。getDraftAutomation() 関数を確認して、前のセクションのスターター変数とアクション変数を使用して自動化の下書きを構成する方法について詳しく学びましょう。
DraftViewModel.kt ファイルのステップ 4.4.1 のコメントを解除して、スターター トレイトが OnOff の場合に自動化グラフの作成に必要な「select」式を作成します。
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()
     ...
}
DraftViewModel.kt ファイルのステップ 4.4.2 のコメントを解除して、選択したアクション トレイトが 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. を作成することです。
HomeAppViewModel.kt ファイルのステップ 4.4.3 のコメントを解除して、Home API を呼び出し、例外を処理することで自動化を作成します。
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 フィールドで詳細を確認してください。
自動化が有効でアクティブに設定されていることを確認してから、自動化を試してください。
自動化を試す
自動化は次の 2 つの方法で実行できます。
- 開始条件アクティビティを使用する。条件が一致すると、自動化で設定したアクションがトリガーされます。
- 手動実行 API 呼び出しを使用する。
自動化の下書きに、自動化の下書き DSL ブロックで定義された manualStarter() がある場合、自動化エンジンはその自動化の手動実行をサポートします。これは、サンプルアプリのコード例にすでに存在します。
モバイル デバイスでオートメーション ビューの画面が表示されたままになっているので、[手動で実行] ボタンをタップします。これにより automation.execute() が呼び出され、自動化の設定時に選択したデバイスでアクション コマンドが実行されます。
API を使用して手動実行でアクション コマンドを検証したら、定義したスターターを使用して実行されているかどうかを確認します。
[デバイス] タブに移動し、アクション デバイスとトレイトを選択して、別の値に設定します(たとえば、次のスクリーンショットに示すように、light2 の LevelControl(明るさ)を 50% に設定します)。

次に、開始デバイスを使用して自動化をトリガーしてみます。自動化の作成時に選択した開始デバイスを選択します。選択したトレイトを切り替えます(たとえば、starter outlet1 の OnOff を On に設定します)。

これにより、自動化が実行され、アクション デバイス light2 の LevelControl トレイトが元の値である 100% に設定されます。

おめでとうございます。Home API を使用して自動化を作成できました。
Automation API の詳細については、Android Automation API をご覧ください。
5. 機能の検出
Home API には、Discovery API という専用の API が含まれています。デベロッパーはこの 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 をサポートしています。ActionViewModel.kt で定義された commandMap のステップ 5.1.2 のコメントを解除して、サポートされている次のトレイトを設定します。
    // 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
)
Discovery API を呼び出して、サンプルアプリでサポートされている結果をフィルタできるようになりました。次に、これをエディタに統合する方法について説明します。

Discovery API について詳しくは、Android でのデバイス検出の活用をご覧ください。
エディタを統合する
検出されたアクションの最も一般的な使用方法は、エンドユーザーに提示して選択してもらうことです。ユーザーが自動化のドラフト フィールドを選択する直前に、検出されたアクションのリストを表示し、選択された値に応じて、自動化のドラフトのアクションノードに事前入力できます。
CandidatesView.kt ファイルには、検出された候補を表示するビュークラスが含まれています。ステップ 5.2.1 のコメントを解除して、CandidateListItem の .clickable{} 関数を有効にします。この関数は 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)) }
        }) {
            ...
        }
    }
}
HomeAppView.kt のステップ 4.3 と同様に、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 で実現できる高度な機能の例です。
Time of Day as Starter
Google Home API では、デバイスの特性に加えて、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
  ...
  }
}
アクションとしての Assistant ブロードキャスト
AssistantBroadcast トレイトは、SpeakerDevice のデバイスレベルのトレイト(スピーカーがサポートしている場合)または構造レベルのトレイトとして使用できます(Google スピーカーと Android モバイル デバイスはアシスタント ブロードキャストを再生できるため)。次に例を示します。
automation {
  name = "AutomationName"
  description = "An example automation description."
  isActive = true
  description = "Broadcast in Speaker when ..."
  sequential {
    // starter
      ...
    // action
    action(structure) {
      command(
      AssistantBroadcast.broadcast("Time is up!!")
      )
    }
  }
}
DelayFor と suppressFor を使用する
Automation 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 は、家に誰かいるかどうかを検出する構造レベルのトレイトです。
たとえば、次の例は、午後 10 時以降に誰かが家にいるときにドアを自動的にロックする方法を示しています。
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. 完了
おめでとうございます!Google Home API を使用した Android アプリの開発の第 2 部が完了しました。この Codelab では、Automation API と Discovery API について説明しました。
Google Home エコシステム内のデバイスをクリエイティブに制御するアプリを構築し、Home API を使用してエキサイティングな自動化シナリオを構築していただければ幸いです。
次のステップ
- トラブルシューティングを読んで、アプリを効果的にデバッグし、Home API に関する問題のトラブルシューティングを行う方法を学びましょう。
- ご提案や問題のご報告は、Issue Tracker のスマートホーム サポート トピックからお寄せください。