Android'de otomasyon oluşturma

Automation API'lerine Android için Home API'leri üzerinden erişilebilir ancak giriş noktaları bir yapı üzerinden olduğu için kullanılabilmeleri için önce yapıya izin verilmesi gerekir.

Bir yapı için izinler verildikten sonra bu paketleri uygulamanıza aktarın:


import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.Id
import com.google.home.Structure

Bir yapı, aşağıdaki otomasyona özel yöntemleri içeren bir HasAutomations arayüzü içerir:

API Açıklama
automations() Yapıya ait tüm otomasyonları listeleme Yalnızca Home API'leri aracılığıyla oluşturduğunuz otomasyonlar döndürülür.
createAutomation(automation) Bir yapı için otomasyon örneği oluşturun.
deleteAutomation(automationId) Kimliğine göre bir otomasyon örneğini siler.

Otomasyon oluşturma

Home örneği oluşturup kullanıcıdan izin aldıktan sonra yapıyı ve cihazları alın:

val structure = homeManager.structures().list().single()
val device = homeManager.devices().get(Id("myDevice"))!!

Ardından, Automation DSL'yi kullanarak otomasyonunuzun mantığını tanımlayın. Home API'lerinde otomasyon, Automation arayüzüyle temsil edilir. Bu arayüzde bir dizi özellik bulunur:

  • Ad ve açıklama gibi meta veriler.
  • Örneğin, otomasyonun yürütülüp yürütülemeyeceğini gösteren işaretler.
  • Otomasyonun mantığını içeren ve otomasyon grafiği olarak adlandırılan düğümlerin listesi. automationGraph özelliğiyle temsil edilir.

automationGraph, varsayılan olarak SequentialFlow türündedir. Bu, sıralı düzende yürütülen düğümlerin listesini içeren bir sınıftır. Her düğüm, otomasyonun bir öğesini (ör. başlatıcı, koşul veya işlem) temsil eder.

Otomasyona bir name ve description atayın.

Otomasyon oluşturulduğunda isActive işareti varsayılan olarak true değerini alır. Bu nedenle, otomasyonun başlangıçta devre dışı bırakılmasını istemediğiniz sürece bu işareti açıkça ayarlamanız gerekmez. Bu senaryoda, oluşturma sırasında işareti false olarak ayarlayın.

DraftAutomation arayüzü otomasyon oluşturmak, Automation arayüzü ise bilgi almak için kullanılır. Örneğin, bir cihaz açıldığında başka bir cihazı açan otomasyonun Otomasyon DSL'si şu şekildedir:

import com.google.home.automation.Action
import com.google.home.automation.Automation
import com.google.home.automation.Condition
import com.google.home.automation.DraftAutomation
import com.google.home.automation.Equals
import com.google.home.automation.Node
import com.google.home.automation.SequentialFlow
import com.google.home.automation.Starter
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.matter.standard.OnOff
import com.google.home.Structure

...

val automation: DraftAutomation = automation {
  name = "MyFirstAutomation"
  description = "Turn on a device when another device is turned on."
  sequential {
    val starterNode = starter<_>(device1, OnOffLightDevice, trait=OnOff)
    condition() { expression = stateReaderNode.onOff equals true }
    action(device2, OnOffLightDevice) { command(OnOff.on()) }
  }
}

Otomasyon DSL'si tanımlandıktan sonra DraftAutomation örneğini oluşturmak için bunu createAutomation() yöntemine iletin:

val createdAutomation = structure.createAutomation(automation)

Buradan, otomasyonda execute(), stop() ve update() gibi diğer tüm otomasyon yöntemlerini kullanabilirsiniz.

Doğrulamayla ilgili hatalar

Otomasyon oluşturma işlemi doğrulamayı geçemezse bir uyarı veya hata mesajı, sorun hakkında bilgi verir. Daha fazla bilgi için ValidationIssueType referansına bakın.

createAutomation(), istisna oluşturmadan tamamlansa bile oluşturulan otomasyon geçerli veya yürütülebilir olmayabilir. Arka uç, geçersiz otomasyon taslaklarının kaydedilmesine izin verir (örneğin, kullanıcı Tanıdık Yüz Algılama gibi gerekli izinleri vermediyse veya cihaz özellikleri eksikse).

Döndürülen Automation örneğinde her zaman isValid ve validationIssues değerlerini doğrulamanız gerekir:

val createdAutomation = structure.createAutomation(automation)

if (!createdAutomation.isValid) {
  // Iterate through validation issues to identify errors and warnings
  for (issue in createdAutomation.validationIssues) {
    when (issue.severity) {
      ValidationIssueSeverity.ERROR -> {
        Log.e(
          "Automation",
          "Validation error on node ${issue.node}: ${issue.issueType}"
        )
        // Handle error (for example, prompt the user to enable missing
        // consents or device features)
      }
      ValidationIssueSeverity.WARNING -> {
        Log.w(
          "Automation",
          "Validation warning on node ${issue.node}: ${issue.issueType}"
        )
      }
      else -> {}
    }
  }
}

Kod örnekleri

Burada, Android'de otomasyon tasarlama sayfasında açıklanan varsayımsal otomasyonların bölümlerini uygulamak için kullanılabilecek bazı örnek kodlar sunuyoruz.

Basit otomasyon

Saat 08:00'de storları açan bir otomasyon şu şekilde uygulanabilir:

// get all the automation node candidates in the structure
val allCandidates = structure.allCandidates().first()
// determine whether a scheduled automation can be constructed
val isSchedulingSupported =
  allCandidates.any {
    it is EventCandidate &&
      it.eventFactory == Time.ScheduledTimeEvent &&
      it.unsupportedReasons.isEmpty()
  }
// get the blinds present in the structure
val blinds =
  allCandidates
    .filter {
      it is CommandCandidate &&
        it.commandDescriptor == WindowCoveringTrait.UpOrOpenCommand &&
        it.unsupportedReasons.isEmpty()
    }
    .map { it.entity }
    .filterIsInstance<HomeDevice>()
    .filter { it.has(WindowCoveringDevice) }
 if (isSchedulingSupported && blinds.isNotEmpty()) {
  // Proceed to create automation
  val automation: DraftAutomation = automation {
    name = "Day time open blinds"
    description = "Open all blinds at 8AM everyday"
    isActive = true
    sequential {
      // At 8:00am local time....
      val unused =
        starter(structure, Time.ScheduledTimeEvent) {
          parameter(Time.ScheduledTimeEvent.clockTime(LocalTime.of(8, 0, 0, 0)))
        }
        // ...open all the blinds
       parallel {
        for (blind in blinds) {
          action(blind, WindowCoveringDevice) { command(WindowCovering.upOrOpen()) }
        }
      }
    }
  }
   val createdAutomation = structure.createAutomation(automation)
} else if (!isSchedulingSupported) {
  // Cannot create automation.
  // Set up your address on the structure, then try again.
} else {
  // You don't have any WindowCoveringDevices.
  // Try again after adding some blinds to your structure.
}

Karmaşık otomasyon

Hareket algılandığında ışıkların yanıp sönmesini tetikleyen bir otomasyon şu şekilde uygulanabilir:

import com.google.home.Home
import com.google.home.HomeClient
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure
import com.google.home.automation.action
import com.google.home.automation.automation
import com.google.home.automation.equals
import com.google.home.automation.parallel
import com.google.home.automation.starter
import com.google.home.google.AssistantBroadcast
import com.google.home.matter.standard.OnOff
import com.google.home.matter.standard.OnOff.Companion.toggle
import com.google.home.matter.standard.OnOffLightDevice
import java.time.Duration

// get all the automation node candidates in the structure
val allCandidates = structure.allCandidates().first()

// get the lights present in the structure
val availableLights = allCandidates.filter {
   it is CommandCandidate &&
   it.commandDescriptor == OnOffTrait.OnCommand
}.map { it.entity }
.filterIsInstance<HomeDevice>()
.filter {it.has(OnOffLightDevice) ||
         it.has(ColorTemperatureLightDevice) ||
         it.has(DimmableLightDevice) ||
         it.has(ExtendedColorLightDevice)}

val selectedLights = ... // user selects one or more lights from availableLights

automation {
isActive = true

sequential {
   // If the presence state changes...
   val starterNode = starter<_>(structure, AreaPresenceState)
   // ...and if the area is occupied...
   condition() {
      expression = starterNode.presenceState equals PresenceState.PresenceStateOccupied
   }
   // "blink" the light(s)
   parallel {
            for(light in selectedLights) {
            action(light, OnOffLightDevice) { command(OnOff.toggle()) }
            delayFor(Duration.ofSeconds(1))
            action(light, OnOffLightDevice) { command(OnOff.toggle()) }
            delayFor(Duration.ofSeconds(1))
            action(light, OnOffLightDevice) { command(OnOff.toggle()) }
            delayFor(Duration.ofSeconds(1))
            action(light, OnOffLightDevice) { command(OnOff.toggle())}
         }
      }
   }
}

Varlık filtreleriyle cihazları dinamik olarak seçme

Otomasyon yazarken belirli cihazları belirtmekle sınırlı değilsiniz. Varlık filtreleri adlı bir özellik, otomasyonunuzun çeşitli ölçütlere göre çalışma zamanında cihazları seçmesine olanak tanır.

Örneğin, otomasyonunuz varlık filtrelerini kullanarak şunları hedefleyebilir:

  • belirli bir cihaz türündeki tüm cihazlar
  • belirli bir odadaki tüm cihazlar
  • Belirli bir odadaki belirli bir cihaz türüne ait tüm cihazlar
  • açık olan tüm cihazlar
  • belirli bir odada açık olan tüm cihazlar

Öğe filtrelerini kullanmak için:

  1. Structure veya Room üzerinden atExecutionTime() numaralı telefonu arayın. Bu işlem, TypedExpression<TypedEntity<StructureType>> döndürür.
  2. Bu nesnede getDevicesOfType() yöntemini çağırın ve DeviceType nesnesini iletin.

Öğe filtreleri başlatıcılarda, durum okuyucularda ve işlemlerde kullanılabilir.

Örneğin, herhangi bir açma/kapatma ışığının bir başlatıcıdan otomasyonu tetiklemesini sağlamak için:

// If any light is turned on or off
val starter =
  starter(
    entityExpression = structure.atExecutionTime().getDevicesOfType(OnOffLightDevice),
    trait = OnOff,
  )

Bir yapıdaki tüm ışıkların (özellikle açma/kapatma ışıkları) OnOff durumunu bir durum okuyucuda yakalamak için:

// Build a Map<Entity, OnOff>
val onOffStateOfAllLights =
  stateReader(
    entityExpression = structure.atExecutionTime().getDevicesOfType(OnOffLightDevice),
    trait = OnOff,
  )

Belirli bir odadaki ışıkları alıp bir koşulda kullanmak için:

val livingRoomLights =
  stateReader(
    entityExpression = livingRoom.atExecutionTime().getDevicesOfType(OnOffLightDevice),
    trait = OnOff,
  )
// Are any of the lights in the living room on?
condition { expression = livingRoomLights.values.any { it.onOff equals true } }

Çalışma zamanında:

Senaryo Sonuç
Başlatıcıdaki ölçütleri karşılayan cihaz yoksa. Otomasyon tetiklenmiyor.
Durum okuyucudaki ölçütleri karşılayan cihaz yok. Otomasyon başlar ancak koşul düğümüne göre devam eder.
Bir işlemdeki ölçütleri karşılayan cihaz yok. Otomasyon başlar ancak işlem hiçbir şey yapmaz.

Aşağıdaki örnekte, tek bir ışık kapatıldığında hol ışığı hariç tüm ışıkları kapatan bir otomasyon gösterilmektedir:

val unused = automation {
  sequential {
    // If any light is turned on or off
    val starter =
      starter(
        entityExpression = structure.atExecutionTime().getDevicesOfType(OnOffLightDevice),
        trait = OnOff,
      )
    condition {
      // Check to see if the triggering light was turned off
      expression = starter.onOff equals false
    }
    // Turn off all lights except the hall light
    action(
      entityExpression =
        structure.atExecutionTime().getDevicesOfType(OnOffLightDevice).filter {
          it notEquals entity(hallwayLight, OnOffLightDevice)
        }
    ) {
      command(OnOff.on())
    }
  }
}

Otomasyon yürütme

execute() yöntemini kullanarak oluşturulan bir otomasyonu çalıştırma:

createdAutomation.execute()

Otomasyonda manuel başlatıcı varsa execute(), manuel başlatıcıdan önceki tüm düğümleri yok sayarak otomasyonu bu noktadan başlatır. Otomasyonun manuel başlatıcısı yoksa yürütme, ilk başlatıcı düğümünden sonraki düğümden başlar.

execute() işlemi başarısız olursa HomeException hatası verilebilir. Hata işleme bölümüne bakın.

Otomasyonu durdurma

stop() yöntemini kullanarak çalışan bir otomasyonu durdurma:


createdAutomation.stop()

stop() işlemi başarısız olursa HomeException hatası verilebilir. Hata işleme bölümüne bakın.

Bir yapıdaki otomasyonların listesini alma

Otomasyonlar yapı düzeyinde tanımlanır. Otomasyonların Flow erişmek için yapının automations() simgesine dokunun:


import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
structure.automations().collect {
  println("Available automations:")
  for (automation in it) {
    println(String.format("%S %S", "$automation.id", "$automation.name"))
  }
}

Alternatif olarak, yerel bir Collection öğesine atayın:

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

var myAutomations: Collection<Automation> = emptyList()
myAutomations = structure.automations()

Kimliğe göre otomasyon alma

Otomasyon kimliğine göre bir otomasyon almak için yapıda automations() yöntemini çağırın ve kimlikle eşleştirin:

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
val automation: DraftAutomation = structure.automations().mapNotNull {
  it.firstOrNull
    { automation -> automation.id == Id("automation-id") }
  }.firstOrNull()

Yanıt:

// Here's how the automation looks like in the get response.
// Here, it's represented as if calling a println(automation.toString())

Automation(
  name = "automation-name",
  description = "automation-description",
  isActive = true,
  id = Id("automation@automation-id"),
  automationGraph = SequentialFlow(
    nodes = [
      Starter(
        entity="device@test-device",
        type="home.matter.0000.types.0101",
        trait="OnOff@6789..."),
      Action(
        entity="device@test-device",
        type="home.matter.0000.types.0101",
        trait="OnOff@8765...",
        command="on")
    ]))

Otomasyonu ada göre alma

Kotlin'deki filter() yöntemi, API çağrılarını daha da hassaslaştırmak için kullanılabilir. Bir otomasyonu adıyla almak için yapının otomasyonlarını alın ve otomasyon adına göre filtreleyin:

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
val automation: DraftAutomation = structure.automations().filter {
  it.name.equals("Sunset Blinds") }

Bir cihazın tüm otomasyonlarını alma

Belirli bir cihazı referans alan tüm otomasyonları almak için iç içe yerleştirilmiş filtrelemeyi kullanarak her otomasyonun automationGraph öğesini tarayın:

import android.util.Log
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure
import com.google.home.automation.Action
import com.google.home.automation.Automation
import com.google.home.automation.Automation.automationGraph
import com.google.home.automation.Node
import com.google.home.automation.ParallelFlow
import com.google.home.automation.SelectFlow
import com.google.home.automation.SequentialFlow
import com.google.home.automation.Starter
import com.google.home.automation.StateReader

...

fun collectDescendants(node: Node): List<Node> {
  val d: MutableList<Node> = mutableListOf(node)

  val children: List<Node> =
    when (node) {
      is SequentialFlow -> node.nodes
      is ParallelFlow -> node.nodes
      is SelectFlow -> node.nodes
      else -> emptyList()
    }
  for (c in children) {
    d += collectDescendants(c)
  }
  return d
}

val myDeviceId = "device@452f78ce8-0143-84a-7e32-1d99ab54c83a"
val structure = homeManager.structures().list().single()
val automations =
  structure.automations().first().filter {
    automation: Automation ->
    collectDescendants(automation.automationGraph!!).any { node: Node ->
      when (node) {
        is Starter -> node.entity.id.id == myDeviceId
        is StateReader -> node.entity.id.id == myDeviceId
        is Action -> node.entity.id.id == myDeviceId
        else -> false
      }
    }
  }

Otomasyonu güncelleme

Bir otomasyonun meta verilerini güncellemek için update() yöntemini çağırın ve meta verileri ayarlayan bir lambda ifadesi iletin:

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
val automation: DraftAutomation = structure.automations().mapNotNull {
  it.firstOrNull
    { automation -> automation.id == Id("automation-id") }
  }.firstOrNull()
automation.update { this.name = "Flashing lights 2" }

update() yöntemi, otomasyon grafiğinin tamamen değiştirilmesini destekler ancak grafiğin düğüm bazında düzenlenmesini desteklemez. Düğümler arasındaki karşılıklı bağımlılıklar nedeniyle düğüm bazında düzenleme yapmak hataya açıktır. Bir otomasyonun mantığını değiştirmek istiyorsanız yeni bir grafik oluşturun ve mevcut grafiği tamamen değiştirin.

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
val automation: Automation = structure.automations().mapNotNull {
  it.firstOrNull
    { automation -> automation.id == Id("automation-id") }
  }.firstOrNull()
automation.update {
  this.automationGraph = sequential {
    val laundryWasherCompletionEvent =
      starter<_>(laundryWasher, LaundryWasherDevice, OperationCompletionEvent)
    condition {
      expression =
        laundryWasherCompletionEvent.completionErrorCode equals
          // UByte 0x00u means NoError
          0x00u
    }
    action(speaker, SpeakerDevice) { command(AssistantBroadcast.broadcast("laundry is done")) }
    }
  }
}

Otomasyon silme

Bir otomasyonu silmek için yapının deleteAutomation() yöntemini kullanın. Otomasyonlar kimlikleri kullanılarak silinmelidir.

import com.google.home.automation.Automation
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.HomeManager
import com.google.home.Id
import com.google.home.Structure

...

val structure = homeManager.structures().list().single()
val automation: DraftAutomation = structure.automations().first()
structure.deleteAutomation(automation.id)

Silme işlemi başarısız olursa HomeException hatası gösterilebilir. Hata işleme bölümüne bakın.

Cihaz silme işleminin otomasyonlar üzerindeki etkisi

Kullanıcı, otomasyonda kullanılan bir cihazı silerse silinen cihaz hiçbir başlatıcıyı tetikleyemez ve otomasyon, cihazdan özellikleri okuyamaz veya cihaza komut veremez. Örneğin, bir kullanıcı evinden bir OccupancySensorDevice silerse ve bir otomasyonun OccupancySensorDevice'e bağlı bir başlatıcısı varsa bu başlatıcı artık otomasyonu etkinleştiremez.