Conceptos de DSL de Android

Esta es una descripción general de los conceptos fundamentales del DSL de Automation en Android.

Componentes de automatización

Una automatización consta de los siguientes componentes básicos, que suelen evaluarse en este orden:

  1. Activador: Define las condiciones iniciales que activan la automatización, como un cambio en un rasgo. Una automatización debe tener un activador.
  2. Condición: Son las restricciones adicionales que se deben evaluar después de que se activa una automatización. La expresión de una condición debe evaluarse como true para que se ejecuten las acciones de una automatización.
  3. Acción: Son los comandos o las actualizaciones de estado que se realizan cuando se cumplen todas las condiciones.

Por ejemplo, tal vez tengas una automatización que atenúa las luces de una habitación cuando se enciende la TV en esa habitación entre el atardecer y el amanecer. En este ejemplo:

  1. Starter: Se encendió la TV, lo que representa un cambio de estado en un rasgo de la TV.
  2. Condición: Se evalúa la hora actual de la casa en la que se encuentra la TV.
  3. Acción: Las luces de la misma habitación que la TV se atenúan.

La automatización se activaría cuando se encienda la TV de la habitación, pero solo se ejecutará si se cumple la condición de "la hora está entre el atardecer y el amanecer".

Además de la estructura básica, las automatizaciones en las APIs de Home también contienen metadatos, como name y description, que se pueden usar para identificarlas para desarrolladores y usuarios.

Nodos

En las APIs de Home, la estructura lógica de una automatización consta de nodos. Los nodos son unidades abstractas y reutilizables que representan comportamientos de entidades o flujos de ejecución. Cada nodo puede tener variables de entrada y variables de salida que otros nodos pueden consumir.

Tabla: Tipos de nodos de automatización
Nodo Tipo de nodo Implementación de Kotlin Descripción
Starter Comportamiento StarterNodeDsl Inicia una automatización cuando cambia el estado de un rasgo (cualquier atributo).
StateReader Comportamiento StateReaderNodeDsl Lee un atributo de rasgo y te permite capturar su valor para usarlo en nodos de condición.
Acción Comportamiento ActionNodeDsl Invoca comandos de rasgos.
Secuencial Flujo de ejecución SequentialFlow Ejecuta nodos de acción anidados en secuencia. Este es el comportamiento de ejecución predeterminado.
Paralelo Flujo de ejecución ParallelFlow Ejecuta nodos de acción anidados en paralelo.
Afección Flujo de ejecución ConditionNodeDsl Cambiar de forma condicional el flujo de ejecución en función de las evaluaciones de expresiones lógicas Las condiciones pueden estar asociadas con un activador (condiciones específicas del activador) o ser globales (se aplican a todos los activadores).
Seleccionar Flujo de ejecución SelectFlow Permite que más de un activador inicie una automatización.
Expresión Valor Expression Puede ser el valor de un atributo del rasgo, una constante o un valor literal, y debe evaluarse como una lista, un número, un valor booleano o una cadena.

Nodos de comportamiento

Los nodos, como los activadores y las acciones, son nodos de comportamiento. Los activadores activan una automatización según los cambios en los atributos del dispositivo. Las acciones envían comandos a los dispositivos o actualizan atributos.

Por lo general, los nodos de comportamiento se vinculan a rasgos del dispositivo y generan el estado del rasgo para usarlo como entrada en otros nodos.

Nodos de flujo de ejecución

Algunos nodos representan flujos de ejecución, como los secuenciales y los paralelos. Cada uno de estos nodos contiene los nodos de comportamiento que definen la automatización.

Por ejemplo, un flujo secuencial puede contener nodos que se ejecutan en orden secuencial. Por lo general, estos serían el activador, la condición y la acción.

Flujos de ejecución secuenciales
Figura 1: Flujo de automatización secuencial

Un flujo paralelo puede tener varios nodos de acción que se ejecutan al mismo tiempo, como encender varias luces simultáneamente. Los nodos que siguen un flujo paralelo no se ejecutarán hasta que finalicen todas las ramas del flujo paralelo.

Flujos de ejecución paralela
Figura 2: Flujo de automatización paralelo

Otro tipo de flujo de ejecución es un flujo de condición, que puede cambiar el flujo de ejecución según la evaluación de una expresión.

Por ejemplo, tal vez tengas una automatización que realice una acción según si es de noche. Un nodo de condición verifica la hora del día y, luego, sigue la ruta de ejecución adecuada según esa evaluación.

Flujo de condición
Figura 3: Flujo de condiciones

Un flujo de selección es útil cuando deseas tener más de un activador que pueda iniciar tu automatización. Cuando encierras dos o más activadores en un flujo de select, cualquiera de ellos puede activar la automatización.

Por ejemplo, podrías escribir una automatización que baje las persianas al atardecer, si la temperatura supera un umbral determinado o si el brillo supera un umbral. Tres iniciadores separados controlan cada una de estas situaciones, y los tres se incluirían en un flujo de select.

Seleccionar flujo
Figura 4: Flujo de selección

Flujos anidados

En las automatizaciones complejas, los nodos de flujo de ejecución también se pueden anidar. Por ejemplo, es posible que tengas un flujo secuencial que ejecute un flujo paralelo.

Flujos de ejecución anidados
Figura 5: Flujos de ejecución anidados

Los nodos de DSL se pueden anidar y combinar de varias maneras para satisfacer tus necesidades específicas, según las restricciones que se describen en la siguiente tabla. La columna Builder vincula a la documentación del compilador de acceso seguro a tipos de Kotlin, que detalla lo que se permite usar en cada tipo de nodo.

Tabla: Cómo se pueden combinar los nodos
Nodo Puede contener el siguiente tipo de nodo y datos Debe estar dentro de uno de los siguientes tipos de nodos
Starter Expresión Seleccionar, secuencial
ManualStarter Seleccionar, secuencial
StateReader Expresión (por lo general, consta de un valor de atributo de rasgo) Acción, condición
Acción Command, Entity, Expression Paralelo, Seleccionar, Secuencial
Secuencial Paralelo, Seleccionar, Secuencial
Paralelo Acción Secuencial
Afección Expresión Paralelo, secuencial
Seleccionar Condición, Secuencial, Activador, ManualStarter Secuencial y debe ser el primer nodo del flujo

DSL de automatización

En las APIs de Home, las automatizaciones se definen con el DSL de Automation (lenguaje específico del dominio). La DSL de Automation se implementa como un DSL de Kotlin (lenguaje específico del dominio), con compiladores de acceso seguro a tipos de Kotlin y está diseñada específicamente para definir plantillas de automatización.

Cuando se compila una automatización, los compiladores de Kotlin con seguridad de tipos generan clases de datos de Kotlin que luego se serializan en JSON de búfer de protocolo, que se usa para realizar llamadas a los servicios de automatización de Google.

El DSL de Automation simplifica y agiliza el proceso de creación de automatizaciones. Utiliza de forma nativa el mismo modelo de datos de los rasgos estándar Matter y los rasgos smart home que se incluyen en la API de Device.

El DSL de Automation también define la lógica de una automatización en términos de tipos de dispositivos abstractos, en lugar de instancias de dispositivos específicos ubicados en la casa de un usuario. Permite que el desarrollador proporcione parámetros de entrada que se pueden usar en el tiempo de ejecución para especificar instancias de dispositivos reales, así como otros valores de parámetros importantes.

La sintaxis de la DSL es similar a la de Kotlin y es igual de segura en cuanto a tipos, pero una automatización escrita en la DSL de Automation es más simple y concisa que la misma automatización escrita en Kotlin puro.

Ejemplo

A continuación, se muestra un ejemplo de automatización que enciende un dispositivo, escrito con el DSL de Automation:

val automation = automation {
  name = "MyFirstAutomation"
  description = "If light1 is on, turn on light2."
  isActive = true
  sequential {
    val onOffTrait = starter<_>(device1, OnOffLightDevice, OnOff)
    condition() { expression = onOffTrait.onOff equals true }
    action(device2, OnOffLightDevice) { command(OnOff.on()) }
  }
}

Esta automatización es muy básica: cuando se enciende device1, una luz (el atributo onOff cambia a true), se envía el comando on() para encender device2.

La automatización usa un nodo sequential, que indica que sus nodos se ejecutarán en orden secuencial.

Dentro del nodo sequential, se encuentran nodos de comportamiento, como starter, condition y action. El resultado del nodo starter se asigna a una variable para usarla en el nodo condition.