Concetti DSL di Android

Questa è una panoramica dei concetti fondamentali del linguaggio DSL di automazione su Android.

Componenti di automazione

Un'automazione è costituita dai seguenti componenti di base, in genere valutati in questo ordine:

  1. Comando iniziale: definisce le condizioni iniziali che attivano l'automazione, ad esempio una modifica a una caratteristica. Un'automazione deve avere un comando iniziale.
  2. Condizione: eventuali vincoli aggiuntivi da valutare dopo l'attivazione di un'automazione. L'espressione in una condizione deve restituire true affinché le azioni di un'automazione possano procedere.
  3. Azione: comandi o aggiornamenti di stato eseguiti quando tutte le condizioni sono soddisfatte.

Ad esempio, potresti avere un'automazione che abbassa l'intensità delle luci in una stanza quando la TV della stanza viene accesa tra il tramonto e l'alba. In questo esempio:

  1. Avvio: la TV è stata accesa, il che rappresenta una modifica dello stato di una caratteristica della TV.
  2. Condizione: viene valutata l'ora attuale della casa in cui si trova la TV.
  3. Azione: le luci nella stessa stanza della TV vengono attenuate.

L'automazione si attiverebbe quando la TV nella stanza è accesa, ma viene eseguita solo se è soddisfatta la condizione "l'ora è compresa tra il tramonto e l'alba".

Oltre alla struttura di base, le automazioni nelle API Home contengono anche metadati, come name e description, che possono essere utilizzati per identificarle per sviluppatori e utenti.

Nodi

Nelle API Home, la struttura logica di un'automazione è costituita da nodi. I nodi sono unità astratte e riutilizzabili che rappresentano i comportamenti o i flussi di esecuzione delle entità. Ogni nodo può avere variabili di input, nonché variabili di output che possono essere utilizzate da altri nodi.

Tabella: tipi di nodi di automazione
Nodo Tipo di nodo Implementazione di Kotlin Descrizione
Starter Comportamentale StarterNodeDsl Avvia un'automazione quando cambia lo stato di una caratteristica (qualsiasi attributo).
StateReader Comportamentale StateReaderNodeDsl Legge un attributo del tratto e ti consente di acquisirne il valore da utilizzare nei nodi di condizione.
Azione Comportamentale ActionNodeDsl Richiama i comandi delle caratteristiche.
Sequenziale Flusso di esecuzione SequentialFlow Esegue i nodi di azione nidificati in sequenza. Questo è il comportamento di esecuzione predefinito.
Parallelo Flusso di esecuzione ParallelFlow Esegue i nodi di azione nidificati in parallelo.
Condizione Flusso di esecuzione ConditionNodeDsl Modifica in modo condizionale il flusso di esecuzione in base alle valutazioni delle espressioni logiche. Le condizioni possono essere associate a un comando iniziale (condizioni specifiche del comando iniziale) o essere globali (si applicano a tutti i comandi iniziali).
Seleziona Flusso di esecuzione SelectFlow Consente a più di un comando iniziale di attivare un'automazione.
Expression Valore Expression Può essere il valore di un attributo di un tratto, una costante o un valore letterale e deve restituire un elenco, un numero, un valore booleano o una stringa.

Nodi comportamentali

I nodi come i comandi iniziali e le azioni sono nodi comportamentali. I comandi iniziali attivano un'automazione in base alle modifiche degli attributi del dispositivo. Le azioni inviano comandi al dispositivo o aggiornano gli attributi.

I nodi comportamentali sono in genere associati alle caratteristiche del dispositivo e allo stato delle caratteristiche di output da utilizzare come input in altri nodi.

Nodi del flusso di esecuzione

Alcuni nodi rappresentano flussi di esecuzione, ad esempio sequenziali e paralleli. Ciascuno di questi nodi contiene i nodi comportamentali che definiscono l'automazione.

Ad esempio, un flusso sequenziale può contenere nodi che vengono eseguiti in ordine sequenziale. In genere, si tratta di comando iniziale, condizione e azione.

Flussi di esecuzione sequenziali
Figura 1: flusso di automazione sequenziale

Un flusso parallelo può avere più nodi di azione in esecuzione contemporaneamente, ad esempio l'accensione di più luci contemporaneamente. I nodi che seguono un flusso parallelo non vengono eseguiti finché non terminano tutti i rami del flusso parallelo.

Flussi di esecuzione parallela
Figura 2: flusso di automazione parallelo

Un altro tipo di flusso di esecuzione è un flusso di condizione, che può modificare il flusso di esecuzione in base alla valutazione di un'espressione.

Ad esempio, potresti avere un'automazione che esegue un'azione in base all'ora del giorno. Un nodo di condizione controlla l'ora del giorno, quindi segue il percorso di esecuzione appropriato in base a questa valutazione.

Flusso di condizioni
Figura 3: flusso delle condizioni

Un flusso di selezione è utile quando vuoi avere più di un trigger che può attivare l'automazione. Quando includi due o più comandi iniziali in un flusso select, uno qualsiasi dei comandi iniziali può attivare l'automazione.

Ad esempio, puoi scrivere un'automazione che abbassi le tapparelle al tramonto, se la temperatura supera una determinata soglia o se la luminosità supera una soglia. Tre diversi starter gestiscono ciascuno di questi scenari e tutti e tre sarebbero inclusi in un flusso select.

Seleziona flusso
Figura 4: flusso di selezione

Flussi nidificati

Nelle automazioni complesse, i nodi del flusso di esecuzione possono anche essere nidificati. Ad esempio, potresti avere un flusso sequenziale che esegue un flusso parallelo.

Flussi di esecuzione nidificati
Figura 5: Flussi di esecuzione nidificati

I nodi DSL possono essere nidificati e combinati in vari modi per soddisfare le tue esigenze specifiche, in base ai vincoli descritti nella tabella seguente. La colonna Builder rimanda alla documentazione del builder typesafe Kotlin, che descrive in dettaglio cosa è consentito per l'utilizzo in ogni tipo di nodo.

Tabella: come possono essere combinati i nodi
Nodo Potrebbe contenere il seguente tipo di nodo e dati Deve rientrare in uno dei seguenti tipi di nodi
Starter Espressione Seleziona, Sequenziale
ManualStarter Seleziona, Sequenziale
StateReader Espressione (in genere costituita da un valore dell'attributo caratteristica) Azione, Condizione
Azione Command, Entity, Expression Parallel, Select, Sequential
Sequenziale Parallel, Select, Sequential
Parallelo Azione Sequenziale
Condizione Espressione Parallel, Sequential
Seleziona Condizione, Sequenziale, Iniziale, ManualeIniziale Sequenziale e deve essere il primo nodo del flusso

DSL di automazione

Nelle API Home, le automazioni vengono definite utilizzando il linguaggio specifico del dominio (DSL) per l'automazione. La DSL di automazione è implementata come DSL Kotlin (linguaggio specifico del dominio), utilizzando builder Kotlin type-safe ed è progettata specificamente per definire i modelli di automazione.

Quando un'automazione viene compilata, i builder Kotlin type-safe generano classi di dati Kotlin che vengono poi serializzate in JSON di protocol buffer, utilizzato per effettuare chiamate ai servizi di automazione di Google.

Il linguaggio DSL di automazione semplifica e ottimizza il processo di creazione delle automazioni. Utilizza in modo nativo lo stesso modello di dati delle caratteristiche standard Matter e delle caratteristiche smart home presenti nell'API Device.

L'DSL di automazione definisce anche la logica di un'automazione in termini di tipi di dispositivi astratti, anziché istanze di dispositivi specifiche che si trovano nella casa di un utente. Consente allo sviluppatore di fornire parametri di input che possono essere utilizzati in fase di runtime per specificare le istanze effettive del dispositivo, nonché altri valori di parametri importanti.

La sintassi DSL è simile a quella di Kotlin ed è altrettanto sicura per i tipi, ma un'automazione scritta in DSL di automazione è più semplice e concisa della stessa automazione scritta in Kotlin puro.

Esempio

Di seguito è riportato un esempio di automazione che accende un dispositivo, scritto utilizzando il linguaggio DSL di automazione:

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

Questa automazione è molto semplice: quando device1, una luce si accende (l'attributo onOff cambia in true), quindi invia il comando on() per accendere device2.

L'automazione utilizza un nodo sequential, il che indica che i nodi verranno eseguiti in ordine sequenziale.

All'interno del nodo sequential si trovano nodi comportamentali come starter, condition e action. L'output del nodo starter viene assegnato a una variabile da utilizzare nel nodo condition.