Packages - how to avoid repeatable code

After 2 years of using HA and collecting a lot of self-made entities (manually configured mqtt ones + related templates) I decided to look at options of get rid of repeating code.

Here is an example of package containing declaration of window sensor (Shelly DW) through MQTT providing open/close and tilt combined into single entity, as well as battery sensor.

pck_dw_bathroom:
  binary_sensor:
    - platform: mqtt
      name: "Window Bathroom State"
      state_topic: "shellies/shellydw-bathroom/sensor/state"
      payload_on: "open"
      payload_off: "close"
      device_class: "window"
    - platform: template
      sensors:
        window_bathroom:
          friendly_name: Window Bathroom
          device_class: "window"
          value_template: >-
              {{ is_state("binary_sensor.window_bathroom_state", "on") }}
          icon_template: >-
              {% if is_state("binary_sensor.window_bathroom_state", "off") %}
                mdi:window-closed
              {% elif states('sensor.window_bathroom_tilt')|float > 2 %}
                mdi:angle-acute
              {% else %}
                mdi:window-open
              {% endif %}
          attribute_templates:
              battery: >-
                {{ state_attr('sensor.window_bathroom_battery','battery') }}
              tilt: >-
                {{ state_attr('binary_sensor.window_bathroom_tilt','tilt') }}
              count: >-
                {{ state_attr('sensor.doorwindow_counter','counters')['window_bathroom_state'] }}
  sensor:
    - platform: mqtt
      name: "Window Bathroom Battery"
      state_topic: "shellies/shellydw-bathroom/sensor/battery"
      unit_of_measurement: "%"
      force_update: true
      device_class: battery
    - platform: mqtt
      name: "Window Bathroom Tilt"
      state_topic: "shellies/shellydw-bathroom/sensor/tilt"
      unit_of_measurement: "degrees"

I have lot of such sensors. All differs only by names (entity and mqtt topic ones) and optionally by tilt angle.
Is there any way to maintain somehow customized single snippet of such code?
I tried to find any information of using yaml anchors but seems this method is not feasible. I though it’s possible to set anchors values on top of file, then include the “template”, but when having more such files with anchors named the same way all templates are filled with the same values (so anchors doen’t work like local variables for the file they are set in)

What are your workflows to cope with similar patterns?

with regards

use a template to write the code for you in the template editor.

Also, why don’t you just make a cover out of that?

{% set my_configs = [
  ("Window Bathroom","window_bathroom","shellies/shellydw-bathroom/sensor"),
  ("Window Livingroom","window_livingroom","shellies/shellydw-livingroom/sensor"),
  ("Window Kitchen","window_kitchen","shellies/shellydw-kitchen/sensor"),
] %}

# configs to paste

{% set text = '
  binary_sensor:
    - platform: mqtt
      name: "{0} State"
      state_topic: "{2}/state"
      payload_on: "open"
      payload_off: "close"
      device_class: "window"
    - platform: template
      sensors:
        {1}:
          friendly_name: {0}
          device_class: "window"
          value_template: >-
              {{{{ is_state("binary_sensor.{1}_state", "on") }}}}
          icon_template: >-
              {{% if is_state("binary_sensor.{1}_state", "off") %}}
                mdi:window-closed
              {{% elif states("sensor.{1}_tilt")|float > 2 %}}
                mdi:angle-acute
              {{% else %}}
                mdi:window-open
              {{% endif %}}
          attribute_templates:
              battery: >-
                {{{{ state_attr("sensor.{1}_battery","battery") }}}}
              tilt: >-
                {{{{ state_attr("binary_sensor.{1}_tilt","tilt") }}}}
              count: >-
                {{{{ state_attr("sensor.doorwindow_counter","counters")["{1}_state"] }}}}
  sensor:
    - platform: mqtt
      name: {0} Battery
      state_topic: "{2}/battery"
      unit_of_measurement: "%"
      force_update: true
      device_class: battery
    - platform: mqtt
      name: {0} Tilt
      state_topic: "{2}/tilt"
      unit_of_measurement: "degrees"
' %}
{% for name, id, topic in my_configs %}
  {{ text.format(name, id, topic) }}
{% endfor %}

output:

# configs to paste



  
  binary_sensor:
    - platform: mqtt
      name: "Window Bathroom State"
      state_topic: "shellies/shellydw-bathroom/sensor/state"
      payload_on: "open"
      payload_off: "close"
      device_class: "window"
    - platform: template
      sensors:
        window_bathroom:
          friendly_name: Window Bathroom
          device_class: "window"
          value_template: >-
              {{ is_state("binary_sensor.window_bathroom_state", "on") }}
          icon_template: >-
              {% if is_state("binary_sensor.window_bathroom_state", "off") %}
                mdi:window-closed
              {% elif states("sensor.window_bathroom_tilt")|float > 2 %}
                mdi:angle-acute
              {% else %}
                mdi:window-open
              {% endif %}
          attribute_templates:
              battery: >-
                {{ state_attr("sensor.window_bathroom_battery","battery") }}
              tilt: >-
                {{ state_attr("binary_sensor.window_bathroom_tilt","tilt") }}
              count: >-
                {{ state_attr("sensor.doorwindow_counter","counters")["window_bathroom_state"] }}
  sensor:
    - platform: mqtt
      name: Window Bathroom Battery
      state_topic: "shellies/shellydw-bathroom/sensor/battery"
      unit_of_measurement: "%"
      force_update: true
      device_class: battery
    - platform: mqtt
      name: Window Bathroom Tilt
      state_topic: "shellies/shellydw-bathroom/sensor/tilt"
      unit_of_measurement: "degrees"


  
  binary_sensor:
    - platform: mqtt
      name: "Window Livingroom State"
      state_topic: "shellies/shellydw-livingroom/sensor/state"
      payload_on: "open"
      payload_off: "close"
      device_class: "window"
    - platform: template
      sensors:
        window_livingroom:
          friendly_name: Window Livingroom
          device_class: "window"
          value_template: >-
              {{ is_state("binary_sensor.window_livingroom_state", "on") }}
          icon_template: >-
              {% if is_state("binary_sensor.window_livingroom_state", "off") %}
                mdi:window-closed
              {% elif states("sensor.window_livingroom_tilt")|float > 2 %}
                mdi:angle-acute
              {% else %}
                mdi:window-open
              {% endif %}
          attribute_templates:
              battery: >-
                {{ state_attr("sensor.window_livingroom_battery","battery") }}
              tilt: >-
                {{ state_attr("binary_sensor.window_livingroom_tilt","tilt") }}
              count: >-
                {{ state_attr("sensor.doorwindow_counter","counters")["window_livingroom_state"] }}
  sensor:
    - platform: mqtt
      name: Window Livingroom Battery
      state_topic: "shellies/shellydw-livingroom/sensor/battery"
      unit_of_measurement: "%"
      force_update: true
      device_class: battery
    - platform: mqtt
      name: Window Livingroom Tilt
      state_topic: "shellies/shellydw-livingroom/sensor/tilt"
      unit_of_measurement: "degrees"


  
  binary_sensor:
    - platform: mqtt
      name: "Window Kitchen State"
      state_topic: "shellies/shellydw-kitchen/sensor/state"
      payload_on: "open"
      payload_off: "close"
      device_class: "window"
    - platform: template
      sensors:
        window_kitchen:
          friendly_name: Window Kitchen
          device_class: "window"
          value_template: >-
              {{ is_state("binary_sensor.window_kitchen_state", "on") }}
          icon_template: >-
              {% if is_state("binary_sensor.window_kitchen_state", "off") %}
                mdi:window-closed
              {% elif states("sensor.window_kitchen_tilt")|float > 2 %}
                mdi:angle-acute
              {% else %}
                mdi:window-open
              {% endif %}
          attribute_templates:
              battery: >-
                {{ state_attr("sensor.window_kitchen_battery","battery") }}
              tilt: >-
                {{ state_attr("binary_sensor.window_kitchen_tilt","tilt") }}
              count: >-
                {{ state_attr("sensor.doorwindow_counter","counters")["window_kitchen_state"] }}
  sensor:
    - platform: mqtt
      name: Window Kitchen Battery
      state_topic: "shellies/shellydw-kitchen/sensor/battery"
      unit_of_measurement: "%"
      force_update: true
      device_class: battery
    - platform: mqtt
      name: Window Kitchen Tilt
      state_topic: "shellies/shellydw-kitchen/sensor/tilt"
      unit_of_measurement: "degrees"

EDIT Then just keep a copy of the commented out template in a file so you can update it in the future and add/remove crap from it.

1 Like

Thank you for that idea. I hoped in something similar evaluated by HA in runtime (on its start). Something like loop in Ansible.

Your way might definitively help but is sensitive to loosing the source template :wink:

You can try to use lovelace_gen

Liked the idea, and I want to use this method in Lovelace card - to reduce a code.
Does it only work with Lovelace gen?
Tried to use this method in some Lovelace yaml file for some view, got an error…

Only works with Lovelace gen might work with Lovelace gen. Lovelace gen is for Lovelace not the normal configuration. Although it might work for the regular config

Thank you!

Then I start using it in config first…
Could you clarify this:

      value_template: >-
              {{{{ is_state("binary_sensor.{1}_state", "on") }}}}
          icon_template: >-
              {{% if is_state("binary_sensor.{1}_state", "off") %}}
                mdi:window-closed
              {{% elif states("sensor.{1}_tilt")|float > 2 %}}
                mdi:angle-acute
              {{% else %}}
                mdi:window-open
              {{% endif %}}

Does it mean that in templates I must use {{% instead of {%, {{{{ instead of {{ ?

You have to escape {, and you do that by doubling it. However that will be slightly different with Lovelace gen, you should check out it’s docs because it’s limited in comparison to HA jinja.

Just tried it for my weather sensors.
There were 3 places, 3 weather providers = 3x3 = 9 files.
Now it is ONE file.
Thank you very much!

UPDATE:
Unfortunately, I got the idea in a wrong way…
I believed that I can put that code into some yaml-file and all sensors will be generated then from this code.
But - all this is just to generate the code in the Templates window & then to put the generated code into yaml-files… Surely, it saves lot of time for writing a code & checking it. But it would be great to pass the “Paste to Template window” stage)))

If the sensors are MQTT, then you can automate their definition in loops. The idea is to use an automation that runs for instance at home assistant start and sends MQTT discovery messages, that can create MQTT sensors on the fly.

An example that I use for todo-lists (I really should create a topic about those when I have time):

  automation:
    - id: parametragtodolistmqtt
      alias: Paramétrage TodoList MQTT
      description: ''
      mode: single
      trigger:
        - platform: homeassistant
          event: start
      condition: []
      action:
        - repeat:
            for_each:
              - name: croquettes
                duration: 8
              - name: draps_lucas
                duration: 312
              - name: draps_matisse
                duration: 312
              - name: draps_parents
                duration: 312
              - name: eponges
                duration: 168
              - name: filtre_sable
                duration: 744
            sequence:
              - variables:
                  payload:
                    device:
                      name: Todo List
                      identifiers: todolist
                    command_topic: "TodoList/Action"
                    payload_on: "DO {{repeat.item.name}}"
                    payload_off: "UNDO {{repeat.item.name}}"
                    state_topic: "TodoList/State/{{repeat.item.name}}"
                    state_on: "DONE"
                    state_off: "TODO"
                    json_attributes_topic: |
                      TodoList/LastDone/{{repeat.item.name}}
                    json_attributes_template: "
                      {{ '{{' }}
                        { 'duration': '{{repeat.item.duration}}',
                          'last_done': value,
                          'expiry': (value|as_datetime +
                                timedelta(hours={{repeat.item.duration}})
                                ).isoformat() }
                        | tojson
                      {{ '}}' }}"
                    unique_id: "todo_list_{{repeat.item.name}}"
                    name: "todo_{{repeat.item.name}}"
              - service: mqtt.publish
                data:
                  topic: |
                    homeassistant/switch/todo_{{repeat.item.name}}/config
                  payload: |
                    {{ payload | tojson }}

Note that to escape {{ the jinja documentation tells to use the {{ '{{' }} construct, or use {%raw%} markers if there are bigger chunks that you want to protect.

1 Like