Simplifying Home Assistant's configuration with ytt
How you can use templating for your Home Assistant configuration with ytt
I have been a light user of Home Assistant to automate my home for a few years, and have recently lost all my configuration (I could blame Raspberry Pi’s SD card usage for that, but I should have anticipated with backups…).
This was a good opportunity to reconfigure everything “as code” (using YAML files instead of the GUI), and in this post I will show how I have simplified my configuration using ytt.
What is ytt ?
Ytt stands for “YAML Templating Tool”, it adds templating syntax to YAML files and can be used in many “YAML-oriented” scenarios: K8S configuration, CI/CD pipelines authoring, etc.
It’s part of the Carvel set of tools, which is a CNCF Sandbox project.
The best way to start with ytt is the official documentation here, but in a nutshell it uses a specific comment syntax starting with #@
to turn any YAML file into a template:
1
2
3
4
#@ for room in ['Bedroom', 'Living room', 'Bathroom']:
- platform: generic_thermostat
name: #@ "{} thermostat".format(room)
#@ end
This (uncompleted) snippet will for instance create 3 thermostats with distincts names: Bedroom thermostat
, Living room thermostat
and Bathroom thermostat
.
Ytt has many features and can be used in many ways. If you are new to ytt, the best way to start learning is the interactive playground to gradually go through its key concepts.
My Home Assistant setup
I assume that if you are still reading this you are familiar with Home Assistant but if it’s not the case, you should start here or here.
The only automated things in my home are electric heaters with “pilot wires”, each room contains the following physical devices:
- A heater with a pilot wire
- A on/off module connected to the pilot wire
- A temperature/humidity sensor
The sensor and the on/off module communicate with Home Assistant using the RFXCOM integration. I’m currently planning to move to another platform but that’s not important here.
In Home Assistant, I just need to regulate the temperature according to a different schedule for each room, so I have:
- A generic thermostat with presets (eco, comfort, away, …) for each room
- A schedule to define when each thermostat should use the comfort preset
- Automations to actually change the thermostat’s presets according to the schedules
Your installation is probably different but that doesn’t matter, what’s interesting here is how we can use ytt to simplifying editing Home Assistant’s YAML configuration files.
The YAML snippets in this post are just samples based on my usage, not the real files I am using for my installation (those are stored in a private repo).
How ytt can help
The main problem in my setup is that each room has the same devices but a proper configuration: the temperature schedule is not the same in my kid’s room and in my home office. So some settings are shared, others are specific.
Let’s see how ytt will help to deal with this situation.
DRY: Writing required YAML only
The only YAML I need to edit to adjust my home automation settings is a file that lists all the rooms in my house:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
rooms:
- name: living_room
display_name: Living room
weekend_confort_start: "08:45:00"
weekend_confort_end: "22:00:00"
- name: home_office
display_name: Home office
eco_temp: 16
comfort_temp: 18.5
weekday_confort_am_start: "07:45:00"
weekday_confort_pm_end: "18:00:00"
- name: bathroom
display_name: Bathroom
comfort_temp: 20
weekday_confort_pm_end: "07:30:00"
- name: bedroom
display_name: Bedroom
eco_temp: 15
comfort_temp: 18
It uses the ytt concept of data values, ie variables that can be referenced in templates.
Defining a schema for the rooms
In the previous files, you’ll notice that some properties are not declared for each room. There is indeed a schema document that defines the properties, sets default values, and if properties are mandatory or not:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#@data/values-schema
---
rooms:
#@schema/validation min_len=1
- name: ""
#@schema/validation min_len=1
display_name: ""
min_temp: 14
max_temp: 21
away_temp: 15
comfort_temp: 19.0
eco_temp: 17
keep_alive: "00:00:30"
weekday_confort_am_start: "06:30:00"
#@schema/nullable
weekday_confort_am_end: "09:00:00"
#@schema/nullable
weekday_confort_pm_start: "17:00:00"
weekday_confort_pm_end: "22:00:00"
#@schema/nullable
weekend_confort_start: "08:45:00"
#@schema/nullable
weekend_confort_end: "22:00:00"
This schema uses just a few features of ytt’s schema validations: mark some properties as required using schema/validation min_len
and others as nullable using schema/nullable
. This page in the docs is a great reference for the other schema validation possibilities.
Using the data and schema to generate Home Assistant’s objects
This is where the fun begins, let’s put the schema and values in action, starting with the generation of thermostats:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#@ load("@ytt:data", "data")
---
#@ for room in data.values.rooms:
- platform: generic_thermostat
unique_id: #@ "thermostat_" + room.name
name: #@ "Thermostat in " + room.display_name)
heater: #@ "switch.heater_" + room.name
target_sensor: #@ "sensor.temp_" + room.name
min_temp: #@ room.min_temp
max_temp: #@ room.max_temp
away_temp: #@ room.away_temp
comfort_temp: #@ room.comfort_temp
eco_temp: #@ room.eco_temp
keep_alive: #@ room.keep_alive
#@ end
This files uses a for
loop to iterate through the rooms and declare for each room an object following the YAML schema of the generic_thermostat
integration of Home Assistant.
Moving on to the schedules, the fun continues with for
loops for week days and weekends inside a loop over the rooms:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#@ load("@ytt:data", "data")
---
#@ for room in data.values.rooms:
#@yaml/text-templated-strings
'schedule_thermostat_(@= room.name @)':
name: #@ "Schedule for thermostat in " + room.display_name
#@ for day in ['monday', 'tuesday', 'wednesday', 'thursday', 'friday']:
(@= day @):
#@ if room.weekday_confort_am_end != None:
- from: #@ room.weekday_confort_am_start
to: #@ room.weekday_confort_am_end
- from: #@ room.weekday_confort_pm_start
to: #@ room.weekday_confort_pm_end
#@ else:
- from: #@ room.weekday_confort_am_start
to: #@ room.weekday_confort_pm_end
#@ end
#@ end
#@ if room.weekend_confort_start != None:
#@ for day in ['saturday', 'sunday']:
(@= day @):
- from: #@ room.weekend_confort_start
to: #@ room.weekend_confort_end
#@ end
#@ end
#@ end
A few things to mention in the above file:
- The
#@yaml/text-templated-strings
line followed by'schedule_thermostat_(@= room.name @)':
brings the use of ytt’s text templating feature. It allows templating for property names, which is not possible using the comment-based (#@
) syntax. The feature is also used on lines 8 and 21 ((@= day @)
) to use the days names as properties. - The
#@ if <condition> != None:
lines shows how nullable values are handled in templates. Note that nullable properties are different from properties with default values
Lastly this the template file for automations:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#@ load("@ytt:data", "data")
---
#@ for room in data.values.rooms:
- id: #@ "heating_{}_on".format(room.name)
alias: #@ "Heating on in room {}".format(room.display_name)
description: #@ "Switch to comfort preset in room {}".format(room.display_name)
triggers:
- trigger: state
entity_id:
- #@ "schedule.schedule_thermostat_{}".format(room.name)
to: "on"
actions:
- action: climate.set_preset_mode
target:
entity_id: #@ "climate.thermostat_{}".format(room.name)
data:
preset_mode: comfort
- id: #@ "heating_{}_off".format(room.name)
alias: #@ "Heating off in room {}".format(room.display_name)
description: #@ "Switch to eco preset in room {}".format(room.display_name)
triggers:
- trigger: state
entity_id:
- #@ "schedule.schedule_thermostat_{}".format(room.name)
to: "off"
actions:
- action: climate.set_preset_mode
target:
entity_id: #@ "climate.thermostat_{}".format(room.name)
data:
preset_mode: eco
#@ end
Nothing new in this file, each room will have two automations: one for setting its thermostat to the comfort preset when an event starts in its schedule, and one for setting it to the eco preset once the event ends.
Deploying to Home Assistant
Let’s see how to move this YAML files to Home Assistant. First I use the following script to generate the actual YAML files using ytt:
1
2
3
4
#!/bin/sh
script_dir=$(dirname "$0")
ytt -f "$script_dir/config" --data-values-file "$script_dir/values/rooms.yaml" --output-files "$script_dir/out"
The generated YAML files are output in an out
directory, there is one file for automations, one for thermostats, etc.
Deploying these files to Home Assistant is done with the GUI using the File Editor add-on.
First, theses lines must be added to the main configuration file to reference the new files:
1
2
3
automation: !include automations.yaml
climate: !include climate.yaml
schedule: !include schedules.yaml
Then, each file can be created and edited using File Editor in the GUI. Once the file are up-to-date in Home Assistant, I run a configuration check and if everything is ok, I reload the configuration to apply the changes.
As you have seen my workflow is not fully automated, as I still deploy configuration files “by hand”. I might automate this step in the future but I’m happy with it for now.
Also it’s tricky to automate everything, for instance I add physical devices with the GUI as it requires physical interaction (pushing buttons) on each device, so it can’t be done with a zero-touch approach.
Adding new features
There is a lot of YAML content in this blog post, but it only implements simple thermostats for now. This is a good starting point, once you are familiar with this approach, you can extend your installation with new features.
For instance I have added a “heating mode” input select with several modes to automatically lower the temperature in the whole house when the family is away for a few days, put it back to normal a few hours before we are back home, switch all heaters off in springtime, etc.
It relies on the use of a new automation triggered when the input select changes, and conditions on existing automations.
I won’t go further in the details, as this post is already long enough. I encourage you to build-up your skills with ytt, as you might use it for other tasks, and explore the great documentation of Home Assistant to understand how you can manage your configuration with templated YAML files instead of performing many manual actions in the GUI. At least, it works pretty well for me.
Wrapping-up
That’s it for the last post of the year, this is my approach to bring an “as code” flavour to my Home Assistant usage. I hope this make sense and is useful, as I have written this post quickly and shared only snippets of my “code” instead of the full repo that is private.
Don’t hesitate to reach out in the comment if you need, cheers 🤓