Criar uma automação no Android

As APIs de automação podem ser acessadas pelas APIs Home para Android, mas como o ponto de entrada delas é por uma estrutura, a permissão precisa ser concedida na estrutura antes de serem usadas.

Depois que as permissões forem concedidas para uma estrutura, importe estes pacotes para seu app:


import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.Id
import com.google.home.Structure

Uma estrutura contém uma HasAutomations interface com os seguintes métodos específicos de automação:

API Descrição
automations() Lista todas as automações que pertencem à estrutura. Somente as automações criadas pelas APIs Home são retornadas.
createAutomation(automation) Cria uma instância de automação para uma estrutura.
deleteAutomation(automationId) Exclui uma instância de automação pelo ID.

Criar uma automação

Depois de criar uma instância do Home e receber permissões do usuário, receba a estrutura e os dispositivos:

val structure = homeManager.structures().list().single()
val device = homeManager.devices().get(Id("myDevice"))!!

Em seguida, defina a lógica da automação usando a DSL de automação. Nas APIs Home, uma automação é representada pela interface Automation. Essa interface contém um conjunto de propriedades:

  • Metadados, como nome e descrição.
  • Flags que indicam, por exemplo, se a automação pode ou não ser executada.
  • Uma lista de nós que contêm a lógica da automação, chamada de gráfico de automação, representada pela propriedade automationGraph.

automationGraph, por padrão, é do tipo SequentialFlow, que é uma classe que contém uma lista de nós que são executados em ordem sequencial. Cada nó representa um elemento da automação, como um acionador, uma condição ou uma ação.

Atribua um name e uma description à automação.

A criação de uma automação define a flag isActive como true. Portanto, não é necessário definir essa flag explicitamente, a menos que você queira que a automação seja desativada inicialmente. Nesse cenário, defina a flag como false durante a criação.

A interface DraftAutomation é usada para criar automações, e a interface Automation é usada para recuperação. Por exemplo, confira a DSL de automação para uma automação que liga um dispositivo quando outro é ligado:

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()) }
  }
}

Depois que a DSL de automação for definida, transmita-a para o createAutomation() método para criar a DraftAutomation instância:

val createdAutomation = structure.createAutomation(automation)

A partir daqui, você pode usar todos os outros métodos de automação, como execute(), stop() e update().

Erros de validação

Se a criação de automação não passar na validação, uma mensagem de aviso ou erro vai fornecer informações sobre o problema. Para mais informações, consulte a ValidationIssueType referência.

Exemplos de código

Aqui, apresentamos alguns exemplos de código que podem ser usados para implementar partes das automações hipotéticas descritas na página Criar uma automação no Android.

Automação simples

Uma automação que levanta as persianas às 8h pode ser implementada assim:

// 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.
}

Automação complexa

Uma automação que aciona luzes piscando quando o movimento é detectado pode ser implementada assim:

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())}
         }
      }
   }
}

Selecionar dispositivos dinamicamente com filtros de entidade

Ao escrever uma automação, você não está limitado a especificar dispositivos específicos. Um recurso chamado filtros de entidade permite que a automação selecione dispositivos no momento da execução com base em vários critérios.

Por exemplo, usando filtros de entidade, a automação pode segmentar:

  • todos os dispositivos de um tipo específico
  • todos os dispositivos em uma sala específica
  • todos os dispositivos de um tipo específico em uma sala específica
  • todos os dispositivos que estão ligados
  • todos os dispositivos que estão ligados em uma sala específica

Para usar filtros de entidade:

  1. Em Structure ou Room, chame atExecutionTime(). Isso retorna um TypedExpression<TypedEntity<StructureType>>.
  2. Nesse objeto, chame getDevicesOfType(), transmitindo um DeviceType.

Os filtros de entidade podem ser usados em acionadores, leitores de estado e ações.

Por exemplo, para que qualquer luz ligada/desligada acione uma automação de uma ativação:

// If any light is turned on or off
val starter =
  starter(
    entityExpression = structure.atExecutionTime().getDevicesOfType(OnOffLightDevice),
    trait = OnOff,
  )

Para capturar o estado OnOff de todas as luzes em uma estrutura (especificamente, luzes ligadas/desligadas) em um leitor de estado:

// Build a Map<Entity, OnOff>
val onOffStateOfAllLights =
  stateReader(
    entityExpression = structure.atExecutionTime().getDevicesOfType(OnOffLightDevice),
    trait = OnOff,
  )

Para acender as luzes em uma sala específica e usá-las em uma condição:

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 } }

No momento da execução:

Cenário Resultado
Nenhum dispositivo atende aos critérios em um acionador. A automação não é acionada.
Nenhum dispositivo atende aos critérios em um leitor de estado. A automação é iniciada, mas continua com base no nó de condição.
Nenhum dispositivo atende aos critérios em uma ação. A automação é iniciada, mas a ação não faz nada.

O exemplo a seguir é uma automação que desliga todas as luzes, exceto a luz do corredor, sempre que uma luz individual é desligada:

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())
    }
  }
}

Executar uma automação

Execute uma automação criada usando o execute() método:

createdAutomation.execute()

Se a automação tiver um acionador manual, execute() vai iniciar a automação a partir desse ponto, ignorando todos os nós que precedem o acionador manual. Se a automação não tiver um acionador manual, a execução será iniciada no nó após o primeiro nó do acionador.

Se a operação execute() falhar, um HomeException poderá ser gerado. Consulte Tratamento de erros.

Interromper uma automação

Interrompa uma automação em execução usando o método stop():


createdAutomation.stop()

Se a operação stop() falhar, um HomeException poderá ser gerado. Consulte Tratamento de erros.

Receber uma lista de automações para uma estrutura

As automações são definidas no nível da estrutura. Colete na estrutura's automations() para acessar um Flow de automações:


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"))
  }
}

Como alternativa, atribua-o a uma Collection local:

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()

Receber uma automação por ID

Para receber uma automação pelo ID, chame o automations() método na estrutura e faça a correspondência pelo 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()

Resposta:

// 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")
    ]))

Receber uma automação por nome

O filter() método no Kotlin pode ser usado para refinar ainda mais as chamadas de API. Para receber uma automação por nome, receba as automações da estrutura e filtre pelo nome da automação:

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") }

Receber todas as automações de um dispositivo

Para receber todas as automações que fazem referência a um determinado dispositivo, use a filtragem aninhada para verificar o automationGraph de cada automação:

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
      }
    }
  }

Atualizar uma automação

Para atualizar os metadados de uma automação, chame o update() método dela, transmitindo uma expressão lambda que define os metadados:

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" }

O update() método oferece suporte à substituição completa de um gráfico de automação, mas não à edição por nó de o gráfico. A edição por nó é propensa a erros devido às interdependências de nós. Se você quiser mudar a lógica de uma automação, gere um novo gráfico e substitua totalmente o atual.

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")) }
    }
  }
}

Excluir uma automação

Para excluir uma automação, use o método deleteAutomation() da estrutura. Uma automação precisa ser excluída usando o ID dela.

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)

Se a exclusão falhar, um HomeException poderá ser gerado. Consulte Tratamento de erros.

Impacto da exclusão de dispositivos em automações

Se um usuário excluir um dispositivo usado em uma automação, o dispositivo excluído não poderá acionar nenhum acionador, e a automação não poderá ler atributos dele nem emitir comandos para ele. Por exemplo, se um usuário excluir um OccupancySensorDevice da casa e uma automação tiver um acionador que dependa de o OccupancySensorDevice, esse acionador não poderá mais ativar a automação.