Use the following guide to understand how various Automation DSL nodes may be used to construct an automation.
All automation DSL is placed within a single automation
node. The
automation
node forms the boundary between the outer Swift language
context and the embedded DSL context.
Sequential flow
The sequential flow is the default type of automation flow.
Here's a very basic Automation DSL template that uses a sequential flow consisting of a starter, a condition, and an action:
import GoogleHomeSDK
import GoogleHomeTypes
automation (
...
) {
starter(...)
condition {...}
action {...}
}
This can be refined by adding additional nodes.
Starter
Starter nodes define the initial circumstances that activate an automation. For
example, a change in state or value. An automation must have at least one
starter, otherwise it will fail validation. In order to add more than one
starter to an automation, you must use a select
node.
Starter based on trait attribute
When declaring a starter node that's based on a trait attribute, specify:
- the device
- the device type to which the trait belongs
- the trait
starter(
thermostat,
Matter.TemperatureSensorDeviceType.self,
Matter.TemperatureMeasurementTrait.self
)
The device type parameter is required because it lets you specify which device
type within a device the automation addresses. For example, a device might be
composed of a
FanDeviceType
and a
HeatingCoolingUnitDeviceType
,
both of which contain the
OnOffTrait
trait. By specifying the device type, there is no ambiguity about which part of
the device triggers the automation.
Starter based on event
When declaring a starter node that's based on an event, specify:
- the device
- the device type to which the trait belongs
- the event
starter(
doorbell,
Google.GoogleDoorbellDeviceType.self,
Google.DoorbellPressTrait.DoorbellPressedEvent
)
Starter based on a structure and event, with parameters
Some events can have parameters, so these parameters also need to be included in the starter.
For example, this starter uses the
TimeTrait
's ScheduledEvent
to activate the automation at 7:00am:
typealias TimeTrait = Google.TimeTrait
let earlyMorning = starter(
structure,
TimeTrait.ScheduledEvent.self
) {
TimeTrait.ScheduledEvent.clockTime(TimeOfDay(hours: 7, minutes: 0))
}
Manual starter
A manual starter is a special type of starter that lets the user manually run the automation.
When declaring a manual starter:
- Don't specify a trait or device type.
- Provide a UI element that calls
Automation.execute()
.
When placing a manual starter in a select
flow along with another starter, the
manual starter overrides the other starter:
select {
manualStarter()
starter(
thermostat,
Matter.TemperatureSensorDeviceType.self,
Matter.TemperatureMeasurementTrait.self
)
}
Note that any condition
nodes following a manual starter will be evaluated,
and could block execution of the automation, depending on the condition
expression.
One way to structure your automation so that condition
nodes don't block an
automation that was activated with a manual starter is to put the other starter
in a separate sequential flow along with its condition
:
import GoogleHomeSDK
import GoogleHomeTypes
automation (
...
) {
select {
sequential {
starter(...)
condition {...}
}
sequential {
manualStarter()
}
}
action {...}
}
Reference the value of an attribute
To use the value of an attribute in an expression, use the following syntax.
With a stateReader
:
typealias TimeTrait = Google.TimeTrait
let time = stateReader(structure, TimeTrait.self)
time
let currTime = time.currentTime
With a starter
:
typealias LaundryWasherDeviceType = Matter.LaundryWasherDeviceType
typealias OnOffTrait = Google.OnOffTrait
let starterNode = starter(device1, LaundryWasherDeviceType.self, OnOffTrait.self)
starterNode
condition {
starterNode.onOff.equals(true)
}
Condition nodes and expressions
A condition node represents a decision point that determines whether the
automation continues or not. An automation can have multiple condition
nodes.
If any condition
node's expression evaluates to false
, execution of the
entire automation ends.
Within a condition
node, you can combine multiple condition criteria using
various operators, as long as the
expression evaluates to a single boolean value. If the resulting value is
true
, the condition is met and the automation continues execution of the
next node. If it's false
, the automation stops executing at that point.
Expressions are formed similarly to expressions in Swift, and may contain primitive values such as numbers, characters, strings, and booleans, as well as Enum values. Grouping sub expressions with parentheses lets you control the order in which they're evaluated.
Here's an example of a condition
which combines multiple subexpressions into a
single expression:
condition {
let exp1 = starterNode.lockState.equals(.unlocked)
let exp2 = stateReaderNode.lockState.equals(true)
let exp3 = occupancySensingDevice.occupied.notEquals(0)
(exp1.and(exp2)).or(exp3)
}
You may reference the value of a trait accessed through a starter:
typealias OnOffTrait = Matter.OnOffTrait
let starterNode = starter(device, OnOffTrait.self)
starterNode
condition {
starterNode.onOff.equals(true)
}
val starterNode = starter<_>(device, OnOff)
condition() { expression = starterNode.onOff equals true }
stateReader
The other way to reference trait attribute values in a condition
node is with
a stateReader
node.
To do this, first capture the trait attribute value in a stateReader
node. A
stateReader
takes the structure
and the trait as arguments:
typealias ActivatedCarbonFilterMonitoringTrait = Matter.ActivatedCarbonFilterMonitoringTrait
let filterMonitoringState = stateReader(structure, ActivatedCarbonFilterMonitoringTrait.self)
Then reference the stateReader
in the condition
node:
condition {
filterMonitoringState.changeIndication.equals(.warning)
}
Using comparison
and
logical operators,
multiple stateReaders
may be used in a condition
node:
typealias ArmDisarm = Google.ArmDisarmTrait
typealias DoorLockDevice = Matter.DoorLockDeviceType
typealias DoorLock = Matter.DoorLockTrait
let armState = stateReader(doorLock, DoorLockDevice.self, ArmDisarm )
let doorLockState = stateReader(doorLock, DoorLockDevice.self, DoorLock)
armState
doorLockState
condition {
let exp1 = armState.armState
let exp2 = doorLockState.lockState
exp1.and(exp2)
}
Condition duration
In addition to a boolean expression in a condition, you can specify a timeframe during which the expression must be true in order to run the automation. For example, you can define a condition that fires only if a light has been on for ten minutes.
condition(for: .seconds(600)) {
lightStateReader.onOff.equals(true)
}
The duration can range from one to 30 minutes.
Action nodes
The action node is where the work of the automation takes place.
In this example, the action invokes the
AssistantBroadcastTrait
's
broadcast()
command:
action(speaker, SpeakerDeviceType.self) {
Google.AssistantBroadcastTrait.broadcast(msg: "Oven Cycle Complete")
}