iOS DSL concepts

This is an overview of the fundamental concepts of the Automation DSL iOS.

Automation components

An automation consists of the following basic components, typically evaluated in this order:

  1. Starter — Defines the initial conditions that activate the automation, such as a change to a trait. An automation must have a starter.
  2. Condition — Any additional constraints to evaluate after an automation has been activated. The expression in a Condition must evaluate to true in order for the actions of an automation to proceed.
  3. Action — Commands or state updates that are performed when all conditions have been met.

For example, perhaps you have an automation that dims the lights in a room when the TV in that room is turned on between sunset and sunrise. In this example:

  1. Starter — The TV was turned on, which is a change in state on a TV trait.
  2. Condition— The current time for the home the TV is in is evaluated.
  3. Action — The lights in the same room as the TV are dimmed.

The automation would be activated when the TV in the room is turned on, but the automation only executes if the condition of "time is between sunset and sunrise" is met.

In addition to the basic structure, automations in the Home APIs for iOS also contain metadata, such as name and description, which can be used to identify them for developers and users.

Nodes

In the Home APIs for iOS, the logical structure of an automation consists of nodes. Nodes are abstract, reusable units that represent entity behaviors or execution flows. Each node can have input variables, as well as output variables that may be consumed by other nodes.

Table: Types of automation nodes
Node Node Type Swift implementation Description
Starter Behavioral Starter Starts an automation when a trait's state (any attribute) changes.
StateReader Behavioral StateReader Reads a trait attribute and lets you to capture its value for use in condition nodes.
Action Behavioral Action Invokes trait command(s).
Sequential Execution flow SequentialFlow Executes nested action nodes in sequence. This is the default execution behavior.
Parallel Execution flow ParallelFlow Executes nested action nodes in parallel.
Condition Execution flow Condition Conditionally change execution flow based on evaluations of logical expressions. Conditions may be associated with a starter (starter specific conditions) or be global (apply to all starters).
Select Execution flow SelectFlow Allows more than one starter to activate an automation.
Expression Value Expression May be the value of a trait's attribute, a constant, or a literal value, and must evaluate to a list, number, boolean, or string.

Behavioral nodes

Nodes such as starters and actions are behavioral nodes. Starters activate an automation based on device attribute changes. Actions issue device commands or update attributes.

Behavioral nodes are typically tied to device traits and output trait state for use as input in other nodes.

Execution flow nodes

Some nodes represent execution flows, such as sequential and parallel. Each of these nodes contain the behavioral nodes that define the automation.

For example, a sequential flow may contain nodes that execute in sequential order. Typically, these would be starter, condition, and action.

Sequential execution flows
Figure 1: Sequential automation flow

A parallel flow may have multiple action nodes executing at the same time, such as turning on multiple lights at the same time. Nodes following a parallel flow won't execute until all branches of the parallel flow finish.

Parallel execution flows
Figure 2: Parallel automation flow

Another type of execution flow is a condition flow, which can change the execution flow based on the evaluation of an expression.

For example, perhaps you have an automation that performs an action based on whether it is nighttime. A condition node checks the time of day, then follows the appropriate execution path based on that evaluation.

Condition flow
Figure 3: Condition flow

A select flow is useful when you want to have more than one starter that can activate your automation. When you enclose two or more starters in a select flow, any one of the starters can activate the automation.

For example, you could write an automation that lowers the blinds at sunset, if the temperature rises above a certain threshold, or if the brightness exceeds a threshold. Three separate starters handle each of these scenarios, and all three would be wrapped in a select flow.

Select flow
Figure 4: Select flow

Nested flows

In complex automations, execution flow nodes can also be nested. For example, you may have a sequential flow that executes a parallel flow.

Nested execution flows
Figure 5: Nested execution flows

DSL nodes can be nested and combined in various ways to meet your specific needs, according to constraints outlined in the following table.

Table: How nodes may be combined
Node May contain the following node type and data Must be within one of the following node types
Starter Expression Select, Sequential
ManualStarter Select, Sequential
StateReader Expression (typically consisting of a trait attribute value) Action, Condition
Action Command, Entity, Expression Parallel, Select, Sequential
Sequential Parallel, Select, Sequential
Parallel Action Sequential
Condition Expression Parallel, Sequential
Select Condition, Sequential, Starter, ManualStarter Sequential, and must be the first node in the flow

Automation DSL for iOS

In the Home APIs for iOS, automations are defined using the Automation DSL (Domain-Specific Language). The Automation DSL for iOS is specifically designed for defining automation templates and is implemented using type-safe builders, some of which leverage Swift Result Builders for building such flows and parameters. For example, FlowBuilder is used to build a list of nodes for a flow and ParameterBuilder to build parameters.

The Automation DSL for iOS simplifies and streamlines the process of building automations. It natively uses the same data model of Matter standard traits and smart home traits featured in the Device API.

The Automation DSL for iOS also defines the logic of an automation in terms of abstract device types, as opposed to specific device instances located in a user's home. It allows the developer to provide input parameters that may be used at runtime to specify actual device instances, as well as other important parameter values.

Example

The following is an example automation that turns on a device, written using the Automation DSL for iOS:

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

This automation is very basic: when light1, a light, turns on (the onOff attribute changes to true), then sends the on() command to turn light2 on.

An automation by default uses a sequential node as the first node, which indicates that its child nodes will run in sequential order. This node type does not need to be specified for the first node.

Within the sequential node are behavioral nodes such as starter, condition, and action. The output of the starter node is assigned to a variable for use in the condition node.

Void assignment statements

Note that for flow result builders in the Automation DSL for iOS, statements with a void result are ignored by the FlowBuilder. Since an assignment statement is void, a separate statement of the assignment result is needed to add it to the flow.

This is seen in the earlier example:

{
  // Assignment not auto-added to the flow
  let onOffStarter = starter(light1, OnOffLightDeviceType.self, OnOffTrait.self)
  // Required to add onOffStarter to the flow
  onOffStarter

  // Auto-added to the flow
  condition {
    onOffStarter.onOff.equals(true)
  }

  // Auto-added to the flow
  action(light2, OnOffLightDeviceType.self) {
    OnOffTrait.on()
  }
}

This pattern is required when building automations in Swift using the Automation DSL.