Tworzenie automatyzacji na urządzeniu z Androidem

Do interfejsów Automation API można uzyskać dostęp za pomocą interfejsów Home API na Androida, ale ponieważ punkt wejścia do nich jest strukturą, przed ich użyciem należy najpierw przyznać uprawnienia do struktury.

Po przyznaniu uprawnień 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 specyficznymi dla automatyzacji:

Interfejs API Opis
automations() Wyświetl wszystkie automatyzacje należące do struktury. Zwracane są tylko automatyzacje utworzone przez Ciebie za pomocą interfejsów Home API.
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 pobierz strukturę i urządzenia:

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

Następnie zdefiniuj logikę automatyzacji za pomocą języka Automation DSL. W interfejsach Home API automatyzacja jest reprezentowana przez interfejs Automation. Ten interfejs zawiera zestaw właściwości:

  • metadane, takie jak nazwa i opis;
  • Flagi, które wskazują np., czy automatyzację można wykonać.
  • Lista węzłów zawierających logikę automatyzacji, zwana wykresem automatyzacji, reprezentowana przez właściwość automationGraph.

automationGraph jest domyślnie typu SequentialFlow, czyli klasy zawierającej listę węzłów, które są wykonywane w kolejności sekwencyjnej. Każdy węzeł reprezentuje element automatyzacji, np. element uruchamiający, warunek lub działanie.

Przypisz automatyzacji namedescription.

Podczas tworzenia automatyzacji flaga isActive jest domyślnie ustawiana na true, więc nie musisz jej ustawiać, chyba że na początku chcesz, aby automatyzacja była wyłączona. W takim przypadku ustaw flagę na false podczas tworzenia.

Interfejs DraftAutomation służy do tworzenia automatyzacji, a interfejs Automation – do pobierania danych. Oto na przykład język DSL automatyzacji dla automatyzacji, która włącza urządzenie, gdy inne urządzenie jest włączone:

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 DSL automatyzacji przekaż ją do metody createAutomation(), aby utworzyć instancję DraftAutomation:

val createdAutomation = structure.createAutomation(automation)

Możesz teraz używać wszystkich innych metod automatyzacji, takich jak execute(), stop()update().

Błędy weryfikacji

Jeśli tworzenie automatyzacji nie przejdzie weryfikacji, pojawi się komunikat z ostrzeżeniem lub błędem, który będzie zawierał informacje o problemie. Więcej informacji znajdziesz w ValidationIssueType.

Przykłady kodu

Poniżej znajdziesz przykładowy kod, który można wykorzystać do wdrożenia części hipotetycznych automatyzacji opisanych na stronie Projektowanie automatyzacji na Androidzie.

Prosta automatyzacja

Automatyzacja, która podnosi rolety o 8:00 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żona automatyzacja

Automatyzacja, która włącza 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())}
         }
      }
   }
}

Dynamiczne wybieranie urządzeń za pomocą filtrów elementów

Podczas tworzenia automatyzacji nie musisz określać konkretnych urządzeń. Funkcja filtrów encji umożliwia automatyzacji wybieranie urządzeń w czasie działania na podstawie różnych kryteriów.

Na przykład za pomocą filtrów dotyczących podmiotów automatyzacja może kierować reklamy na:

  • wszystkie urządzenia określonego typu,
  • wszystkie urządzenia w danym pomieszczeniu,
  • wszystkie urządzenia określonego typu w danym pomieszczeniu;
  • wszystkie włączone urządzenia,
  • wszystkie urządzenia włączone w danym pomieszczeniu;

Aby użyć filtrów dotyczących podmiotów:

  1. Zadzwoń pod numer atExecutionTime() na Structure lub Room. Zwraca to wartość TypedExpression<TypedEntity<StructureType>>.
  2. W tym obiekcie wywołaj funkcję getDevicesOfType(), przekazując jej DeviceType.

Filtrów encji można używać w urządzeniach początkowych, czytnikach stanu i działaniach.

Jeśli na przykład chcesz, aby dowolne światło włączone lub wyłączone uruchamiało automatyzację z polecenia inicjującego:

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

Aby w czytniku stanu zarejestrować stan OnOff wszystkich świateł w budynku (w szczególności świateł włączonych i wyłączonych):

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

Aby uzyskać dostęp do oświetlenia w określonym pomieszczeniu i użyć go w warunku:

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

W czasie wykonywania:

Scenariusz Wynik
Żadne urządzenia nie spełniają kryteriów urządzenia z początkiem rutyny. Automatyzacja nie jest uruchamiana.
Żadne urządzenia nie spełniają kryteriów w czytniku stanu. Automatyzacja się rozpoczyna, ale nic nie robi.
Żadne urządzenia nie spełniają kryteriów w działaniu. Automatyzacja się rozpoczyna, ale działanie nie przynosi żadnego efektu.

Poniższy przykład to automatyzacja, która wyłącza wszystkie światła z wyjątkiem światła w przedpokoju, gdy wyłączone zostanie dowolne światło:

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

Uruchamianie automatyzacji

Uruchom utworzoną automatyzację za pomocą metody execute():

createdAutomation.execute()

Jeśli automatyzacja ma ręczny element uruchamiający, execute() uruchamia automatyzację od tego momentu, ignorując wszystkie węzły poprzedzające ręczny element uruchamiający. Jeśli automatyzacja nie ma ręcznego wyzwalacza, wykonanie rozpoczyna się od węzła następującego po pierwszym węźle wyzwalacza.

Jeśli operacja execute() się nie powiedzie, może zostać zgłoszony wyjątek 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ć zgłoszony wyjątek HomeException. Zobacz obsługę błędów.

Pobieranie listy automatyzacji dla domu

Automatyzacje są definiowane na poziomie domu. Zbieraj na 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 pobrać automatyzację według identyfikatora, wywołaj metodę automations() w strukturze i dopasuj ją do 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

Metoda filter() w języku Kotlin może służyć do dalszego doprecyzowania wywołań interfejsu API. Aby uzyskać automatyzację według nazwy, pobierz automatyzacje struktury i filtruj 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 jej metodę update() i przekaż 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 pełne zastąpienie wykresu automatyzacji, ale nie edytowanie poszczególnych węzłów wykresu. Edytowanie poszczególnych węzłów jest podatne na błędy ze względu na wzajemne zależności między węzłami. Jeśli chcesz zmienić logikę automatyzacji, wygeneruj nowy wykres i całkowicie zastąp nim dotychczasowy.

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

Usuwanie automatyzacji

Aby usunąć automatyzację, użyj metody deleteAutomation() struktury. Automatyzację należy usunąć za pomocą jej 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().first()
structure.deleteAutomation(automation.id)

Jeśli usunięcie się nie powiedzie, może zostać zgłoszony wyjątek HomeException. Zobacz obsługę błędów.

Wpływ usunięcia urządzenia na automatyzacje

Jeśli użytkownik usunie urządzenie używane w automatyzacji, nie będzie ono mogło wywoływać żadnych elementów rozpoczynających, a automatyzacja nie będzie mogła odczytywać jego atrybutów ani wydawać mu poleceń. Jeśli na przykład użytkownik usunie OccupancySensorDevice z domu, a automatyzacja ma starter, który zależy od OccupancySensorDevice, ten starter nie będzie już mógł aktywować automatyzacji.