Clever way to define multiple nearly identical entities (just different names) in configuration?

Hi,

I’ve got a situation which I guess is fairly common however I haven’t been able to find any particularly clever solutions (yet). Hoping some YAML / macro / anchor / jinja / automation geniuses can help here :slight_smile:

Essentially I have a large number of devices which I need to define as fan template entities. The templates are fairly large and the only real differences between them are name & friendly name (which appear in multiple places, sometimes within a larger name, throughout the template). I’m really really hoping that there’s some clever way I can define these entities without copy/pasting 20+ times and manually editing - this would create a really large, unwieldy, and difficult to maintain config file.

I’ve managed to create a template fan for one of those that works really well.

With the way I’ve set up the template the only thing that needs to change between each fan instance is literally just the name (ie “master_bedroom” / “guest_bedroom” / “child1_bedroom” / “lounge01” etc etc etc). and friendly name (“Master Bedroom Fan” etc).

Whilst I could get some efficiencies by invoking scripts in the templates (ie for turn on/off), it’s really frustrating that I’d still have to duplicate a lot else.

Would it be possible to use scripting to create all these entities, or can I use some other ‘trick’ such as anchors, macros, automations etc?

Any & all help and hints much appreciated!

FYI, the template fan looks like this (each entity also relies on an additional input boolean and input number with the same naming convention). I guess it would be easy enough to write a script that takes a template and a list of names and creates a ginormous yaml file, but the problem is you still end up with a ginormous yaml file…:

(for extra credit, the fan speed calculation uses a constant that is just the speed_count divided by 100. Could we parameterise that as well???)

fan:
  - platform: template
    fans:
      master_bedroom_fan:
        friendly_name: "Master Bedroom Fan"
        unique_id: master_bedroom_fan

        speed_count: 6

        value_template: >
          {{ states('input_boolean.master_bedroom_fan_state') }}

        turn_on:
          - service: fan.set_percentage
            target:
              entity_id: fan.master_bedroom_fan
            data:
              percentage: "{{ states('input_number.master_bedroom_fan_percentage') }}"

        turn_off:
          - service: input_boolean.turn_off
            target:
              entity_id: input_boolean.master_bedroom_fan_state
          - service: remote.send_command
            target:
              entity_id: remote.linknlink01
            data:
              device: master_bedroom_fan
              num_repeats: 1
              delay_secs: 0.4
              hold_secs: 0
              command: turn_off

        direction_template: "{{states('input_text.master_bedroom_fan_direction')}}"
        set_direction:
          - service: input_text.set_value
            target:
              entity_id: input_text.master_bedroom_fan_direction
            data:
              value: "{{direction}}"
          - service: remote.send_command
            target:
              entity_id: remote.linknlink01
            data:
              device: master_bedroom_fan
              num_repeats: 1
              delay_secs: 0.4
              hold_secs: 0
              command: reverse

        percentage_template: >
          {{ states('input_number.master_bedroom_fan_percentage') if is_state('input_boolean.master_bedroom_fan_state', 'on') else 0 }}
        set_percentage:
          - service: input_boolean.turn_{{ 'on' if percentage > 0 else 'off' }}
            target:
              entity_id: input_boolean.master_bedroom_fan_state
          - service: input_number.set_value
            target:
              entity_id: input_number.master_bedroom_fan_percentage
            data:
              value: "{{ percentage }}"
          - service: remote.send_command
            target:
              entity_id: remote.linknlink01
            data:
              device: master_bedroom_fan
              num_repeats: 1
              delay_secs: 0.4
              hold_secs: 0
              command: >
                {% set speed_value = (percentage|float * 0.06)|round(0) %}
                {% if (speed_value|float) > 0 %}
                  speed_{{speed_value}}
                {% else %}
                  turn_off
                {% endif %}

I suggest you consider using YAML Anchors and Aliases.

Example

Way to create multiple sensors per device with template? - #7 by 123.

Thanks. Would be great to couple that with some sort of name substitution logic as well!

Is there a way, for instance, to use a macro (or similar) to pull in the entity name in other parts of that entity’s definition?

When Home Assistant starts, the YAML processor executes before the Jinja processor.

In other words, Jinja templates are evaluated after the YAML processor was used to load entity configurations. In addition, a template’s scope is limited to the YAML key where it’s defined; it can’t reference other YAML keys.

Just use your text editor to copy-paste multiple instances (employing anchors and aliases).

Aaaah. Makes sense. Thanks.

Any hints on how I can get an entity’s id, name, friendly name, and unique_id in a Jinja template? Is there some sort of entity object I can inspect?

Your question suggests you’re attempting to do something that may not work (due to the processing order of YAML first and Jinja second). For example, the value of a Template Sensor’s unique_id must be a string yet it appears you are trying to template it.

What I’m thinking, for instance, is rather than this:

turn_on:
          - service: fan.set_percentage
            target:
              entity_id: fan.master_bedroom_fan
            data:
              percentage: "{{ states('input_number.master_bedroom_fan_percentage') }}"

Do something like this instead:

turn_on:
          - service: fan.set_percentage
            target:
              entity_id: {{ *this_entity's_id* }}
            data:
              percentage: "{{ states('input_number.{{ the *master_bedroom_fan* part of this entity's id}}_percentage') }}"

Does that make sense?

If I can do that, then I can pretty much anchor the entire block.

This might work and only because those particular options support templates (and they’re evaluated after the entity has been configured):

        turn_on:
          - service: fan.set_percentage
            target:
              entity_id: '{{ this.entity_id }}'
            data:
              percentage: "{{ states('input_number.' ~ this.object_id ~'_percentage') }}"

this refers to the entity (i.e. to itself) and is a State Object. entity_id and object_id are properties of a State Object.


For future reference, you cannot nest templates like this:

"{{ states('input_number.{{ the *master_bedroom_fan* part of this entity's id}}_percentage') }}"
1 Like

This is superb - I define the fan once (master_bedroom_fan in this case) and then through the use of the above templating & YAML anchors definition of the remaining fans is done in just 3 lines (not counting the additional input_boolean & input_text entities, but they’re trivial definitions so copy/pasta is not an issue):

     spare_room_fan:
        <<: *hunter-fan-definition
        friendly_name: "Spare Room Fan"
        unique_id: spare_room_fan

One minor issue with this - I get warnings in HA Core Log for the overrides (though everything works fine!):

YAML file /config/configuration.yaml contains duplicate key "friendly_name". Check lines 178 and 253
YAML file /config/configuration.yaml contains duplicate key "unique_id". Check lines 179 and 254

I guess I can just ignore that…

Final challenge:
I use the number of speeds that the fan has in two places. The first is a simple kvp:

      speed_count: 6

…and the second is used in a formula (using speed count divided by 100 - the constant that I multiply by percentage):

      command: >
                {% set speed_value = (percentage|float * 0.06)|round(0) %}
                {% if (speed_value|float) > 0 %}
                  speed_{{speed_value}}
                {% else %}
                  turn_off
                {% endif %}

I thought I could use another alias for that, ie:

    speed_count: &fan-speed-increments 6

      command: >
                {% set speed_value = (percentage|float * *fan-speed-increments|float / 100)|round(0) %}
                {% if (speed_value|float) > 0 %}
                  speed_{{speed_value}}
                {% else %}
                  turn_off
                {% endif %}

… but this doesn’t work. I get error:

Master Bedroom Fan: Error executing script. Error for call_service at pos 1: Error rendering data template: ValueError: Template error: float got invalid input '*fan-speed-increments' when rendering template '{% set speed_value = (percentage|float * '*fan-speed-increments'|float / 100)|round(0) %} {% if (speed_value|float) > 0 %} speed_{{speed_value}} {% else %} turn_off {% endif %}' but no default was specified

Any tips for that?

Finally, FYI, this is the whole piece (for now):

fan:
  - platform: template
    fans:
      master_bedroom_fan:  &hunter-fan-definition
        friendly_name: "Master Bedroom Fan"
        unique_id: master_bedroom_fan

        speed_count: &fan-speed-increments 6

        value_template: "{{ states('input_boolean.' ~ this.object_id ~'_state') }}"

        turn_on:
          - service: fan.set_percentage
            target:
              entity_id: '{{ this.entity_id }}'
            data:
              percentage: "{{ states('input_number.' ~ this.object_id ~'_percentage') }}"

        turn_off:
          - service: input_boolean.turn_off
            target:
              entity_id: 'input_boolean.{{this.object_id}}_state'
          - service: remote.send_command
            target:
              entity_id: remote.linknlink01
            data:
              device: '{{ this.object_id }}'
              num_repeats: 1
              delay_secs: 0.4
              hold_secs: 0
              command: turn_off

        direction_template: "{{states('input_text.' ~ this.object_id ~'_direction')}}"
        set_direction:
          - service: input_text.set_value
            target:
              entity_id: 'input_text.{{this.object_id}}_direction'
            data:
              value: "{{direction}}"
          - service: remote.send_command
            target:
              entity_id: remote.linknlink01
            data:
              device: '{{ this.object_id }}'
              num_repeats: 1
              delay_secs: 0.4
              hold_secs: 0
              command: reverse

        percentage_template: "{{ states('input_number.' ~ this.object_id ~'_percentage') if is_state('input_boolean.' ~ this.object_id ~'_state', 'on') else 0 }}"
        set_percentage:
          - service: input_boolean.turn_{{ 'on' if percentage > 0 else 'off' }}
            target:
              entity_id: 'input_boolean.{{this.object_id}}_state'
          - service: input_number.set_value
            target:
              entity_id: 'input_number.{{this.object_id}}_percentage'
            data:
              value: "{{ percentage }}"
          - service: remote.send_command
            target:
              entity_id: remote.linknlink01
            data:
              device: '{{ this.object_id }}'
              num_repeats: 1
              delay_secs: 0.4
              hold_secs: 0
              command: >
                {% set speed_value = (percentage|float * 0.06)|round(0) %}
                {% if (speed_value|float) > 0 %}
                  speed_{{speed_value}}
                {% else %}
                  turn_off
                {% endif %}


      spare_room_fan:
        <<: *hunter-fan-definition
        friendly_name: "Spare Room Fan"
        unique_id: spare_room_fan


You shouldn’t ignore it. The warnings are legitimate.

In my linked example, the anchor is located at the start of what will be referenced by the alias (i.e. excludes what shouldn’t be repeated). Alternately, an alias can override a key in the anchor’s block (see the Override section here).

Because *fan-speed-increments is a YAML alias and you’re using it in a Jinja template. Anchors and aliases are a YAML concept and are not valid in Jinja templates.

Thanks to your hints I’ve worked out a different… better… way to calculate speed value:

{% set speed_value = (percentage|float / this.attributes.percentage_step|float)|round(0)|int %}

Ah… I had mistakenly thought that the YAML processor would see that first & replace it with the value ‘6’ that’s then passed to Jinja…

That would require the YAML processor to inspect the contents of Jinja templates and have a sufficient understanding of Jinja to pick apart YAML from Jinja. It doesn’t do that.

Hmmmm… I’ve located the anchor at the start of the block to be referenced by the alias, and - as far as I can tell - I’m using the overrides as per the doc you referred.

Everything seems to work… I just see a warning in the log.

Here is where I’m at now (alias & overrides right down the bottom):

fan:
  - platform: template
    fans:
      master_bedroom_fan:  &hunter-fan-definition
        friendly_name: "Master Bedroom Fan"
        unique_id: master_bedroom_fan

        speed_count: 6

        value_template: "{{ states('input_boolean.' ~ this.object_id ~'_state') }}"

        turn_on:
          - service: fan.set_percentage
            target:
              entity_id: '{{ this.entity_id }}'
            data:
              percentage: "{{ states('input_number.' ~ this.object_id ~'_percentage') }}"

        turn_off:
          - service: input_boolean.turn_off
            target:
              entity_id: 'input_boolean.{{this.object_id}}_state'
          - service: remote.send_command
            target:
              entity_id: remote.linknlink01
            data:
              device: '{{ this.object_id }}'
              num_repeats: 1
              delay_secs: 0.4
              hold_secs: 0
              command: turn_off

        direction_template: "{{states('input_text.' ~ this.object_id ~'_direction')}}"
        set_direction:
          - service: input_text.set_value
            target:
              entity_id: 'input_text.{{this.object_id}}_direction'
            data:
              value: "{{direction}}"
          - service: remote.send_command
            target:
              entity_id: remote.linknlink01
            data:
              device: '{{ this.object_id }}'
              num_repeats: 1
              delay_secs: 0.4
              hold_secs: 0
              command: reverse

        percentage_template: "{{ states('input_number.' ~ this.object_id ~'_percentage') if is_state('input_boolean.' ~ this.object_id ~'_state', 'on') else 0 }}"
        set_percentage:
          - service: input_boolean.turn_{{ 'on' if percentage > 0 else 'off' }}
            target:
              entity_id: 'input_boolean.{{this.object_id}}_state'
          - service: input_number.set_value
            target:
              entity_id: 'input_number.{{this.object_id}}_percentage'
            data:
              value: "{{ percentage }}"
          - service: remote.send_command
            target:
              entity_id: remote.linknlink01
            data:
              device: '{{ this.object_id }}'
              num_repeats: 1
              delay_secs: 0.4
              hold_secs: 0
              command: >
                {% set speed_value = (percentage|float / this.attributes.percentage_step|float)|round(0)|int %}
                {% if (speed_value|int) > 0 %}
                  speed_{{speed_value}}
                {% else %}
                  turn_off
                {% endif %}


      spare_room_fan:
        <<: *hunter-fan-definition
        friendly_name: "Spare Room Fan"
        unique_id: spare_room_fan


Review the example in my very first reply. Take note of where the anchor is defined. Namely the location of this thing:

    <<: &dimmer

Sorry - this notation is confusing me… &dimmer is the anchor, <<: is saying insert here… but that’s what you use with aliases… non?

I thought the anchor was purely defined by &… and the override alias by <<: *…

I can’t find any examples or explanations of <<: &dimmer type notation.

Obviously have a lot to learn… :crazy_face:

More examples:

1 Like

These examples are very clear, thank you @123!

The weird thing is not only can I not find any formal documentation that describes the "<<: &" notation you are using (all I can find is ‘use “&…” as the anchor, and “<<: *…” as the override alias’), but when I try it with the Template Fan integration HA does not like it at all (developer / YAML check protests loudly).

I have tried:

fan:
  - platform: template
    fans:
      master_bedroom_fan:  
      <<: &hunter-fan-definition
        friendly_name: "Master Bedroom Fan"
        unique_id: master_bedroom_fan
fan:
  - platform: template
    fans:
      master_bedroom_fan:  
        <<: &hunter-fan-definition
        friendly_name: "Master Bedroom Fan"
        unique_id: master_bedroom_fan
fan:
  - platform: template
    fans:
      master_bedroom_fan:  
        friendly_name: "Master Bedroom Fan"
        unique_id: master_bedroom_fan
        <<: &hunter-fan-definition

and

fan:
  - platform: template
    fans:
      master_bedroom_fan:  <<: &hunter-fan-definition
        friendly_name: "Master Bedroom Fan"
        unique_id: master_bedroom_fan

… none work unfortunately.

The only thing that works is:

fan:
  - platform: template
    fans:
      master_bedroom_fan:  &hunter-fan-definition
        friendly_name: "Master Bedroom Fan"
        unique_id: master_bedroom_fan

This works well and allows me to create multiple additional fans (all of which work nicely) which only require a few lines of YAML each (plus the additional input boolean, number, and text required per fan):

      spare_room_fan:
        <<: *hunter-fan-definition
        friendly_name: "Spare Room Fan"
        unique_id: spare_room_fan

… the only downside is that HA complains about the overrides being duplicate keys. Is this maybe a bug in HA YAML implementation?

In your second example, indent all options under the anchor.

fan:
  - platform: template
    fans:
      master_bedroom_fan:
        friendly_name: "Master Bedroom Fan"
        unique_id: master_bedroom_fan
        <<: &hunter_fan_definition
          speed_count: 6
          value_template: "{{ states('input_boolean.' ~ this.object_id ~'_state') }}"
          turn_on:
            - service: fan.set_percentage
              target:
                entity_id: '{{ this.entity_id }}'
              data:
                percentage: "{{ states('input_number.' ~ this.object_id ~'_percentage') }}"
          turn_off:
            - service: input_boolean.turn_off
              target:
                entity_id: 'input_boolean.{{this.object_id}}_state'
          ... etc ...

      spare_room_fan:
        friendly_name: "Spare Room Fan"
        unique_id: spare_room_fan
        <<: *hunter_fan_definition

      some_other_room_fan:
        friendly_name: "Some Other Room Fan"
        unique_id: some_other_room_fan
        <<: *hunter_fan_definition