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ż ich punkt wejścia jest w ramach struktury, przed ich użyciem należy najpierw przyznać uprawnienia dla tej 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 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) Usuwanie instancji automatyzacji według identyfikatora

Utwórz automatyzację

Po utworzeniu instancji Home i otrzymaniu 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ą Automation DSL. W interfejsach API Home automatyzacja jest reprezentowana przez interfejs Automation. Ten interfejs zawiera zestaw właściwości:

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

Domyślnie automationGraph 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 uruchamiający, warunek lub działanie.

Przypisz automatyzacji nazwę namedescription.

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

Interfejs DraftAutomation służy do tworzenia i wybierania automatyzacji, a interfejs Automation – do ich pobierania. Oto na przykład automatyzacja DSL, 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 prześlij 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ę komunikat ostrzeżenia lub błędu z informacjami o problemie. Więcej informacji znajdziesz w dokumentacji ValidationIssueType.

Przykłady kodu

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

Proste automatyzacje

Automatyzacja, która podnosi żaluzje 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żona automatyzacja

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

dynamicznie wybierać urządzenia za pomocą filtrów elementów,

Podczas tworzenia automatyzacji nie musisz ograniczać się do określonych urządzeń. Funkcja filtrów elementów umożliwia automatyzacji wybieranie urządzeń w czasie wykonywania na podstawie różnych kryteriów.

Za pomocą filtrów elementów automatyzacja może na przykład kierować się na:

  • wszystkie urządzenia danego typu.
  • wszystkich urządzeniach w danym pokoju.
  • wszystkie urządzenia danego typu w określonym pokoju
  • wszystkich włączonych urządzeniach.
  • wszystkie włączone urządzenia w danym pokoju

Aby użyć filtrów elementów:

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

Aby na przykład uzyskać stan OnOff wszystkich świateł w strukturze (szczególnie włączonych lub wyłączonych świateł):

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

Aby uzyskać światła w określonym pokoju i użyć ich 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 } }

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

W czasie wykonywania:

Scenariusz Wynik
Żadne urządzenia nie spełniają kryteriów w urządzeniu początkowym. Automatyzacja się nie uruchamia.
Żadne urządzenia nie spełniają kryteriów działania. Automatyzacja się rozpoczyna, ale działanie nie powoduje żadnego efektu.
Żadne urządzenie nie spełnia kryteriów w czytniku stanu. Automatyzacja się uruchamia, ale nic nie robi.

Ten przykład to automatyzacja, która wyłącza wszystkie światła oprócz światła w przedpokoju, gdy wyłączysz światło w pomieszczeniu.

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

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 wyjątek HomeException. Zobacz obsługę błędów.

Zatrzymywanie automatyzacji

Zatrzymaj uruchomioną automatyzację, korzystając z metody stop():


createdAutomation.stop()

Jeśli operacja stop() się nie powiedzie, może zostać wywołana wyjątek 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ę na podstawie identyfikatora automatyzacji, wywołaj metodę automations() w strukturze i dopasuj 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().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 dopracowywania wywołań interfejsu API. Aby uzyskać automatyzację według nazwy, pobierz automatyzacje struktury i przefiltruj je 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.

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ę 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.