Home Assistant / Templating

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:

{{ expand(states.media_player)
   | selectattr("entity_id", "match", "^media_player\.sonos_") }}

And this selects all light.bedroom_ entities:

{{ expand(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 re4, "match" is equivalent to re.match.

Likewise, you can also use "search":

{{ expand(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 expand(states.light) since the regex is already matching the light domain, so this can be shortened:

{{ expand(states) | selectattr("entity_id", "match", "^light\.bedroom_") }}

# less iterating, more regex
{{ expand(states.light)
   | selectattr("entity_id", "match", "^.*\.bedroom_[a-z_]+$") }}

We don't actually need to use expand either. These work equally well though, and all return the same entities:

# using re.match
{{ states | selectattr("entity_id", "match", "^light\.bedroom_") }}
{{ states.light | selectattr("entity_id", "match", "^.*\.bedroom_") }}

# using re.search
{{ states.light | selectattr("entity_id", "search", "\.bedroom_") }}

The Home Assistant documentation on templating5 uses expand, so I've tried to stick with it).

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:

{{ expand(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 generators with all entities for an area, and all devices for an area respectively:

{{ expand(area_entities("bedroom")) }}
{{ expand(device_entities("bedroom")) }}

If you want to get all light entites 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.