Cómo compilar una automatización en Android

Se puede acceder a las APIs de Automation a través de las APIs de Home para Android, pero, como su punto de entrada es a través de una estructura, primero se debe otorgar permiso en la estructura para poder usarlas.

Una vez que se otorguen los permisos para una estructura, importa estos paquetes a tu app:


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

Una estructura contiene una interfaz HasAutomations con los siguientes métodos específicos de automatización:

API Descripción
automations() Enumera todas las automatizaciones que pertenecen a la estructura. Solo se muestran las automatizaciones que creaste a través de las APIs de Home.
createAutomation(automation) Crea una instancia de automatización para una estructura.
deleteAutomation(automationId) Borra una instancia de automatización por su ID.

Crea una automatización

Después de crear una instancia de Home y recibir los permisos del usuario, obtén la estructura y los dispositivos:

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

Luego, define la lógica de tu automatización con la DSL de Automation. En las APIs de Home, la interfaz Automation representa una automatización. Esta interfaz contiene un conjunto de propiedades:

  • Metadatos, como el nombre y la descripción
  • Marcas que indican, por ejemplo, si se puede ejecutar o no la automatización.
  • Es una lista de nodos que contiene la lógica de la automatización, llamada gráfico de automatización, representada por la propiedad automationGraph.

De forma predeterminada, automationGraph es del tipo SequentialFlow, que es una clase que contiene una lista de nodos que se ejecutan en orden secuencial. Cada nodo representa un elemento de la automatización, como un activador, una condición o una acción.

Asigna un name y un description a la automatización.

La creación de una automatización establece de forma predeterminada la marca isActive en true, por lo que no es necesario configurarla de forma explícita, a menos que quieras que la automatización esté inhabilitada inicialmente. En ese caso, establece la marca en false durante la creación.

La interfaz DraftAutomation se usa para compilar y crear automatizaciones, y la interfaz Automation se usa para la recuperación. Por ejemplo, esta es la DSL de automatización para una automatización que enciende un dispositivo cuando se enciende otro:

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

Una vez que se defina la DSL de automatización, pásala al método createAutomation() para crear la instancia de DraftAutomation:

val createdAutomation = structure.createAutomation(automation)

Desde aquí, puedes usar todos los demás métodos de automatización, como execute(), stop() y update().

Errores de validación

Si la creación de la automatización no pasa la validación, un mensaje de advertencia o error proporciona información sobre el problema. Para obtener más información, consulta la referencia de ValidationIssueType.

Ejemplos de código

Aquí presentamos un código de ejemplo que se podría usar para implementar partes de las automatizaciones hipotéticas que se describen en la página Cómo diseñar una automatización en Android.

Automatización sencilla

Una automatización que levanta las cortinas a las 8:00 a.m. se podría implementar de la siguiente manera:

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

Automatización compleja

Una automatización que active luces intermitentes cuando se detecte movimiento se podría implementar de la siguiente manera:

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

Cómo seleccionar dispositivos de forma dinámica con filtros de entidades

Cuando escribes una automatización, no estás limitado a especificar dispositivos específicos. Una función llamada filtros de entidades permite que la automatización seleccione dispositivos en el tiempo de ejecución según varios criterios.

Por ejemplo, con los filtros de entidades, tu automatización podría segmentarse para lo siguiente:

  • todos los dispositivos de un tipo de dispositivo en particular
  • todos los dispositivos de una sala en particular
  • todos los dispositivos de un tipo específico en una habitación determinada
  • todos los dispositivos que están encendidos
  • todos los dispositivos que están encendidos en una habitación en particular

Para usar filtros de entidades, sigue estos pasos:

  1. En Structure o Room, llama a atExecutionTime(). Esto muestra un TypedExpression<TypedEntity<StructureType>>.
  2. En este objeto, llama a getDevicesOfType() y pásale un DeviceType.

Por ejemplo, para obtener el estado OnOff de todas las luces de una estructura (específicamente, luces de encendido/apagado), haz lo siguiente:

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

Para obtener las luces de una habitación en particular y usarlas en una condición, haz lo siguiente:

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

Los filtros de entidades se pueden usar en activadores, lectores de estado y acciones.

En el tiempo de ejecución:

Situación Resultado
No hay dispositivos que cumplan con los criterios de un activador. La automatización no se activa.
Ningún dispositivo cumple con los criterios de una acción. La automatización se inicia, pero la acción no realiza ninguna acción.
Ningún dispositivo cumple con los criterios de un lector de estado. La automatización se inicia, pero no hace nada.

El siguiente ejemplo es una automatización que apaga todas las luces, excepto la del pasillo, cada vez que se apaga una luz individual.

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

Ejecuta una automatización

Ejecuta una automatización creada con el método execute():

createdAutomation.execute()

Si la automatización tiene un iniciador manual, execute() inicia la automatización desde ese punto y omite todos los nodos que preceden al activador manual. Si la automatización no tiene un activador manual, la ejecución comienza desde el nodo que sigue al primer nodo activador.

Si la operación execute() falla, es posible que se arroje una HomeException. Consulta Manejo de errores.

Detén una automatización

Detén una automatización en ejecución con el método stop():


createdAutomation.stop()

Si la operación stop() falla, es posible que se arroje una HomeException. Consulta Manejo de errores.

Obtén una lista de automatizaciones de una estructura

Las automatizaciones se definen a nivel de la estructura. Recopila en el automations() de la estructura para acceder a un Flow de automatizaciones:


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, asígnale un 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()

Cómo obtener una automatización por ID

Para obtener una automatización por ID de automatización, llama al método automations() en la estructura y haz coincidir el 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()

Respuesta:

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

Cómo obtener una automatización por nombre

El método filter() en Kotlin se puede usar para definir mejor las llamadas a la API. Para obtener una automatización por nombre, obtén las automatizaciones de la estructura y filtra según el nombre de la automatización:

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

Cómo obtener todas las automatizaciones de un dispositivo

Para obtener todas las automatizaciones que hacen referencia a un dispositivo determinado, usa el filtrado anidado para analizar el automationGraph de cada automatización:

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

Cómo actualizar una automatización

Para actualizar los metadatos de una automatización, llama a su método update() y pásale una expresión lambda que establezca los metadatos:

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

El método update() admite reemplazar por completo un grafo de automatización, pero no la edición por nodo del grafo. La edición por nodo es propensa a errores debido a las interdependencias de los nodos. Si deseas cambiar la lógica de una automatización, genera un gráfico nuevo y reemplaza por completo el existente.

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

Cómo borrar una automatización

Para borrar una automatización, usa el método deleteAutomation() de la estructura. Una automatización se debe borrar con su 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().first()
structure.deleteAutomation(automation.id)

Si la eliminación falla, es posible que se arroje una HomeException. Consulta Manejo de errores.

Impacto de la eliminación de dispositivos en las automatizaciones

Si un usuario borra un dispositivo que se usa en una automatización, este no puede activar ningún activador, y la automatización no podrá leer atributos ni enviarle comandos. Por ejemplo, si un usuario borra un OccupancySensorDevice de su casa y una automatización tiene un activador que depende de OccupancySensorDevice, ese activador ya no puede activar la automatización.