Automation API には Home 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
型です。これは、順番に実行されるノードのリストを含むクラスです。各ノードは、開始条件、条件、アクションなどの自動化の要素を表します。
自動化に 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 時にブラインドを開く自動化は、次のように実装できます。
// 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()
メソッドを呼び出し、メタデータを設定するラムダ式を渡します。
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
に依存する開始条件が含まれている場合、その開始条件は自動化を有効にできなくなります。