自動化 API 可透過 Home 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() |
列出結構體中的所有自動化動作。系統只會傳回你透過 Google Home API 建立的自動化動作。 |
createAutomation(automation) |
為結構體建立自動化動作執行個體。 |
deleteAutomation(automationId) |
根據 ID 刪除自動化動作執行個體。 |
建立自動化作業
建立 Home 執行個體並接收使用者授予的權限後,請取得結構體和裝置:
val structure = homeManager.structures().list().single()
val device = homeManager.devices().get(Id("myDevice"))!!
接著,使用 Automation DSL 定義自動化動作的邏輯。在 Google Home API 中,自動化動作會以 Automation
介面表示。這個介面包含一組屬性:
- Metadata,例如名稱和說明。
- 旗標,例如表示自動化動作是否可執行。
- 包含自動化動作邏輯的節點清單,稱為自動化動作圖表,由
automationGraph
屬性代表。
根據預設,automationGraph
的類型為 SequentialFlow
,這是一個包含以順序執行的節點清單的類別。每個節點都代表自動化動作的元素,例如啟動條件、限制條件或動作。
指派自動化動作的 name
和 description
。
建立自動化動作時,系統會將 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 API、Device API 和 Discovery 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
,那麼該啟動條件就無法再啟動自動化動作。