Do interfejsów Automation API można uzyskać dostęp za pomocą interfejsów Home API, ale ponieważ ich punkt wejścia znajduje się w ramach struktury, przed ich użyciem należy najpierw przyznać uprawnienia do tej struktury.
Gdy przyznasz uprawnienia do struktury, zaimportuj te pakiety do aplikacji:
import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.Id
import com.google.home.Structure
Struktura zawiera interfejs HasAutomations
z tymi metodami dotyczącymi automatyzacji:
Interfejs API | Opis |
---|---|
automations() |
Lista wszystkich automatyzacji należących do struktury. Zwracane są tylko automatyzacje utworzone za pomocą interfejsów API Home. |
createAutomation(automation) |
Utwórz instancję automatyzacji dla struktury. |
deleteAutomation(automationId) |
Usuń instancję automatyzacji według jej identyfikatora. |
Utwórz automatyzację
Po utworzeniu instancji Home i uzyskaniu uprawnień od użytkownika uzyskaj informacje o strukturze i urządzeniach:
val structure = homeManager.structures().list().single()
val device = homeManager.devices().get(Id("myDevice"))!!
Następnie określ logikę automatyzacji za pomocą języka Automation DSL. W interfejsie interfejsów API Home automatyzacja jest reprezentowana przez interfejs Automation
. Ten interfejs zawiera zestaw właściwości:
- metadane, takie jak nazwa i opis;
- Flagi wskazujące na przykład, czy automatyzacja może zostać wykonana.
- Lista węzłów zawierających logikę automatyzacji, zwana grafem automatyzacji, reprezentowana przez właściwość
automationGraph
.
automationGraph
domyślnie ma typ SequentialFlow
, który jest klasą zawierającą listę węzłów, które są wykonywane w kolejności. Każdy węzeł reprezentuje element automatyzacji, np. element startowy, warunek lub działanie.
Przypisz automatyzacji name
i description
.
Podczas tworzenia automatyzacji flaga isActive
jest domyślnie ustawiona na true
, więc nie trzeba jej ustawiać, chyba że chcesz, aby automatyzacja była początkowo wyłączona. W tym przypadku podczas tworzenia ustaw flagę na false
.
Interfejs DraftAutomation
służy do tworzenia i automatyzacji, a interfejs Automation
– do pobierania. Oto na przykład Automation DSL dla automatyzacji, która włącza urządzenie, gdy włączone jest inne urządzenie:
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()) }
}
}
Po zdefiniowaniu automatyzacji DSL przekaż ją do metody createAutomation()
, aby utworzyć instancję DraftAutomation
:
val createdAutomation = structure.createAutomation(automation)
Tutaj możesz użyć wszystkich innych metod automatyzacji, takich jak execute()
, stop()
i update()
.
Błędy weryfikacji
Jeśli tworzenie automatyzacji nie przejdzie weryfikacji, pojawi się ostrzeżenie lub komunikat o błędzie zawierający informacje o problemie. Więcej informacji znajdziesz w dokumentacji ValidationIssueType
.
Przykłady kodu
Poniżej przedstawiamy przykładowy kod, który można wykorzystać do implementacji części hipotetycznych automatyzacji opisanych na stronie Projektowanie automatyzacji.
Prosta automatyzacja
Automatyzacja, która podnosi rolety o 8:00 rano, może być zaimplementowana w ten sposób:
// 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.
}
Złożone automatyzacje
Automatyzacja, która powoduje miganie świateł po wykryciu ruchu, może być zaimplementowana w ten sposób:
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())}
}
}
}
}
Automatyzacja ogólna
Poniższy kod pokazuje, jak można zbierać dane o domu użytkownika za pomocą interfejsów Structure API, Device API i Discovery API, aby zaimplementować aplikację, która poprowadzi użytkownika przez tworzenie własnych automatyzacji ogólnych (jak opisano w artykule Projektowanie automatyzacji: automatyzacje ogólne).
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)
}
}
Wykonywanie automatyzacji
Uruchom utworzoną automatyzację za pomocą metody execute()
:
createdAutomation.execute()
Jeśli automatyzacja ma element ręcznego uruchamiania, execute()
uruchamia automatyzację od tego punktu, ignorując wszystkie węzły poprzedzające ten element. Jeśli automatyzacja nie ma ręcznego elementu startowego, wykonywanie rozpoczyna się od węzła następującego po pierwszym węźle startowym.
Jeśli operacja execute()
się nie powiedzie, może zostać wywołana funkcja HomeException
. Zobacz obsługę błędów.
Zatrzymywanie automatyzacji
Aby zatrzymać uruchomioną automatyzację, użyj metody stop()
:
createdAutomation.stop()
Jeśli operacja stop()
się nie powiedzie, może zostać wywołana funkcja HomeException
. Zobacz obsługę błędów.
Pobieranie listy automatyzacji dla domu
Automatyzacje są definiowane na poziomie struktury. Zbieraj dane o strukturze automations()
, aby uzyskać dostęp do Flow
automatyzacji:
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"))
}
}
Możesz też przypisać go do lokalnego Collection
:
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()
Pobieranie automatyzacji według identyfikatora
Aby uzyskać automatyzację według identyfikatora automatyzacji, wywołaj metodę automations()
w strukturze i dopasuj według identyfikatora:
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()
Odpowiedź:
// 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")
]))
Pobieranie automatyzacji według nazwy
Metody filter()
w Kotlinie można używać do dalszego doprecyzowania wywołań interfejsu API. Aby uzyskać automatyzację według nazwy, pobierz automatyzacje struktury i odfiltruj według nazwy automatyzacji:
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') }
Pobieranie wszystkich automatyzacji dla urządzenia
Aby uzyskać wszystkie automatyzacje, które odwołują się do danego urządzenia, użyj filtrowania zagnieżdżonego, aby przeskanować automationGraph
każdej automatyzacji:
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
}
}
}
Aktualizowanie automatyzacji
Aby zaktualizować metadane automatyzacji, wywołaj metodę update()
, przekazując jej wyrażenie lambda, które ustawia metadane:
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" }
Metoda update()
umożliwia całkowite zastąpienie automatyzacji, ale nie umożliwia edycji poszczególnych węzłów automatyzacji. Edytowanie poszczególnych węzłów jest podatne na błędy z powodu wzajemnych zależności węzłów. Jeśli chcesz zmienić logikę automatyzacji, wygeneruj nowy graf i całkowicie go zastosuj zamiast dotychczasowego.
Usuwanie automatyzacji
Aby usunąć automatyzację, użyj metody deleteAutomation()
struktury. Automatyzację można usunąć, podając jej identyfikator.
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)
Jeśli nie uda się usunąć pliku, może zostać wywołana funkcja HomeException
. Zobacz obsługę błędów.
Wpływ usuwania urządzeń na automatyzacje
Jeśli użytkownik usunie urządzenie używane w automatyzacji, to urządzenie nie będzie mogło uruchamiać żadnych inicjatorów, a automatyzacja nie będzie mogła odczytywać jego atrybutów ani wydawać mu poleceń. Jeśli na przykład użytkownik usunie OccupancySensorDevice
ze swojego domu, a automatyzacja ma element startowy zależny od OccupancySensorDevice
, ten element nie będzie już mógł aktywować automatyzacji.