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 the GoogleHomeSDK
and
GoogleHomeTypes
modules into your app:
import GoogleHomeSDK
import GoogleHomeTypes
GoogleHomeSDK
contains APIs for Commissioning, Device & Structure, and
Automations. GoogleHomeTypes
contains APIs for all traits and device types.
A structure conforms to the AutomationManager
protocol with the following
automation-specific methods:
API | Description |
---|---|
listAutomations() |
List all automations that belong to the structure. Only automations you have created through the Home APIs are returned. |
createAutomation(_:) |
Create an automation instance for a structure. |
deleteAutomation(id:) |
Delete an automation instance by its ID. |
deleteAutomation(_:) |
Delete an automation object. |
Create an automation
After creating an instance of Home and receiving permissions from the user, get the structure and device(s):
let structure = try await self.home.structures().list().first()
let homeDevices = try await self.home.devices().list()
Then define the logic of your automation using Automation DSL for iOS. 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 typealias
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:
typealias OnOffTrait = Matter.OnOffTrait
let draftAutomation = automation(
name: "MyFirstAutomation",
description: "Turn on a device when another device is turned on."
) {
let onOffStarter = starter(light1, OnOffLightDeviceType.self, OnOffTrait.self)
onOffStarter
condition {
onOffStarter.onOff.equals(true)
}
action(light2, OnOffLightDeviceType.self) {
OnOffTrait.on()
}
}
Once the automation is defined, pass it to the
createAutomation(_:)
method to create the Automation
instance:
self.myAutomation = try await structure.createAutomation(draftAutomation)
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 example
Here we present some example code that could be used to implement parts of the hypothetical automations described on the Design an automation on iOS page.
Simple automation
An automation that raises the blinds at 8:00am might be implemented like this:
private func createAutomationThatRaisesBlinds(structure: Structure) async throws -> String {
// Get all candidates in the structure.
let allCandidates = try await structure.candidates().list()
// Determine whether a scheduled automation can be constructed.
guard
allCandidates
.compactMap({ $0.node as? EventCandidate })
.contains(where: {
$0.event is Google.TimeTrait.ScheduledEvent.Type
&& $0.unsupportedReasons.isEmpty
})
else {
// Cannot create automation. Set up your address on the structure, then try again.
return ""
}
let blinds =
allCandidates
.compactMap({ $0.node as? CommandCandidate })
.filter {
$0.command is Matter.WindowCoveringTrait.UpOrOpenCommand.Type
&& $0.unsupportedReasons.isEmpty
}.compactMap { $0.homeObject as? HomeDevice }.filter {
$0.types.contains(WindowCoveringDeviceType.self)
}
guard blinds.count > 0 else {
// You don't have any WindowCoveringDevices. Try again after adding some blinds to your
// structure.
return ""
}
let draftAutomation = automation(
name: "Day time open blinds",
description: "Open all blinds at 8AM everyday"
) {
starter(structure, Google.TimeTrait.ScheduledEvent.self) {
Google.TimeTrait.ScheduledEvent.clockTime(.init(hours: 8, minutes: 0))
}
// ...open all blinds
parallel {
for blind in blinds {
action(blind, WindowCoveringDeviceType.self) {
Matter.WindowCoveringTrait.upOrOpen()
}
}
}
}
let automation = try await structure.createAutomation(draftAutomation)
return automation.id
}
Complex automation
An automation that triggers blinking lights when motion is detected might be implemented like this:
private func createAutomationThatTriggersBlinkingLightsWhenMotionIsDetected(structure: Structure)
async throws -> String
{
// Get all candidates in the structure.
let allCandidates = try await structure.candidates().list()
// Get the lights present in the structure.
let availableLights =
allCandidates
.compactMap({ $0.node as? CommandCandidate })
.compactMap({ $0.homeObject as? HomeDevice })
.filter {
$0.types.contains(OnOffLightDeviceType.self)
|| $0.types.contains(DimmableLightDeviceType.self)
|| $0.types.contains(ColorTemperatureLightDeviceType.self)
|| $0.types.contains(ExtendedColorLightDeviceType.self)
}
let selectedLights = // user selects one or more lights from availableLights
let draftAutomation = automation {
// If the presence state changes...
let areaPresenceStateStarter = starter(structure, Google.AreaPresenceStateTrait.self)
areaPresenceStateStarter
// ...and if the area is occupied...
condition { areaPresenceStateStarter.presenceState.equals(.presenceStateOccupied) }
// "blink" the light(s)
parallel {
for light in selectedLights {
action(light, OnOffLightDeviceType.self) { Matter.OnOffTrait.toggle() }
delay(for: .seconds(1))
action(light, OnOffLightDeviceType.self) { Matter.OnOffTrait.toggle() }
delay(for: .seconds(1))
action(light, OnOffLightDeviceType.self) { Matter.OnOffTrait.toggle() }
delay(for: .seconds(1))
action(light, OnOffLightDeviceType.self) { Matter.OnOffTrait.toggle() }
}
}
}
let automation = try await structure.createAutomation(draftAutomation)
return automation.id
}
Generic automation
The following code shows how one could gather data about the user's home using the Structure API for iOS, the Device API for iOS, and the Discovery API for iOS to implement an app that guides the user through creating their own generic automation (as described in Design an automation: Generic automation).
// Get a snapshot of the structure.
guard
let structure = try await self.home.structures().list().first(where: { $0.id == structureID })
else {
return
}
// Dictionary of devices where key is the device ID.
let devicesByID: [String: HomeDevice] = try await self.home.devices().list().reduce(into: [:]) {
$0[$1.id] = $1
}
// Dictionary of rooms where key is the room name.
let roomsByName: [String: Room] = try await self.home.rooms().list().reduce(into: [:]) {
$0[$1.name] = $1
}
// Dictionary of action candidates where key is associated trait ID.
var actionsByTrait: [String: [any ActionCandidate]] = [:]
// Dictionary of action candidates where key is device type ID
var actionsByDeviceType: [String: [any ActionCandidate]] = [:]
// Dictionary of action candidates where key is the entity ID.
var actionsByEntity: [String: [any ActionCandidate]] = [:]
// Dictionary of starter candidates where key is the trait ID.
var startersByTrait: [String: [any StarterCandidate]] = [:]
// Dictionary of starter candidates where key is the device type ID.
var startersByDeviceType: [String: [any StarterCandidate]] = [:]
// Dictionary of starter candidates where key is the entity ID.
var startersByEntity: [String: [any StarterCandidate]] = [:]
// Dictionary of state reader candidates where key is the trait ID.
var stateReadersByTrait: [String: [any StateReaderCandidate]] = [:]
// Dictionary of state reader candidates where key is the device type ID.
var stateReadersByDeviceType: [String: [any StateReaderCandidate]] = [:]
// Dictionary of state reader candidates where key is the entity ID.
var stateReadersByEntity: [String: [any StateReaderCandidate]] = [:]
let allCandidates = try await structure.candidates().list()
for candidate in allCandidates {
if let actionCandidate = candidate.node as? any ActionCandidate {
actionsByTrait[actionCandidate.trait.identifier, default: []].append(actionCandidate)
for deviceType in actionCandidate.deviceTypes {
actionsByDeviceType[deviceType.identifier, default: []].append(actionCandidate)
}
actionsByEntity[actionCandidate.homeObject.id, default: []].append(actionCandidate)
}
if let starterCandidate = candidate.node as? any StarterCandidate {
startersByTrait[starterCandidate.trait.identifier, default: []].append(starterCandidate)
for deviceType in starterCandidate.deviceTypes {
startersByDeviceType[deviceType.identifier, default: []].append(starterCandidate)
}
startersByEntity[starterCandidate.homeObject.id, default: []].append(starterCandidate)
}
if let stateReaderCandidate = candidate.node as? any StateReaderCandidate {
stateReadersByTrait[stateReaderCandidate.trait.identifier, default: []].append(
stateReaderCandidate)
for deviceType in stateReaderCandidate.deviceTypes {
stateReadersByDeviceType[deviceType.identifier, default: []].append(stateReaderCandidate)
}
stateReadersByEntity[stateReaderCandidate.homeObject.id, default: []].append(
stateReaderCandidate)
}
}
}
Execute an automation
Run a created automation using the
execute()
method:
try await self.myAutomation.execute()
If the automation has a
ManualStarter
,
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 HomeError
may be thrown.
Stop an automation
Stop a running automation using the stop()
method:
try await self.myAutomation.stop()
If the stop()
operation fails, a HomeError
may be thrown.
Get a list of automations for a structure
Automations are defined at the structure level. Call
listAutomations()
on the structure to access an array of automations:
let automations = try await structure.listAutomations()
Get an automation by ID
To get an automation by automation ID, call the
listAutomations()
method and match on ID:
guard let structure = try await home.structures().list().first() else {
return nil
}
return try await structure.listAutomations().first { $0.id == "automation-id" }
Response:
// Here's how the automation looks like in the get response.
Automation(id: "c4b0f847...",
structure: structure@test-structure,
name: "automation-name",
description: "automation-description",
isActive: true,
automationGraph: SequentialFlow(nodes: [
Starter(
entity: "device@test-device",
deviceType: "home.matter.0000.types",
trait: "OnOff@6789..."),
Action(
entity: "device@test-device",
deviceType: "home.matter.0000.types.0101",
trait: "OnOff@8765...",
command: "on")
]))
Get an automation by name
To get an automation by name, get the structure's automations and filter on the automation name:
guard let structure = try await home.structures().list().first() else {
return nil
}
return try await structure.listAutomations().first { $0.name == "automation-name" }
Update an automation
To update an automation's metadata, call its
update(_:)
method, passing it a lambda expression that sets the metadata:
let myAutomation = try await structure.listAutomations().first {
$0.id == "automation-id"
}
try await myAutomation.update {
$0.name = "new-automation-name"
$0.description = "new-automation-description"
}
The update(_:)
method is strictly for updating automation metadata, not for manipulating the
automation graph. Directly modifying the automation graph is neither practical,
recommended, nor supported, because of the interdependencies between the nodes.
If you want to change the logic of an automation, regenerate the entire
automation graph.
Delete an automation
To delete an automation, use the
deleteAutomation()
method. An automation must be deleted directly:
let myAutomation = try await structure.listAutomations().first {
$0.id == "automation-id"
}
try await structure.deleteAutomation(myAutomation)
An automation can also be deleted by ID:
try await structure.deleteAutomation(id: myAutomation.id)
If the deletion fails, a HomeError
may be thrown.
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
OccupancySensorDeviceType
from their home, and an automation has a starter that depends on
the OccupancySensorDeviceType
, that starter can no longer activate the automation.