YAML in automations and scripts
I see many people struggle with the formatting for automations (and scripts) because the official docs are structured in a manner that assumes you’re going to read them in order. So, I thought I’d pull together a little primer. If you’re a YAML purist you probably want to look away now, I’m aiming to make this accessible not 100% technically correct.
YAML (very) basics
YAML largely consists of two things, dictionaries and lists. Dictionaries are the really important piece here, because they hold everything (ultimately). For example:
homeassistant:
Yup, that’s a dictionary. In this case it’s empty, but it’s a dictionary. Dictionaries can hold other dictionaries, or lists. What’s a list? Here’s a list example from the documentation:
whitelist_external_dirs:
- /usr/var/dumping-ground
- /tmp
That’s a list, with two entries (values). Each entry is marked by the hyphen (-
).
You can also separate list items with commas, like this, but I find that it’s often less readable:
whitelist_external_dirs: [ /usr/var/dumping-ground, /tmp ]
The question that always comes up is when you use a dictionary and when you use a list. The unfortunate answer is that you have to check the documentation. Even more unfortunately, a single entry list doesn’t need that - so you’ll sometimes see a list written like:
whitelist_external_dirs:
/usr/var/dumping-ground
That is perfectly valid, but you can see why it causes confusion.
Here’s a little more from that page:
homeassistant:
latitude: 32.87336
longitude: 117.22743
whitelist_external_dirs:
- /usr/var/dumping-ground
- /tmp
That’s a dictionary (homeassistant
) holding:
- Two dictionaries (
latitude
andlongitude
) - One list (
whitelist_external_dirs
)
These are the basic building blocks. If you want more on the subject see this blog post.
Spaces
With YAML, spaces (indenting) are critical. You need to keep your indenting consistent (the advice is generally to use two spaces per level. Oh, and don’t use tabs, unless your editor converts them to spaces. Tabs cause Home Assistant to spit out errors too. Here’s an example with broken indenting that I’ve seen people try to use:
automation:
- alias: "My test automation"
id: "unique_value_42"
initial_state: "on"
trigger:
- platform: state
entity_id:
- switch.furnace
- switch.extractor
Here is how it should look:
automation:
- alias: "My test automation"
id: "unique_value_42"
initial_state: "on"
trigger:
- platform: state
entity_id:
- switch.furnace
- switch.extractor
Running a configuration check will warn you when the YAML is invalid, and it’s something you should do any time you write any YAML.
Quoting
You don’t have to quote things in YAML, but for strings (text) you should or the result may be unexpected. What do I mean? Well "On"
is a string, but On
is a boolean value. These things to humans look the same, but to the computer they’re different. Similarly if you have a number, like 42
, that’s a number, but if you put it in quotes, like "42"
, then that’s a string.
When you need to use multiple quotes on a line, then you need to use different ones for the outer quotes than the inner quotes, like:
automation:
- alias: "Tom's alarm is ringing"
This becomes particularly important if you’re using templates.
Case sensitivity
You also need to keep in mind that computers don’t treat capital letters the same as lower case letters. The string "home"
is different to the computer from "Home"
, so you need to be sure which it is you’re looking for.
States
The state you see in Home Assistant’s user interface may not be the actual state. You need to look at the entity in Developer Tools and then States to see the actual state of the entity. For example, a door/window sensor will show as Open or Closed in the user interface, but is actually 'on'
and 'off'
.
Automation Structure
An automation has four parts
- Information about the automation
- Trigger
- Condition (optional)
- Action
About the automation
There are up to three things you’ll have here.
- The first is the alias, the human readable name. This will typically be used to form the entity_id of the automation.
- Optionally there’s an id. This is used by the automation editor and if present is used to form the entity_id (instead of the alias).
- Finally there’s an optional initial_state. This is used to force the automation to be enabled or disabled at startup.
automation:
- alias: "My test automation"
id: "unique_value_42"
initial_state: "on"
The automation is a list entry, in the automation dictionary.
Trigger
This is the bit that kicks it all off, your list of triggers. When any trigger statement becomes true then the processing begins. You have to have at least one trigger, and you can have as many as you need. It’s worth taking the time now to read the triggers page, and see what’s possible (and what causes a trigger to become true). Here’s a simple one:
trigger:
- platform: state
entity_id: device_tracker.tinkerer
to: 'home'
I’ve used the list indicator here, even though I only have one trigger. This is to make it clearer that triggers are a list. This will trigger when device_tracker.tinkerer
becomes 'home'
. Here is a more complex one:
trigger:
- platform: state
entity_id: device_tracker.tinkerer
to: 'home'
- platform: state
entity_id: device_tracker.better_half
to: 'home'
Two triggers - here the automation will start processing if either device_tracker.tinkerer
or device_tracker.better_half
become 'home'
. Since both are looking for the same target state, I could have written the more compact version:
trigger:
- platform: state
entity_id:
- device_tracker.tinkerer
- device_tracker.better_half
to: 'home'
I prefer this, partly because it’s more compact, but also because it’s much easier to read.
Conditions
Conditions are optional - you don’t need any if your automation doesn’t call for one.
All the conditions in the list have to be currently true for the automation to run. There are many available conditions, including some that allow you to say that this or that must be true. Conditions are particularly useful when you have multiple triggers, and want them all to be true for the automation to run. With conditions however you have to list each separately, you can’t combine them like we did for triggers:
condition:
- condition: state
entity_id: device_tracker.tinkerer
state: 'home'
- condition: state
entity_id: device_tracker.better_half
state: 'home'
Here we require that both device trackers are currently in the state 'home'
. If the automation is triggered by me arriving home, while the wife is still away, then one condition will be false, and the automation won’t run.
Actions
This is where things happen. Actions are basically scripts - a list of actions to take in sequence. That means you can call services (do things), check conditions, pause for a fixed time (delay
) or until a template becomes true (wait_template
), or send events. You have at least one, and potentially many actions. If any step fails (or returns false) then the automation stops at that point. This is handy for condition checks, and not so handy at other times.
With a service call you can generally give one, or many, entities, for example:
action:
- service: switch.turn_on
entity_id:
- switch.furnace
- switch.extractor
- condition: state
entity_id: sun.sun
state: 'below_horizon'
- service: light.turn_on
data:
entity_id: light.basement, light.stairwell
Here I’ve shown the two different ways of listing multiple entities - I prefer the first method. You’ll see that one of these has a data block, and one doesn’t. Generally speaking, you need a data block where you’re providing more than just an entity_id
, but it’s best to simply follow what’s in the documentation.
This particular action block will turn on two switches, and then only if the state of sun.sun
is 'below_horizon'
will it turn on the two lights.