Templating in Home Assistant
Overview
YAML is (mostly1) great for configuration that is easily parsed by both computers and people, and Jinja is a decent templating language. But using Jinja inside of YAML is kind of a terrible way to program. It's a half solution, that combines the worst of both and not a whole lot of the good parts.
Selecting entities
We have a good naming standard, and its more useful if we use it. Unfortuantely, Jinja is a bit limited at working with iterators as it lacks list comprehensions like standard Python has.
Selecting based on entity_id
Jinja provides a useful collection of filters2, and we can
match entity_id
using filters and regular expressions. This selects all
media_player.sonos_
entities:
{{
states.media_player
| selectattr("entity_id", "match", "^media_player\.sonos_")
}}
And this selects all light.bedroom_
entities:
{{
states.light
| selectattr("entity_id", "match", "^light\.bedroom_")
}}
The selectattr
filter3 (and its inverse
rejectattr
) accepts an iterator and a test (argument passed as a string,
here "match"
) to each item, filtering the list.
The regex tests used in the filter are direct mappings to the the Python
Standard Library module re
4, "match"
is equivalent to
re.match
.
Likewise, you can also use "search"
:
{{
states.light
| selectattr("entity_id", "search", "light\.bedroom_")
}}
This is equivalent to using re.search
.
Note that match
requires you to match the string from the start so this won't
match anything:
selectattr("entity_id", "match", "\.bedroom_")
Instead you need to do:
selectattr("entity_id", "match", "^.*\.bedroom_")
You can drop the ^
token, this will also match:
selectattr("entity_id", "match", ".*\.bedroom_")
Using search
, the regex doesn't need to match from the start of the string, and
you can use:
selectattr("entity_id", "search", "\.bedroom_")
Using search
, you don't need to match the literal .
after the domain,
since for example "bedroom_"
is a valid regex. However that could match other
entities that don't start with "bedroom_"
, something like
sensor.foo_bedroom_bar
or sensor.notbedroom_foo
will also match.
Technically we don't need to iterate over states.light
since the regex is already
matching the light
domain -- we can also iterate over states
directly:
{{
states
| selectattr("entity_id", "match", "^light\.bedroom_")
}}
# less iterating, more regex
{{
states.light
| selectattr("entity_id", "match", "^.*\.bedroom_[a-z_]+$")
}}
Optionally we can use the expand
function
# using re.match
{{
states
| selectattr("entity_id", "match", "^light\.bedroom_")
}}
{{
expand(states)
| selectattr("entity_id", "match", "^light\.bedroom_")
}}
{{
expand(states.light)
| selectattr("entity_id", "match", "^.*\.bedroom_")
}}
# using re.search
{{
states.light
| selectattr("entity_id", "search", "\.bedroom_")
}}
{{
expand(states.light)
| selectattr("entity_id", "search", "\.bedroom_")
}}
All of these return the same entities. Using expand()
will expand a group of
entities before other filters are evaluated, so depending on how you construct the
expression this can cause Jinja to listen to a higher number of state events.
The Home Assistant documentation on templating5
uses expand
in most examples.
Selecting based on domain
It's also possible to filter based on the domain
attribute, if you are tolerant to
passing operators as strings/data:
{{
states
| selectattr("domain", "==", "light")
| selectattr("entity_id", "match", "^.*\.bedroom_")
}}
If you are fine with using string-aliases for operators, you can also write
this as selectattr("domain", "eq", "light")
. This is arguably even worse, but
you'll inevitably find a whole bunch of examples on the Home Assistant forums
using notation like this.
Selecting based on area
You can also select based on areas, and this actually feels more natural in the Home Asssitant templating. But assigning devices or entities to areas can only be done in the UI and cannot be managed in code, which kind of negates that.
The first building blocks to using this are area_entitites
and area_devices
.
This will return lists with the state of all entities for an area, and all the
states of all devices for an area respectively:
{{ expand(area_entities("bedroom")) }}
{{ expand(device_entities("bedroom")) }}
If you want to get all light
entity states in an area:
{{
expand(area_entities("bedroom")
| selectattr("domain", "==", "light")
}}
Which doesnt rely on the entity_id
following a naming standard, but does rely
on it having been assigned to the correct area in the UI.
Selecting based on label
The new labels in Home Assistant are a pleasantly powerful tool to organize entities and devices. While they can only be assigned to entities using the UI, it’s possible to read them in templates:
{{
label_entities(label)
| select("match", "^sensor.")
| select("is_state", state)
| list
| count
| round(0)
}}
Returns the number of sensor
entities labeled with label
that have a given state
.
Selecting entities in an area, in a domain, by state
All of the above are based on using regular expressions, but you can also use
expand
along with other helpers provided by Home Assistant.
{{
expand(area_entities("bedroom"))
| selectattr("domain", "eq", "light")
| map(attribute="entity_id")
| select("is_state", "on")
| list
}}
This returns the entity_id
of all light
entities in the bedroom
area
whose state is "on"
/true
.
Selecting binary_sensor
entities by state
Given a list of entity_id
s, to check if any of them are true
/"on"
:
{{
entity_list
| select("has_value")
| select("is_state", "on")
| list
| length > 0
}}
Where entity_list
is an iterator with entity_id
strings. This can for example
be expand
, area_entities
or label_entities
(etc), or a defined list.
Default values
Handling unknown/unavailable sensor states in a template sensor can be cumbersome.
There is an availability
configuration key for the template
platform:
template:
- sensor:
name: template sensor
availability: |
{{ has_value("sensor.foo") }}
state:
{{ states("sensor.foo") | float | round(1) }}
For numeric values, a default value can be set using the default
argument to float
and int
:
{{ states("sensor.foo") | float(default=0.0) | round(1) }}
Note that the default value itself does not need to be an int
or float
, so it can
be used to render "unknown"
or "unavailable"
:
{{ states("sensor.foo") | float(default="unknown") }}
But then the round
filter cannot be used, since that will raise an exception when
the template returns a string, since round
requires a numeric value (which can be
a string).
Source entity_id
in attribute
To reduce repitition, you can store the source entity_id
s that the template sensor
uses in the attributes of your template sensor. But if this is done in the template
platform then it will raises errors until the sensor returns a value, since it seems
that the attributes are not registered until after the state of the sensor has successfully
returned a value.
Instead set the attributes with the source entity_ids
using homeassistant.customize
:
homeassistant:
customize:
sensor.templated_sensor:
source_entity_ids:
- sensor.source_sensor_1
- sensor.source_sensor_2
template:
- sensor:
- name: templated_sensor
availability: |
{{
this.attributes.get("source_entity_ids")
| select("has_value")
| list
| length > 0
}}
state: |
{{
this.attributes.get("source_entity_ids")
| map("states")
| select("is_number")
| map("float")
| list
| median
| round(0)
}}
This also works for binary_sensor
:
homeassistant:
customize:
binary_sensor.templated_binary_sensor_1:
source_entity_id: "binary_sensor.source_binary_sensor"
binary_sensor.templated_binary_sensor_2:
source_entity_ids:
- binary_sensor.source_binary_sensor_1
- binary_sensor.source_binary_sensor_2
template:
- binary_sensor:
- name: templated_binary_sensor_1
availability: |
{{ has_value(this.attributes.get("source_entity_id")) }}
state: |
{{ is_state(this.attributes.get("source_entity_id"), "on") }}
- name: templated_binary_sensor_2
availability: |
{{
this.attributes.get("source_entity_ids")
| select("has_value")
| list
| length > 0
}}
state:
{{
this.attributes.get("source_entity_ids")
| select("is_state", "on")
| list
| length > 0
}}
Since the entity gets the attributes from the homeassistant.customize
platform, it has access to them when the template
platform sensor is
evaluated.
Using the availibility
configuration keys reduces errors in the logs
but it is also possible to write the state
jinja to always return valid
values, though it is sometimes tricky to get it right.