Build an automation

The Automation APIs may be accessed through the Home APIs, but since their entry point is through a structure, permission must first be granted on the structure before they can be used.

Once permissions are granted for a structure, import these packages into your app:


import com.google.home.Home
import com.google.home.HomeDevice
import com.google.home.Id
import com.google.home.Structure

A structure contains a HasAutomations interface with the following automation-specific methods:

API Description
automations() List all automations that belong to the structure. Only automations you have created through the Home APIs are returned.
createAutomation(automation) Create an automation instance for a structure.
deleteAutomation(automationId) Delete an automation instance by its ID.

Create an automation

After creating an instance of Home and receiving permissions from the user, get the structure and device(s):

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

Then define the logic of your automation using Automation DSL. In the Home APIs, an automation is represented by the Automation interface. This interface contains a set of properties:

  • Metadata, such as name and description.
  • Flags that indicate, for example, whether or not the automation can be executed.
  • A list of nodes that contain the logic of the automation, called the automation graph, represented by the automationGraph property.

automationGraph, by default, is of the type SequentialFlow, which is a class that contains a list of nodes that execute in sequential order. Each node represents an element of the automation, such as a starter, condition, or action.

Assign the automation a name and description.

Creation of an automation defaults the isActive flag to true, therefore it's not necessary to explicitly set this flag unless you initially want the automation to be disabled. In that scenario, set the flag to false during creation.

The DraftAutomation interface is used for building and creating automations, and the Automation interface is used for retrieval. For example, here's the Automation DSL for an automation that turns a device on when another device is turned on:

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

Once the automation DSL is defined, pass it to the createAutomation() method to create the DraftAutomation instance:

val createdAutomation = structure.createAutomation(automation)

From here, you can use all the other automation methods on the automation, such as execute(), stop(), and update().

Validation errors

If automation creation does not pass validation, a warning or error message provides information about the issue. For more information, refer to the ValidationIssueType reference.

Code examples

Here we present some example code that could be used to implement parts of the hypothetical automations described on the Design an automation page.

Simple automation

An automation that raises the blinds at 8:00am might be implemented like this:

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

Complex automation

An automation that triggers blinking lights when motion is detected might be implemented like this:

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

Generic automation

The following code shows how one could gather data about the user's home using the Structure API, the Device API, and the Discovery API to implement an app that guides the user through creating their own generic automation (as described in Design an automation: Generic automation).

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)

  }
}

Execute an automation

Run a created automation using the execute() method:

createdAutomation.execute()

If the automation has a manual starter, execute() starts the automation from that point, ignoring all nodes that precede the manual starter. If the automation doesn't have a manual starter, execution starts from the node following the first starter node.

If the execute() operation fails, a HomeException may be thrown. See Error handling.

Stop an automation

Stop a running automation using the stop() method:


createdAutomation.stop()

If the stop() operation fails, a HomeException may be thrown. See Error handling.

Get a list of automations for a structure

Automations are defined at the structure level. Collect on the structure's automations() to access a Flow of automations:


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

Alternatively, assign it to a local 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()

Get an automation by ID

To get an automation by automation ID, call the automations() method on the structure, and match on 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()

Response:

// 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")
    ]))

Get an automation by name

The filter() method in Kotlin can be used to further refine API calls. To get an automation by name, get the structure's automations and filter on the automation name:

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

Get all automations for a device

To get all the automations that reference a given device, use nested filtering to scan each automation's automationGraph:

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

Update an automation

To update an automation's metadata, call its update() method, passing it a lambda expression that sets the metadata:

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

The update() method supports fully replacing an automation graph, but not per-node editing of the graph. Per-node editing is error-prone due to node interdependencies. If you want to change the logic of an automation, generate a new graph and fully replace the existing one.

Delete an automation

To delete an automation, use the structure's deleteAutomation() method. An automation must be deleted using its 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)

If the deletion fails, a HomeException may be thrown. See Error handling.

Impact of device deletion on automations

If a user deletes a device that is used in an automation, the deleted device can't trigger any starters, and the automation won't be able to read attributes from it, or issue commands to it. For example, if a user deletes an OccupancySensorDevice from their home, and an automation has a starter that depends on the OccupancySensorDevice, that starter can no longer activate the automation.