构建自动化操作

您可以通过 Home API 访问自动化 API,但由于其入口点是通过结构实现的,因此必须先向结构授予权限,然后才能使用这些 API。

为结构授予权限后,将以下软件包导入您的应用:


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) 根据 ID 删除自动化操作实例。

创建自动化操作

创建 Home 实例并从用户处获得权限后,获取结构和设备:

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

然后,使用 Automation DSL 定义自动化操作的逻辑。在 Home API 中,自动化操作由 Automation 接口表示。此接口包含一组属性:

  • 元数据,例如名称和说明。
  • 例如,用于指示能否执行自动化操作的标志。
  • 包含自动化操作逻辑的节点列表(称为自动化操作图),由 automationGraph 属性表示。

默认情况下,automationGraph 的类型为 SequentialFlow,这是一个类,其中包含按顺序执行的节点列表。每个节点代表自动化操作的元素,例如启动器、条件或操作。

为自动化操作分配 namedescription

创建自动化操作时,系统会将 isActive 标志默认设置为 true,因此除非您最初希望停用自动化操作,否则无需明确设置此标志。在这种情况下,请在创建期间将该标志设置为 false

DraftAutomation 接口用于构建和创建自动化操作,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 APIDevice APIDiscovery API 实现一款应用,引导用户创建自己的通用自动化操作(如设计自动化操作:通用自动化操作中所述)。

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。请参阅错误处理

获取结构的自动化操作列表

自动化操作是在结构级别定义的。收集结构的 automations() 以访问一系列自动化操作的 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"))
  }
}

或者,将其分配给本地 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()

根据 ID 获取自动化操作

如需按自动化操作 ID 获取自动化操作,请对结构调用 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")
    ]))

按名称获取自动化操作

Kotlin 中的 filter() 方法可用于进一步优化 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() 方法,并向其传递用于设置元数据的 lambda 表达式:

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() 方法。必须使用自动化操作的 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().first()
structure.deleteAutomation(automation.id)

如果删除失败,系统可能会抛出 HomeException。请参阅错误处理

删除设备对自动化操作的影响

如果用户删除了自动化操作中使用的设备,则已删除的设备将无法触发任何启动器,并且自动化操作将无法从中读取属性或向其发出命令。例如,如果用户从住宅中删除 OccupancySensorDevice,而某个自动化操作的启动器依赖于 OccupancySensorDevice,则该启动器将无法再激活该自动化操作。