Template sensor: using "this" variable

In 2022.5 a “this” variable was introduced:

Template entities now have a this variable available, which is a reference to the state of the template entity itself.

Check the blog article here.

Please have a look at this example:

sensor:
  - platform: template
    sensors:

      test_this_template:
        value_template: >-
          {% if is_state("input_boolean.test_boolean","on") -%}
          {{ "red" }}
          {%- else -%}
          {{ "brown" }}
          {%- endif %}
        attribute_templates:
          color: >-
            {{ states(this.entity_id) }}
        availability_template: >-
          {{ true }}

This seems to work.
But let’s compare with a bit different implementation:

        attribute_templates:
          color: >-
            {{ states("sensor.test_this_template") }}

Here the attribute’s value is defined based on the sensor’s state.
I guess that the difference is that:

  1. Using “this” - using the current sensor’s state.
  2. Using “sensor.test_this_template” - using the previous sensor’s state.

Although in this particular test case the results are same, I wonder is it correct to use the “this” variable.
State & attributes may change asynchronously, this may cause some problems… Probably using external sensors to define attributes or state could be more reliable in some cases.
Please correct me if I am wrong.

Test card:

type: entities
entities:
  - entity: input_boolean.test_boolean
    name: red color
  - type: section
    label: sensor with "this"
  - entity: sensor.test_this_template
    name: state
  - type: attribute
    entity: sensor.test_this_template
    attribute: color
    name: '"color" attribute'
  - type: section
    label: sensor with entity_id
  - entity: sensor.test_this_template_2
    name: state
  - type: attribute
    entity: sensor.test_this_template_2
    attribute: color
    name: '"color" attribute'

image

My understanding is you should get the same result whether you do this.state or states('<entity ID>') in an attribute or icon template. Although just an FYI I’m not sure your test is really valid. You seem to be doing states(this.entity_id) instead of just this.state. states(this.entity_id) will always give the same result as states('<entity id>') since the entity ID doesn’t change, that’s basically testing the same thing twice.

Assuming this.state and states('<entity ID>') are equivalent, the advantages of using this.state IMO are:

  1. It’s shorter. Always a plus
  2. YAML anchors. If you have a few different sensors that are basically the same template but vary only in the ID of the entity (ex. one per room that does area_entities('<entity ID>') | <do stuff with those entities>) then you basically had to copy and paste that template before or employ some seriously hacky tricks with trigger IDs. Now you can just replace the hard-coded entity ID with this.entity_ID and actually use the same template repeatedly via YAML anchors.

There’s probably others but #2 was what made me excited about it personally. Although currently this isn’t available in trigger template entities so I’m still waiting for that.

1 Like

Oh, now this seems to be absolutely clear; shame on me I did not think about it…

I think that one of the most important reasons to use “this” could be related to a sequence in which data are initialized / changed in the sensor (tried to describe it here with my poor english).

Btw, currently using of the “this” is not documented (at least here).
I managed to find out how to address attributes.
So, to address a state use:

this.state

To address an attribute use:

this.attributes['attr_name']
this.attributes.attr_name
``
1 Like

Well no because that’s the article on templating in HA in general. This is specifically a feature of template sensors so it is documented here. Although that documentation perhaps should link to the state object to remind people what the schema of this is.

Not sure I understand what you mean here. If you’re referring to the use case at the top then that makes sense but like I said I would expect these two to be equivalent (re-wrote it slightly to not use legacy config and be shorter):

template:
  - sensors:
      - name: Test this template
        state: >-
          {{ 'red' if is_state("input_boolean.test_boolean","on") else 'brown' }}
        attributes:
          color: "{{ states('sensor.test_this_template') }}"
template:
  - sensors:
      - name: Test this template
        state: >-
          {{ 'red' if is_state("input_boolean.test_boolean","on") else 'brown' }}
        attributes:
          color: "{{ this.state }}"

I believe both of these should be identical just the second one is shorter. But it’s possible I’m wrong. I don’t really have many template sensors with attributes in my config. However I do have a lot of icon templates and can confirm these two are equivalent:

template:
  - sensors:
      - name: Test this template
        state: >-
          {{ 'red' if is_state("input_boolean.test_boolean","on") else 'brown' }}
        icon: "mdi:palette{{ '-outline' if is_state('sensor.test_this_template', 'brown') }}"
template:
  - sensors:
      - name: Test this template
        state: >-
          {{ 'red' if is_state("input_boolean.test_boolean","on") else 'brown' }}
        icon: "mdi:palette{{ '-outline' if this.state == 'brown' }}"

I did a refactor of basically all my icon templates during the beta to shrink them up and try this new feature out and changing from style 1 to style 2 caused no issues for me.

My assumptions below are based on my understanding and may be wrong.

  1. Suppose our sensor depends on some other sensors (let’s call them “outer sensors”).
    Our sensor’s state & attributes are defined dependingly of these outer sensors.

  2. Outer sensors are changing asynchronously - i.e. our sensor’s data are also changing asynchronously.

  3. Our sensor’s state & attributes should be defined at some particular moment of time dependingly on conditions of outer sensors at THIS particular moment of time, not at some PAST time.

  4. Suppose that for some attribute we use a states('sensor.our_sensor') code - but by using this code we are referring to the previous state - i.e. PAST time. Alternatively, we may use a this.state (or this.attributes.attr_1) code - referring to the currents state. But what if this particular state/attribute is not updated yet? May be it is better not to refer to some own state/attribute - but to refer to some outer sensors?

Hope I managed to express my thought.

So I mean try it out but I don’t really think it works like this. this is a copy of the state object made before it begins rendering whatever template it is rendering. But states also calls into that same source of truth. And while this is technically true:

It’s not quite that simple (or complicated depending on how you view async lol). HA is a python app and python does async via tasks in an event loop. Basically its a big pile of tasks and it keeps pulling them off and running them until the task gives up control (awaits something else, yields a value or ends via return, raise or running out of code). Tasks block the event loop while they are running though (hence the recent “blocking code in the event loop” warnings).

So while a template is actually being rendered, nothing else is updating. That task is blocking until it completes and all tasks that would update the state engine have to wait for it to finish.

Because of this I don’t think there is a way for states('sensor.our_sensor') to get a different result from this.state while rendering a template for sensor.our_sensor. HA is quite complex so perhaps there’s an edge case? But in general I think the two expressions should have the same result.

EDIT: Just to be clear I am very much oversimplifying async in python. If you are interested in digging in feel free to do some reading and then explore HA’s code (maybe start with the PR that added the capability). But I mostly wanted to point out the task + event loop interaction makes it so even though it’s asynchronous that doesn’t mean anything can be a race condition. There are specific points at which control is yielded.

Thank you very much for helping & detailed description!

I keep receiving errors on HA startup with this test example:

sensor:
  - platform: template
    sensors:

      test_this_template:
        value_template: >-
          {% if is_state("input_boolean.test_boolean","on") -%}
          {{ "red" }}
          {%- else -%}
          {{ "brown" }}
          {%- endif %}
        attribute_templates:
          color_1: >-
            {{ this.state + "_1" }}
          color_2: >-
            {{ this.attributes['color_1'] + "_2" }}
          color_3: >-
            {{ this.attributes.color_1 + "_3" }}
        availability_template: >-
          {{ true }}

These errors are observed only at HA startup; then the code seems to work properly, no errors are logged.

Log:

2022-05-10 16:55:26 ERROR (MainThread) [homeassistant.helpers.event] Error while processing template: Template("{% if this != none -%}
{%- if this.attributes['color_1'] != none -%}
{{ this.attributes['color_1'] + "_2" }}
{%- endif -%}
{%- endif %}")
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 409, in async_render
render_result = _render_with_context(self.template, compiled, **kwargs)
File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 1859, in _render_with_context
return template.render(**kwargs)
File "/usr/local/lib/python3.9/site-packages/jinja2/environment.py", line 1291, in render
self.environment.handle_exception()
File "/usr/local/lib/python3.9/site-packages/jinja2/environment.py", line 926, in handle_exception
raise rewrite_traceback_stack(source=source)
File "<template>", line 3, in top-level template code
jinja2.exceptions.UndefinedError: 'homeassistant.util.read_only_dict.ReadOnlyDict object' has no attribute 'color_1'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 525, in async_render_to_info
render_info._result = self.async_render(variables, strict=strict, **kwargs)
File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 411, in async_render
raise TemplateError(err) from err
homeassistant.exceptions.TemplateError: UndefinedError: 'homeassistant.util.read_only_dict.ReadOnlyDict object' has no attribute 'color_1'
2022-05-10 16:55:26 ERROR (MainThread) [homeassistant.helpers.event] Error while processing template: Template("{% if this.attributes.color_1 != none -%}
{{ this.attributes.color_1 + "_3" }}
{%- endif %}")
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 409, in async_render
render_result = _render_with_context(self.template, compiled, **kwargs)
File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 1859, in _render_with_context
return template.render(**kwargs)
File "/usr/local/lib/python3.9/site-packages/jinja2/environment.py", line 1291, in render
self.environment.handle_exception()
File "/usr/local/lib/python3.9/site-packages/jinja2/environment.py", line 926, in handle_exception
raise rewrite_traceback_stack(source=source)
File "<template>", line 2, in top-level template code
jinja2.exceptions.UndefinedError: 'homeassistant.util.read_only_dict.ReadOnlyDict object' has no attribute 'color_1'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 525, in async_render_to_info
render_info._result = self.async_render(variables, strict=strict, **kwargs)
File "/usr/src/homeassistant/homeassistant/helpers/template.py", line 411, in async_render
raise TemplateError(err) from err
homeassistant.exceptions.TemplateError: UndefinedError: 'homeassistant.util.read_only_dict.ReadOnlyDict object' has no attribute 'color_1'
2022-05-10 16:55:26 ERROR (MainThread) [homeassistant.components.template.template_entity] TemplateError('UndefinedError: 'homeassistant.util.read_only_dict.ReadOnlyDict object' has no attribute 'color_1'') while processing template 'Template("{% if this != none -%}
{%- if this.attributes['color_1'] != none -%}
{{ this.attributes['color_1'] + "_2" }}
{%- endif -%}
{%- endif %}")' for attribute 'color_2' in entity 'sensor.test_this_template'
2022-05-10 16:55:26 ERROR (MainThread) [homeassistant.components.template.template_entity] TemplateError('UndefinedError: 'homeassistant.util.read_only_dict.ReadOnlyDict object' has no attribute 'color_1'') while processing template 'Template("{% if this.attributes.color_1 != none -%}
{{ this.attributes.color_1 + "_3" }}
{%- endif %}")' for attribute 'color_3' in entity 'sensor.test_this_template'

I corrected templates to intercept exceptions with no success:

          color_2: >-
            {% if this != none -%}
              {%- if this.attributes['color_1'] != none -%}
                {{ this.attributes['color_1'] + "_2" }}
              {%- endif -%}
            {%- endif %}
          color_3: >-
            {% if this.attributes.color_1 != none -%}
              {{ this.attributes.color_1 + "_3" }}
            {%- endif %}

Checks defined a bit differently for attributes for testing purpose.

I started experimenting with addressing attributes earlier, but did not check the log for presence of errors.

{% if this is not none %}
{% if this.attributes.color_1 is defined %}
1 Like

Are these methods both valid?

states.sensor.my_sensor.attributes.my_attr is defined
state_attr("sensor.my_sensor","my_attr") != none

Probably I should not use these methods:

states.sensor.my_sensor.attributes.my_attr != none   ### gives an error
state_attr("sensor.my_sensor","my_attr") is defined   #### seems to always return True

Yes

it’s valid, but not suggested. You should never check != or == none, always use is none or is not none. state_attr("sensor.my_sensor","my_attr") is not none.

How interesting… Thank you very much again!

I tried to use this:

states.sensor.my_sensor.attributes.my_attr is not none

but it gives True even for non-existing attributes…

image

That will not work, if the attribute doesn’t exist it will error, you have to use is defined on attributes in a dictionary

is defined checks to see if the dictionary contains the key. or if the variable is infact declared.

Defined vs none

{% set my_dict = { 'a': none } %}
{{ my_dict.a is defined }} # TRUE
{{ my_dict.a is none }} # TRUE

{% set my_item = none %}
{{ my_item is defined }} # TRUE
{{ my_item  is none }} #TRUE

when defined but not none

{% set my_dict = { 'a': 2 } %}
{{ my_dict.a is defined }} # TRUE
{{ my_dict.a is none }} # FALSE

{% set my_item = 2 %}
{{ my_item is defined }} # TRUE
{{ my_item  is none }} #FALSE

when not defining…

{% set my_dict = { } %}
{{ my_dict.a is defined }} # FALSE
{{ my_dict.a is none }} # WILL ERROR
 
{{ my_item is defined }} # FALSE
{{ my_item  is none }}  # WILL ERROR
3 Likes

Very detailed… Need to recheck my whole code. Thank you!

FYI if you haven’t seen it take a look at built-in tests in the Jinja doc. Actually would strongly recommend at least skimming the doc in general as it is very helpful. But built-in tests and built-in filters are almost worth bookmarking as they are a handy reference.

That’s basically what Petro is showing here. When you see is <something> or is not <something> then <something> is a jinja test and you can find the doc on it there.

The home assistant templating guide only covers the extensions to Jinja that HA has added, it simply links to the Jinja doc for all the OOTB stuff. It’s also a great reference but you should definitely look at both.

from what I can tell, this will always be defined. So it will be either none or a state object. So your check should be this is not none.

Attributes inside the state object may or may not exist, therefore you should be using is defined and possibly is not none as well if the attribute might be populated with null/none.

I just want to point out that self-references has been frowned upon before (discouraged) – and I understand why. Also see this example (pun).

1 Like

the new variable this should be an exception to the rule. I can’t comment for sure as I didn’t add this, but I would assume that it can properly reference itself safely.

1 Like