Is it not possible to concatenate some text in a template sensor with nested ifs and show it correctly in a markdown card?

I think that’s complaining about the template that creates the alle variable. That’s surprising (to me) because your original version references the current_position attribute of each one of the ten cover entities. Anyway, that’s the part I could not test on my system because I don’t have ten covers (only one cover and it doesn’t report its position as a percentage).

As an experiment, copy-paste this into the Template Editor. It should produce a list of ten numbers where each number represents a cover’s current_position.

          {% set alle = expand(
              'cover.master_bedroom', 'cover.voorraam_klein', 'cover.voorraam_groot',
              'cover.computer_kamer', 'cover.hal_1e_etage', 'cover.zijraam',
              'cover.badkamer', 'cover.logeerkamer', 'cover.achterdeur', 'cover.achterraam')
              | selectattr('attributes.current_position', 'defined')
              | map(attribute='current_position') | map('int', 0) | list %}
          {{ alle }}

It’s nearly identical to what you already tried but it also contains this line which only selects covers that have the current_position attribute defined.

              | selectattr('attributes.current_position', 'defined')

If it fails to produce a list of ten numbers then I’m not sure what’s the reason.

When I execute this in the template editor I get this error:

UndefinedError: ‘homeassistant.helpers.template.TemplateState object’ has no attribute ‘current_position’

Now that I see it with fresh eyes, the problem seems obvious: I didn’t use attributes.brightness in the first map filter. :man_facepalming:

Please try this corrected version and let me know if the result is a list of numbers.

          {% set alle = expand(
              'cover.master_bedroom', 'cover.voorraam_klein', 'cover.voorraam_groot',
              'cover.computer_kamer', 'cover.hal_1e_etage', 'cover.zijraam',
              'cover.badkamer', 'cover.logeerkamer', 'cover.achterdeur', 'cover.achterraam')
              | selectattr('attributes.current_position', 'defined')
              | map(attribute='attributes.current_position') | map('int', 0) | list %}
          {{ alle }}

Yes that works.

That’s good news! Hopefully that was the only mistake.

Here’s a revised version that incorporates the correction for attributes.current_position.

  - sensor:
      - name: "Rolluiken_status"
        state: >
          {%- macro staat(a, av, plaats, naam) -%}
          {%- set x = a | average -%}
          {%- if x in [0,100] -%}
          {{ plaats }} **{{'OPEN' if x == 100 else 'GESLOTEN'}}**.
          {%- elif a == av -%}
          {{ plaats }} **VENTILATIE**.
          {%- else -%}
          {%- set ns = namespace(s=[]) -%}
          {%- for i in range(0, a | length) -%}
          {%- set ns.s = ns.s + ['{}: **{}**'.format(naam[i], {100:'OPEN', 0:'GESLOTEN', av[i]:'VENTI'}.get(a[i], (100 - a[i]) ~ '%'))] -%}
          {%- endfor -%}
          {{ ns.s | join(' / ') }}
          {%- endif -%}
          {%- endmacro -%}

          {% set alle = expand(
              'cover.master_bedroom', 'cover.voorraam_klein', 'cover.voorraam_groot',
              'cover.computer_kamer', 'cover.hal_1e_etage', 'cover.zijraam',
              'cover.badkamer', 'cover.logeerkamer', 'cover.achterdeur', 'cover.achterraam')
              | selectattr('attributes.current_position', 'defined')
              | map(attribute='attributes.current_position') | map('int', 0) | list %}

          {% set alle_ventilatie = expand(
              'input_number.master_bedroom_cover_ventilatie_positie', 'input_number.voorraam_klein_cover_ventilatie_positie', 'input_number.voorraam_groot_cover_ventilatie_positie',
              'input_number.computer_kamer_cover_ventilatie_positie', 'input_number.hal_1e_etage_cover_ventilatie_positie', 'input_number.zijraam_cover_ventilatie_positie',
              'input_number.badkamer_cover_ventilatie_positie', 'input_number.logeerkamer_cover_ventilatie_positie',
              'input_number.achterdeur_cover_ventilatie_positie', 'input_number.achterraam_ventilatie_positie')
              | map('int', 0) | list %}

          {%- set x = alle | average -%}
          {%- if x in [0,100] -%}
          Alle rolluiken zijn **{{'OPEN' if x == 100 else 'GESLOTEN'}}**.
          {%- else -%}
          {{ staat(alle[0:3], alle_ventilatie[0:3], 'Voorkant', ['Master', 'Klein', 'Groot']) }}
          {{ staat(alle[3:6], alle_ventilatie[3:6], 'Westkant', ['Computerkr', 'Hal', 'Zijraam']) }}
          {{ staat(alle[6:], alle_ventilatie[6:], 'Acterkant', ['Badkamer', 'Logeerkamer', 'Achterdeur', 'Achterraam']) }}
          {%- endif -%}

I don’t get an error, but the states are not correct:

Can you give me an example of what it reports compared to what your original version reports (or do you no longer wish to continue with this experiment)?

Sorry for the late reaction:

You see 3 blocks:

  • The first one is with 1 combined sensor. It is correct
  • The second one is the version with 4 seperate sensors.
  • The tirth one is the marco version and is not correct. So it seems to be that there are items swapped.

What’s not correct, you have to understand that people don’t know what that language is, and to me everything looks wrong in all 3 versions. So what’s swapped? The percentages against the names? The order of the names? The words? Elaborate please.

Voorkant GESLOTEN is wrong.

Voorkant → are the next 3 covers: Master + Klein + Groot

It should be: “Master: GESLOTEN / Klein: 61% / Groot: 62%”

It’s challenging to solve this puzzle (I don’t have ten cover entities for experimentation) but I think I know what is happening.

In the first two examples, they both agree that:

  • Groot: 62%
  • Klein: 61%
  • Zigraam: 63%

I have presented the list sorted alphabetically by name because I think that’s an important clue.

In the third example (my macro-based version) those three values appear as the last three results for Logeerkamer, Achterdeur, and Achterraam (62%, 61%, 63%).

Look at the screenshot you posted earlier and notice that, if all the covers are sorted alphabetically by name, Groot, Klein, and Zigraam appear at the end of the list. Those last three positions are where we would expect to have Logeerkamer, Achterdeur, and Achterraam.

I think that’s why the values have been assigned to the wrong rooms because the list of covers in the alle variable, and the list of input_numbers in the alle_ventilatie variable, have been sorted alphabetically by name; they’re not in the original order shown within the expand function.

It would also explain why the macro-based version concluded that all covers in Voorkant are closed (Voorkant GESLOTEN) because, given a sorted list, the first three covers would be Achterdeur, Achterraam, and Badkamer. Those three are in fact closed and so it reports “Voorkant GESLOTEN”.

I don’t know why the list is being sorted but that seems like the reason why the values are being assigned to the wrong rooms.

Yes I think that indeed the sorting is the problem.

I just performed an experiment and can confirm that it’s the expand function that sorts the entities alphabetically.

Unfortunately (for me) that ruins the method I chose because it requires the covers to be in a very specific order (first three covers are for Voorkant, next three are for Westkant, and last four are for Achterkant).

As an alternative, this might work:

          {% set alle = [
              states.cover.master_bedroom, states.cover.voorraam_klein, states.cover.voorraam_groot,
              states.cover.computer_kamer, states.cover.hal_1e_etage, states.cover.zijraam,
              states.cover.badkamer, states.cover.logeerkamer, states.cover.achterdeur, states.cover.achterraam]
              | selectattr('attributes.current_position', 'defined')
              | map(attribute='attributes.current_position') | map('int', 0) | list %}

You didn’t wait long to try this one out eh? :slight_smile:

Try this version. It replaces the expand with a list.

  - sensor:
      - name: "Rolluiken_status"
        state: >
          {%- macro staat(a, av, plaats, naam) -%}
          {%- set x = a | average -%}
          {%- if x in [0,100] -%}
          {{ plaats }} **{{'OPEN' if x == 100 else 'GESLOTEN'}}**.
          {%- elif a == av -%}
          {{ plaats }} **VENTILATIE**.
          {%- else -%}
          {%- set ns = namespace(s=[]) -%}
          {%- for i in range(0, a | length) -%}
          {%- set ns.s = ns.s + ['{}: **{}**'.format(naam[i], {100:'OPEN', 0:'GESLOTEN', av[i]:'VENTI'}.get(a[i], (100 - a[i]) ~ '%'))] -%}
          {%- endfor -%}
          {{ ns.s | join(' / ') }}
          {%- endif -%}
          {%- endmacro -%}

          {% set alle = [
              states.cover.master_bedroom, states.cover.voorraam_klein, states.cover.voorraam_groot,
              states.cover.computer_kamer, states.cover.hal_1e_etage, states.cover.zijraam,
              states.cover.badkamer, states.cover.logeerkamer, states.cover.achterdeur, states.cover.achterraam ]
              | selectattr('attributes.current_position', 'defined')
              | map(attribute='attributes.current_position') | map('int', 0) | list %}

          {% set alle_ventilatie = [
              states.input_number.master_bedroom_cover_ventilatie_positie, states.input_number.voorraam_klein_cover_ventilatie_positie, states.input_number.voorraam_groot_cover_ventilatie_positie,
              states.input_number.computer_kamer_cover_ventilatie_positie, states.input_number.hal_1e_etage_cover_ventilatie_positie, states.input_number.zijraam_cover_ventilatie_positie,
              states.input_number.badkamer_cover_ventilatie_positie, states.input_number.logeerkamer_cover_ventilatie_positie,
              states.input_number.achterdeur_cover_ventilatie_positie, states.input_number.achterraam_ventilatie_positie ]
              | map('int', 0) | list %}

          {%- set x = alle | average -%}
          {%- if x in [0,100] -%}
          Alle rolluiken zijn **{{'OPEN' if x == 100 else 'GESLOTEN'}}**.
          {%- else -%}
          {{ staat(alle[0:3], alle_ventilatie[0:3], 'Voorkant', ['Master', 'Klein', 'Groot']) }}
          {{ staat(alle[3:6], alle_ventilatie[3:6], 'Westkant', ['Computerkr', 'Hal', 'Zijraam']) }}
          {{ staat(alle[6:], alle_ventilatie[6:], 'Acterkant', ['Badkamer', 'Logeerkamer', 'Achterdeur', 'Achterraam']) }}
          {%- endif -%}

Big thanks to petro for the average filter; simplified the test to confirm if all covers were 100 or 0.

Almost there :slight_smile:

The percentage are showed at the correct covers. There is still 1 problem left:

Master: VENTI
Computerkr: VENTI
Hal: VENTI

VENTI means ‘ventilation’ mode of the covers (so they are not closed completely).

cover_template2

But those covers (master / computerkr / hal) are all closed.

I acknowledge it reports ‘VENTI’ instead of ‘GESLOTEN’ but I don’t know the reason for it.

This is the line in the macro that is responsible for reporting the cover’s state for a given room:

{%- set ns.s = ns.s + ['{}: **{}**'.format(naam[i], {100:'OPEN', 0:'GESLOTEN', av[i]:'VENTI'}.get(a[i], (100 - a[i]) ~ '%'))] -%}

The important part is this:

{100:'OPEN', 0:'GESLOTEN', av[i]:'VENTI'}.get(a[i] ...etc
  • a[i] is the integer value of the cover’s current position.
  • av[i] is the integer value of the cover’s associated input_number (i.e. its ventilation position).
  1. If a[i] is 100 it will match the first key and report OPEN.
  2. If a[i] is 0 it will match the second key and report GESLOTEN.
  3. If a[i] is the same value as av[i] it will report VENTI.

The result you are seeing is that a[i] is 0 but it’s matching, for some unknown reason, the value of av[i] and reporting VENTI. :thinking:


I don’t have ten covers so I simulate them with a list of numbers. Here’s an example for the four rooms representing Achterkant.

{%- set naam = ['Badkamer', 'Logeerkamer', 'Achterdeur', 'Achterraam'] -%}
{%- set a = [0, 30, 100, 55] -%}
{%- set av = [30, 30, 30, 30] -%}

{%- set ns = namespace(s=[]) -%}
{%- for i in range(0, a | length) -%}
{%- set ns.s = ns.s + ['{}: **{}**'.format(naam[i], {100:'OPEN', 0:'GESLOTEN', av[i]:'VENTI'}.get(a[i], (100 - a[i]) ~ '%'))] -%}
{%- endfor -%}
{{ ns.s }}

The current position of the covers in the four rooms are represented by:

[0, 30, 100, 55]

Each room’s ventilation position is the same value (30) and represented by:

[30, 30, 30, 30]

Based on this information, the template correctly reports:

  "Badkamer: **GESLOTEN**",
  "Logeerkamer: **VENTI**",
  "Achterdeur: **OPEN**",
  "Achterraam: **45%**"`

Why the same code fails to properly report GESLOTEN for your covers isn’t clear to me (yet).

Okay your explanation is clear.

But it is indeed strange that complete marco is giving a different result.

I think I may have discovered the reason.

If a cover’s ventilation position is 0, meaning it’s the same value as when it’s closed (gesloten), this:

{0:'GESLOTEN', 100:'OPEN', av[i]:'VENTI'}.get

effectively becomes this:

{0:'GESLOTEN', 100:'OPEN', 0:'VENTI'}.get

The dictionary contains two identical keys (0) and, based on my test, it always picks VENTI not GESLOTEN.

It seems that when the dictionary contains two identical keys, it has a preference for which one it chooses. My first guess would be that it picks the first matching 0, containing GESLOTEN, but apparently not because it obviously prefers to pick the one produced by av[i]. Maybe this is also due to a sorting issue.

Here’s the test where I set Badkamer’s ventilation position to 0. It’s current position is 0 and the macro reports Badkamer is VENTI instead of GESLOTEN.

{%- set naam = ['Badkamer', 'Logeerkamer', 'Achterdeur', 'Achterraam'] -%}
{%- set a = [0, 30, 100, 55] -%}
{%- set av = [0, 30, 30, 30] -%}

{%- set ns = namespace(s=[]) -%}
{%- for i in range(0, a | length) -%}
{%- set ns.s = ns.s + ['{}: **{}**'.format(naam[i], {0:'GESLOTEN', 100:'OPEN', av[i]:'VENTI'}.get(a[i], (100 - a[i]) ~ '%'))] -%}
{%- endfor -%}
{{ ns.s }}

I’ll have to think about how to fix this …