Доступ к Automation API можно получить через Home API для Android, но поскольку точка входа – это структура, сначала нужно предоставить разрешение на уровне структуры.
После того как вы предоставите разрешения для структуры, импортируйте эти пакеты в приложение:
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. В 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.
Даже если функция createAutomation() завершается без исключения, созданная автоматизация может быть недействительной или невыполнимой. В серверной части можно сохранять недействительные черновики автоматизации (например, если пользователь не дал необходимые согласия, такие как на распознавание знакомых лиц, или если на устройстве нет нужных функций).
Всегда проверяйте isValid и validationIssues в возвращенном экземпляре Automation:
val createdAutomation = structure.createAutomation(automation)
if (!createdAutomation.isValid) {
// Iterate through validation issues to identify errors and warnings
for (issue in createdAutomation.validationIssues) {
when (issue.severity) {
ValidationIssueSeverity.ERROR -> {
Log.e(
"Automation",
"Validation error on node ${issue.node}: ${issue.issueType}"
)
// Handle error (for example, prompt the user to enable missing
// consents or device features)
}
ValidationIssueSeverity.WARNING -> {
Log.w(
"Automation",
"Validation warning on node ${issue.node}: ${issue.issueType}"
)
}
else -> {}
}
}
}
Примеры кода
Ниже приведены примеры кода, который можно использовать для реализации некоторых частей гипотетических автоматизаций, описанных на странице Разработка автоматизации на Android.
Простая автоматизация
Автоматизация, которая поднимает жалюзи в 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илиRoomвызовите методatExecutionTime(). Возвращается значениеTypedExpression<TypedEntity<StructureType>>. - Для этого объекта вызовите метод
getDevicesOfType()и передайте ему объектDeviceType.
Фильтры объектов можно использовать в триггерах, считывателях состояний и действиях.
Например, чтобы любое включение или выключение света запускало автоматизацию:
// If any light is turned on or off val starter = starter( entityExpression = structure.atExecutionTime().getDevicesOfType(OnOffLightDevice), trait = OnOff, )
Чтобы получить состояние OnOff всех светильников в структуре (в частности, светильников, которые можно включать и выключать) в считывателе состояний:
// Build a Map<Entity, OnOff> val onOffStateOfAllLights = stateReader( entityExpression = structure.atExecutionTime().getDevicesOfType(OnOffLightDevice), trait = OnOff, )
Чтобы получить информацию о светильниках в определенной комнате и использовать ее в условии:
val livingRoomLights = stateReader( entityExpression = livingRoom.atExecutionTime().getDevicesOfType(OnOffLightDevice), trait = OnOff, ) // Are any of the lights in the living room on? condition { expression = livingRoomLights.values.any { it.onOff equals true } }
Во время выполнения:
| Сценарий | Результат |
|---|---|
| Ни одно устройство не соответствует критериям, заданным в событии. | Автоматизация не запускается. |
| Ни одно устройство не соответствует критериям в считывателе состояния. | Автоматизация запускается, но продолжит работу в зависимости от узла условия. |
| Ни одно устройство не соответствует критериям действия. | Автоматизация запускается, но действие не выполняется. |
В примере ниже показана автоматизация, которая выключает все светильники, кроме светильника в прихожей, когда выключается любой из светильников:
val unused = automation { sequential { // If any light is turned on or off val starter = starter( entityExpression = structure.atExecutionTime().getDevicesOfType(OnOffLightDevice), trait = OnOff, ) condition { // Check to see if the triggering light was turned off expression = starter.onOff equals false } // Turn off all lights except the hall light action( entityExpression = structure.atExecutionTime().getDevicesOfType(OnOffLightDevice).filter { it notEquals entity(hallwayLight, OnOffLightDevice) } ) { command(OnOff.on()) } } }
Как запустить программу
Запустить созданную программу автоматизации с помощью метода
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()
Как получить автоматизацию по идентификатору
Чтобы получить автоматизацию по ее идентификатору, вызовите метод 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() позволяет полностью заменить граф автоматизации, но не редактировать отдельные узлы графа. Редактирование каждого узла по отдельности может привести к ошибкам из-за взаимозависимости узлов. Если вы хотите изменить логику автоматизации, создайте новый граф и полностью замените им существующий.
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: Automation = structure.automations().mapNotNull {
it.firstOrNull
{ automation -> automation.id == Id("automation-id") }
}.firstOrNull()
automation.update {
this.automationGraph = sequential {
val laundryWasherCompletionEvent =
starter<_>(laundryWasher, LaundryWasherDevice, OperationCompletionEvent)
condition {
expression =
laundryWasherCompletionEvent.completionErrorCode equals
// UByte 0x00u means NoError
0x00u
}
action(speaker, SpeakerDevice) { command(AssistantBroadcast.broadcast("laundry is done")) }
}
}
}
Как удалить программу
Чтобы удалить автоматизацию, используйте метод 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, это условие больше не сможет активировать программу.