Guía de DSL de Android

Usa la siguiente guía para comprender cómo se pueden usar varios nodos de Automation DSL para crear una automatización.

Toda la DSL de automatización se coloca dentro de un solo nodo automation. El nodo automation forma el límite entre el contexto externo del lenguaje Kotlin y el contexto de DSL incorporado.

Flujo secuencial

El flujo secuencial es el tipo predeterminado de flujo de automatización.

Ejemplo de DSL secuencial

Aquí tienes una plantilla de DSL de automatización muy básica que usa un flujo secuencial que consta de un activador, una condición y una acción:


import com.google.home.automation.action
import com.google.home.automation.automation
import com.google.home.automation.condition
import com.google.home.automation.sequential
import com.google.home.automation.starter

...

automation {
  sequential {
    starter<_>(...)
    condition {...}
    action {...}
  }
}

Esto se puede mejorar si se agregan nodos adicionales.

Starter

Los nodos de inicio definen las circunstancias iniciales que activan una automatización. Por ejemplo, un cambio de estado o valor. Una automatización debe tener al menos un activador; de lo contrario, fallará la validación. Para agregar más de un activador a una automatización, debes usar un nodo select.

Starter basado en el atributo de trait

Cuando declares un nodo de inicio basado en un atributo de trait, especifica lo siguiente:

  • el dispositivo
  • el tipo de dispositivo al que pertenece el trait
  • el trait
starter<_>(thermostat, TemperatureSensorDevice, TemperatureMeasurement)

El parámetro de tipo de dispositivo es obligatorio porque te permite especificar a qué tipo de dispositivo dentro de un dispositivo se dirige la automatización. Por ejemplo, un dispositivo puede estar compuesto por un FanDevice y un HeatingCoolingUnitDevice, que contienen el OnOff trait. Si especificas el tipo de dispositivo, no hay ambigüedad sobre qué parte del dispositivo activa la automatización.

Starter basado en eventos

Cuando declares un nodo de inicio basado en un evento, especifica lo siguiente:

  • el dispositivo
  • el tipo de dispositivo al que pertenece el trait
  • el evento
starter<_>(doorBell, GoogleDoorbellDevice, DoorbellPressed)

Starter basado en una estructura y un evento, con parámetros

Algunos eventos pueden tener parámetros, por lo que también deben incluirse en el activador.

Por ejemplo, este activador usa el Time trait's ScheduledTimeEvent para activar la automatización a las 7:00 a.m.:

val earlyMorning = starter<_>(structure, Time.ScheduledTimeEvent) {
  parameter(Time.ScheduledTimeEvent.clockTime(
    LocalTime.of(7, 0, 0, 0)))
}

Starter basado en el clima

Puedes especificar las condiciones climáticas actuales o pronosticadas en un activador con el trait Weather:

val weatherState = starter<_>(structure, trait = Weather)

Consulta Cierra las persianas si es probable que llueva en la página Ejemplo de automatizaciones.

Starter manual

Un activador manual es un tipo especial de activador que permite al usuario ejecutar la automatización de forma manual.

Cuando declares un activador manual, haz lo siguiente:

  • No especifiques un trait ni un tipo de dispositivo.
  • Proporciona un elemento de IU que llame a Automation.execute().

Cuando colocas un activador manual en un flujo select junto con otro activador, el activador manual anula el otro:

select {
  manualStarter()
  starter<_>(thermostat, TemperatureSensorDevice, TemperatureMeasurement)
}

Ten en cuenta que se evaluarán todos los nodos condition que sigan a un activador manual y podrían bloquear la ejecución de la automatización, según la expresión condition.

Cómo separar un iniciador manual de uno condicional

Una forma de estructurar tu automatización para que los nodos condition no bloqueen una automatización que se activó con un activador manual es colocar el otro activador en un flujo secuencial separado junto con su condition:

automation_graph {
  sequential {
    select {
      sequential {
        starter<_>(...)
        condition {...}
      }
      sequential {
        manualStarter()
      }
    }
    action {...}
  }
}

Haz referencia al valor de un atributo

Para usar el valor de un atributo en una expresión, usa la siguiente sintaxis.

Con un stateReader:

val time = stateReader<_>(structure, Structure, Time)
val currTime = time.currentTime

Con un starter:

val starterNode = starter<_>(device1, LaundryWasherDevice, OnOff)
condition() {
  expression = starterNode.onOff equals true
}

Nodos y expresiones de condición

Un nodo de condición representa un punto de decisión que determina si la automatización continúa o no. Una automatización puede tener varios nodos condition. Si la expresión de algún nodo condition se evalúa como false, finaliza la ejecución de toda la automatización.

Dentro de un nodo condition, puedes combinar varios criterios de condición con varios operadores, siempre que la expresión se evalúe como un solo valor booleano. Si el valor resultante es true, se cumple la condición y la automatización continúa la ejecución del siguiente nodo. Si es false, la automatización deja de ejecutarse en ese punto.

Las expresiones se forman de manera similar a las expresiones en Kotlin y pueden contener valores primitivos, como números, caracteres, cadenas y booleanos, así como valores de Enum. Si agrupas las subexpresiones con paréntesis, puedes controlar el orden en que se evalúan.

Aquí tienes un ejemplo de una condition que combina varias subexpresiones en una sola expresión:

condition() {
  val expr1 = starterNode.lockState equals DlLockState.Unlocked
  val expr2 = stateReaderNode.lockState equals true

  val expr3 = occupancySensingDevice.occupied notEquals 0
  val expr4 = timeStateReaderNode
    .currentTime
    .between(
      timeStateReaderNode.sunsetTime,
      timeStateReaderNode.sunriseTime)
  expression = (expr1 and expr2) or (expr3 and expr4)
}

Puedes hacer referencia al valor de un trait al que se accede a través de un activador:

val starterNode = starter<_>(device, OnOff)
condition() { expression = starterNode.onOff equals true }

stateReader

La otra forma de hacer referencia a los valores de los atributos de trait en un nodo condition es con un nodo stateReader.

Para ello, primero captura el valor del atributo de trait en un nodo stateReader. Un stateReader toma la structure y el trait como argumentos:

import com.google.home.automation.stateReader
...
val filterMonitoringState = stateReader<_>(structure, ActivatedCarbonFilterMonitoring)

Luego, haz referencia al stateReader en el nodo condition:

condition() {
  expression =
    filterMonitoringState.changeIndication
      .equals(ChangeIndicationEnum.Warning)
}

Con operadores lógicos y de comparación, se pueden usar varios stateReaders en un nodo condition:

val armState = stateReader<_>(doorLock, DoorLockDevice, ArmDisarm )
val doorLockState = stateReader<_>(doorLock, DoorLockDevice, DoorLock)
condition() {
  expression =
    (armState.armState equals true)
    and
    (doorLockState.lockState equals true)
}

Duración de la condición

Además de una expresión booleana en una condición, puedes especificar un período durante el cual la expresión debe ser verdadera para ejecutar la automatización. Por ejemplo, puedes definir una condición que se active solo si una luz está encendida durante diez minutos.

  condition {
    expression(lightStateReader.onOff == true)
    forDuration(Duration.ofMinutes(10))
  }

La duración puede variar de 5 segundos a 24 horas.

Nodos de acción

El nodo de acción es donde se realiza el trabajo de la automatización. En este ejemplo, la acción invoca el AssistantBroadcast trait's broadcast() comando:

action(device, SpeakerDevice) {
  command(AssistantBroadcast.broadcast("Intruder detected!"))
}

Declaraciones de importación

Cuando desarrollas automatizaciones, no siempre es obvio cómo importar varios elementos de las APIs de Home a tu código.

Los atributos de trait se importan del objeto Companion del trait:

import com.google.home.matter.standard.OnOff.Companion.onOff

Las estructuras de datos definidas por un trait se importan de la clase de trait cuyo nombre termina en "-Trait":

import com.google.home.matter.standard.MediaPlaybackTrait.PlaybackStateEnum

Los comandos de trait se importan del objeto Companion del trait:

import com.google.home.matter.standard.Thermostat.Companion.setTemperatureSetpointHold