Otomasyon oluşturma

Otomasyon API'lerine Home API'leri üzerinden erişilebilir ancak giriş noktaları bir yapı olduğundan, kullanılabilmeleri için önce yapıda izin verilmelidir.

Bir yapı için izinler verildikten sonra aşağıdaki 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

Yapılar, otomasyona özgü aşağıdaki yöntemleri içeren bir HasAutomations arayüzü içerir:

API Açıklama
automations() Yapıya ait tüm otomasyonları listeleyin. 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) Bir otomasyon örneğini kimliğine göre silme.

Otomasyon oluşturma

Ev örneği oluşturduktan ve 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, otomasyonunuzun mantığını Automation DSL'yi kullanarak tanımlayın. Home API'lerinde otomasyonlar Automation arayüzüyle temsil edilir. Bu arayüz bir dizi özellik içerir:

  • Ad ve açıklama gibi meta veriler.
  • Örneğin, otomasyonun yürütülüp yürütülemeyeceğini belirten işaretler.
  • Otomasyonun mantığını içeren düğümlerin listesi (automationGraph mülkü tarafından temsil edilen otomasyon grafiği).

automationGraph, varsayılan olarak SequentialFlow türüne sahiptir. SequentialFlow, sıralı olarak 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.

Bir otomasyon oluşturulduğunda isActive işareti varsayılan olarak true olur. Bu nedenle, otomasyonun başlangıçta devre dışı bırakılmasını istemediğiniz sürece bu işareti açıkça ayarlamak gerekmez. Bu senaryoda, işareti oluşturma sırasında false olarak ayarlayın.

DraftAutomation arayüzü, otomasyon oluşturmak ve oluşturmak için kullanılır. Automation arayüzü ise getirme için kullanılır. Örneğin, başka bir cihaz açıldığında bir cihazı açan otomasyon için otomasyon DSL'si aşağıda verilmiştir:

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 createAutomation() yöntemine iletin:

val createdAutomation = structure.createAutomation(automation)

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

Doğrulamayla ilgili hatalar

Otomasyon oluşturma işlemi doğrulamayı geçemezse sorunla ilgili bilgiler bir uyarı veya hata mesajında sağlanır. Daha fazla bilgi için ValidationIssueType referansına bakın.

Kod örnekleri

Aşağıda, Otomasyon tasarlama sayfasında açıklanan varsayımsal otomasyonların bazı bölümlerini uygulamak için kullanılabilecek bazı örnek kodlar verilmiştir.

Basit otomasyon

Perdeleri sabah 8:00'de açan bir otomasyon aşağıdaki gibi 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ı yanıp sönen bir otomasyon aşağıdaki gibi 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())}
         }
      }
   }
}

Genel otomasyon

Aşağıdaki kodda, kullanıcının kendi genel otomasyonunu oluşturması için ona yol gösterecek bir uygulama uygulamak amacıyla Structure API, Device API ve Discovery API'yi kullanarak kullanıcının evi hakkında nasıl veri toplanabileceği gösterilmektedir (Otomasyon tasarlama: Genel otomasyon bölümünde açıklandığı gibi).

val structureFlow: Flow<Structure> = home.structures().itemFlow(myStructureId)

// Get a snapshot of the structure.
val structure: Structure = structureFlow.first()

   // map of devices where key is device id
val devicesById = mutableMapOf<Id,HomeDevice>()

home.devices().list().map { devicesById.put(it.id, it) }

// map of rooms where key is room id
val roomsByName: MutableMap<String,Room?> = mutableMapOf<String,Room?>()

structure.rooms().list().map { roomsByName.put(it.name, it) }

// map of commands where key is trait id
val commandsByTrait: MutableMap<String,MutableList<CommandCandidate>?> = mutableMapOf<String,MutableList<CommandCandidate>?>()

// map of commands where key is DeviceType.Metadata.hashcode()
val commandsByDeviceType: MutableMap<Id,MutableList<CommandCandidate>?> = mutableMapOf<Id,MutableList<CommandCandidate>?>()

// map of commands where key is entity id
val commandsByEntity: MutableMap<Id,MutableList<CommandCandidate>?> = mutableMapOf<Id,MutableList<CommandCandidate>?>()


// map of stateReaders where key is trait id
val stateReadersByTrait: MutableMap<Int,MutableList<StateReaderCandidate>?> = mutableMapOf<Int,MutableList<StateReaderCandidate>?>()

// map of stateReaders where key is DeviceType.Metadata.hashcode()
val stateReadersByDeviceType: MutableMap<Int,MutableList<StateReaderCandidate>?> = mutableMapOf<Int,MutableList<StateReaderCandidate>?>()

// map of stateReaders where key is  entity id
val stateReadersByEntity: MutableMap<Int,MutableList<StateReaderCandidate>?> = mutableMapOf<Int,MutableList<StateReaderCandidate>?>()

// map of starters where key is trait id
val startersByTrait: MutableMap<Int,MutableList<StarterCandidate>?> = mutableMapOf<Int,MutableList<StarterCandidate>?>()

// map of starters where key is DeviceType.Metadata.hashcode()
val startersByDeviceType: MutableMap<Int,MutableList<CommandCandidate>?> = mutableMapOf<Int,MutableList<CommandCandidate>?>()

// map of starters where key is entity id
val startersByEntity: MutableMap<Int,MutableList<StarterCandidate>?> = mutableMapOf<Int,MutableList<StarterCandidate>?>()

// populate candidate maps
structure.allCandidates().first().map {
  when (it) {
   is CommandCandidate -> {

      // update commandsByTrait
      // TODO refactor into generic function to eliminate duplicate code
      var commandsByTraitList: MutableList<CommandCandidate>? = commandsByTrait.get(it.trait.factory.traitId: String)
      if(commandsByTraitList == null) { commandsByTraitList = arrayListOf<CommandCandidate>() }

      commandsByTraitList.add(it)
      commandsByTrait.put(it.trait.factory.traitId, commandsByTraitList)

      // update commandsByDeviceType
      // TODO refactor into generic function to eliminate duplicate code
      for (t in it.types) {

      //TODO filter out device types not present in the home

      //TODO how to get a reference to device type id? 

      var commandsByDeviceTypeList: MutableList<CommandCandidate>? = commandsByDeviceType.get(t.factory.typeId: Id)

      if (commandsByDeviceTypeList == null)  { commandsByDeviceTypeList = arrayListOf<CommandCandidate>() }

      commandsByDeviceTypeList.add(it)

      commandsByDeviceType.put(t.factory.typeId, commandsByDeviceTypeList)
      }

      // update commandsByEntity
      // TODO refactor into generic function to eliminate duplicate code
     var commandsByEntityList: MutableList<CommandCandidate>? = commandsByEntity.get(it.entity.id: Id)
      if ( commandsByEntityList == null ) {commandsByEntityList = arrayListOf<CommandCandidate>()}
      commandsByEntityList.add(it)
      commandsByEntity.put(it.entity.id, commandsByEntityList)
   }

/*
   is StateReaderCandidate -> {

      // update stateReadersByTrait

      var stateReadersList: MutableList<StateReaderCandidate>? = stateReadersByTrait.get(it.trait.factory.traitId)
      if(stateReadersList == null) { stateReadersList = arrayListOf<StateReaderCandidate>() }

      stateReadersList.add(it)
      stateReadersByTrait.put(it.trait.factory.traitId, stateReadersList)

   // update stateReadersByDeviceType

      for (t in it.types) {
      //TODO filter out device types not present in the home
      var stateReadersList: MutableList<StateReaderCandidate>? = stateReadersByDeviceType.get(t.metadata.hashcode())

      if (stateReadersList == null)  { stateReadersList = arrayListOf<StateReaderCandidate>() }

      stateReadersList.put(it)

      stateReadersByDeviceType.put(t.metadata.hashCode(),deviceTypeStateReaderList)
      }

      // update stateReadersByEntity

      MutableList<StateReaderCandidate> entityStateReaderList? = stateReadersByEntity.get(it.entity.id)
      if  entityStateReaderList  is null  {entityStateReaderList = arrayListOf<StateReaderCandidate>()}
      entityStateReaderList.add(it)
      stateReadersByEntity.put(it.entity.id, entityStateReaderList)
   }

   */

   /*
   is StarterCandidate  -> {

      // update startersByTrait

      var startersList: MutableList<StateReaderCandidate>? = startersByTrait.get(it.trait.factory.traitId)
      if(startersList == null) { startersList = arrayListOf<StateReaderCandidate>() }

      startersList.add(it)
      startersByTrait.put(it.trait.factory.traitId, startersList)

   // update startersByDeviceType

      for (t in it.types) {
      //TODO filter out device types not present in the home
      var startersList: MutableList<StateReaderCandidate>? = startersByDeviceType.get(t.metadata.hashcode())

      if (startersList == null)  { startersList = arrayListOf<StateReaderCandidate>() }

      startersList.put(it)

      startersByDeviceType.put(t.metadata.hashCode(),deviceTypeStateReaderList)
      }

      // update startersByEntity

      MutableList<StateReaderCandidate> entityStateReaderList? = startersByEntity.get(it.entity.id)
      if  entityStateReaderList  is null  {entityStateReaderList = arrayListOf<StateReaderCandidate>()}
      entityStateReaderList.add(it)
      stateReadersByEntity.put(it.entity.id, entityStateReaderList)

      }
*/
 else -> println("unknown type encountered: " + it)

  }
}

Otomasyonu yürütme

Oluşturulan bir otomasyonu execute() yöntemini kullanarak çalıştırın:

createdAutomation.execute()

Otomasyonun manuel başlatıcısı varsa execute(), manuel başlatıcıdan önceki tüm düğümleri yoksayarak otomasyonu o noktadan başlatır. Otomasyonda manuel başlatıcı yoksa yürütme, ilk başlatıcı düğümünü takip eden düğümden başlar.

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

Otomasyonu durdurma

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


createdAutomation.stop()

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

Bir yapının otomasyonlarının listesini alma

Otomasyonlar yapı düzeyinde tanımlanır. Bir dizi otomasyona erişmek için yapının automations() bölümünde veri toplayın:Flow


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'e 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ıdaki automations() yöntemini çağırın ve kimliğe göre 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")
    ]))

Adıyla otomasyon alma

Kotlin'deki filter() yöntemi, API çağrılarını daha da hassaslaştırmak için kullanılabilir. Bir otomasyonu adla almak için yapının otomasyonlarını alın ve otomasyon adını 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 cihaza ait tüm otomasyonları alma

Belirli bir cihaza atıfta bulunan tüm otomasyonları almak için her otomasyonun automationGraph özelliğini taramak üzere iç içe yerleştirilmiş filtrelemeyi kullanı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ğırarak 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, bir otomasyon grafiğinin tamamen değiştirilmesini destekler ancak grafiğin düğüm başına düzenlenmesini desteklemez. Düğümler arasındaki bağımlılık nedeniyle düğüm başına düzenleme, hataya açıktır. Bir otomasyonun mantığını değiştirmek istiyorsanız yeni bir grafik oluşturun ve mevcut otomasyonu tamamen değiştirin.

Otomasyonu 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ı oluşabilir. Hata işleme bölümüne bakın.

Cihaz silme işleminin otomasyonlara etkisi

Bir kullanıcı, otomasyonda kullanılan bir cihazı silerse silinen cihaz herhangi bir başlatıcıyı tetikleyemez ve otomasyon, cihazdan özellik okuyamaz veya cihaza komut veremez. Örneğin, bir kullanıcı evinde bir OccupancySensorDevice silebilirse ve bir otomasyonda OccupancySensorDevice'a bağlı bir başlatıcı varsa bu başlatıcı artık otomasyonu etkinleştiremez.