È possibile accedere alle API Automation tramite le API Home per Android, ma poiché il loro entry point è tramite una struttura, è necessario concedere l'autorizzazione alla 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 che appartengono alla struttura. 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 suo 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"))!!
Poi, 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, come nome e descrizione.
- Flag che indicano, ad esempio, se l'automazione può essere eseguita o meno.
- 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 comando iniziale, una condizione o un'azione.
Assegna all'automazione un name e un description.
La creazione di un'automazione imposta il flag isActive su true per impostazione predefinita, pertanto non è necessario impostare esplicitamente questo flag, a meno che tu non voglia disattivare l'automazione inizialmente. In questo scenario, imposta il flag su false durante la creazione.
L'interfaccia DraftAutomation viene utilizzata per creare e generare automazioni,
mentre l'interfaccia Automation viene utilizzata per il recupero. Ad esempio, ecco il 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 definito il DSL di automazione, passalo al metodo createAutomation() per creare l'istanza DraftAutomation:
val createdAutomation = structure.createAutomation(automation)
Da qui, puoi utilizzare tutti gli altri metodi di automazione, come 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.
Anche se createAutomation() viene completato senza generare un'eccezione, l'automazione creata potrebbe non essere valida o eseguibile. Il backend consente di salvare bozze di automazione non valide (ad esempio, se un utente non ha concesso i consensi richiesti, come il rilevamento dei volti riconosciuti, o se mancano le funzionalità del dispositivo).
Devi sempre verificare isValid e controllare validationIssues nell'istanza Automation restituita:
val createdAutomation = structure.createAutomation(automation)
if (!createdAutomation.isValid) {
// Iterate through validation issues to identify errors and warnings
for (issue in createdAutomation.validationIssues) {
when (issue.severity) {
ValidationIssueSeverity.ERROR -> {
Log.e(
"Automation",
"Validation error on node ${issue.node}: ${issue.issueType}"
)
// Handle error (for example, prompt the user to enable missing
// consents or device features)
}
ValidationIssueSeverity.WARNING -> {
Log.w(
"Automation",
"Validation warning on node ${issue.node}: ${issue.issueType}"
)
}
else -> {}
}
}
}
Esempi di codice
Di seguito presentiamo alcuni esempi di codice che potrebbero essere utilizzati per implementare parti delle automazioni ipotetiche descritte nella pagina Progetta un'automazione su Android.
Automazione semplice
Un'automazione che alza le tapparelle alle 08:00 potrebbe essere implementata in questo 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 un 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())}
}
}
}
}
Seleziona dinamicamente i dispositivi con i filtri delle entità
Quando scrivi un'automazione, non devi limitarti a specificare dispositivi specifici. Una funzionalità chiamata filtri delle 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 avere come target:
- tutti i dispositivi di un determinato tipo
- tutti i dispositivi di una stanza specifica
- Tutti i dispositivi di un determinato tipo in una stanza specifica
- tutti i dispositivi accesi
- tutti i dispositivi accesi in una stanza specifica
Per utilizzare i filtri delle entità:
- Su
StructureoRoom, chiamaatExecutionTime(). Viene restituito unTypedExpression<TypedEntity<StructureType>>. - Su questo oggetto, chiama
getDevicesOfType(), trasmettendogli unDeviceType.
I filtri delle 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 (nello specifico,
luci On/Off) in un lettore di stato:
// Build a Map<Entity, OnOff> val onOffStateOfAllLights = stateReader( entityExpression = structure.atExecutionTime().getDevicesOfType(OnOffLightDevice), trait = OnOff, )
Per recuperare le luci di una stanza specifica 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 } }
In fase di runtime:
| Scenario | Risultato |
|---|---|
| Nessun dispositivo soddisfa i criteri di un comando iniziale. | L'automazione non si attiva. |
| Nessun dispositivo soddisfa i criteri in un lettore di stato. | L'automazione si avvia, ma continuerà in base al nodo della condizione. |
| Nessun dispositivo soddisfa i criteri di un'azione. | L'automazione viene avviata, ma l'azione non fa nulla. |
L'esempio seguente è un'automazione che spegne tutte le luci, tranne quella dell'ingresso, ogni volta che viene spenta una singola luce:
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()) } } }
Esegui 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 comando iniziale manuale, l'esecuzione inizia dal nodo successivo al primo nodo di comando iniziale.
Se l'operazione execute() non va a buon fine, potrebbe essere generato un errore HomeException. Consulta Gestione degli errori.
Interrompi un'automazione
Interrompi un'automazione in esecuzione utilizzando il metodo stop():
createdAutomation.stop()
Se l'operazione stop() non va a buon fine, potrebbe essere generato un errore HomeException. Consulta Gestione degli errori.
Recupera un elenco di automazioni per una struttura
Le automazioni sono definite a livello di struttura. Raccogli i dati 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")
]))
Recupera un'automazione per 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") }
Recupera tutte le automazioni per un dispositivo
Per ottenere tutte le automazioni che fanno riferimento a un determinato dispositivo, utilizza il filtro nidificato per eseguire la scansione di 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 relativo 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 dei 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")) }
}
}
}
Elimina un'automazione
Per eliminare un'automazione, utilizza il metodo
deleteAutomation()
della struttura. Un'automazione deve essere eliminata utilizzando il suo 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 errore HomeException. Consulta 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 comando iniziale e l'automazione non potrà leggere gli attributi
dal dispositivo o inviargli comandi. Ad esempio, se un utente elimina un
OccupancySensorDevice
dalla sua casa e un'automazione ha un comando iniziale che dipende da
OccupancySensorDevice, quel comando iniziale non può più attivare l'automazione.