Support Variables in Template Sensors

Release 0.115 introduced Variables for automations and scripts. Variables would also be useful for creating Template Sensors, Template Binary Sensors, Template Switches, etc.

For example, here’s a Template Sensor I use to report the quantity of open doors. It also has an attribute reporting the names of all open doors. The two templates it uses are nearly identical.

    open_doors:
      friendly_name: 'Open Doors'
      value_template: >
        {{ expand('group.doors_all')
          | selectattr('state', 'eq', 'on')
          | list | count }}
      attribute_templates:
        names: >-
          {{ expand('group.doors_all')
            | selectattr('state', 'eq', 'on')
            | map(attribute='name') | list | join(', ') }}

If variables were available, the resulting Template Sensor would have less duplication:

    open_doors:
      friendly_name: 'Open Doors'
      variables:
        doors: >
          {{ expand('group.doors_all')
             | selectattr('state', 'eq', 'on')
             | map(attribute='name') | list }}
      value_template: "{{ doors | count }}"
      attribute_templates:
        names: "{{ doors | join(', ') }}"

Yes please!
adding to the above, only to illustrate some more sensor configuration variables:

      alerts_notifying:
        variables:
          alerts: >
            {{expand('group.alerts')|selectattr('state','eq','on')|list}}
          count: >
            {{alerts|length}}
        value_template: >
          {count}}
        friendly_name_template: >
          {% set phrase = 'Alert' if count == '1' else 'Alerts' %}
            {{count}} {{phrase}} on
        attribute_templates:
          message: >-
            {%- if count == 0 -%} Ok, no alerts, all is well
            {%- elif count == 1 -%}{{cnt}} Alert: {{alerts[0]}}
            {%- elif count == 2 -%}
              {{count}} Alerts: {{alerts[0]}} and {{alerts[1]}}
            {%- else -%}
              {{count}} Alerts:{{alerts[:-1]|join(', ')}}, and {{alerts[-1]}}
            {%- endif -%}
        icon_template: >
          mdi:numeric-{{count}}-box

instead of the continuous repetition in:

      alerts_notifying:
        value_template: >
          {% set count = expand('group.alerts')|selectattr('state','eq','on')
             |list|length %}
          {{count}}
        friendly_name_template: >
          {% set count = expand('group.alerts')|selectattr('state','eq','on')
             |list|length %}
          {% set phrase = 'Alert' if count == '1' else 'Alerts' %}
            {{count}} {{phrase}} on
        attribute_templates:
          message: >-
            {% set alerts = expand('group.alerts')|selectattr('state','eq','on')
               |map(attribute='name')|list -%}
            {% set count = alerts|length %}
            {%- if count == 0 -%} Ok, no alerts, all is well
            {%- elif count == 1 -%}{{cnt}} Alert: {{alerts[0]}}
            {%- elif count == 2 -%}
              {{count}} Alerts: {{alerts[0]}} and {{alerts[1]}}
            {%- else -%}
              {{count}} Alerts:{{alerts[:-1]|join(', ')}}, and {{alerts[-1]}}
            {%- endif -%}
        icon_template: >
          {% set count = expand('group.alerts')|selectattr('state','eq','on')
             |list|length %}
          mdi:numeric-{{count}}-box

Oh that’d be very helpful… indeed.

My use case: kind of “self-reference” of a template sensor availability definition (example in New template sensor definition: how to set entity_id and availability?).

Currently I need to use the content of the state in availability again plus adding a check. Depending on the state code complexity/amount, this would reduce the code by almost 50 %.

i can see another major advantage here

if you have a lot of sensors that have the same logic, you can make them way easier copy and pastable and especially shareable

  template:

    - binary_sensor:
        - name: "Living Room Occupation"
          unique_id: "4d3004f9e7514ecb947715b5de3356c5"
          variables:
            motion_1: "{{ is_state('binary_sensor.living_room_motion_1', 'on') }}"
            motion_2: "{{ is_state('binary_sensor.living_room_motion_2', 'on') }}"
          state: >
            {{ motion_1 or motion_2 }}
          availability: >
            {{ motion_1 not in ["unknown", "unavailable"] or 
              motion_2 not in ["unknown", "unavailable"] }}
          device_class: "presence"
          delay_off: 180
1 Like

Yes please

I would love this to happen

this is exactly what I was looking for, I am hoping that will be added…

because if I want to use the same code for the state (value) and for the icon (choose) the only option - I think - I have is to copy the it and paste it 2 times…and it’s a waste of resources.

In the nearly 3 years since this thread started there have been numerous features added to template sensors. You can use the self-referencing variable “this” to cover your icon issue. Additionally, trigger-based template sensors allow the use of both the trigger variable and trigger-attached variables.

Not sure if this would fulfill the Feature Request but a trigger variable might. The this variable is complicated by where it’s employed and how.

Here’s my understanding of how it works:

The state option’s template is evaluated before any attribute’s template. So if you use this.state in the state option, its value is the Template Sensor’s existing value (i.e. the value before the state option’s template is evaluated). However if you use this.state in an attribute, it’s the value after the state option’s template is evaluated.

This can make it tricky to get the desired pre or post evaluation value of this.state.

The template of a proper variable (as requested in this FR) would be evaluated before either state or any attributes (like the trigger variable for a Trigger-based Template Sensor).

2 Likes

Fwiw, the recent addition of custom_templates has been very useful in this regard.

It’s not the exact same thing, and requires some care in setting up, and some repetitive imports…
but at least we can import the same value in any of the states/attributes now and prevent re-evaluation in all of those

Form complex macros to simpler strings, and it is not frustrated by the intricacies of the this variable which is especially useless difficult to use in trigger based templates …

can even nest those custom_templates, see the state: or attribute laatste of the below sensor.

template:

  - trigger:
#       - platform: time_pattern
#         hours: 0
#         minutes: 0
      - platform: state
        entity_id: sensor.date
      - platform: homeassistant
        event: start
      - platform: event
        event_type: event_template_reloaded

    sensor:

      - unique_id: marijn_verjaardag
        state: >
          {% from 'feestdata.jinja' import vj_marijn %}
          {% set event = vj_marijn %}
          {% set year = 1 if event.month < now().month or
                            (event.month == now().month and event.day < now().day)
                         else 0 %}
          {% from 'easy_time.jinja' import count_the_days %}
          {{count_the_days(event.replace(year=now().year + year))}}
        picture: /local/family/marijn.jpg
        unit_of_measurement: dagen
        attributes:
          id: Marijn
          datum: !secret vj_marijn
          type: Verjaardag
          leeftijd: >
            {% from 'feestdata.jinja' import vj_marijn %}
            {% set event = vj_marijn %}
            {% set year = 0 if event.month < now().month or
                              (event.month == now().month and event.day <= now().day)
                           else 1 %}
            {{now().year - event.year - year}}
#             {{(now() - event).days // 365.24}}
          feest: >
            {% from 'feestdata.jinja' import vj_marijn %}
            {% set event = vj_marijn %}
            {{now().strftime('%m-%d') == event.strftime('%m-%d')}}
          laatste: >
            {% from 'feestdata.jinja' import vj_marijn %}
            {% set event = vj_marijn %}
            {% set year = 0 if event.month < now().month or
                              (event.month == now().month and event.day < now().day)
                           else -1 %}
            {% from 'easy_time.jinja' import count_the_days %}
            {{count_the_days(event.replace(year=now().year + year))|int|abs}}
          dagen: >
            {% from 'feestdata.jinja' import vj_marijn %}
            {% set event = vj_marijn %}
            {{(now() - event).days}}