Template with custom_templates import does not update correctly

c&p from Discord:

how odd, template sensor using custom_templates:

      - unique_id: alerts_notifying
        state: >
          {% from 'alerts.jinja' import alerts %}
          {{alerts|count}}
        name: >
          {% from 'alerts.jinja' import alerts %}
          {% set count = alerts|length %}
           {% set phrase = 'Alert' if count == 1 else 'Alerts' %}
           {{count}} {{phrase}} actief
        icon: >
          {% from 'alerts.jinja' import alerts %}
          {% set count = alerts|length %}
          mdi:numeric-{{count}}-box

returns 0 on all counts, while dev tools template

{% from 'alerts.jinja' import alerts %}
{{alerts}}
{{alerts|count}}

clearly shows thats not the case…

|count or |length dont make a difference in this case as can be seen

I moved to import that custom_template, because I had this expand:

         state: >
           {% set alerts = state_attr('binary_sensor.alerts','entity_id') %}
           {{expand(alerts)
           |selectattr('state','eq','on')|map(attribute='entity_id')|list|count}}

in all options before, and figured that might be more efficient (expanding only once, and then import)

this is my alerts.jinja file:

{% set alerts = expand(state_attr('binary_sensor.alerts','entity_id'))
               |selectattr('state','eq','on')|map(attribute='name')|list %}

{% set hubs_offline = expand(state_attr('group.hub_device_trackers','entity_id'))
                                  |selectattr('state','eq','not_home')|list %}

{% set critical_off = expand('switch.critical_switches')
                                  |selectattr('state','eq','off')|list %}

what’s more, when pasting the full template config in the dev tools, it clearly shows its correct…

so no wondering if this import is behind somehow, or what could it be?

thanks for having a look

You have to use macros for those types of calculations, you can’t use set. The only time you can use set and import from the set is when the information is static. I.e.

{% set x = 4 %}

Then import that.

If your template calculates something, you have to make it a macro.

could you help me out a bit more here, I dont think I understand.
check the state, where no set is used, and merely a |count

or are you referring to the custom_templates templates set usage?

Uh… you’re using set.

yes, in the custom_templates .jinja file, not in the yaml template: configs…

I am not sure how I can turn this into macros? other than:

{% macro alerts() %}
{% set alerts = expand(state_attr('binary_sensor.alerts','entity_id'))
               |selectattr('state','eq','on')|map(attribute='name')|list %}
{% endmacro %}

but what to enter inside the alert() there?

you return the list, just like any other macro

{% macro ... %}
{{ expand.... }}
{% endmacro %}

Keep in mind that it will return a string.

And before you ask, no there is no way to make macros return native types.

btw, why does this custom_template work as is then?

{% set batteries = states.sensor
              |selectattr('attributes.device_class','defined')
              |selectattr('attributes.device_class','eq','battery')
              |rejectattr('state','in',['unknown','unavailable'])
              |map(attribute='entity_id')|list %}

it does not. It’s a fluke that it’s working for you at that moment.

o dear that is why I didnt notice before, as in some of the others I use…

before I rewrite all of them, should this do it:

{% macro alerts %}
{{ expand(state_attr('binary_sensor.alerts','entity_id'))
               |selectattr('state','eq','on')|map(attribute='name')|list }}
{% endmacro %}

{% macro hubs_offline %}
{{ expand(state_attr('group.hub_device_trackers','entity_id'))
                                  |selectattr('state','eq','not_home')|list }}
{% endmacro %}

{% macro critical_off %}
{{ expand('switch.critical_switches')|selectattr('state','eq','off')|list }}
{% endmacro %}

or should I have those macro names use the (), even if there is no further input for those?

you need the (), and I recommend a | join(',') so you can split(',') if you need to iterate the list of entity_ids. Otherwise it comes through as ['...','...'], with | join(',') it returns ...,... which you can easily get the entity_id’s out from. Also keep in mind, that returning a list of state objects is impossible via a macro, which you’re doing for critical_off and hubs_offline.

1 Like

ok, so now I am officially lost…

need to get back. before, we were told to check in dev tools, so that is what I do:

mind you this is with the original alerts.jinja (using the set)

and its my yaml config for the sensor.alerts_notifying

so the dev tools template works, the sensor itself not

this is my old sensor, which I was trying to rewrite:

        state: >
          {% set alerts = state_attr('binary_sensor.alerts','entity_id') %}
          {{expand(alerts)
          |selectattr('state','eq','on')|map(attribute='entity_id')|list|count}}
        name: >
          {% set count = expand(state_attr('binary_sensor.alerts','entity_id'))
             |selectattr('state','eq','on')|list|length %}
           {% set phrase = 'Alert' if count == 1 else 'Alerts' %}
           {{count}} {{phrase}} on
        icon: >
          {% set count = expand(state_attr('binary_sensor.alerts','entity_id'))
             |selectattr('state','eq','on')|list|length %}
          mdi:numeric-{{count}}-box
        attributes:
          message: >-
            {% set alerts = expand(state_attr('binary_sensor.alerts','entity_id'))
               |selectattr('state','eq','on')|map(attribute='name')|list %}

            {% set count = alerts|length %}
            {%- if count == 0 -%} Ok, no alerts, all is well
            {%- elif count == 1 -%}{{count}} Alert: {{alerts[0]}}
            {%- elif count == 2 -%}
              {{count}} Alerts: {{alerts[0]}} and {{alerts[1]}}
            {%- else -%}
              {{count}} Alerts:{{alerts[:-1]|join(', ')}}, and {{alerts[-1]}}
            {%- endif -%}

You’re getting a count in all those, just return the count from the macro and be done with it. In some cases you might have to convert the count to an int because macros return strings.

Yes I can see/do that.

My point is the sensor does not return the same output as the dev template with the exact same yaml . I don’t understand why that is, or how to fix that

seems the template: engine is just not properly evaluating those in the backend, and the dev tools template is.
(btw, this is a regular template sensor, not a trigger based template, so that cause can be ruled out)

I’ve already explained it. A set does not execute code. It’s already set. When you import it, it does not run it. It just gets the value that it has. You have to use a macro to run anything. And because you have to use a macro, you have to account for the output always being a string when you use the macro.

You can also doe | to_json at the end of the macro, and then | from_json when using it in a template

eg:

{%- macro alerts -%}
{{- expand(state_attr('binary_sensor.alerts','entity_id'))
               |selectattr('state','eq','on')|map(attribute='name')|list | to_json -}}
{%- endmacro -%}
{% from 'alerts.jinja' import alerts %}
{{ alerts | from_json }}

I would also reccomend to add hyphens everywhere you can in the macro, to avoid whitespace issues.

Yes but that’s running potentially expensive operations when a split on a comma separated list isn’t too expensive.

Personally, I use to/from json as a last resort. I make all my macros perform the operations with simple returned values.

1 Like

wasn’t aware that there is such a difference in these operations

To_json has to touch every object inside what your turning into json and from_json does as well.

so I changed that to:

{% macro alerts_macro() %}
{{ expand(state_attr('binary_sensor.alerts','entity_id'))
               |selectattr('state','eq','on')|map(attribute='name')|list }}
{% endmacro %}

but that makes

{% from 'alerts.jinja' import alerts_macro %}
{{ alerts_macro }}

return
<Macro ‘alerts_macro’>

maybe I am just better off using the this variable in these type of templates.

at least this works:

      - unique_id: alerts_notifying_this
        state: >
          {{this.attributes.alerts|count}}
        name: >
          {% set count = this.state|int(default=-1) %}
           {% set phrase = 'Alert' if count == 1 else 'Alerts' %}
           {{count}} {{phrase}} actief
        icon: >
          {% set count = this.state|int(default=-1) %}
          mdi:numeric-{{count}}-box
        attributes:
          alerts: >
            {{ expand(state_attr('binary_sensor.alerts','entity_id'))
               |selectattr('state','eq','on')|map(attribute='name')|list }}
          message: >-
            {% set count = this.state|int(default=-1) %}
            {% set alerts = this.attributes.get('alerts') %}
            {%- if count == 0 -%} Ok, geen alerts, alles is in orde
            {%- elif count == 1 -%}{{count}} Alert: {{alerts[0]}}
            {%- elif count == 2 -%}
              {{count}} Alerts: {{alerts[0]}} en {{alerts[1]}}
            {%- else -%}
              {{count}} Alerts:{{alerts[:-1]|join(', ')}}, en {{alerts[-1]}}
            {%- endif %}

… I am not sure though if this is just as efficient as using the custom_templates. as in, whether the attribute is evaluated only once, and not in every single config option of the template sensor

You’re missing () in your macro call