Доступ к 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) | Удалить экземпляр автоматизации по его идентификатору. |
Создайте автоматизацию
После создания экземпляра Home и получения разрешений от пользователя получите структуру и устройства:
val structure = homeManager.structures().list().single()
val device = homeManager.devices().get(Id("myDevice"))!!
Затем определите логику вашей автоматизации с помощью Automation DSL. В домашних 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: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
Чтобы получить автоматизацию по идентификатору автоматизации, вызовите метод 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()
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
, этот стартер больше не сможет активировать автоматизацию.