Создайте автоматизацию на Android

Доступ к API автоматизации можно получить через API Home для 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 .

Примеры кода

Здесь мы приводим пример кода, который можно использовать для реализации частей гипотетических автоматизаций, описанных на странице Разработка автоматизации на 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())}
         }
      }
   }
}

Динамический выбор устройств с помощью фильтров сущностей

При написании автоматизации вы не ограничены указанием конкретных устройств. Функция, называемая фильтрами сущностей, позволяет вашей автоматизации выбирать устройства во время выполнения на основе различных критериев.

Например, используя фильтры сущностей, ваша автоматизация может быть нацелена на:

  • все устройства определенного типа
  • все устройства в определенной комнате
  • все устройства определенного типа в определенной комнате
  • все устройства, которые включены
  • все устройства, включенные в определенной комнате

Чтобы использовать фильтры сущностей:

  1. Для Structure или Room вызовите atExecutionTime() . Возвращается TypedExpression<TypedEntity<StructureType>> .
  2. Для этого объекта вызовите 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 . См. раздел Обработка ошибок .

Получить список автоматики для конструкции

Автоматизации определяются на уровне структуры. Для доступа к Flow автоматизаций используйте 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()
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() поддерживает полную замену графа автоматизации, но не редактирование его отдельных узлов. Редактирование отдельных узлов подвержено ошибкам из-за взаимозависимости узлов. Если вы хотите изменить логику автоматизации, создайте новый граф и полностью замените существующий.

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 , этот пускатель больше не сможет активировать эту систему.