Creare un'automazione su Android

È possibile accedere alle API Automation tramite le API Home per Android, ma poiché il loro punto di accesso è una struttura, è necessario concedere l'autorizzazione per la struttura prima di poterle utilizzare.

Una volta concesse le autorizzazioni per una struttura, importa questi pacchetti nella tua app:


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

Una struttura contiene un'interfaccia HasAutomations con i seguenti metodi specifici per l'automazione:

API Descrizione
automations() Elenca tutte le automazioni appartenenti alla casa. Vengono restituite solo le automazioni che hai creato tramite le API Home.
createAutomation(automation) Crea un'istanza di automazione per una struttura.
deleteAutomation(automationId) Elimina un'istanza di automazione in base al relativo ID.

Crea un'automazione

Dopo aver creato un'istanza di Home e aver ricevuto le autorizzazioni dall'utente, recupera la struttura e i dispositivi:

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

Quindi, definisci la logica dell'automazione utilizzando Automation DSL. Nelle API Home, un'automazione è rappresentata dall'interfaccia Automation. Questa interfaccia contiene un insieme di proprietà:

  • Metadati, ad esempio nome e descrizione.
  • Flag che indicano, ad esempio, se l'automazione può essere eseguita.
  • Un elenco di nodi che contengono la logica dell'automazione, chiamato grafico dell'automazione, rappresentato dalla proprietà automationGraph.

automationGraph, per impostazione predefinita, è di tipo SequentialFlow, ovvero una classe che contiene un elenco di nodi che vengono eseguiti in ordine sequenziale. Ogni nodo rappresenta un elemento dell'automazione, ad esempio un trigger, una condizione o un'azione.

Assegna all'automazione un name e un description.

La creazione di un'automazione imposta il flag isActive su true, pertanto non è necessario impostarlo esplicitamente, a meno che non si voglia disattivare inizialmente l'automazione. In questo scenario, imposta il flag su false durante la creazione.

L'interfaccia DraftAutomation viene utilizzata per creare automazioni, mentre l'interfaccia Automation viene utilizzata per il recupero. Ad esempio, ecco l'DSL di automazione per un'automazione che accende un dispositivo quando un altro dispositivo viene acceso:

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 volta definita la DSL di automazione, passala al metodo createAutomation() per creare l'istanza DraftAutomation:

val createdAutomation = structure.createAutomation(automation)

Da qui, puoi utilizzare tutti gli altri metodi di automazione, ad esempio execute(), stop() e update().

Errori di convalida

Se la creazione dell'automazione non supera la convalida, un messaggio di avviso o di errore fornisce informazioni sul problema. Per saperne di più, consulta il riferimento ValidationIssueType.

Esempi di codice

Di seguito è riportato un codice di esempio che potrebbe essere utilizzato per implementare parti delle automazioni ipotetiche descritte nella pagina Progettare un'automazione su Android.

Automazione semplice

Un'automazione che alza le tapparelle alle 8:00 potrebbe essere implementata nel seguente modo:

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

Automazione complessa

Un'automazione che attiva le luci lampeggianti quando viene rilevato movimento potrebbe essere implementata in questo modo:

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

Selezionare dinamicamente i dispositivi con i filtri delle entità

Quando scrivi un'automazione, non sei limitato a specificare dispositivi particolari. Una funzionalità chiamata filtri entità consente all'automazione di selezionare i dispositivi in fase di runtime in base a vari criteri.

Ad esempio, utilizzando i filtri delle entità, l'automazione potrebbe scegliere come target:

  • tutti i dispositivi di un determinato tipo
  • tutti i dispositivi in una stanza specifica
  • tutti i dispositivi di un determinato tipo in una stanza specifica
  • tutti i dispositivi accesi
  • tutti i dispositivi accesi in una determinata stanza

Per utilizzare i filtri per entità:

  1. Su Structure o Room, chiama il numero atExecutionTime(). Viene restituito un TypedExpression<TypedEntity<StructureType>>.
  2. Su questo oggetto, chiama getDevicesOfType(), passando un DeviceType.

I filtri per entità possono essere utilizzati in comandi iniziali, lettori di stato e azioni.

Ad esempio, per fare in modo che qualsiasi luce on/off attivi un'automazione da un comando iniziale:

// If any light is turned on or off
val starter =
  starter(
    entityExpression = structure.atExecutionTime().getDevicesOfType(OnOffLightDevice),
    trait = OnOff,
  )

Per acquisire lo stato OnOff di tutte le luci di una struttura (in particolare, luci On/Off) in un lettore di stato:

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

Per ottenere le luci di una determinata stanza e utilizzarle in una condizione:

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

Al runtime:

Scenario Risultato
Nessun dispositivo soddisfa i criteri di un comando iniziale. L'automazione non viene attivata.
Nessun dispositivo soddisfa i criteri in un lettore di stato. L'automazione si avvia, ma non fa nulla.
Nessun dispositivo soddisfa i criteri in un'azione. L'automazione viene avviata, ma l'azione non fa nulla.

Il seguente esempio è un'automazione che spegne tutte le luci tranne quella dell'ingresso ogni volta che una singola luce viene spenta:

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

Eseguire un'automazione

Esegui un'automazione creata utilizzando il metodo execute():

createdAutomation.execute()

Se l'automazione ha un comando iniziale manuale, execute() avvia l'automazione da quel punto, ignorando tutti i nodi che precedono il comando iniziale manuale. Se l'automazione non ha un avvio manuale, l'esecuzione inizia dal nodo successivo al primo nodo di avvio.

Se l'operazione execute() non riesce, potrebbe essere generato un HomeException. Consulta la sezione Gestione degli errori.

Interrompere un'automazione

Interrompi un'automazione in esecuzione utilizzando il metodo stop():


createdAutomation.stop()

Se l'operazione stop() non riesce, potrebbe essere generato un HomeException. Consulta la sezione Gestione degli errori.

Visualizzare un elenco di automazioni per una struttura

Le automazioni vengono definite a livello di struttura. Raccogli informazioni sulla automations() della struttura per accedere a un Flow di automazioni:


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

In alternativa, assegnalo a un Collection locale:

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

Ottieni un'automazione in base all'ID

Per ottenere un'automazione in base all'ID automazione, chiama il metodo automations() sulla struttura e trova la corrispondenza in base all'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()

Risposta:

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

Ottenere un'automazione in base al nome

Il metodo filter() in Kotlin può essere utilizzato per perfezionare ulteriormente le chiamate API. Per ottenere un'automazione per nome, recupera le automazioni della struttura e filtra in base al nome dell'automazione:

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

Recuperare tutte le automazioni per un dispositivo

Per ottenere tutte le automazioni che fanno riferimento a un determinato dispositivo, utilizza il filtro nidificato per esaminare il automationGraph di ogni automazione:

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

Aggiorna un'automazione

Per aggiornare i metadati di un'automazione, chiama il metodo update(), passandogli un'espressione lambda che imposta i metadati:

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

Il metodo update() supporta la sostituzione completa di un grafico di automazione, ma non la modifica per nodo del grafico. La modifica per nodo è soggetta a errori a causa delle interdipendenze tra i nodi. Se vuoi modificare la logica di un'automazione, genera un nuovo grafico e sostituisci completamente quello esistente.

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

Eliminare un'automazione

Per eliminare un'automazione, utilizza il metodo deleteAutomation() della struttura. Un'automazione deve essere eliminata utilizzando il relativo 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)

Se l'eliminazione non va a buon fine, potrebbe essere generato un HomeException. Consulta la sezione Gestione degli errori.

Impatto dell'eliminazione dei dispositivi sulle automazioni

Se un utente elimina un dispositivo utilizzato in un'automazione, il dispositivo eliminato non può attivare alcun trigger e l'automazione non sarà in grado di leggere gli attributi o inviare comandi. Ad esempio, se un utente elimina un OccupancySensorDevice dalla sua casa e un'automazione ha un comando iniziale che dipende dal OccupancySensorDevice, questo comando iniziale non può più attivare l'automazione.