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

1. Başlamadan önce

Bu codelab, Google Home API'lerini kullanarak Android uygulaması oluşturma serisinin 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 uygulamalar hakkında bazı ipuçları veriyoruz. İlk codelab olan Android'de Home API'lerini kullanarak mobil uygulama oluşturma'yı henüz tamamlamadıysanız bu codelab'e başlamadan önce onu tamamlamanızı öneririz.

Google Home API'leri, Android geliştiricilerin Google Home ekosistemindeki akıllı ev cihazlarını kontrol etmesi için bir dizi kitaplık sağlar. Bu yeni API'ler sayesinde geliştiriciler, önceden tanımlanmış koşullara göre cihaz özelliklerini kontrol edebilen akıllı ev otomasyonları ayarlayabilecek. Google, cihazlara hangi özelliklerin ve komutların desteklendiğini öğrenmek için sorgu göndermenize olanak tanıyan bir Discovery API de sunar.

Ö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
  • Uygulamalarınızı Home API'leriyle oluştururken en iyi uygulamalardan nasıl yararlanabilirsiniz?

2. Projenizi oluşturma

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

Android uygulaması için Home API'lerinin mimarisi

  • Uygulama Kodu: Geliştiricilerin, uygulamanın kullanıcı arayüzünü oluşturmak ve Home API'leri SDK'sı ile etkileşim kurma mantığını geliştirmek için üzerinde çalıştığı temel kod.
  • Home APIs SDK: Google tarafından sağlanan Home APIs SDK, akıllı ev cihazlarını kontrol etmek için GMSCore'daki Home APIs Service ile birlikte çalışır. Geliştiriciler, Home API'leri SDK'sı ile paketleyerek Home API'leriyle çalışan uygulamalar oluşturur.
  • Android'de GMSCore: Google Play Hizmetleri olarak da bilinen GMSCore, temel sistem hizmetleri sağlayan ve tüm sertifikalı Android cihazlarda temel işlevleri etkinleştiren 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ı codelab'de ele aldığımız konuları temel alacağız.

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

Örnek Uygulamayı Edinme

Örnek Uygulama'nın kaynak kodu, GitHub'daki google-home/google-home-api-sample-app-android deposunda mevcuttur.

Bu codelab'de, örnek uygulamanın codelab-branch-2 dalındaki örnekler kullanılır.

Projeyi kaydetmek istediğiniz yere gidin ve codelab-branch-2 şubesini 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 bölümünde kullanılan daldan farklı olduğunu unutmayın. Kod tabanının bu dalı, ilk kod laboratuvarının kaldığı yerden devam eder. Bu kez örnekler, otomasyonların nasıl oluşturulacağını adım adım açıklar. Önceki codelab'i tamamladıysanız ve tüm işlevleri çalışır duruma getirdiyseniz bu codelab'i tamamlamak için codelab-branch-2 yerine aynı Android Studio projesini kullanmayı tercih edebilirsiniz.

Kaynak kodu derlenip mobil cihazınızda çalıştırılmaya hazır olduğunda sonraki bölüme geçin.

3. Otomasyonlar hakkında bilgi

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

Otomasyonlar, düğümler 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. Başlatıcı: Otomasyonu etkinleştiren ilk koşulları (ör. bir özellik değerinde yapılan değişiklik) tanımlar. Otomasyonun bir Başlatıcısı olmalıdır.
  2. Koşul: Otomasyon tetiklendikten sonra değerlendirilecek ek kısıtlamalar. Bir otomasyonun işlemlerinin yürütülmesi için Koşul'daki ifadenin doğru olarak değerlendirilmesi gerekir.
  3. İşlem: Tüm koşullar karşılandığında gerçekleştirilen komutlar veya durum güncellemeleri.

Örneğin, bir odadaki anahtar açıldığında odadaki ışıkları karartan ve aynı odadaki TV'yi açan bir otomasyon oluşturabilirsiniz. Bu örnekte:

  • Başlangıç: Odadaki anahtar açılır.
  • Koşul: TV'nin Açık/Kapalı durumu "Açık" olarak değerlendirilir.
  • İşlem: Anahtarla aynı odadaki ışıklar karartılır.

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

image5.png

Sıralı Akış, sıralı olarak yürütülen düğümleri içerir. Bunlar genellikle başlangıç, koşul ve işlem olur.

image6.png

Paralel akışta aynı anda birden fazla işlem düğümü yürütülebilir. Örneğin, aynı anda birden fazla ışık açılabilir. Paralel 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. Bu düğümler hakkında daha fazla bilgiyi Home API'leri 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ürlerdeki düğümleri birleştirebilir:

image13.png

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

Otomasyon DSL'sini keşfetme

Alana özgü dil (DSL), sistem davranışını kodda yakalamak için kullanılan bir dildir. Derleyici, protokol arabelleği JSON'a serileştirilmiş ve Google'ın otomasyon hizmetlerine ç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ü senkronize eder. device1'nın OnOff durumu On (onOffTrait.onOff equals true) olarak değiştiğinde device2'nin OnOff durumu On (command(OnOff.on()) olarak değiştirilir.

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

Otomasyonlar, akıllı evde otomatik özellikler oluşturmak için çok kullanışlı bir araçtır. En temel kullanım alanında, belirli cihazları ve özellikleri kullanmak için otomasyonu açıkça kodlayabilirsiniz. Ancak daha pratik bir kullanım alanı, uygulamanın kullanıcının 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 otomasyonların başlatıcılar kullanılarak nasıl tetikleneceğini seçebileceği 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ı, belirli bir etkinlik gerçekleştiğinde otomasyonu tetikler. Örnek uygulamada, StarterViewModel kaynak dosyasında bulunan StarterViewModel.kt sınıfını kullanarak otomasyon başlatıcılarını yakalıyoruz ve StarterView (StarterView.kt) kullanarak düzenleyici görünümünü gösteriyoruz.

Başlangıç düğümü için aşağıdaki öğeler gerekir:

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

Cihaz ve özellik, Devices API tarafından döndürülen nesneler arasından 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 takip eder:

// 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 atanabilir değerleri takip eder:

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 takip eder:

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

Uygulama daha sonra, kullanıcıların gerekli alanları seçmek için kullanabileceği bir dizi görünüm öğesi gösterir.

Tüm başlangıç cihazlarını oluşturmak ve DropdownMenu içinde tıklama geri araması uygulamak için StarterView.kt dosyasında 4.1.1. adımı yorumdan çıkarı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şlangıç cihazının tüm özelliklerini oluşturmak ve DropdownMenu içinde tıklama geri araması uygulamak için StarterView.kt dosyasında 4.1.2 adımının yorumunu 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 tıklama geri aramasını bir DropdownMenu içinde uygulamak için StarterView.kt dosyasında 4.1.3. adımı yorumdan çıkarı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 tıklama geri aramayı DropdownMenu içinde uygulamak için StarterView.kt dosyasında 4.1.4. adımı yorumdan çıkarı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 (draftVM.starterVMs) değişkeninde depolamak için StarterView.kt dosyasında 4.1.5. adımı yorumdan çıkarı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ırmak ve yeni bir otomasyon ile başlatıcı seçmek aşağıdaki gibi bir görünüm oluşturmalıdır:

79beb3b581ec71ec.png

Örnek Uygulama yalnızca cihaz özelliklerine dayalı başlangıçları destekler.

İşlemleri ayarlama

Otomasyon işlemi, otomasyonun temel amacını, fiziksel dünyada nasıl bir değişiklik yaptığını yansıtır. Örnek uygulamada, otomasyon işlemlerini ActionViewModel sınıfını kullanarak yakalıyor ve düzenleyici görünümünü ActionView sınıfını kullanarak gösteriyoruz.

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

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

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

Cihaz ve özellik, Devices API tarafından döndürülen nesneler arasından 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 takip eder:

 // 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 bir değişken de vardır:

   val valueLevel: MutableStateFlow<UByte?>

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

Tüm işlem cihazlarını oluşturmak ve actionDeviceVM değerini ayarlamak için DropdownMenu içinde tıklama geri çağırmasını uygulamak üzere ActionView.kt dosyasında 4.2.1. adımı yorumdan çıkarı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
//         }
//     )
// }
}

ActionView.kt dosyasında 4.2.2. adımı yorumdan çıkararak actionDeviceVM öğesinin tüm özelliklerini oluşturun ve DropdownMenu içinde tıklama geri çağırmasını uygulayarak komutun ait olduğu özelliği temsil eden actionTrait öğesini ayarlayın.

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 dosyasında 4.2.3. adımı yorumdan çıkararak actionTrait öğesinin tüm kullanılabilir işlemlerini oluşturun ve seçilen otomasyon işlemini temsil eden actionAction değerini ayarlamak için DropdownMenu içinde tıklama geri aramasını 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) kullanılabilir değerlerini oluşturmak ve değeri, değer değişikliği geri çağırma işlevinde actionValueLevel içine kaydetmek için ActionView.kt dosyasındaki 4.2.4. adımı yorumdan çıkarı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
//      )
//  }
...
}

Tüm işlem ViewModel değişkenlerini taslak otomasyonun işlem ViewModel'inde (draftVM.actionVMs) depolamak için ActionView.kt dosyasında 4.2.5. adımı yorumdan çıkarı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ırmak ve yeni bir otomasyon ile işlem seçmek aşağıdaki gibi bir görünümle sonuçlanmalıdır:

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 içinde:

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ılar ve işlemler oluşturmayı öğrendiğinize göre artık bir otomasyon taslağı oluşturup Automation API'ye gönderebilirsiniz. API'de, bağımsız değişken olarak otomasyon taslağı alan ve yeni bir otomasyon örneği döndüren bir createAutomation() işlevi bulunur.

Otomasyon taslağı hazırlama işlemi, örnek uygulamadaki 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şlangıç özelliği OnOff olduğunda otomasyon grafiğini oluşturmak için gereken "select" ifadelerini oluşturmak üzere DraftViewModel.kt dosyasında 4.4.1. adımı yorumdan çıkarı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çili işlem özelliği LevelControl ve seçili işlem MOVE_TO_LEVEL olduğunda otomasyon grafiğini oluşturmak için gereken paralel ifadeleri oluşturmak üzere DraftViewModel.kt dosyasında 4.4.2. Adım'ın yorumunu kaldırı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) }
          ...
}

Otomasyonu tamamlamanın son adımı, getDraftAutomation işlevini uygulayarak AutomationDraft. oluşturmaktır.

Home API'lerini çağırarak ve istisnaları işleyerek otomasyonu oluşturmak için HomeAppViewModel.kt dosyasında 4.4.3. adımı yorumdan çıkarı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
  }
}

Şimdi uygulamayı çalıştırın ve cihazınızdaki değişiklikleri görün.

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

ec551405f8b07b8e.png

Otomasyonunuza benzersiz bir ad verdiğinizden emin olun. Ardından, API'leri çağırması ve otomasyonunuzla birlikte sizi otomasyon listesi görünümüne geri getirmesi gereken Otomasyon Oluştur düğmesine dokunun:

8eebc32cd3755618.png

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

931dba7c325d6ef7.png

API'nin, otomasyonun geçerli ve 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çmeyen otomasyonlar oluşturulabilir. Bir otomasyon ayrıştırması doğrulamayı geçemezse 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 deneme

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

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

Bir taslak otomasyonun otomasyon taslağı DSL bloğunda manualStarter() tanımlanmışsa otomasyon motoru, bu otomasyon için manuel yürütmeyi 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 Yürüt düğmesine dokunun. Bu işlem, otomasyonu ayarlarken seçtiğiniz cihazda işlem komutunuzu çalıştıran automation.execute() işlevini çağırmalıdır.

API'yi kullanarak manuel yürütme yoluyla işlem komutunu doğruladıktan sonra, tanımladığınız başlatıcıyı kullanarak da yürütülüp yürütülmediğini kontrol etme 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'nın LevelControl (parlaklık) değerini %50 olarak ayarlayın):

d0357ec71325d1a8.png

Şimdi, başlatıcı cihazı kullanarak otomasyonu tetiklemeyi deneyeceğiz. Otomasyonu oluştururken seçtiğiniz başlatıcı cihazı belirleyin. Seçtiğiniz özelliği açın veya kapatın (örneğin, starter outlet1'nın OnOff özelliğini On olarak ayarlayın):

230c78cd71c95564.png

Bunun otomasyonu da yürüttüğünü ve işlem cihazının light2 LevelControl özelliğini orijinal değer olan %100'e 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. Özellikleri keşfetme

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 öğrenmek için bu API'yi kullanabileceğiniz bir örnek sunar.

Komutları keşfetme

Bu bölümde, desteklenen CommandCandidates nasıl keşfedileceğini ve keşfedilen aday düğümlerine dayalı bir otomasyonun nasıl oluşturulacağını ele alıyoruz.

Örnek uygulamada, aday listesini almak için device.candidates() çağrısı yapıyoruz. Bu liste, CommandCandidate, EventCandidate veya TraitAttributesCandidate örneklerini içerebilir.

HomeAppViewModel.kt dosyasına gidin ve aday listesini almak ve Candidate türüyle filtrelemek için 5.1.1. adımı yorumdan çıkarı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. API tarafından döndürülen adaylar farklı türlere aittir. Örnek uygulama, CommandCandidate'yı destekler. ActionViewModel.kt içinde tanımlanan commandMap içinde 5.1.2. adımı yorumdan çıkararak desteklenen bu özellikleri ayarlayı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
)

Artık Discovery API'yi çağırabildiğimize ve örnek uygulamada desteklediğimiz sonuçları filtreleyebildiğimize göre, bu API'yi düzenleyicimize nasıl entegre edebileceğimizi ele alacağı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, son kullanıcının seçebileceği şekilde sunmaktır. Kullanıcı, taslak otomasyon alanlarını seçmeden hemen önce, keşfedilen işlemlerin listesini gösterebiliriz. Seçtikleri değere bağlı olarak, otomasyon taslağındaki işlem düğümünü önceden doldurabiliriz.

CandidatesView.kt dosyası, keşfedilen adayları gösteren görünüm sınıfını içerir. CandidateListItem'nın .clickable{} işlevini etkinleştirmek için 5.2.1. adımı yorumdan çıkarın. Bu işlev, homeAppVM.selectedDraftVM değerini candidateVM olarak ayarlar:

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 bölümündeki 4.3. adıma benzer şekilde, selectedDraftVM ayarlandığında DraftView(...) in DraftView.kt` oluşturulur:

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 seçeneğine dokunarak tekrar deneyin. Bu seçenek, aday komutuna dayalı yeni bir otomasyon oluşturmanızı ister:

15e67763a9241000.png

Örnek Uygulama'da otomasyon oluşturma hakkında bilgi edindiğinize göre artık uygulamalarınıza otomasyonları entegre edebilirsiniz.

6. Gelişmiş Otomasyon Örnekleri

Son olarak, bazı ek otomasyon DSL'si örneklerini ele alacağız. Bunlar, API'lerle elde edebileceğiniz bazı gelişmiş özellikleri gösterir.

Başlangıç olarak günün saati

Google Home API'leri, cihaz özelliklerinin yanı sıra Time gibi yapı tabanlı özellikler de sunar. Aşağıdaki gibi zamana dayalı bir başlatıcıya sahip otomasyonlar 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'ın İşlem Olarak Yayın Özelliği

AssistantBroadcast özelliği, SpeakerDevice içinde cihaz düzeyinde bir özellik (hoparlör destekliyorsa) veya yapı düzeyinde bir özellik olarak kullanılabilir (Google hoparlörler ve Android mobil cihazlar Asistan yayınlarını oynatabildiğinden). Ö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 özelliğini kullanın

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

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 varken 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 öğrendiğinize göre artık harika uygulamalar oluşturabilirsiniz.

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 boyunca Otomasyon ve Keşif API'lerini incelediniz.

Google Home ekosistemindeki cihazları yaratıcı bir şekilde kontrol eden uygulamalar geliştirirken ve Home API'lerini kullanarak heyecan verici otomasyon senaryoları oluştururken keyifli bir deneyim yaşamanızı dileriz.

Sonraki adımlar

  • Uygulamalarda etkili bir şekilde nasıl 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.
  • Önerilerinizi bizimle paylaşabilir veya sorunları Issue Tracker (Sorun İzleyici) üzerinden bildirebilirsiniz. Bunun için Akıllı Ev destek konusunu kullanabilirsiniz.