È possibile accedere alle API di automazione tramite le API Home, ma poiché il punto di accesso avviene tramite una struttura, è necessario concedere prima 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 relativo ID. |
Crea un'automazione
Dopo aver creato un'istanza di Home e aver ricevuto le autorizzazioni dall'utente, ottieni 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 o meno.
- Un elenco di nodi contenenti la logica dell'automazione, chiamato
grafo dell'automazione, rappresentato dalla proprietà
automationGraph
.
Per impostazione predefinita, automationGraph
è di tipo SequentialFlow
, che è 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
.
Quando viene creata un'automazione, il flag isActive
viene impostato per impostazione predefinita su true
, pertanto non è necessario impostarlo esplicitamente, a meno che non vogliate inizialmente disattivare l'automazione. In questo caso, imposta il flag su false
durante la creazione.
L'interfaccia DraftAutomation
viene utilizzata per creare e sviluppare automazioni, mentre l'interfaccia Automation
viene utilizzata per il recupero. Ad esempio, di seguito è riportato il DSL di automazione per un'automazione che accende un dispositivo quando viene attivato un altro dispositivo:
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, ad esempio execute()
, stop()
e update()
.
Errori di convalida
Se la creazione dell'automazione non supera la convalida, viene visualizzato un messaggio di avviso o di errore con informazioni sul problema. Per ulteriori informazioni, 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 Creare un'automazione.
Automazione semplice
Un'automazione che alza le persiane 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 nel seguente 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())}
}
}
}
}
Automazione generica
Il codice seguente mostra come raccogliere dati sulla casa dell'utente utilizzando l'API Structure, l'API Device e l'API Discovery per implementare un'app che guidi l'utente nella creazione della propria automazione generica (come descritto in Creare un'automazione: automazione generica).
val structureFlow: Flow<Structure> = home.structures().itemFlow(myStructureId)
// Get a snapshot of the structure.
val structure: Structure = structureFlow.first()
// map of devices where key is device id
val devicesById = mutableMapOf<Id,HomeDevice>()
home.devices().list().map { devicesById.put(it.id, it) }
// map of rooms where key is room id
val roomsByName: MutableMap<String,Room?> = mutableMapOf<String,Room?>()
structure.rooms().list().map { roomsByName.put(it.name, it) }
// map of commands where key is trait id
val commandsByTrait: MutableMap<String,MutableList<CommandCandidate>?> = mutableMapOf<String,MutableList<CommandCandidate>?>()
// map of commands where key is DeviceType.Metadata.hashcode()
val commandsByDeviceType: MutableMap<Id,MutableList<CommandCandidate>?> = mutableMapOf<Id,MutableList<CommandCandidate>?>()
// map of commands where key is entity id
val commandsByEntity: MutableMap<Id,MutableList<CommandCandidate>?> = mutableMapOf<Id,MutableList<CommandCandidate>?>()
// map of stateReaders where key is trait id
val stateReadersByTrait: MutableMap<Int,MutableList<StateReaderCandidate>?> = mutableMapOf<Int,MutableList<StateReaderCandidate>?>()
// map of stateReaders where key is DeviceType.Metadata.hashcode()
val stateReadersByDeviceType: MutableMap<Int,MutableList<StateReaderCandidate>?> = mutableMapOf<Int,MutableList<StateReaderCandidate>?>()
// map of stateReaders where key is entity id
val stateReadersByEntity: MutableMap<Int,MutableList<StateReaderCandidate>?> = mutableMapOf<Int,MutableList<StateReaderCandidate>?>()
// map of starters where key is trait id
val startersByTrait: MutableMap<Int,MutableList<StarterCandidate>?> = mutableMapOf<Int,MutableList<StarterCandidate>?>()
// map of starters where key is DeviceType.Metadata.hashcode()
val startersByDeviceType: MutableMap<Int,MutableList<CommandCandidate>?> = mutableMapOf<Int,MutableList<CommandCandidate>?>()
// map of starters where key is entity id
val startersByEntity: MutableMap<Int,MutableList<StarterCandidate>?> = mutableMapOf<Int,MutableList<StarterCandidate>?>()
// populate candidate maps
structure.allCandidates().first().map {
when (it) {
is CommandCandidate -> {
// update commandsByTrait
// TODO refactor into generic function to eliminate duplicate code
var commandsByTraitList: MutableList<CommandCandidate>? = commandsByTrait.get(it.trait.factory.traitId: String)
if(commandsByTraitList == null) { commandsByTraitList = arrayListOf<CommandCandidate>() }
commandsByTraitList.add(it)
commandsByTrait.put(it.trait.factory.traitId, commandsByTraitList)
// update commandsByDeviceType
// TODO refactor into generic function to eliminate duplicate code
for (t in it.types) {
//TODO filter out device types not present in the home
//TODO how to get a reference to device type id?
var commandsByDeviceTypeList: MutableList<CommandCandidate>? = commandsByDeviceType.get(t.factory.typeId: Id)
if (commandsByDeviceTypeList == null) { commandsByDeviceTypeList = arrayListOf<CommandCandidate>() }
commandsByDeviceTypeList.add(it)
commandsByDeviceType.put(t.factory.typeId, commandsByDeviceTypeList)
}
// update commandsByEntity
// TODO refactor into generic function to eliminate duplicate code
var commandsByEntityList: MutableList<CommandCandidate>? = commandsByEntity.get(it.entity.id: Id)
if ( commandsByEntityList == null ) {commandsByEntityList = arrayListOf<CommandCandidate>()}
commandsByEntityList.add(it)
commandsByEntity.put(it.entity.id, commandsByEntityList)
}
/*
is StateReaderCandidate -> {
// update stateReadersByTrait
var stateReadersList: MutableList<StateReaderCandidate>? = stateReadersByTrait.get(it.trait.factory.traitId)
if(stateReadersList == null) { stateReadersList = arrayListOf<StateReaderCandidate>() }
stateReadersList.add(it)
stateReadersByTrait.put(it.trait.factory.traitId, stateReadersList)
// update stateReadersByDeviceType
for (t in it.types) {
//TODO filter out device types not present in the home
var stateReadersList: MutableList<StateReaderCandidate>? = stateReadersByDeviceType.get(t.metadata.hashcode())
if (stateReadersList == null) { stateReadersList = arrayListOf<StateReaderCandidate>() }
stateReadersList.put(it)
stateReadersByDeviceType.put(t.metadata.hashCode(),deviceTypeStateReaderList)
}
// update stateReadersByEntity
MutableList<StateReaderCandidate> entityStateReaderList? = stateReadersByEntity.get(it.entity.id)
if entityStateReaderList is null {entityStateReaderList = arrayListOf<StateReaderCandidate>()}
entityStateReaderList.add(it)
stateReadersByEntity.put(it.entity.id, entityStateReaderList)
}
*/
/*
is StarterCandidate -> {
// update startersByTrait
var startersList: MutableList<StateReaderCandidate>? = startersByTrait.get(it.trait.factory.traitId)
if(startersList == null) { startersList = arrayListOf<StateReaderCandidate>() }
startersList.add(it)
startersByTrait.put(it.trait.factory.traitId, startersList)
// update startersByDeviceType
for (t in it.types) {
//TODO filter out device types not present in the home
var startersList: MutableList<StateReaderCandidate>? = startersByDeviceType.get(t.metadata.hashcode())
if (startersList == null) { startersList = arrayListOf<StateReaderCandidate>() }
startersList.put(it)
startersByDeviceType.put(t.metadata.hashCode(),deviceTypeStateReaderList)
}
// update startersByEntity
MutableList<StateReaderCandidate> entityStateReaderList? = startersByEntity.get(it.entity.id)
if entityStateReaderList is null {entityStateReaderList = arrayListOf<StateReaderCandidate>()}
entityStateReaderList.add(it)
stateReadersByEntity.put(it.entity.id, entityStateReaderList)
}
*/
else -> println("unknown type encountered: " + it)
}
}
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 comando iniziale manuale, l'esecuzione inizia dal nodo successivo al primo nodo iniziale.
Se l'operazione execute()
non va a buon fine, 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 va a buon fine, 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 i dati sul
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()
Ottenere un'automazione per ID
Per ottenere un'automazione tramite l'ID automazione, chiama il metodo
automations()
sulla struttura e esegui la corrispondenza per 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")
]))
Recuperare un'automazione per nome
Il metodo filter()
in Kotlin può essere utilizzato per perfezionare ulteriormente le chiamate API. Per ottenere un'automazione
per nome, ottieni 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') }
Ottenere tutte le automazioni per un dispositivo
Per ottenere tutte le automazioni che fanno riferimento a un determinato dispositivo, utilizza i filtri nidificati per eseguire la scansione del 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
}
}
}
Aggiornare 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 grafo di automazione, ma non la modifica del grafo per nodo. La modifica a livello di 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.
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 errore HomeException
. Consulta la sezione Gestione degli errori.
Impatto dell'eliminazione del dispositivo 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
o inviare comandi. Ad esempio, se un utente elimina un
OccupancySensorDevice
dalla propria casa e un'automazione ha un comando iniziale che dipende dalOccupancySensorDevice
, questo comando iniziale non può più attivare l'automazione.