Guida all'DSL Android

Utilizza la seguente guida per capire come utilizzare i vari nodi di Automation DSL per creare un'automazione.

Tutto Automation DSL è inserito in un singolo nodo automation. Il nodo automation forma il limite tra il contesto del linguaggio Kotlin esterno e il contesto DSL incorporato.

Flusso sequenziale

Il flusso sequenziale è il tipo di flusso di automazione predefinito.

Esempio di DSL sequenziale

Ecco un modello di Automation DSL molto semplice che utilizza un flusso sequenziale composto da un comando iniziale, una condizione e un'azione:


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

Questo può essere perfezionato aggiungendo altri nodi.

Starter

I nodi iniziali definiscono le circostanze iniziali che attivano un'automazione. Ad esempio, una modifica dello stato o del valore. Un'automazione deve avere almeno un comando iniziale, altrimenti la convalida non andrà a buon fine. Per aggiungere più di un comando iniziale a un'automazione, devi utilizzare un nodo select.

Starter basato sull'attributo del tratto

Quando dichiari un nodo iniziale basato su un attributo del tratto, specifica:

  • Il dispositivo
  • Il tipo di dispositivo a cui appartiene il tratto
  • Il tratto
starter<_>(thermostat, TemperatureSensorDevice, TemperatureMeasurement)

Il parametro del tipo di dispositivo è obbligatorio perché ti consente di specificare a quale tipo di dispositivo all'interno di un dispositivo si rivolge l'automazione. Ad esempio, un dispositivo potrebbe essere composto da un FanDevice e un HeatingCoolingUnitDevice, entrambi contenenti il OnOff tratto. Se specifichi il tipo di dispositivo, non ci sono ambiguità su quale parte del dispositivo attiva l'automazione.

Starter basato sull'evento

Quando dichiari un nodo iniziale basato su un evento, specifica:

  • Il dispositivo
  • Il tipo di dispositivo a cui appartiene il tratto
  • L'evento
starter<_>(doorBell, GoogleDoorbellDevice, DoorbellPressed)

Starter basato su una struttura e un evento, con parametri

Alcuni eventi possono avere parametri, quindi questi parametri devono essere inclusi anche nel comando iniziale.

Ad esempio, questo comando iniziale utilizza il Time tratto ScheduledTimeEvent per attivare l'automazione alle 7:00:

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

Starter basato sul meteo

Puoi specificare le condizioni meteorologiche attuali o previste in un comando iniziale utilizzando il tratto Meteo:

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

Consulta Chiudere le tende se è probabile che piova nella pagina Automazioni di esempio.

Starter manuale

Uno starter manuale è un tipo speciale di starter che consente all'utente di eseguire manualmente l'automazione.

Quando dichiari uno starter manuale:

  • Non specificare un tratto o un tipo di dispositivo.
  • Fornisci un elemento dell'interfaccia utente che chiama Automation.execute().

Quando inserisci uno starter manuale in un flusso select insieme a un altro starter, lo starter manuale sostituisce l'altro starter:

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

Tieni presente che tutti i nodi condition che seguono uno starter manuale verranno valutati e potrebbero bloccare l'esecuzione dell'automazione, a seconda dell'espressione condition.

Separare un comando iniziale manuale da uno condizionale

Un modo per strutturare l'automazione in modo che i nodi condition non blocchino un'automazione attivata con un comando iniziale manuale è inserire l'altro comando iniziale in un flusso sequenziale separato insieme alla relativa condition:

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

Fare riferimento al valore di un attributo

Per utilizzare il valore di un attributo in un'espressione, utilizza la seguente sintassi.

Con un stateReader:

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

Con uno starter:

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

Nodi ed espressioni di condizione

Un nodo di condizione rappresenta un punto decisionale che determina se l'automazione continua o meno. Un'automazione può avere più nodi condition. Se l'espressione di un nodo condition restituisce false, l'esecuzione dell'intera automazione termina.

All'interno di un nodo condition, puoi combinare più criteri di condizione utilizzando vari operatori, purché l'espressione restituisca un singolo valore booleano. Se il valore risultante è true, la condizione è soddisfatta e l'automazione continua l'esecuzione del nodo successivo. Se è false, l'automazione interrompe l'esecuzione a quel punto.

Le espressioni sono formate in modo simile alle espressioni in Kotlin e possono contenere valori primitivi come numeri, caratteri, stringhe e valori booleani, nonché valori Enum. Il raggruppamento delle sottoespressioni con le parentesi consente di controllare l'ordine in cui vengono valutate.

Ecco un esempio di condition che combina più sottoespressioni in un'unica espressione:

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

Puoi fare riferimento al valore di un tratto a cui accedi tramite uno starter:

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

stateReader

L'altro modo per fare riferimento ai valori degli attributi del tratto in un nodo condition è con un nodo stateReader.

Per farlo, acquisisci prima il valore dell'attributo del tratto in un nodo stateReader. Un stateReader accetta la structure e il tratto come argomenti:

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

Quindi fai riferimento a stateReader nel nodo condition:

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

Utilizzando operatori di confronto e logici, è possibile utilizzare più stateReaders in 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)
}

Durata della condizione

Oltre a un'espressione booleana in una condizione, puoi specificare un intervallo di tempo durante il quale l'espressione deve essere vera per eseguire l'automazione. Ad esempio, puoi definire una condizione che si attiva solo se una luce è accesa da dieci minuti.

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

La durata può variare da 5 secondi a 24 ore.

Nodi di azione

Il nodo di azione è il punto in cui si svolge il lavoro dell'automazione. In questo esempio, l'azione richiama il AssistantBroadcast del tratto broadcast() comando:

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

Istruzioni di importazione

Quando sviluppi automazioni, non è sempre ovvio come importare vari elementi delle API Home nel tuo codice.

Gli attributi del tratto vengono importati dall'oggetto Companion del tratto:

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

Le strutture di dati definite da un tratto vengono importate dalla classe del tratto il cui nome termina con "-Trait":

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

I comandi del tratto vengono importati dall'oggetto Companion del tratto:

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