Membuat otomatisasi menggunakan Home API di Android

1. Sebelum memulai

Ini adalah codelab kedua dalam seri tentang membangun aplikasi Android menggunakan Google Home API. Dalam codelab ini, kita akan membahas cara membuat otomatisasi rumah dan memberikan beberapa tips tentang praktik terbaik penggunaan API. Jika Anda belum menyelesaikan codelab pertama, Membangun aplikasi seluler menggunakan Home API di Android, sebaiknya selesaikan codelab tersebut sebelum memulai codelab ini.

API Google Home menyediakan serangkaian library bagi developer Android untuk mengontrol perangkat smart home dalam ekosistem Google Home. Dengan API baru ini, developer akan dapat menyetel otomatisasi untuk smart home yang dapat mengontrol kemampuan perangkat berdasarkan kondisi yang telah ditentukan. Google juga menyediakan Discovery API yang memungkinkan Anda membuat kueri perangkat untuk mengetahui atribut dan perintah yang didukungnya.

Prasyarat

Yang akan Anda pelajari

  • Cara membuat otomatisasi untuk perangkat smart home menggunakan Home API.
  • Cara menggunakan Discovery API untuk menjelajahi kemampuan perangkat yang didukung.
  • Cara menerapkan praktik terbaik saat membangun aplikasi Anda dengan Home API.

2. Menyiapkan project

Diagram berikut mengilustrasikan arsitektur aplikasi Home API:

Arsitektur Home API untuk aplikasi Android

  • Kode Aplikasi: Kode inti yang dikerjakan developer untuk membangun antarmuka pengguna aplikasi dan logika untuk berinteraksi dengan Home APIs SDK.
  • Home APIs SDK: Home APIs SDK yang disediakan oleh Google berfungsi dengan Layanan Home API di GMSCore untuk mengontrol perangkat smart home. Developer membuat aplikasi yang berfungsi dengan Home API dengan memaketkannya dengan SDK Home API.
  • GMSCore di Android: GMSCore, yang juga dikenal sebagai layanan Google Play, adalah platform Google yang menyediakan layanan sistem inti, sehingga mengaktifkan fungsi utama di semua perangkat Android bersertifikasi. Modul beranda layanan Google Play berisi layanan yang berinteraksi dengan Home API.

Dalam codelab ini, kita akan melanjutkan pembahasan yang telah kita bahas dalam Membangun aplikasi seluler menggunakan Home API di Android.

Pastikan Anda memiliki struktur dengan setidaknya dua perangkat yang didukung yang disiapkan dan berfungsi di akun. Karena kita akan menyiapkan otomatisasi dalam codelab ini (perubahan pada status perangkat memicu tindakan di perangkat lain), Anda akan memerlukan dua perangkat untuk melihat hasilnya.

Mendapatkan Aplikasi Contoh

Kode sumber untuk Aplikasi Contoh tersedia di GitHub di repositori google-home/google-home-api-sample-app-android.

Codelab ini menggunakan contoh dari cabang codelab-branch-2 Aplikasi Contoh.

Buka tempat Anda ingin menyimpan project dan clone cabang codelab-branch-2:

$ git clone -b codelab-branch-2 https://github.com/google-home/google-home-api-sample-app-android.git

Perhatikan bahwa ini adalah cabang yang berbeda dengan yang digunakan dalam Membangun aplikasi seluler menggunakan Home API di Android. Cabang codebase ini dibuat berdasarkan tempat codelab pertama dihentikan. Kali ini, contohnya akan memandu Anda membuat otomatisasi. Jika Anda telah menyelesaikan codelab sebelumnya dan dapat membuat semua fungsi berjalan, Anda dapat memilih untuk menggunakan project Android Studio yang sama untuk menyelesaikan codelab ini, bukan menggunakan codelab-branch-2.

Setelah Anda mengompilasi kode sumber dan siap dijalankan di perangkat seluler, lanjutkan ke bagian berikutnya.

3. Mempelajari Otomatisasi

Otomatisasi adalah serangkaian pernyataan "jika ini, maka itu" yang dapat mengontrol status perangkat berdasarkan faktor yang dipilih, secara otomatis. Developer dapat menggunakan otomatisasi untuk membuat fitur interaktif lanjutan di API mereka.

Otomatisasi terdiri dari tiga jenis komponen berbeda yang dikenal sebagai node: pemicu, tindakan, dan kondisi. Node ini bekerja sama untuk mengotomatiskan perilaku menggunakan perangkat smart home. Biasanya, mereka dievaluasi dalam urutan berikut:

  1. Pemicu — Menentukan kondisi awal yang mengaktifkan otomatisasi, seperti perubahan pada nilai karakteristik. Otomatisasi harus memiliki Pemicu.
  2. Kondisi — Batasan tambahan apa pun yang akan dievaluasi setelah otomatisasi dipicu. Ekspresi dalam Kondisi harus dievaluasi sebagai benar agar tindakan otomatisasi dapat dijalankan.
  3. Tindakan — Perintah atau pembaruan status yang dilakukan saat semua kondisi telah terpenuhi.

Misalnya, Anda dapat memiliki otomatisasi yang meredupkan lampu di ruangan saat sakelar diaktifkan, sementara TV di ruangan tersebut dinyalakan. Dalam contoh ini:

  • Starter — Sakelar di ruangan diaktifkan.
  • Kondisi— Status OnOff TV dievaluasi sebagai Aktif.
  • Tindakan — Lampu di ruangan yang sama dengan Sakelar akan diredupkan.

Node ini dievaluasi oleh Automation Engine secara berurutan atau paralel.

image5.png

Sequential Flow berisi node yang dieksekusi secara berurutan. Biasanya, ini adalah pemicu, kondisi, dan tindakan.

image6.png

Alur Paralel dapat memiliki beberapa node tindakan yang dijalankan secara bersamaan, seperti menyalakan beberapa lampu pada waktu yang sama. Node yang mengikuti alur paralel tidak akan dieksekusi hingga semua cabang alur paralel selesai.

Ada jenis node lain dalam skema otomatisasi. Anda dapat mempelajari lebih lanjut di bagian Node di Panduan Developer Home API. Selain itu, developer dapat menggabungkan berbagai jenis node untuk membuat otomatisasi yang kompleks, seperti berikut:

image13.png

Developer menyediakan node ini ke Automation Engine menggunakan bahasa khusus domain (DSL) yang dibuat khusus untuk otomatisasi Google Home.

Mempelajari DSL Otomatisasi

Bahasa khusus domain (DSL) adalah bahasa yang digunakan untuk merekam perilaku sistem dalam kode. Compiler menghasilkan class data yang diserialisasi ke JSON buffer protokol, dan digunakan untuk melakukan panggilan ke Layanan Otomatisasi Google.

DSL mencari skema berikut:

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()) }
  }
}

Otomatisasi dalam contoh sebelumnya menyinkronkan dua bohlam lampu. Saat status OnOff device1 berubah menjadi On (onOffTrait.onOff equals true), status OnOff device2 diubah menjadi On (command(OnOff.on()).

Saat Anda bekerja dengan otomatisasi, ketahui bahwa ada batas resource.

Otomatisasi adalah alat yang sangat berguna untuk membuat kemampuan otomatis di smart home. Dalam kasus penggunaan paling dasar, Anda dapat secara eksplisit mengodekan otomatisasi untuk menggunakan perangkat dan karakteristik tertentu. Namun, kasus penggunaan yang lebih praktis adalah saat aplikasi memungkinkan pengguna mengonfigurasi perangkat, perintah, dan parameter otomatisasi. Bagian berikutnya menjelaskan cara membuat editor otomatisasi yang memungkinkan pengguna melakukan hal tersebut.

4. Membangun editor otomatisasi

Dalam Aplikasi Contoh, kita akan membuat editor otomatisasi yang dapat digunakan pengguna untuk memilih perangkat, kemampuan (tindakan) yang ingin mereka gunakan, dan cara pemicuan otomatisasi menggunakan pemicu.

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

Menyiapkan pemicu

Pemicu otomatisasi adalah titik entri untuk otomatisasi. Pemicu memulai otomatisasi saat peristiwa tertentu terjadi. Di Aplikasi Contoh, kita merekam pemicu otomatisasi menggunakan class StarterViewModel, yang ada di file sumber StarterViewModel.kt, dan menampilkan tampilan editor menggunakan StarterView (StarterView.kt).

Node awal memerlukan elemen berikut:

  • Perangkat
  • Ciri
  • Operasi
  • Nilai

Perangkat dan karakteristik dapat dipilih dari objek yang ditampilkan oleh Devices API. Perintah dan parameter untuk setiap perangkat yang didukung adalah masalah yang lebih kompleks dan perlu ditangani secara terpisah.

Aplikasi menentukan daftar operasi yang telah ditetapkan sebelumnya:

   // 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
    }

Kemudian, untuk setiap trait yang didukung, operasi yang didukung akan dilacak:

// 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
))

Dengan cara yang sama, Aplikasi Contoh melacak nilai yang dapat ditetapkan ke sifat:

enum class OnOffValue {
   On,
   Off,
}
enum class ThermostatValue {
  Heat,
  Cool,
  Off,
}

Dan melacak pemetaan antara nilai yang ditentukan oleh aplikasi dan nilai yang ditentukan oleh 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,
)

Kemudian, aplikasi akan menampilkan serangkaian elemen tampilan yang dapat digunakan pengguna untuk memilih kolom yang diperlukan.

Hapus tanda komentar Langkah 4.1.1 dalam file StarterView.kt untuk merender semua perangkat awal dan menerapkan callback klik di 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
//         }
//     )
// }
}

Hapus tanda komentar Langkah 4.1.2 dalam file StarterView.kt untuk merender semua karakteristik perangkat awal dan menerapkan callback klik di 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
//         }
//     )
}
}

Batalkan komentar Langkah 4.1.3 dalam file StarterView.kt untuk merender semua operasi trait yang dipilih dan menerapkan callback klik di 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
    //          }
    //      )
    //  }
}

Batalkan komentar Langkah 4.1.4 dalam file StarterView.kt untuk merender semua nilai sifat yang dipilih dan menerapkan callback klik dalam 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 -> {
              ...
      }
   }

Batalkan komentar Langkah 4.1.5 dalam file StarterView.kt untuk menyimpan semua variabel ViewModel pemicu ke ViewModel pemicu otomatisasi draf (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)) }

Menjalankan aplikasi dan memilih otomatisasi serta pemicu baru akan menampilkan tampilan seperti berikut:

79beb3b581ec71ec.png

Aplikasi Contoh hanya mendukung starter berdasarkan karakteristik perangkat.

Menyiapkan tindakan

Tindakan otomatisasi mencerminkan tujuan utama otomatisasi, yaitu bagaimana tindakan tersebut memengaruhi perubahan di dunia fisik. Di Aplikasi Contoh, kita mengambil tindakan otomatisasi menggunakan class ActionViewModel , dan menampilkan tampilan editor menggunakan class ActionView.

Aplikasi Contoh menggunakan entitas Home API berikut untuk menentukan node tindakan otomatisasi:

  • Perangkat
  • Ciri
  • Perintah
  • Nilai (Opsional)

Setiap tindakan perintah perangkat menggunakan perintah, tetapi beberapa tindakan juga akan memerlukan nilai parameter yang terkait dengannya, seperti MoveToLevel() dan persentase target.

Perangkat dan karakteristik dapat dipilih dari objek yang ditampilkan oleh Devices API.

Aplikasi menentukan daftar perintah yang telah ditentukan sebelumnya:

   // List of operations available when creating automation starters:
enum class Action {
  ON,
  OFF,
  MOVE_TO_LEVEL,
  MODE_HEAT,
  MODE_COOL,
  MODE_OFF,
}

Aplikasi melacak operasi yang didukung untuk setiap karakteristik yang didukung:

 // 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,
)

Untuk perintah yang menggunakan satu atau beberapa parameter, ada juga variabel:

   val valueLevel: MutableStateFlow<UByte?>

API menampilkan serangkaian elemen tampilan yang dapat digunakan pengguna untuk memilih kolom yang diperlukan.

Hapus komentar Langkah 4.2.1 dalam file ActionView.kt untuk merender semua perangkat tindakan dan menerapkan callback klik dalam DropdownMenu untuk menyetel 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
//         }
//     )
// }
}

Batalkan komentar Langkah 4.2.2 dalam file ActionView.kt untuk merender semua sifat actionDeviceVM dan menerapkan callback klik di DropdownMenu untuk menetapkan actionTrait, yang merepresentasikan sifat yang menjadi tujuan perintah.

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
//         }
//     )
// }
}

Batalkan komentar Langkah 4.2.3 dalam file ActionView.kt untuk merender semua tindakan actionTrait yang tersedia dan menerapkan callback klik dalam DropdownMenu untuk menyetel actionAction, yang merepresentasikan tindakan otomatisasi yang dipilih.

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
//         }
//     )
// }
}

Hapus tanda komentar pada Langkah 4.2.4 di file ActionView.kt untuk merender nilai tindakan (perintah) karakteristik yang tersedia dan menyimpan nilai ke actionValueLevel dalam callback perubahan nilai:

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
//      )
//  }
...
}

Batalkan komentar Langkah 4.2.5 dalam file ActionView.kt untuk menyimpan semua variabel ViewModel tindakan dalam ViewModel tindakan otomatisasi draf (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)) }

Menjalankan aplikasi dan memilih otomatisasi serta tindakan baru akan menghasilkan tampilan seperti berikut:

6efa3c7cafd3e595.png

Kami hanya mendukung tindakan berdasarkan karakteristik perangkat di Aplikasi Contoh.

Merender otomatisasi draf

Setelah DraftViewModel selesai, DraftViewModel dapat dirender oleh HomeAppView.kt:

fun HomeAppView (homeAppVM: HomeAppViewModel) {
  ...
  // If a draft automation is selected, show the draft editor:
  if (selectedDraftVM != null) {
    DraftView(homeAppVM)
  }
  ...
}

Di DraftView.kt:

fun DraftView (homeAppVM: HomeAppViewModel) {
   val draftVM: DraftViewModel = homeAppVM.selectedDraftVM.collectAsState().value!!
    ...
// Draft Starters:
   DraftStarterList(draftVM)
// Draft Actions:
   DraftActionList(draftVM)
}

Membuat otomatisasi

Setelah mempelajari cara membuat pemicu dan tindakan, Anda siap membuat draf otomatisasi dan mengirimkannya ke Automation API. API memiliki fungsi createAutomation() yang menggunakan draf otomatisasi sebagai argumen, dan menampilkan instance otomatisasi baru.

Persiapan otomatisasi draf dilakukan di class DraftViewModel di Aplikasi Contoh. Lihat fungsi getDraftAutomation() untuk mempelajari lebih lanjut cara kami menyusun draf otomatisasi menggunakan variabel starter dan tindakan di bagian sebelumnya.

Hapus komentar Langkah 4.4.1 dalam file DraftViewModel.kt untuk membuat ekspresi "select" yang diperlukan untuk membuat grafik otomatisasi saat sifat awal adalah 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()
     ...
}

Batalkan komentar Langkah 4.4.2 dalam file DraftViewModel.kt untuk membuat ekspresi paralel yang diperlukan untuk membuat grafik otomatisasi saat sifat tindakan yang dipilih adalah LevelControl dan tindakan yang dipilih adalah 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) }
          ...
}

Langkah terakhir untuk menyelesaikan otomatisasi adalah menerapkan fungsi getDraftAutomation untuk membuat AutomationDraft.

Batalkan komentar Langkah 4.4.3 di file HomeAppViewModel.kt untuk membuat otomatisasi dengan memanggil Home API dan menangani pengecualian:

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
  }
}

Sekarang jalankan aplikasi dan lihat perubahannya di perangkat Anda.

Setelah memilih pemicu dan tindakan, Anda siap membuat otomatisasi:

ec551405f8b07b8e.png

Pastikan Anda memberi nama otomatisasi dengan sesuatu yang unik, lalu ketuk tombol Create Automation, yang akan memanggil API dan mengarahkan Anda kembali ke tampilan daftar otomatisasi dengan otomatisasi Anda:

8eebc32cd3755618.png

Ketuk otomatisasi yang baru saja Anda buat, dan lihat cara otomatisasi tersebut ditampilkan oleh API.

931dba7c325d6ef7.png

Perhatikan bahwa API menampilkan nilai yang menunjukkan apakah otomatisasi valid dan aktif saat ini atau tidak. Anda dapat membuat otomatisasi yang tidak lulus validasi saat diuraikan di sisi server. Jika penguraian otomatisasi gagal divalidasi, isValid akan disetel ke false, yang menunjukkan bahwa otomatisasi tidak valid dan tidak aktif. Jika otomatisasi Anda tidak valid, periksa kolom automation.validationIssues untuk mengetahui detailnya.

Pastikan otomatisasi Anda ditetapkan sebagai valid dan aktif, lalu Anda dapat mencoba otomatisasi tersebut.

Mencoba otomatisasi

Otomatisasi dapat dijalankan dengan dua cara:

  1. Dengan peristiwa pemicu. Jika kondisi cocok, tindakan yang Anda tetapkan dalam otomatisasi akan dipicu.
  2. Dengan panggilan API eksekusi manual.

Jika otomatisasi draf memiliki manualStarter() yang ditentukan dalam blok DSL draf otomatisasi, mesin otomatisasi akan mendukung eksekusi manual untuk otomatisasi tersebut. Hal ini sudah ada dalam contoh kode di Aplikasi Contoh.

Karena Anda masih berada di layar tampilan otomatisasi di perangkat seluler, ketuk tombol Eksekusi Manual. Tindakan ini akan memanggil automation.execute(), yang menjalankan perintah tindakan Anda di perangkat yang Anda pilih saat menyiapkan otomatisasi.

Setelah Anda memvalidasi perintah tindakan melalui eksekusi manual menggunakan API, sekarang saatnya untuk melihat apakah perintah tersebut juga dieksekusi menggunakan pemicu yang Anda tentukan.

Buka Tab Perangkat, pilih perangkat tindakan dan sifat, lalu tetapkan ke nilai yang berbeda (misalnya, tetapkan light2's LevelControl (kecerahan) ke 50%, seperti yang diilustrasikan dalam screenshot berikut:

d0357ec71325d1a8.png

Sekarang kita akan mencoba memicu otomatisasi menggunakan perangkat pemicu. Pilih perangkat pemicu yang Anda pilih saat membuat otomatisasi. Alihkan sifat yang Anda pilih (misalnya, tetapkan OnOff starter outlet1 ke On):

230c78cd71c95564.png

Anda akan melihat bahwa hal ini juga menjalankan otomatisasi dan menyetel karakteristik LevelControl perangkat tindakan light2 ke nilai aslinya, 100%:

1f00292128bde1c2.png

Selamat, Anda berhasil menggunakan Home API untuk membuat otomatisasi.

Untuk mempelajari lebih lanjut Automation API, lihat Android Automation API.

5. Menemukan Kemampuan

Home API mencakup API khusus yang disebut Discovery API, yang dapat digunakan developer untuk membuat kueri tentang sifat yang kompatibel dengan otomatisasi yang didukung di perangkat tertentu. Aplikasi Contoh memberikan contoh tempat Anda dapat menggunakan API ini untuk menemukan perintah yang tersedia.

Menemukan Perintah

Di bagian ini, kita membahas cara menemukan CommandCandidates yang didukung dan cara membuat otomatisasi berdasarkan node kandidat yang ditemukan.

Di Aplikasi Contoh, kita memanggil device.candidates() untuk mendapatkan daftar kandidat, yang dapat mencakup instance CommandCandidate, EventCandidate, atau TraitAttributesCandidate.

Buka file HomeAppViewModel.kt dan hapus komentar Langkah 5.1.1 untuk mengambil daftar kandidat dan memfilter dengan jenis 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)
}

Lihat cara memfilter CommandCandidate. Kandidat yang ditampilkan oleh API termasuk dalam berbagai jenis. Aplikasi Contoh mendukung CommandCandidate. Hapus komentar Langkah 5.1.2 di commandMap yang ditentukan dalam ActionViewModel.kt untuk menetapkan sifat yang didukung ini:

    // 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
)

Setelah dapat memanggil Discovery API, dan memfilter hasil yang kami dukung di Aplikasi Contoh, kita akan membahas cara mengintegrasikannya ke editor.

8a2f0e8940f7056a.png

Untuk mempelajari Discovery API lebih lanjut, buka Memanfaatkan penemuan perangkat di Android.

Mengintegrasikan editor

Cara paling umum untuk menggunakan tindakan yang ditemukan adalah dengan menampilkannya kepada pengguna akhir untuk dipilih. Tepat sebelum pengguna memilih kolom otomatisasi draf, kita dapat menampilkan daftar tindakan yang ditemukan, dan bergantung pada nilai yang dipilih, kita dapat mengisi otomatis node tindakan dalam draf otomatisasi.

File CandidatesView.kt berisi class tampilan yang menampilkan kandidat yang ditemukan. Hapus komentar Langkah 5.2.1 untuk mengaktifkan fungsi .clickable{} CandidateListItem yang menetapkan homeAppVM.selectedDraftVM sebagai 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)) }
        }) {
            ...
        }
    }
}

Mirip dengan Langkah 4.3 di HomeAppView.kt, saat selectedDraftVM disetel, DraftView(...) in DraftView.kt akan dirender:

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)
  }
   ...
}

Coba lagi dengan mengetuk light2 - MOVE_TO_LEVEL, yang ditampilkan di bagian sebelumnya, yang akan meminta Anda membuat otomatisasi baru berdasarkan perintah kandidat:

15e67763a9241000.png

Setelah memahami cara membuat otomatisasi di Aplikasi Contoh, Anda dapat mengintegrasikan otomatisasi di aplikasi Anda.

6. Contoh Otomatisasi Lanjutan

Sebelum mengakhiri, kita akan membahas beberapa contoh DSL otomatisasi tambahan. Contoh ini menggambarkan beberapa kemampuan lanjutan yang dapat Anda capai dengan API.

Waktu dalam Sehari sebagai Pemicu

Selain sifat perangkat, Google Home API menawarkan sifat berbasis struktur, seperti Time. Anda dapat membuat otomatisasi yang memiliki pemicu berbasis waktu, seperti berikut:

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
  ...
  }
}

Siaran Asisten sebagai Tindakan

Ciri AssistantBroadcast tersedia sebagai ciri tingkat perangkat di SpeakerDevice (jika speaker mendukungnya) atau sebagai ciri tingkat struktur (karena speaker Google dan perangkat seluler Android dapat memutar siaran Asisten). Contoh:

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!!")
      )
    }
  }
}

Menggunakan DelayFor dan suppressFor

Automation API juga menyediakan operator lanjutan seperti delayFor, yang digunakan untuk menunda perintah, dan suppressFor, yang dapat mencegah pemicuan otomatisasi oleh peristiwa yang sama dalam rentang waktu tertentu. Berikut beberapa contoh penggunaan operator ini:

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!"))
  }
    ...
}

Menggunakan AreaPresenceState di starter

AreaPresenceState adalah sifat tingkat struktur yang mendeteksi apakah ada orang di rumah.

Misalnya, contoh berikut menunjukkan cara mengunci pintu secara otomatis saat seseorang berada di rumah setelah pukul 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()))
      }
    }
  }

Setelah Anda memahami kemampuan otomatisasi lanjutan ini, mulailah membuat aplikasi yang luar biasa.

7. Selamat!

Selamat! Anda berhasil menyelesaikan bagian kedua pengembangan Aplikasi Android menggunakan Google Home API. Selama codelab ini, Anda telah menjelajahi API Otomatisasi dan Penemuan.

Kami harap Anda menikmati pembuatan aplikasi yang secara kreatif mengontrol perangkat dalam ekosistem Google Home dan membangun skenario otomatisasi yang menarik menggunakan API Home.

Langkah berikutnya

  • Baca Pemecahan Masalah untuk mempelajari cara men-debug aplikasi secara efektif dan memecahkan masalah yang melibatkan Home API.
  • Anda dapat menghubungi kami untuk memberikan rekomendasi atau melaporkan masalah melalui Issue Tracker, topik dukungan Smart Home.