Android'de Home API'lerini kullanarak otomasyon oluşturma

1. Başlamadan önce

Bu, Google Home API'lerini kullanarak Android uygulaması oluşturma konulu serinin ikinci codelab'idir. Bu codelab'de, ev otomasyonlarının nasıl oluşturulacağını adım adım açıklıyor ve API'leri kullanmayla ilgili en iyi uygulamalarla ilgili bazı ipuçları veriyoruz. Henüz ilk kod laboratuvarını (Android'de Home API'lerini kullanarak mobil uygulama oluşturma) tamamlamadıysanız bu kod laboratuvarına başlamadan önce tamamlamanızı öneririz.

Google Home API'leri, Android geliştiricilerin Google Home ekosistemindeki akıllı ev cihazlarını kontrol etmeleri için bir dizi kitaplık sağlar. Geliştiriciler bu yeni API'lerle, akıllı evler için önceden tanımlanmış koşullara göre cihaz özelliklerini kontrol edebilecek otomasyonlar ayarlayabilecek. Google ayrıca, destekledikleri özellikleri ve komutları öğrenmek için cihazları sorgulamanıza olanak tanıyan bir Discovery API de sağlar.

Ön koşullar

Neler öğreneceksiniz?

  • Home API'lerini kullanarak akıllı ev cihazları için otomasyon oluşturma.
  • Desteklenen cihaz özelliklerini keşfetmek için Discovery API'lerini kullanma.
  • Home API'leri kullanarak uygulamalarınızı oluştururken en iyi uygulamaları nasıl kullanacağınız.

2. Projenizi oluşturma

Aşağıdaki şemada, Home APIs uygulamasının mimarisi gösterilmektedir:

Android uygulaması için Home API'lerin mimarisi

  • Uygulama kodu: Geliştiricilerin, uygulamanın kullanıcı arayüzünü ve Home APIs SDK'sıyla etkileşim mantığını oluşturmak için üzerinde çalıştığı temel kod.
  • Home APIs SDK'sı: Google tarafından sağlanan Home APIs SDK'sı, akıllı ev cihazlarını kontrol etmek için GMSCore'daki Home APIs Hizmeti ile çalışır. Geliştiriciler, Home API'leri Home API'ler SDK'sıyla paketleyerek Home API'lerle çalışan uygulamalar oluşturur.
  • Android'de GMSCore: Google Play Hizmetleri olarak da bilinen GMSCore, tüm sertifikalı Android cihazlarda temel işlevleri etkinleştiren temel sistem hizmetleri sağlayan bir Google platformudur. Google Play Hizmetleri'nin ana sayfa modülü, Home API'leriyle etkileşimde bulunan hizmetleri içerir.

Bu codelab'de, Android'de Home API'lerini kullanarak mobil uygulama oluşturma başlıklı makalede ele aldığımız konuların üzerine gideceğiz.

Hesapta en az iki desteklenen cihazın kurulu ve çalıştığından emin olun. Bu codelab'de otomasyonlar ayarlayacağımızdan (bir cihaz durumundaki değişiklik başka bir cihazda işlem tetikler) sonuçları görmek için iki cihaza ihtiyacınız olacak.

Örnek uygulamayı edinme

Örnek uygulamanın kaynak kodunu GitHub'daki google-home/google-home-api-sample-app-android deposunda bulabilirsiniz.

Bu kod laboratuvarında, örnek uygulamanın codelab-branch-2 şubesine ait örnekler kullanılmaktadır.

Projeyi kaydetmek istediğiniz yere gidin ve codelab-branch-2 dalını klonlayın:

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

Bunun, Android'de Home API'lerini kullanarak mobil uygulama oluşturma başlıklı makalede kullanılandan farklı bir dal olduğunu unutmayın. Kod tabanının bu dalı, ilk kod laboratuvarının kaldığı yerden devam eder. Bu kez örnekler, otomasyon oluşturma konusunda size yol gösterir. Önceki codelab'i tamamladıysanız ve tüm işlevleri çalıştırabildiyseniz bu codelab'i tamamlamak için codelab-branch-2 yerine aynı Android Studio projesini kullanmayı tercih edebilirsiniz.

Kaynak kodunu derleyip mobil cihazınızda çalıştırmaya hazır hale getirdikten sonra bir sonraki bölüme geçin.

3. Otomasyonlar hakkında bilgi

Otomasyonlar, cihaz durumlarını seçilen faktörlere göre otomatik olarak kontrol edebilen bir dizi "eğer bu, o zaman şu" ifadesidir. Geliştiriciler, API'lerinde gelişmiş etkileşimli özellikler oluşturmak için otomasyonlardan yararlanabilir.

Otomasyonlar, nodes olarak bilinen üç farklı bileşen türünden oluşur: başlatıcılar, işlemler ve koşullar. Bu düğümler, akıllı ev cihazlarını kullanarak davranışları otomatikleştirmek için birlikte çalışır. Genellikle aşağıdaki sırayla değerlendirilirler:

  1. Starter: Otomasyonu etkinleştiren ilk koşulları (ör. özellik değerinde yapılan bir değişiklik) tanımlar. Otomasyonlarda bir Starter olmalıdır.
  2. Durum: Bir otomasyon tetiklendikten sonra değerlendirilecek ek kısıtlamalar. Bir otomasyonun işlemlerinin yürütülebilmesi için koşul ifadesi doğru olarak değerlendirilmelidir.
  3. İşlem: Tüm koşullar karşılandığında gerçekleştirilen komutlar veya durum güncellemeleri.

Örneğin, bir anahtar açıldığında o odadaki TV açıkken ışıkları karartan bir otomasyon oluşturabilirsiniz. Bu örnekte:

  • Starter: Odadaki anahtar açılıp kapatılır.
  • Koşul: TV'nin açma/kapatma durumu Açık olarak değerlendirilir.
  • İşlem: Anahtarla aynı odadaki ışıklar kısılır.

Bu düğümler, Otomasyon Motoru tarafından seri veya paralel olarak değerlendirilir.

image5.png

Sıralı akış, sıralı olarak çalışan düğümler içerir. Bunlar genellikle başlatıcı, koşul ve işlemdir.

image6.png

Paralel akışta, aynı anda birden fazla işlem düğümü yürütülebilir (ör. aynı anda birden fazla ışığı açma). Paralel bir akışı izleyen düğümler, paralel akışın tüm dalları tamamlanana kadar yürütülmez.

Otomasyon şemasında başka düğüm türleri de vardır. Bunlar hakkında daha fazla bilgiyi Home API'ler Geliştirici Kılavuzu'nun Düğümler bölümünde bulabilirsiniz. Ayrıca geliştiriciler, aşağıdaki gibi karmaşık otomasyonlar oluşturmak için farklı türde düğümleri birleştirebilir:

image13.png

Geliştiriciler, bu düğümleri Google Home otomasyonları için özel olarak oluşturulmuş bir alana özgü dil (DSL) kullanarak Otomasyon Motoru'na sağlar.

Otomasyon DSL'sini keşfetme

Alana özel dil (DSL), sistem davranışını kodda yakalamak için kullanılan bir dildir. Derleyici, protokol arabelleği JSON'a serileştirilen ve Google'ın Otomasyon Hizmetleri'ne çağrı yapmak için kullanılan veri sınıfları oluşturur.

DSL aşağıdaki şemayı arar:

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

Önceki örnekteki otomasyon, iki ampulün senkronize edilmesini sağlar. device1'nin OnOff durumu On (onOffTrait.onOff equals true) olarak değiştiğinde device2'nin OnOff durumu On (command(OnOff.on()) olarak değişir.

Otomasyonlarla çalışırken kaynak sınırlarının olduğunu unutmayın.

Otomasyonlar, akıllı evlerde otomatik özellikler oluşturmak için çok faydalı bir araçtır. En temel kullanım alanında, belirli cihazları ve özellikleri kullanacak şekilde otomasyonu açıkça kodlayabilirsiniz. Ancak daha pratik bir kullanım alanı, uygulamanın kullanıcıya bir otomasyonun cihazlarını, komutlarını ve parametrelerini yapılandırmasına izin verdiği durumdur. Sonraki bölümde, kullanıcının tam olarak bunu yapmasına olanak tanıyan bir otomasyon düzenleyicinin nasıl oluşturulacağı açıklanmaktadır.

4. Otomasyon düzenleyici oluşturma

Örnek uygulamada, kullanıcıların cihazları, kullanmak istedikleri özellikleri (işlemleri) ve başlatıcılar kullanılarak otomasyonların nasıl tetikleneceğini seçebilecekleri bir otomasyon düzenleyici oluşturacağız.

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

Başlatıcıları ayarlama

Otomasyon başlatıcı, otomasyonun giriş noktasıdır. Başlatıcılar, belirli bir etkinlik gerçekleştiğinde otomasyonu tetikler. Örnek Uygulama'da, StarterViewModel.kt kaynak dosyasında bulunan StarterViewModel sınıfını kullanarak otomasyon başlatıcılarını yakalar ve StarterView (StarterView.kt) sınıfını kullanarak düzenleyici görünümünü gösteririz.

Başlatıcı düğüm için aşağıdaki öğeler gereklidir:

  • Cihaz
  • Özellik
  • İşlem
  • Değer

Cihaz ve özellik, Devices API tarafından döndürülen nesnelerden seçilebilir. Desteklenen her cihazın komutları ve parametreleri daha karmaşık bir konudur ve ayrı ayrı ele alınması gerekir.

Uygulama, önceden ayarlanmış bir işlem listesi tanımlar:

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

Ardından, desteklenen her özellik için desteklenen işlemleri izler:

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

Benzer şekilde, Örnek Uygulama, özelliklere atanabilen değerleri izler:

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

Ayrıca, uygulama tarafından tanımlanan değerler ile API'ler tarafından tanımlanan değerler arasındaki eşlemeyi izler:

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

Ardından uygulama, kullanıcıların gerekli alanları seçmek için kullanabileceği bir dizi görüntüleme öğesi gösterir.

Tüm başlatıcı cihazları oluşturmak ve DropdownMenu içinde tıklama geri çağırma işlevini uygulamak için StarterView.kt dosyasındaki 4.1.1 numaralı adımın yorumunu kaldırın:

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

Başlatıcı cihazın tüm özelliklerini oluşturmak ve DropdownMenu içinde tıklama geri çağırma işlevini uygulamak için StarterView.kt dosyasındaki 4.1.2. adımdaki yorumu kaldırın:

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

Seçilen özelliğin tüm işlemlerini oluşturmak ve DropdownMenu içinde tıklama geri çağırma işlevini uygulamak için StarterView.kt dosyasındaki 4.1.3 numaralı adımın yorumunu kaldırın:

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

Seçilen özelliğin tüm değerlerini oluşturmak ve DropdownMenu içinde tıklama geri çağırma işlevini uygulamak için StarterView.kt dosyasındaki 4.1.4 numaralı adımın yorumunu kaldırın:

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

Tüm başlatıcı ViewModel değişkenlerini taslak otomasyonunun başlatıcı ViewModel'ına (draftVM.starterVMs) depolamak için StarterView.kt dosyasındaki 4.1.5 numaralı adımın yorumunu kaldırın.

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

Uygulamayı çalıştırıp yeni bir otomasyon ve başlatıcı seçtiğinizde aşağıdaki gibi bir görünüm gösterilir:

79beb3b581ec71ec.png

Örnek uygulama yalnızca cihaz özelliklerine dayalı başlatıcıları destekler.

İşlemleri ayarlama

Otomasyon işlemi, bir otomasyonun temel amacını ve fiziksel dünyadaki bir değişikliği nasıl etkilediğini yansıtır. Örnek Uygulama'da, otomasyon işlemlerini ActionViewModel sınıfını kullanarak yakalar ve düzenleyici görünümünü ActionView sınıfını kullanarak gösteririz.

Örnek uygulama, otomasyon işlem düğümlerini tanımlamak için aşağıdaki Home API varlıklarını kullanır:

  • Cihaz
  • Özellik
  • Komut
  • Değer (isteğe bağlı)

Her cihaz komutu işlemi bir komut kullanır ancak bazılarında MoveToLevel() ve hedef yüzde gibi, komutla ilişkili bir parametre değeri de gerekir.

Cihaz ve özellik, Devices API tarafından döndürülen nesnelerden seçilebilir.

Uygulama, önceden tanımlanmış bir komut listesi tanımlar:

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

Uygulama, desteklenen her özellik için desteklenen işlemleri izler:

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

Bir veya daha fazla parametre alan komutlar için de bir değişken vardır:

   val valueLevel: MutableStateFlow<UByte?>

API, kullanıcıların gerekli alanları seçmek için kullanabileceği bir dizi görüntüleme öğesi gösterir.

Tüm işlem cihazlarını oluşturmak için ActionView.kt dosyasında 4.2.1. adımdaki yorumu kaldırın ve actionDeviceVM değerini ayarlamak için DropdownMenu içinde tıklama geri çağırma işlevini uygulayın.

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

actionDeviceVM dosyasında 4.2.2. adımındaki yorumu kaldırarak actionDeviceVM'un tüm özelliklerini oluşturun ve komutun ait olduğu özelliği temsil eden actionTrait'ı ayarlamak için DropdownMenu'de tıklama geri çağırma işlevini uygulayın.ActionView.kt

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

actionTrait'un mevcut tüm işlemlerini oluşturmak için ActionView.kt dosyasındaki 4.2.3 numaralı adımın yorumunu kaldırın ve seçili otomasyon işlemini temsil eden actionAction'ı ayarlamak için DropdownMenu içinde tıklama geri çağırma işlevini uygulayın.

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

Özellik işleminin (komut) mevcut değerlerini oluşturmak ve değer değişikliği geri çağırma işlevinde değeri actionValueLevel içine depolamak için ActionView.kt dosyasındaki 4.2.4 numaralı adımın yorumunu kaldırın:

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

ViewModel işleminin tüm değişkenlerini taslak otomasyonun ViewModel işleminde (draftVM.actionVMs) depolamak için ActionView.kt dosyasındaki 4.2.5 numaralı adımın yorumunu kaldırın:

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

Uygulamayı çalıştırıp yeni bir otomasyon ve işlem seçtiğinizde aşağıdaki gibi bir görünüm elde edersiniz:

6efa3c7cafd3e595.png

Örnek uygulamada yalnızca cihaz özelliklerine dayalı işlemler desteklenir.

Taslak otomasyonu oluşturma

DraftViewModel tamamlandığında HomeAppView.kt tarafından oluşturulabilir:

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

DraftView.kt'te:

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

Otomasyon oluşturma

Başlatıcı ve işlem oluşturmayı öğrendiniz. Artık otomasyon taslağı oluşturmaya ve bunu Automation API'ye göndermeye hazırsınız. API'de, bağımsız değişken olarak bir otomasyon taslağı alan ve yeni bir otomasyon örneği döndüren bir createAutomation() işlevi vardır.

Taslak otomasyon hazırlığı, Örnek Uygulama'daki DraftViewModel sınıfında gerçekleşir. Önceki bölümdeki başlatıcı ve işlem değişkenlerini kullanarak otomasyon taslağını nasıl yapılandırdığımız hakkında daha fazla bilgi edinmek için getDraftAutomation() işlevine bakın.

Başlatıcı özelliği OnOff olduğunda otomasyon grafiğini oluşturmak için gereken "select" ifadelerini oluşturmak üzere DraftViewModel.kt dosyasındaki 4.4.1. adımdaki yorumu kaldırın:

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

Seçilen işlem özelliği LevelControl ve seçilen işlem MOVE_TO_LEVEL olduğunda otomasyon grafiğini oluşturmak için gereken paralel ifadeleri oluşturmak üzere DraftViewModel.kt dosyasındaki 4.4.2 adımı yorumundan çıkarın:

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

Bir otomasyonu tamamlamanın son adımı, AutomationDraft. oluşturmak için getDraftAutomation işlevini uygulamaktır.

Home API'lerini çağırarak ve istisnaları ele alarak otomasyonu oluşturmak için HomeAppViewModel.kt dosyasındaki 4.4.3 numaralı adımın yorumunu kaldırın:

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

Artık uygulamayı çalıştırıp cihazınızdaki değişiklikleri görebilirsiniz.

Bir başlatıcı ve işlem seçtikten sonra otomasyonu oluşturmaya hazırsınız:

ec551405f8b07b8e.png

Otomasyonunuza benzersiz bir ad verin ve ardından Otomasyon Oluştur düğmesine dokunun. Bu düğme, API'leri çağırır ve sizi otomasyonunuzla birlikte otomasyonlar liste görünümüne geri getirir:

8eebc32cd3755618.png

Az önce oluşturduğunuz otomasyona dokunun ve API'ler tarafından nasıl döndürüldüğünü görün.

931dba7c325d6ef7.png

API'nin, bir otomasyonun geçerli olup olmadığını ve şu anda etkin olup olmadığını belirten bir değer döndürdüğünü unutmayın. Sunucu tarafında ayrıştırıldığında doğrulamayı geçemeyen otomasyonlar oluşturabilirsiniz. Bir otomasyonun ayrıştırılması doğrulama işleminde başarısız olursa isValid, otomasyonun geçersiz ve etkin olmadığını belirten false olarak ayarlanır. Otomasyonunuz geçersizse ayrıntılar için automation.validationIssues alanını kontrol edin.

Otomasyonunuzun geçerli ve etkin olarak ayarlandığından emin olduktan sonra otomasyonu deneyebilirsiniz.

Otomasyonunuzu deneyin

Otomasyonlar iki şekilde yürütülebilir:

  1. Başlatıcı etkinliğiyle. Koşullar eşleşirse otomasyonda ayarladığınız işlem tetiklenir.
  2. Manuel yürütme API çağrısıyla.

Bir taslak otomasyonda, otomasyon taslağı DSL bloğunda tanımlanmış bir manualStarter() varsa otomasyon motoru, söz konusu otomasyonun manuel olarak yürütülmesini destekler. Bu, Örnek Uygulama'daki kod örneklerinde zaten mevcuttur.

Mobil cihazınızda otomasyon görünümü ekranında olduğunuz için Manuel Olarak Çalıştır düğmesine dokunun. Bu işlem, otomasyonu ayarlarken seçtiğiniz cihazda işlem komutunuzu çalıştıran automation.execute() işlevini çağırır.

API'yi kullanarak manuel yürütme yoluyla işlem komutunu doğruladıktan sonra, işlemin tanımladığınız başlatıcıyı kullanarak da yürütülüp yürütülmediğini kontrol etmenin zamanı gelmiştir.

Cihazlar sekmesine gidin, işlem cihazını ve özelliği seçin ve farklı bir değere ayarlayın (örneğin, aşağıdaki ekran görüntüsünde gösterildiği gibi light2'nin LevelControl (parlaklık) özelliğini %50'ye ayarlayın:

d0357ec71325d1a8.png

Şimdi, başlatıcı cihazı kullanarak otomasyonu tetiklemeye çalışacağız. Otomasyonu oluştururken seçtiğiniz başlatıcı cihazı seçin. Seçtiğiniz özelliği değiştirin (örneğin, starter outlet1'nin OnOff özelliğini On olarak ayarlayın):

230c78cd71c95564.png

Bunun, otomasyonu da yürüttüğünü ve işlem cihazı light2'nin LevelControl özelliğini orijinal değere (%100) ayarladığını görürsünüz:

1f00292128bde1c2.png

Tebrikler, otomasyon oluşturmak için Home API'lerini başarıyla kullandınız.

Automation API hakkında daha fazla bilgi edinmek için Android Automation API başlıklı makaleyi inceleyin.

5. Keşfet Özellikleri

Home API'leri, geliştiricilerin belirli bir cihazda hangi otomasyon özelliklerinin desteklendiğini sorgulamak için kullanabileceği Discovery API adlı özel bir API içerir. Örnek uygulama, hangi komutların kullanılabileceğini keşfetmek için bu API'yi kullanabileceğiniz bir örnek sağlar.

Keşfet Komutları

Bu bölümde, desteklenen CommandCandidates öğelerinin nasıl bulunacağı ve bulunan aday düğümlere göre nasıl otomasyon oluşturulacağı ele alınmaktadır.

Örnek Uygulama'da, CommandCandidate, EventCandidate veya TraitAttributesCandidate örneklerini içerebilecek adayların listesini almak için device.candidates() çağrısını yapıyoruz.

HomeAppViewModel.kt dosyasına gidin ve aday listesini almak ve Candidate türüyle filtrelemek için 5.1.1. adımdaki yorumu kaldırın:

   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. için nasıl filtrelediğini görün. API tarafından döndürülen adaylar farklı türlere aittir. Örnek uygulama CommandCandidate'ü destekler. Aşağıdaki desteklenen özellikleri ayarlamak için ActionViewModel.kt içinde tanımlanan commandMap öğesindeki 5.1.2. adımının yorumunu kaldırın:

    // 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'yi çağırıp örnek uygulamada desteklediğimiz sonuçları filtreleyebildiğimize göre, bunu düzenleyicimize nasıl entegre edebileceğimizi tartışacağız.

8a2f0e8940f7056a.png

Discovery API hakkında daha fazla bilgi edinmek için Android'de cihaz keşfinden yararlanma başlıklı makaleyi inceleyin.

Düzenleyiciyi entegre etme

Keşfedilen işlemleri kullanmanın en yaygın yolu, bunları bir son kullanıcıya sunarak aralarından seçim yapmasına izin vermektir. Kullanıcı taslak otomasyon alanlarını seçmeden hemen önce, keşfedilen işlemlerin listesini gösterebiliriz ve seçtikleri değere bağlı olarak otomasyon taslağındaki işlem düğümünü önceden doldurabiliriz.

CandidatesView.kt dosyası, bulunan adayları gösteren görüntüleme sınıfını içerir. homeAppVM.selectedDraftVM değerini candidateVM olarak ayarlayan CandidateListItem'nin .clickable{} işlevini etkinleştirmek için 5.2.1. adımındaki açıklamanın işaretini kaldırın:

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'teki 4.3. adıma benzer şekilde, selectedDraftVM ayarlandığında DraftView(...) in DraftView.kt'yi oluşturur:

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

Önceki bölümde gösterilen light2 - MOVE_TO_LEVEL öğesine dokunarak tekrar deneyin. Bu öğe, aday komutuna göre yeni bir otomasyon oluşturmanızı ister:

15e67763a9241000.png

Örnek uygulamada otomasyon oluşturma hakkında bilgi sahibi olduğunuza göre otomasyonları uygulamalarınıza entegre edebilirsiniz.

6. Gelişmiş Otomasyon Örnekleri

Dersi tamamlamadan önce bazı ek otomasyon DSL örneklerini ele alacağız. Bunlar, API'lerle elde edebileceğiniz gelişmiş özelliklerden bazılarını göstermektedir.

Başlatıcı olarak saat

Google Home API'leri, cihaz özelliklerine ek olarak Time gibi yapıya dayalı özellikler de sunar. Aşağıdaki gibi zamana dayalı bir başlatıcıya sahip bir otomasyon oluşturabilirsiniz:

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

Asistan İşlemi Olarak Yayınlama

AssistantBroadcast özelliği, SpeakerDevice içinde cihaz düzeyinde bir özellik olarak (hoparlör destekliyorsa) veya yapı düzeyinde bir özellik olarak (Google hoparlörleri ve Android mobil cihazlar Asistan yayınlarını oynatabildiğinden) kullanılabilir. Örneğin:

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 ve suppressFor

Otomasyon API'si, komutları ertelemek için kullanılan delayFor ve belirli bir süre içinde bir otomasyonun aynı etkinlikler tarafından tetiklenmesini engelleyebilecek suppressFor gibi gelişmiş operatörler de sağlar. Bu operatörlerin kullanıldığı bazı örnekleri aşağıda bulabilirsiniz:

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

Başlatıcıda AreaPresenceState kullanma

AreaPresenceState, evde kimsenin olup olmadığını algılayan yapı düzeyinde bir özelliktir.

Örneğin, aşağıdaki örnekte saat 22:00'den sonra evde biri olduğunda kapıların otomatik olarak kilitlenmesi gösterilmektedir:

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

Bu gelişmiş otomasyon özelliklerini öğrendiniz. Şimdi sıra harika uygulamalar oluşturmakta.

7. Tebrikler!

Tebrikler! Google Home API'lerini kullanarak Android uygulaması geliştirmenin ikinci bölümünü başarıyla tamamladınız. Bu codelab'de Otomasyon ve Keşif API'lerini keşfettiniz.

Google Home ekosistemindeki cihazları yaratıcı bir şekilde kontrol eden uygulamalar oluşturmanın ve Home API'lerini kullanarak heyecan verici otomasyon senaryoları oluşturmanın keyfini çıkaracağınızı umuyoruz.

Sonraki adımlar

  • Uygulamalarda nasıl etkili bir şekilde hata ayıklayacağınızı ve Home API'leriyle ilgili sorunları nasıl gidereceğinizi öğrenmek için Sorun giderme bölümünü okuyun.
  • Öneriler paylaşmak veya sorunları bildirmek için Akıllı Ev destek konusundaki Sorun Takip Aracı'nı kullanabilirsiniz.