Hi everyone,
I’ll start this automation guide slow, intro concepts so that new users learn some ropes.
But… We’ll move to advanced HA concepts. If you’re an experimented user, you can jump to point 7 and below directly.
There are God-like automatoes in this forum, able to one-line a whole house. I’m not one of them, this is just hard-learned experience, full of trial/error, pumped by hope and results I was getting over time.
We’ll start from a simple “let’s kill mosquitos” to “I don’t want alarm to be triggered because a spider bungee jumping in front of a camera at 3 am”.
And we’ll end up automating the A/C system of an entire house…
1. Using both GUI and text file-based automation
Since HA evolved a lot toward a larger public, it’s no surprise that an excellent automation GUI has been included. You can do most of the job through it.
Text file-based automation will come in handy if you need very advanced automation, as the ones will see at the end.
To use both of them, add this in your configuration.yaml file:
[...]
automation ui: !include automations.yaml
automation manual: !include_dir_merge_list automations
[...]
Now in the subdirectory […]/config/automations, all your files ending with .yaml will be loaded during start time:
/config/automations/1-security.yaml
/config/automations/2-camera.yaml
/config/automations/3-airconditioning.yaml
[…]
Now you can edit files or use the GUI, depending on the complexity of the task you want to handle.
2. We’ll need a good editor and a good debugger
-
Install Visual Studio Code server from system/addons
-
Prepare a script to log & notify:
notify_and_log:
sequence:
- if: "{{is_state('input_boolean.debug_flag', 'on')}}"
then:
- service: notify.info
data:
title: "{{ title }}"
message: "{{ message }}"
- service: system_log.write
data:
message: "{{ message }}"
level: warning
- Add a button to your frontend interface to enable the input_boolean.debug_flag.
That way, when you debug, you click the button in the frontend, and you get both a log in Home assistant logs and a notification on your mobile.
- Then, in any automation, you can call your script:
- service: script.notify_and_log
data:
title: "Hello"
message: "World"
or, more likely, a templated debug thing you want to see interpreted/iterated and get a result, or where it fails:
- service: script.notify_and_log
data:
title: "{{repeat.item}} settings updated"
message: >
{{ "Opened sensor: " + mapper[repeat.item][2] + "\n" + "Manual override: " + mapper[repeat.item][3] + "\n" + "HVAC mode: " + mapper[repeat.item][1] + "\n" + "A/C temp: " + mapper[repeat.item][0] }}
But more about this later, don’t worry about understanding it now.
3. The basics
Let’s start with some simple concepts you need to understand and their logic. Apart from the trigger/condition/action triad, you’ll usually find some “environmental” variables in an automation:
- id: "230000"
alias: A/C - Pilot all A/C mode & temp
mode: single
description: Set A/C mode & temp, per room, every half an hour
[...]
- Its ID: a unique number to identify this automation. Never reuse one for another purpose, this could create weird situations.
- Its alias: it’s “internal” (entity ID) name is actually derived from that alias and it’s the short form you’ll see in the automation list (settings->automation). Now if you need to call it yourself or pause it, here for example, it’s internal name is “automation.a_c_pilot_all_a_c_mode_temp”. You can get the exact name by clicking the three dots (burger menu) in the settings->automation area and click information and then the upper cogwheel. The field is named “entity ID”, because yes, an automation is also an entity.
- Its mode: single, parallel, queued, restart. If triggered several time, defines if it’s only executed only, if it restarts after the previous iteration, if it’s queued or executed in parallel. Except in particular case you’ll likely identify quickly, stick to single (which is anyway the default mode if not specified)
Triggers
A trigger is an event that starts the evaluation of the conditions or actions listed below.
Is it at fixed times? like 22h30? Is it every ten minutes, is it when the state of an entity is changing, etc.
There can be several triggers, and if so, any of those triggers will start the show.
This is a logical “OR” in boolean logic.
So:
trigger:
- platform: state
entity_id:
- binary_sensor.PIR1_motion_trigger
- binary_sensor.PIR2_motion_trigger
- binary_sensor.PIR3_motion_trigger
to: "on"
- platform: time_pattern
minutes: "/30"
This automation will start if any of the 3 PIR (passive infrared sensor) switch from any previous state to “on” or at precisely every 00 and 30 min of any hour.
Remember also that you can check if something evaluates to true for a certain amount of time before triggering, like:
trigger:
- platform: state
entity_id: group.family_members
from: home
to: not_home
for:
minutes: 3
It’s helpful if you want to avoid stopping the airco just because someone opened a door, to close it a second later. Of if you want to wait for 3 minutes before declaring the last homie left the house.
Conditions
Condition on the other hand are AND logic. Meaning all of them have to be true, except if stated otherwise. You can for a mix of OR and AND logic in conditions if you want. But by default, they have to ALL be evaluated as true to proceed to actions.
condition:
- condition: state
entity_id: binary_sensor.door_sensor_1_is_open
state: "on"
- condition: state
entity_id: binary_sensor.door_sensor_2_is_open
state: "on"
Here, actions described after the conditions will only be taken if both door sensors are in state opened.
ah… btw, you can simplify the above by using the short form:
condition:
- "{{ is_state('binary_sensor.door_sensor_1_is_open', 'on') }}"
- "{{ is_state('binary_sensor.door_sensor_2_is_open', 'on') }}"
Yep, 100% identical, but far more compact. It’s very handy as well, because, if like me, you hate remembering where to put your AND and OR logic in conditions, link them in the above form:
condition:
- "{{ is_state('binary_sensor.door_sensor_1_is_open', 'on') and is_state('binary_sensor.door_sensor_2_is_open', 'on') }}"
and if you want door 1 or door 2 open in your condition:
condition:
- "{{ is_state('binary_sensor.door_sensor_1_is_open', 'on') or is_state('binary_sensor.door_sensor_2_is_open', 'on') }}"
Home assistant is a big state engine. So evaluating conditions and states is fairly low intensive on CPU, whereas actions can sometimes be heavier, so don’t hesitate to use a lot of conditions.
Actions
Action baby, that’s why we’re here.
So, if you want an action to start, simple add it, like a service to turn on the light.
But say you want always to execute action A and action B only if the door is opened:
action:
- service: climate.set_hvac_mode
data:
hvac_mode: "off"
target:
entity_id: climate.ac_parent_bedroom
- condition: state
entity_id: input_boolean.home_away_mode
state: "on"
- service: climate.set_preset_mode
data:
hvac_mode: away
target:
entity_id:
- climate.ac_parent_bedroom
No short form here, though. But the condition structure is the same as a regular condition, just included in an action. Ok so here, provided the previous conditions (door 1 or door 2 opened), we turn off the air conditioning in the parents bedroom. but… if the house is set in away mode (like you pushed a button in your interface that sets the input_boolen.home_away_mode to on), the air conditioner will be set in away mode instead.
Ok this is utterly dumb because if you leave the house, you don’t let the door open, but hey, it’s 00h31 when I wrote this example, so please be kind.
Now if you have other actions below this condition, if the condition is false, then the execution is stopped immediately and no other actions will be done. So here, no matter how many actions I have below the evaluation of the state of input_boolean.home_away_mode, of it’s off, nothing will be done after this action. But all above actions will take place.
Now if you want to only prevent one action from happening, but not the others, it’s written in this form:
action:
- action 1
- action 2
- if: "{{ now().hour not in (1,2,3) }}"
then:
- action 3
- action 4
- action 5
Here, no matter what, action 1,2,4 and 5 will happen where action 3 will only happen if the current hour is not 1, 2 or 3 so basically not between 1:00 am and 3:59 am.
(and yes, here again, you can use the short form, go figure)
4. Basic optimizations
Well, I find the short form is convenient and makes the whole thing lighter and more readable. So this is already a quick win. For further optimizations, don’t forget that if you run a lot of automations, you want them to kick in with consistency and not burden your CPU to evaluate templates every minute.
Typically :
- if: "{{ now().minute == '1' }}"
this will be evaluated every minute, so 1440 per day, even though it’s only true if the current minute is 1.
here, prefer something like:
- platform: time_pattern
hours: "*"
minutes: "1"
Which will only evaluate 24 times daily to check if the minute of any hour is 1.
Also, try to separate automation where possible. Most of time, it’s possible to write automation that does it all, but it’s not wishable for debug purposes. Now if I take the Air conditioning example, you could turn off the AC if a door opens in the room, within the same automation that sends the parameters to the Air co unit. But hey, it’s more readable and easier to debug if you separate those in two distinct automations.
Use groups. Groups can take a value, based on the state of its members.
So if I create a group “cool_people”, which contains all family members:
cool_people:
name: Cool People
entities:
- device_tracker.idevice_him
- device_tracker.idevice_her
The group will take the status of home if anyone is home or not_home if no one is home.
How convenient.
Same goes for a PIR detector group:
PIRs:
name: Outdoor PIR sensors
entities:
- binary_sensor.sw
- binary_sensor.nw
- binary_sensor.south
- binary_sensor.east
- binary_sensor.north
- binary_sensor.gate
Now the logic here is bit different. If any PIR triggers, the whole group will be turned to on if none of them is, it’s off.
Careful though, sometimes triggering on PIR one by one is more interesting because you want to trigger for each of them and not just for the group or even because you want to use trigger_to (we’ll speak about that later). But overall, groups are just great.
5. A Basic automation
Wild guess, let’s say you don’t like mosquitos and want to turn on a plug that has a anti-mosquito diffuser attached to it, only when it’s 21h and for 3h. That way you kill mosquitos before you sleep, but spare the liquid from being vaporized all night, not to mention you don’t breeze it yourself.
Let’s also say we have a classical Sonoff plug to turn on and off using Wifi.
The automation could look like this:
- id: "230061"
mode: single
alias: Home - Anti mosquitto
trigger:
- platform: time
at: "21:00:00"
condition:
- condition: state
entity_id: "group.family_members"
state: "home"
action:
- service: switch.turn_on
entity_id: switch.sonoff_mosquitto_parent
- delay:
hours: 3
- service: switch.turn_off
entity_id: switch.sonoff_mosquitto_parent
Fair, square, nothing wrong about it, and it accounts for the occupation of the house (no need to spray if nobody sleeps in this bedroom).
6. A smarter automation
Let’s improve this first automation.
Let’s now account for the outside temperature. (or maybe you have a mosquito integration with a sensor, whatever). One limitation of the previous approach is that if you restart your home assistant during this 3h delay where the automation is waiting to kick its second action, you’ll likely not turn off the plug until the next day.
- id: "230061"
mode: single
alias: Home - Anti mosquitto
description: Turn on anti mosquitto in the evening
trigger:
- platform: time
at: "21:00:00"
id: mosquitto_plug_start
- platform: time
at: "23:59:00"
id: mosquitto_plug_end
condition:
- "{{ is_state('group.family_members','home')}}"
- "{{ states('sensor.outdoor_temp') | float(0) > 20 }}"
action:
- choose:
- conditions:
- condition: trigger
id: mosquitto_plug_start
sequence:
- service: switch.turn_on
entity_id: switch.sonoff_mosquitto_parent
- conditions:
- condition: trigger
id: mosquitto_plug_end
sequence:
- service: switch.turn_off
entity_id: switch.sonoff_mosquitto_parent
That way, no matter when you restart your HA, no delay is involved, it’s simply turning on at 21h00 and off at 23:59. Both triggers start the automation, but the action depends on the trigger that fired.
There are other ways to do it like:
- id: "230061"
mode: single
alias: Home - Anti mosquitto
description: Turn on anti mosquitto in the evening
trigger:
- platform: time
at: "21:00:00"
- platform: time
at: "23:59:00"
condition:
- "{{ is_state('group.family_members','home')}}"
- "{{ states('sensor.outdoor_temp') | float(0) > 20 }}"
action:
- if: "{{ now().timestamp() | timestamp_custom('%H:%M') == "21:00" }}"
then:
- service: switch.turn_on
entity_id: switch.sonoff_mosquitto_parent
else:
- service: switch.turn_off
entity_id: switch.sonoff_mosquitto_parent
Now we know this automation triggers only at 21:00 or 23:59, so when we check if it’s 21:00 in the action section, if it’s not true, then it can only be 23:59, otherwise, the automation wouldn’t have triggered in the first place.
We could also use the trigger.entity_id to achieve similar logic.
As usual with HA, there are 100 different ways to tackle the same problem.