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 é uma estrutura, primeiro é preciso conceder permissão na estrutura antes de usá-las.

Depois que as permissões forem concedidas a 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 interface HasAutomations com os seguintes métodos específicos de automação:

API Descrição
automations() Liste todas as automações que pertencem à estrutura. Somente as automações criadas por você com as 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 sua 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 ser executada ou não.
  • 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.

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

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

A criação de uma automação define a flag isActive como true por padrão. Portanto, não é necessário definir explicitamente essa flag, 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 método createAutomation() para criar a instância DraftAutomation:

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 da 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 referência de ValidationIssueType.

Exemplos de código

Aqui, apresentamos um exemplo de código que pode ser usado para implementar partes das automações hipotéticas descritas na página Projetar 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 um 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 precisa especificar dispositivos específicos. Com os filtros de entidade, a automação pode selecionar dispositivos em tempo de execução com base em vários critérios.

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

  • todos os dispositivos de um determinado tipo
  • 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 ligados em um determinado cômodo

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 ativações, leitores de estado e ações.

Por exemplo, para que qualquer luz on/off 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 On/Off) em um leitor de estado:

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

Para pegar as luzes de um cômodo específico 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 ambiente de execução:

Cenário Resultado
Nenhum dispositivo atende aos critérios em um inicializador. A automação não é acionada.
Nenhum dispositivo atende aos critérios em um leitor de estado. A automação é iniciada, mas não faz nada.
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 apaga todas as luzes, exceto a do corredor, sempre que uma luz individual é apagada:

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 método execute():

createdAutomation.execute()

Se a automação tiver uma ativação manual, o execute() vai iniciar a automação a partir desse ponto, ignorando todos os nós que precedem a ativação manual. Se a automação não tiver um iniciador manual, a execução começará do nó após o primeiro nó iniciador.

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

Parar uma automação

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


createdAutomation.stop()

Se a operação stop() falhar, uma HomeException poderá ser gerada. 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 automations() da estrutura 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 a um 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 por ID, chame o método automations() na estrutura e faça a correspondência por 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 método filter() em Kotlin pode ser usado para refinar ainda mais as chamadas de API. Para receber uma automação por nome, extraia 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 conferir 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 método update() 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 método update() permite substituir totalmente um gráfico de automação, mas não editar por nó. A edição por nó é propensa a erros devido às interdependências entre eles. 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, uma HomeException poderá ser gerada. Consulte Tratamento de erros.

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

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