Reduce code / templating of configuration (sensor, platform)

Dear community,

i’ve tried to look up this problem/topic in different topics and external forums, but couldn’t be helped yet. How can I prevent repeated code? It is way too expensive (time to set it up and maintain it) to code the same code block repeated over and over again.

My case:
I have multiple rooms (total of 8) with the same sensor equipment of multisensor and window sensor for which I’d copy and pasted a bunch of code to add history stats, mold indicator and a template sensor:

  - platform: history_stats
    name: fenster_badezimmer_og_open_count_1d
    entity_id: binary_sensor.fenster_badezimmer_og
    state: 'on'
    type: count
    start: '{{ now().replace(hour=0, minute=0, second=0) }}'
    end: '{{ now() }}'

  - platform: history_stats
    name: fenster_arbeitszimmer_open_time_1d
    entity_id: binary_sensor.fenster_arbeitszimmer
    state: 'on'
    type: time
    start: '{{ now().replace(hour=0, minute=0, second=0) }}'
    end: '{{ now() }}'
  • You see that only the part of the name “xxx_open_count_1d” and part of entity_id “binary_sensor.xxx” needs to be manually put into a template piece of code. I would think of something like this:
  - platform: template
       platform: history_stats
       id: 'template-history-count'
       name: {{entity_input}}_open_count_1d
       entity_id: binary_sensor.{{entity_input}}
       state: 'on'
       type: count
       start: '{{ now().replace(hour=0, minute=0, second=0) }}'
       end: '{{ now() }}'

  - platform: template
    id: 'template-history-count'
    entity_input:  fenster_badezimmer_og

  - platform: template
    id: 'template-history-count'
    entity_input: fenster_arbeitszimmer

The next case is even more code-intensiv:

  # Badezimmer OG
  - platform: mold_indicator
    name: Schimmelrisiko - Badezimmer OG
    indoor_temp_sensor: sensor.multi_badezimmer_og_temperature
    indoor_humidity_sensor: sensor.multi_badezimmer_og_humidity
    outdoor_temp_sensor: sensor.openweathermap_temperature
    calibration_factor: 1.5

  - platform: template
    sensors:
      schimmelrisikoklasse_badezimmer_og:
        friendly_name: "Schimmelrisikoklasse - Badezimmer OG"
        value_template: >-
          {% if states('sensor.schimmelrisiko_badezimmer_og')|float > 65%}
            Normal
          {% elif states('sensor.schimmelrisiko_badezimmer_og')|float > 70 %}
            Erhöht
          {% elif states('sensor.schimmelrisiko_badezimmer_og')|float > 80 %}
            Kritisch
          {% else %}
            Gering
          {% endif %}
  • Everything stays the same, except of course the name of the sensor based on each room and possibly the calibration_factor. This would be a huge help, otherwise i have at least 1000 lines of code for this simple setup.

I’ve never used them so I can’t really help with questions but you could probably use “yaml anchors”.

Google that and it should get you started

Sorry but why not create a package for each room.
The first room is hand written the next room just copied with a file name change then use search and replace on the entities, you did use a structure for your entity naming didn’t you ?

Yes, you are right - i just structured naming. Your idea is a first simple solution to my problem.
But still it would be quite expensive to change e.g. the if-elif-else thresholds. In your case, if I wanted to change my limits from 80% to 85% would need to do it on all *.yaml files.

Would be best to have it in one place, and then refer to it from each other area/group.

So put ALL those sensors in one file to create the values used in other packages.

I’m not sure what the purpose of this thread is meant to be.
Do you want a solution or just to nit pick small issues ?
This can be done (I do it) if the software could read your mind it would be a lot easier.

That ain’t gonna happen so make the best use of the alternatives you have

Did you look at my suggestion?

I think it will do what you want.

I dont want to nit pick small issues. With your first proposal i would make my life easier, but I was challenging for a better solution. Thought it would be possible. My main aim is to check, if it is currently possible to create complex templates, e.g. simply to clone same settings via an easy call of that template. That this is based on a mold_indicator is just an example, i want to reuse the same concept for other sensors as well:

  ####### My desired template:
  - platform: mold_indicator
    name: Mold - {{ input_sensor }}
    indoor_temp_sensor: {{ input_sensor }}_temperature
    indoor_humidity_sensor: sensor.{{ input_sensor }}_humidity
    outdoor_temp_sensor: sensor.openweathermap_temperature
    calibration_factor: {{ input_factor }}

  - platform: template
    sensors:
      mold_risk_class_{{ input_sensor }}:
        friendly_name: "Mold risk class - {{ input_sensor }}"
        value_template: >-
          {% if states('sensor.{{ input_sensor }}')|float > 65%}
            Normal
          {% elif states('sensor.{{ input_sensor }}')|float > 70 %}
            Erhöht
          {% elif states('sensor.{{ input_sensor }}')|float > 80 %}
            Kritisch
          {% else %}
            Gering
          {% endif %}

  ####### Call that template function:
  - platform:
    template:
       input_sensor: livingroom
       input_factor: 1.5
  - platform:
    template:
       input_sensor: bedroom
       input_factor: 1.8

That concept allows all adjustments on the e.g. if-elif-else statements to be done in one file. I will try both of your ideas @finity and @Mutt.

1 Like

I would drop this and replace it by a Gauge Card in the UI with thresholds and colors Because this „sensor“ is not about gathering data, but visualization

Finity,
I don’t use yaml anchors, my primary thoughts about their use would be to help with repetitive code used in lovelace ??? (please correct me)

The reason I think there would be an issue here is that the OP wants to have the same automations, sensors, helpers available across many rooms - all to the same pattern

So just for one example automation stolen at random from one of mine : -

  - alias: au_light_kitchenled_mosen_on
    mode: single
    max_exceeded: silent
    trigger:
      - platform: state
        entity_id: binary_sensor.bs_light_kitchen_motion
        to: 'on'
      - platform: state
        entity_id: light.fibdim2_kitf_level
        from: 'on'
        to: 'off'
      - platform: state
        entity_id: light.fibdim2_kitr_level
        from: 'on'
        to: 'off'
    condition:
      - condition: state
        entity_id: binary_sensor.bs_light_kitchen_motion
        state:  'on'
      - condition: state
        entity_id: binary_sensor.bs_aalight_mosen_nightslot
        state:  'on'
      - condition: state
        entity_id: light.fibrgbw441_kitn_level_wte
        state:  'off'
      - condition: state
        entity_id: light.fibrgbw441_kitn_level_mstr
        state:  'off'
      - condition: state
        entity_id: light.fibdim2_kitf_level
        state:  'off'
      - condition: state
        entity_id: light.fibdim2_kitr_level
        state:  'off'
    action:
      - service: input_boolean.turn_on
        entity_id: input_boolean.ib_light_kitchenled_mosen_triggered
      - service: light.turn_on
        entity_id: light.fibrgbw441_kitn_level_wte

So lets break this down into bits that could be directed from a yaml anchor and the specific entities that NEED to be in place to make this automation unique to ‘a particular room’

  - alias: au_light_kitchenled_mosen_on # text needed to make the automation unique
anchor01 - text common to all instances of this automation (but only the first part)
#    mode: single
#    max_exceeded: silent
#    trigger:
#      - platform: state  ## so 4 lines replaced with one anchor
        entity_id: binary_sensor.bs_light_kitchen_motion # specific to the entities in this room
anchor02 - text common to all instances of this automation (the second part)
#        to: 'on'
#      - platform: state ## so 2 lines replaced with one anchor
        entity_id: light.fibdim2_kitf_level # specific to the entities in this room
anchor03 - text common to all instances of this automation (the third part)
#        from: 'on'
#        to: 'off'
#      - platform: state ## so 3 lines replaced with one anchor
        entity_id: light.fibdim2_kitr_level # specific to the entities in this room
anchor04 - text common to all instances of this automation (the forth part)
#        from: 'on'
#        to: 'off'
#    condition:
#      - condition: state ## so 4 lines replaced with one anchor
        entity_id: binary_sensor.bs_light_kitchen_motion # specific to the entities in this room
anchor05 - text common to all instances of this automation (the fifth part)
#        state:  'on'
#      - condition: state ## so 2 lines replaced with one anchor
        entity_id: binary_sensor.bs_aalight_mosen_nightslot # specific to the entities in this room
anchor06 - text common to all instances of this automation (the sixth part)
#        state:  'on'
#      - condition: state ## so 2 lines replaced with one anchor
        entity_id: light.fibrgbw441_kitn_level_wte # specific to the entities in this room
anchor07 - text common to all instances of this automation (the seventh part)
#        state:  'off'
#      - condition: state ## so 2 lines replaced with one anchor
        entity_id: light.fibrgbw441_kitn_level_mstr # specific to the entities in this room
anchor08 - text common to all instances of this automation (the eigth part)
#        state:  'off'
#      - condition: state ## so 2 lines replaced with one anchor
        entity_id: light.fibdim2_kitf_level # specific to the entities in this room
anchor09 - text common to all instances of this automation (the ninth part)
#        state:  'off'
#      - condition: state ## so 2 lines replaced with one anchor
        entity_id: light.fibdim2_kitr_level # specific to the entities in this room
anchor10 - text common to all instances of this automation (the tenth part)
#        state:  'off'
#    action:
#      - service: input_boolean.turn_on ## so 3 lines replaced with one anchor
        entity_id: input_boolean.ib_light_kitchenled_mosen_triggered # specific to the entities in this room
anchor11 - text common to all instances of this automation (the eleventh part)
#      - service: light.turn_on ## so 1 line replaced with one anchor
        entity_id: light.fibrgbw441_kitn_level_wte # specific to the entities in this room

So lets see what this would look like : -

  - alias: au_light_kitchenled_mosen_on
      <<: anchor01hm-config
        entity_id: binary_sensor.bs_light_kitchen_motion
      <<: anchor02hm-config
        entity_id: light.fibdim2_kitf_level
      <<: anchor03hm-config
        entity_id: light.fibdim2_kitr_level
      <<: anchor04hm-config
        entity_id: binary_sensor.bs_light_kitchen_motion
      <<: anchor05hm-config
        entity_id: binary_sensor.bs_aalight_mosen_nightslot
      <<: anchor06hm-config
        entity_id: light.fibrgbw441_kitn_level_wte
      <<: anchor07hm-config
        entity_id: light.fibrgbw441_kitn_level_mstr
      <<: anchor08hm-config
        entity_id: light.fibdim2_kitf_level
      <<: anchor09hm-config
        entity_id: light.fibdim2_kitr_level
      <<: anchor10hm-config
        entity_id: input_boolean.ib_light_kitchenled_mosen_triggered
      <<: anchor11hm-config
        entity_id: light.fibrgbw441_kitn_level_wte

This is based on a pnbruckner example where he placed the referenced yaml in a single file, I admit that I may have gotten the syntax wrong as I have never actually used it (again please correct me)
But the result of the 39 line automation would be these 23 lines AND the reference yaml in the file (but that would be reused across all the relevant instances)

However, I couldn’t read this. I couldn’t fault find it and ‘if’ a breaking change came through I’d have no idea where to start.

This is a slightly confusing statement
So what I think you mean by this is (correct me if I’m wrong as the best way to ensure understanding is to restate a concept back to the originator and look for agreement) : -
You want to store a template that takes various entity states (different for each room) processes a statement/mathematical operation, spits out an answer (into each room) so that it can be used as a sensor/in an autmation/set a value ???

In short, the answer is no !

A template only lives for the duration of that template being processed (it’s result does not persist unless you have stored it in (say) an input number. This input number would be defined in the template and therefore would have to be unique and thus it defeats your objective)
You are able to define a ‘macro’ within a template and use it recursively within that template (eg adding units to distinct units : - “2 days 4 hours and 13 minutes”) but this is only within that template so will not survive.
What in essence you are looking for is to define a ‘function’ available to the jinja2 templating engine, to process as you wish. Not just once but for any process you wish, bdraco recently did exactly this with his as_local() function that converted any timestamp (assuming it was UTC (all HA generated timestamps are)) into a local timestamp using the TZ offset stored in the local instance.
He is a developer, with the ear of Paulus and he did it quite quickly but …
Extending this wider would bloat the code base, slowing HA etc. and would be impossible to keep track of.
So definately No

Yep, that would also work but inherently that does not allow the reuse of the defining code. it merely moves it from yaml, into the configuration of the card (probably in JSON within the .storage folder losing the ability to share the code with others) and would require coding for each instance (you could probably copy and tweak but that is what I’m suggesting above anyway)
However your suggestion has the benefit of presenting it nicely in the front end too - so a double win if that is the OP’s secondary goal

I agree with @Mutt here. This would just shift the “problem” into another area. On top, I use that template sensor not as a gauge but as an custom:multiple-entity-row:

type: entities
entities:
  - entity: binary_sensor.fenster_kinderzimmer
    type: 'custom:multiple-entity-row'
    name: Fenster
    secondary_info: last-changed
    state_color: true
    entities:
      - entity: sensor.fenster_kinderzimmer_open_count_1d
        name: Öffnungen (1d)
        tap_action:
          action: none
      - entity: sensor.fenster_kinderzimmer_open_time_1d
        name: Zeit (1d)
  - entity: sensor.schimmelrisikoklasse_kinderzimmer
    type: 'custom:multiple-entity-row'
    icon: 'mdi:air-filter'
    name: Schimmelrisiko
    secondary_info: last-changed
    state_color: true
    entities:
      - entity: sensor.schimmelrisiko_kinderzimmer
        name: Indikator
      - entity: sensor.schimmelrisiko_kinderzimmer
        attribute: dewpoint
        name: Taupunkt
        unit: °C
        format: precision1
        tap_action:
          action: none
      - entity: sensor.schimmelrisiko_kinderzimmer
        attribute: estimated_critical_temp
        name: Krit. Temp
        unit: °C
        format: precision1
        tap_action:
          action: none

that’s true but I don’t think it is limited only to Lovelace. It should work for all yaml configurations.

I’m not sure where it ewas specifically stated that it was to be over several rooms (maybe wishful thinking on your part since that seems to be the go to example in your quest to spread the “package gospel” around here :wink:) but I don’t thyink that would matter.

the “all to the same pattern” is the important bit. And I believe that’s what yaml anchors can do.
But again, I don’t use them either so I can’t say for absolute sure that it would solve the situation.

To my understanding you can use yaml anchors in any file you want. You can even use them in your beloved packages… :slightly_smiling_face:

But. no. I don’t know that I would necessarily use them in any automations. But maybe I would if I knew more about their use.

But anyway, I’ve offered my suggestions and I’m not sure the OP has looked into it yet.

So I guess I’m done here.

1 Like