یک اتوماسیون بسازید

APIهای Automation ممکن است از طریق APIهای Home قابل دسترسی باشند، اما از آنجایی که نقطه ورود آنها از طریق یک ساختار است، ابتدا باید اجازه به ساختار داده شود تا بتوان از آنها استفاده کرد.

پس از اعطای مجوز برای یک ساختار، این بسته ها را به برنامه خود وارد کنید:


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

یک ساختار شامل یک رابط HasAutomations با روش های اتوماسیون خاص زیر است:

API توضیحات
automations() تمام اتوماسیون هایی که به ساختار تعلق دارند را فهرست کنید. فقط اتوماسیون هایی که از طریق Home API ایجاد کرده اید بازگردانده می شوند.
createAutomation(automation) یک نمونه اتوماسیون برای یک ساختار ایجاد کنید.
deleteAutomation(automationId) یک نمونه اتوماسیون را با شناسه آن حذف کنید.

یک اتوماسیون ایجاد کنید

پس از ایجاد یک نمونه از Home و دریافت مجوز از کاربر، ساختار و دستگاه(های) را دریافت کنید:

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

سپس منطق اتوماسیون خود را با استفاده از Automation DSL تعریف کنید. در API های Home، یک اتوماسیون با رابط Automation نشان داده می شود. این رابط شامل مجموعه ای از ویژگی ها است:

  • فراداده، مانند نام و توضیحات.
  • پرچم هایی که به عنوان مثال نشان می دهد که آیا اتوماسیون قابل اجرا است یا خیر.
  • لیستی از گره هایی که حاوی منطق اتوماسیون هستند، به نام گراف اتوماسیون، که با ویژگی automationGraph نمایش داده می شود.

automationGraph ، به طور پیش فرض، از نوع SequentialFlow است، که کلاسی است که شامل لیستی از گره هایی است که به ترتیب اجرا می شوند. هر گره نشان دهنده یک عنصر از اتوماسیون است، مانند یک شروع، شرط یا عمل.

به اتوماسیون یک name و description اختصاص دهید.

ایجاد یک اتوماسیون، پرچم isActive را به طور پیش‌فرض روی true قرار می‌دهد، بنابراین نیازی به تنظیم صریح این پرچم نیست، مگر اینکه در ابتدا بخواهید اتوماسیون غیرفعال شود. در آن سناریو، پرچم را در هنگام ایجاد روی false تنظیم کنید.

رابط DraftAutomation برای ساخت و ایجاد اتوماسیون و رابط Automation برای بازیابی استفاده می شود. برای مثال، در اینجا Automation DSL برای اتوماسیونی است که وقتی دستگاه دیگری روشن است، دستگاهی را روشن می کند:

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

هنگامی که DSL اتوماسیون تعریف شد، آن را به متد createAutomation() ارسال کنید تا نمونه DraftAutomation ایجاد شود:

val createdAutomation = structure.createAutomation(automation)

از اینجا، می‌توانید از سایر روش‌های اتوماسیون روی اتوماسیون، مانند execute() ، stop() و update() استفاده کنید.

خطاهای اعتبارسنجی

اگر ایجاد اتوماسیون اعتبار سنجی را پشت سر نگذارد، یک پیام هشدار یا خطا اطلاعاتی در مورد مشکل ارائه می دهد. برای اطلاعات بیشتر، به مرجع ValidationIssueType مراجعه کنید.

نمونه های کد

در اینجا چند کد نمونه ارائه می کنیم که می تواند برای پیاده سازی بخش هایی از اتوماسیون های فرضی شرح داده شده در صفحه طراحی یک اتوماسیون استفاده شود.

اتوماسیون ساده

اتوماسیونی که پرده‌ها را در ساعت 8:00 صبح بالا می‌برد، ممکن است به این صورت اجرا شود:

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

اتوماسیون پیچیده

اتوماسیونی که هنگام تشخیص حرکت، چراغ های چشمک زن را فعال می کند، ممکن است به صورت زیر اجرا شود:

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

اتوماسیون عمومی

کد زیر نشان می‌دهد که چگونه می‌توان با استفاده از Structure API ، Device API و Discovery داده‌های خانه کاربر را جمع‌آوری کرد تا برنامه‌ای را پیاده‌سازی کند که کاربر را از طریق ایجاد اتوماسیون عمومی خود راهنمایی می‌کند (همانطور که در طراحی اتوماسیون توضیح داده شده است: اتوماسیون عمومی ).

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)

  }
}

یک اتوماسیون را اجرا کنید

یک اتوماسیون ایجاد شده را با استفاده از متد execute() اجرا کنید:

createdAutomation.execute()

اگر اتوماسیون دارای یک راه‌انداز دستی باشد، execute() اتوماسیون را از آن نقطه شروع می‌کند، بدون توجه به تمام گره‌هایی که قبل از شروع دستی هستند. اگر اتوماسیون راه انداز دستی نداشته باشد، اجرا از گره ای که بعد از اولین گره شروع کننده قرار دارد شروع می شود.

اگر عملیات execute() با شکست مواجه شود، ممکن است یک HomeException پرتاب شود. رسیدگی به خطا را ببینید.

یک اتوماسیون را متوقف کنید

با استفاده از متد stop() یک اتوماسیون در حال اجرا را متوقف کنید:


createdAutomation.stop()

اگر عملیات stop() با شکست مواجه شود، ممکن است یک HomeException پرتاب شود. رسیدگی به خطا را ببینید.

لیستی از اتوماسیون های یک سازه را دریافت کنید

اتوماسیون ها در سطح سازه تعریف می شوند. برای دسترسی به Flow of automations، روی automations() ساختار جمع آوری کنید:


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

از طرف دیگر، آن را به یک Collection محلی اختصاص دهید:

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

یک اتوماسیون با شناسه دریافت کنید

برای به دست آوردن یک اتوماسیون با شناسه اتوماسیون، متد automations() را در ساختار فراخوانی کنید و روی ID مطابقت دهید:

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

پاسخ:

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

یک اتوماسیون با نام دریافت کنید

متد filter() در Kotlin می تواند برای اصلاح بیشتر فراخوانی های API استفاده شود. برای به دست آوردن یک اتوماسیون بر اساس نام، اتوماسیون های ساختار را دریافت کنید و روی نام اتوماسیون فیلتر کنید:

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

تمام اتوماسیون های یک دستگاه را دریافت کنید

برای دریافت تمام اتوماسیون هایی که به یک دستگاه معین اشاره می کنند، از فیلتر تودرتو برای اسکن automationGraph هر اتوماسیون استفاده کنید:

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

یک اتوماسیون را به روز کنید

برای به روز رسانی ابرداده یک اتوماسیون، متد update() آن را فراخوانی کنید و یک عبارت لامبدا به آن ارسال کنید که متادیتا را تنظیم می کند:

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() به طور کامل از جایگزینی یک گراف اتوماسیون پشتیبانی می کند، اما از ویرایش گراف در هر گره پشتیبانی نمی کند. ویرایش هر گره به دلیل وابستگی متقابل گره ها مستعد خطا است. اگر می خواهید منطق یک اتوماسیون را تغییر دهید، یک نمودار جدید ایجاد کنید و به طور کامل نمودار موجود را جایگزین کنید.

یک اتوماسیون را حذف کنید

برای حذف یک اتوماسیون، از متد deleteAutomation() ساختار استفاده کنید. یک اتوماسیون باید با استفاده از شناسه آن حذف شود.

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)

اگر حذف نشد، ممکن است یک HomeException پرتاب شود. رسیدگی به خطا را ببینید.

تاثیر حذف دستگاه بر اتوماسیون

اگر کاربر دستگاهی را که در اتوماسیون استفاده می‌شود حذف کند، دستگاه حذف‌شده نمی‌تواند هیچ راه‌اندازی را راه‌اندازی کند، و اتوماسیون نمی‌تواند ویژگی‌ها را از آن بخواند یا دستوراتی برای آن صادر کند. به عنوان مثال، اگر کاربری یک OccupancySensorDevice از خانه خود حذف کند و یک اتوماسیون راه اندازی داشته باشد که به OccupancySensorDevice بستگی دارد، آن استارت دیگر نمی تواند اتوماسیون را فعال کند.