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

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

Huh.

That worked - thank you!

Re the YAML - that’s a clever trick. I had thought “<<: &…” was effectively one thing, but actually it is two. “<<:” says insert the following indented parts (map) after it at that point, and there just happens to be a separate “&” anchor sitting there as well marking the following indented parts (map) for later re-use.

Interesting that I don’t see that sort of usage in very many YAML explainers.

Also interesting that HA complains about duplicate keys when given a ‘typical’ example of anchor usage such as:

default: &default-person
    name: Bill
    height: 175
    age: 5

person1:
    <<: *default-person
    age: 20

Anyways, learn something new every day. Thanks for your help!

1 Like

For anyone interested, final configuration here.

By using references to the state object & YAML anchors, aliases, and overrides we can declare a complex entity once, and then use that as a template for multiple additional entities.

Note, you’ll need to separately declare the required input boolean, text, and number entities as well.

This file (fans-config.yaml) is included with fan: !include fans-config.yaml in configuration.yaml:

- 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'
            - 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

      lounge_room_fan:
        <<: *hunter-fan-definition
        friendly_name: "Lounge Room Fan"
        unique_id: lounge_room_fan

#...etc etc...