Possibility to reduce the code

In my setup I have lots of template sensors with a similar "value_template", specified like this:

  - platform: template
    sensors:
      iiyama_1_ping_availability_status:
        value_template: "{%  set DEV_NAME = 'iiyama_1' -%}
                         {%- set SENSOR_NAME = 'device_tracker.' + DEV_NAME + '_ping_device_tracker' -%}
                         {%- if is_state(SENSOR_NAME,'home') -%}
                         {{ 'on' }}
                         {%- else -%}
                         {{ 'off' }}
                         {%- endif %}"
        device_class: connectivity

      iiyama_2_ping_availability_status:
        value_template: "{%  set DEV_NAME = 'iiyama_2' -%}
                         {%- set SENSOR_NAME = 'device_tracker.' + DEV_NAME + '_ping_device_tracker' -%}
                         {%- if is_state(SENSOR_NAME,'home') -%}
                         {{ 'on' }}
                         {%- else -%}
                         {{ 'off' }}
                         {%- endif %}"
        device_class: connectivity

      iiyama_wifi_ping_availability_status:
        value_template: "{%  set DEV_NAME = 'iiyama_wifi' -%}
                         {%- set SENSOR_NAME = 'device_tracker.' + DEV_NAME + '_ping_device_tracker' -%}
                         {%- if is_state(SENSOR_NAME,'home') -%}
                         {{ 'on' }}
                         {%- else -%}
                         {{ 'off' }}
                         {%- endif %}"
        device_class: connectivity

So, here the idea is:

  1. The 1st line contains a "set XXXX = YYYYY".
  2. Then the "XXXX" is used in the rest of the code for this entity.
  3. Since the code is same for all entities, then the only difference is that "XXXX" variable.

I wonder is it possible to reduce the code?
I was thinking about anchors, but here it does not seem possible.
For me it would be great to have some template which equal to something like this:

      iiyama_2_ping_availability_status:
        value_template: "{%  set DEV_NAME = 'iiyama_2' -%}
        <<: *rest_of_the_code

      iiyama_wifi_ping_availability_status:
        value_template: "{%  set DEV_NAME = 'iiyama_wifi' -%}
        <<: *rest_of_the_code

Have your tried the !include option by splitting?

Create value_template.yaml file with this inside it (ignore lack of indents, I’m on mobile).

"{% set DEV_NAME = 'iiyama_2' -%} {%- set SENSOR_NAME = 'device_tracker.' + DEV_NAME + '_ping_device_tracker' -%} {%- if is_state(SENSOR_NAME,'home') -%} {{ 'on' }} {%- else -%} {{ 'off' }} {%- endif %}"

And then use:
value_template: !include location/of/file/value_template.yaml

Not sure if it works, but I use !include for many things, like complete card setups and card-mod styles.

The problem is that this part must be different:

{% set DEV_NAME = 'iiyama_2' -%}

All sensors are similarly assigned by values - but source sensors are different.

Sure, you could use YAML anchors and aliases or you can reduce the value_template to a single line in each sensor. Copy-paste, change a character or two, done.

  - platform: template
    sensors:
      iiyama_1_ping_availability_status:
        value_template: "{{ 'on' if is_state('device_tracker.iiyama_1', 'home') else 'off' }}"
        device_class: connectivity
      iiyama_2_ping_availability_status:
        value_template: "{{ 'on' if is_state('device_tracker.iiyama_2', 'home') else 'off' }}"
        device_class: connectivity
      ... etc ...

Yes, it is possible, but I wanted to have:

  • unique part
  • standard part

This is simpler for me to avoid errors.
Also, this particular example is quite simple, but other case are more complex; sometimes I needed to use 2…3 “set” variables… And in these cases I would like to not to write the expression as one line.

I do not understand how to use anchors here since each "value_template" contains of “unique” part & “standard part”.

I posted an example here:

As I understood you used the method “Merging while defining” described here: Thomas Lovén - YAML for Non-programmers
But how to use this method for that “unique” & “standard” parts?

Should it be smth like:

      iiyama_2_ping_availability_status:
        value_template: "{%  set DEV_NAME = 'iiyama_2' -%}
                         <<: &rest_of_the_code {%- set SENSOR_NAME = 'device_tracker.' + DEV_NAME + '_ping_device_tracker' -%}
                         {%- if is_state(SENSOR_NAME,'home') -%}
                         {{ 'on' }}
                         {%- else -%}
                         {{ 'off' }}
                         {%- endif %}"

      iiyama_wifi_ping_availability_status:
        value_template: "{%  set DEV_NAME = 'iiyama_wifi' -%}
                         <<: *rest_of_the_code

The method I used is part of the YAML standard. You can use anchors and aliases to substitute an entire YAML key like device_class. However it cannot be used for bits and pieces of Jinja2 within a YAML key (like what you did in your previous post).
Jinja2 is not YAML.

Jinja2 does support macros but, at least for the implementation of Jinja2 in Home Assistant, the macros can only reside within an option and cannot be global.

Ah I see now. Unfortunately I don’t know of other methods. I really wish we had something like auto-entities and decluttering-card, but not for Lovelace cards, but for configuration.yaml. Auto create sensors with just one piece of code is the dream…

yes, this can not be done right now, and the wait really is for template variables, which have been asked for quite a few times…
Ive had the exact same issue with these:

      frontdoor_sensor_light_level_raw:
        value_template: >
          {{state_attr('sensor.frontdoor_sensor_light_level','lightlevel')}}
#        friendly_name_template: >
#          Frontdoor:
#          {% set light_level = state_attr('sensor.frontdoor_sensor_light_level','lightlevel')|int %}
#          {% if light_level < 1 %} dark
#          {% elif light_level < 3000 %} bright moonlight
#          {% elif light_level < 10000 %} night light
#          {% elif light_level < 17000 %} dimmed light
#          {% elif light_level < 22000 %} 'cosy' living room
#          {% elif light_level < 25500 %} 'normal' non-task light
#          {% elif light_level < 28500 %} working / reading
#          {% elif light_level < 33000 %} inside daylight
#          {% elif light_level < 40000 %} maximum to avoid glare
#          {% elif light_level < 51000 %} clear daylight
#          {% else %} direct sunlight
#          {% endif %}

      attic_sensor_light_level_raw:
        value_template: >
          {{state_attr('sensor.attic_sensor_light_level','lightlevel')}}
#        friendly_name_template: >
#          Attic:
#          {% set light_level = state_attr('sensor.attic_sensor_light_level','lightlevel')|int %}
#          {% if light_level < 1 %} dark
#          {% elif light_level < 3000 %} bright moonlight
#          {% elif light_level < 10000 %} night light
#          {% elif light_level < 17000 %} dimmed light
#          {% elif light_level < 22000 %} 'cosy' living room
#          {% elif light_level < 25500 %} 'normal' non-task light
#          {% elif light_level < 28500 %} working / reading
#          {% elif light_level < 33000 %} inside daylight
#          {% elif light_level < 40000 %} maximum to avoid glare
#          {% elif light_level < 51000 %} clear daylight
#          {% else %} direct sunlight
#          {% endif %}

that have the huge section copied on all 24 sensors… and only the naming specific for each sensor.

custom-ui to the rescue though :wink:

    sensor.*_sensor_light_level_raw:
      templates:
        icon_color: >
          if (state < 1) return 'black';
          if (state < 3000) return 'maroon';
          if (state < 10000) return 'firebrick';
          if (state < 17000) return 'orange';
          if (state < 22000) return 'green';
          if (state < 25500) return 'gold';
          if (state < 28500) return 'teal';
          if (state < 33000) return 'dodgerblue';
          if (state < 40000) return 'lightskyblue';
          if (state < 40000) return 'lightblue';
          return 'lightcyan';
        friendly_name: >
          function capitalizeFirstLetter(string) {
              return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
              }
          var id = entity.entity_id.split('.')[1].split('_sensor_light_level_raw')[0].replace(/_/g,' ');
          var id = capitalizeFirstLetter(id);
          if (state < 1) return id + ': dark';
          if (state < 3000 ) return id + ': bright moonlight';
          if (state < 10000) return id + ': night light';
          if (state < 17000) return id + ': dimmed light';
          if (state < 22000) return id + ': \'cosy\' living room';
          if (state < 25500 ) return id + ': \'normal\' non-task light';
          if (state < 28500) return id + ': working / reading';
          if (state < 33000) return id + ': inside daylight';
          if (state < 40000) return id + ': maximum to avoid glare';
          if (state < 51000) return id + ': clear daylight';
          return id + ': direct sunlight';
      unit_of_measurement: lm
      device_class: illuminance

I know, custom-ui is not official, and designed for HA States, so ancient. But it still operates beautifully. And if to were to stop one day, I have all my core templates ready to un-comment :wink:

Sometimes it can help to rethink things, and use different tools to get almost the same outcome:

auto-entities and template-entity-row (which can use a template variable…) to the rescue:

  - type: custom:auto-entities
    card:
      type: entities
      title: Philips light level raw sensors
      show_header_toggle: false
    filter:
      include:
        - entity_id: '*_sensor_light_level'
          options:
            type: custom:template-entity-row
            name: >
              {% if state_attr(config.entity,'friendly_name') is not none %}
              {{state_attr(config.entity,'friendly_name').split(' sensor light level')[0]}}
              {% else %} Tbd
              {% endif %}
            state: >
              {{state_attr(config.entity,'lightlevel')}} lm
            secondary: >
              {% set light_level = state_attr(config.entity,'lightlevel')|int %}
              {% if light_level < 1 %} dark
              {% elif light_level < 3000 %} bright moonlight
              {% elif light_level < 10000 %} night light
              {% elif light_level < 17000 %} dimmed light
              {% elif light_level < 22000 %} 'cosy' living room
              {% elif light_level < 25500 %} 'normal' non-task light
              {% elif light_level < 28500 %} working / reading
              {% elif light_level < 33000 %} inside daylight
              {% elif light_level < 40000 %} maximum to avoid glare
              {% elif light_level < 51000 %} clear daylight
              {% else %} direct sunlight
              {% endif %}
    sort:
      method: state
      numeric: true

Very bad. Otherwise I could create a macros for testing an entity’s state for “unavailable” etc values and then use it as a source value like I do it now without macroses…

It could be used for choosing icons, changing names - but cannot be used for "value_template" ((

actually it can (customize state)

homeassistant:
  customize:
    binary_sensor.my_sensor:
      templates:
        state: if (state == 'on') return 'activated'; else return state;

but I would advice against that as it possibly messes with the HA states machine.

What this boils down to is the need for template variables, so we can use that in all templates of the template sensor, and not have to declare the full template over and over again.

would template-entity-row not be useful for your use case?

I agree it would be useful to allow users to create global Jinja2 macros (the concept is supported by Jinja2, just not in Home Assistant’s implementation of Jinja2). However, in fairness, Home Assistant doesn’t currently support the concept of anything “global” (not macros or variables or anything along those lines). The recent introduction of local variables and trigger_variables is a promising step in that direction so maybe in the future we will see global versions.

FWIW, I would like to have local variables in a Template Sensor. Once that is implemented, it could eventually evolve into a global version.


EDIT

Disclaimer: Maybe there is a way to add global Jinja2 macros but I am simply unaware of how to do it. The Jinja2 interpreter has a configuration file and that’s where you can modify its behavior and add macros. However, I have never explored the system to locate the file and I imagine any changes would be overwritten during an upgrade (docker, etc).

I’d just write a template to write the templates for me… based on what you said…

This code can be simplified

                         {%  set DEV_NAME = 'iiyama_2' -%}
                         {%- set SENSOR_NAME = 'device_tracker.' + DEV_NAME + '_ping_device_tracker' -%}
                         {%- if is_state(SENSOR_NAME,'home') -%}
                         {{ 'on' }}
                         {%- else -%}
                         {{ 'off' }}
                         {%- endif %}

to

value_template: "{{ is_state('device_tracker.' ~ 'iiyama_2' ~ '_ping_device_tracker', 'home') }}"

but if we are generating the code it can be simplified further

value_template: "{{ is_state('device_tracker.iiyama_2_ping_device_tracker', 'home') }}"

then… in the template editor:

binary_sensor:
- platform: template
  sensors:
{%- for s in states.device_tracker if s.entity_id.endswith('ping_device_tracker') %}
{%- set name = s.object_id[:-20] %}
    {{ name }}_ping_availability_status:
      value_template: "{% raw %}{{{% endraw %} is_state('{{s.entity_id}}', 'home') {% raw %}}}{% endraw %}"
{%- endfor %}

then add whatever else you want to add.

Copy/Paste output and you’re done. Keep the script at the top of the file as a comment to reuse in the future.

this is nice indeed!
does it actually create the binary_sensors in HA, or merely in the template editor?

Not sure.
I have sensors (not template sensors):

  • 5 sets based on SNMP (for 2 routers, 3 switches);
  • 2 sets based on Netdata (for 2 linux hosts);
  • 2 sets based on Open hardware monitor (for 2 Win hosts);
  • 2 sets based on Fan (for 2 air venting machines);
    -… and so on…
    Every set includes 5…15 sensors.
    For every sensor I made a template sensor which repeats the source sensor except “unavailable” etc states - and here I have a repeating code which could be replaced by some macro.
    And these template sensors are used in Entities, in mini-graph-cards etc…
    So, that "template-entity-row" is not an option.

For now I would be happy to have a macro which works one-file-wide (not globally in HA).

Surely, it was made exactly like you said - but then I decided to separate templates to TWO parts - unique & standard. Then adding a new template sensor becomes easier:
1.Copy-paste a template.
2.Edit only this line: " {% set DEV_NAME = 'iiyama_2' -%}".
And for me it is easier to check by eyes only one simple line containing only “set” and “iiyama_2”.

This is a big job, thank you!!!
But - it helps to generate a new code, not to reduce whole code.
As a former programmer, I prefer to have ONE function and a lot of function’s calls instead of repeated code…

yes, no use there.
All we can do is upvote the FR Taras posted. Nothing else really helps, other than writing them verbosely…

There’s no concept of scope based on files. If you have a split configuration, everything is read on startup by the YAML processor and “flattened”.

1 Like