YAML config nested {% if %} statements - help? battery_alert.yaml [given up]

Hi all,
I seem to be running into an issue when trying to write some YAML, and admittedly I’m not the best at YAML and I’m a little stuck.

There must be a better way of doing this, and I’m unsure what.
There must be a YAML expert here who can see better than I can?

Here is what I’m trying to do:

        {% macro battery_level() %}
        {% for entity_id in states.group.battery_status.attributes.entity_id if (
          {% if ( states.input_boolean.show_unavailable_batteries.state == true ) %}
            not (
                  is_state_attr(entity_id, 'battery_alert_disabled', true)
                  or is_state_attr(entity_id, 'restored', true)
                )
          {% else %}
            not is_state_attr(entity_id, 'battery_alert_disabled', true)
          {% endif %}
        ) -%}
          {{ state_attr(entity_id, "friendly_name") }} ({{ states(entity_id) }})
        {% endfor -%}
        {% endmacro %}

I have a {% for this if ( stuff ) %} {% endfor %} statement and I’m trying to insert an {% if %} in there based off an input_boolean.

I keep getting errors like:

Invalid config for [automation]: invalid template (TemplateSyntaxError: unexpected '%') for dictionary value @ data['action'][0]['value_template']. Got None. (See ?, line ?).

Can anyone help?

First of all, I think the problem is you forgot to wrap your evaluation in template brackets.

          {% if ( states.input_boolean.show_unavailable_batteries.state == true ) %}
            {{ not (
                  is_state_attr(entity_id, 'battery_alert_disabled', true)
                  or is_state_attr(entity_id, 'restored', true)
                )
            }}
          {% else %}
            {{ not is_state_attr(entity_id, 'battery_alert_disabled', true) }}
          {% endif %}

I would say you should lookup how to use jinja2 selectattr to filter down groups before hand

https://jinja.palletsprojects.com/en/2.11.x/templates/#selectattr

I mean, that’s what I would say if I understood it myself lol.

To make things slightly more readable, you could make that entire condition a separate macro and just use that as the condition

{%- macro condition(entity_id) -%}
  {%- if is_state('input_boolean.show_unavailable_batteries', 'on') -%}
    {{ not( 
      is_state_attr(entity_id, 'battery_alert_disabled', true)
      or is_state_attr(entity_id, 'restored', true)
      )
    }}
  {%- else -%}
    {{ not is_state_attr(entity_id, 'battery_alert_disabled', true) }}
  {%- endif -%}
{%- endmacro -%}

{% for entity_id in states.group.battery_status.attributes.entity_id if condition(entity_id) == 'True' %}
  {{ state_attr(entity_id, "friendly_name") }} ({{ states(entity_id) }})
{% endfor %}

Only downside is the macro will return a string, so you’ll have to string compare True/False (and make sure you strip all extra spaces with the -

Thanks for your response,
I’m still kinda stuck getting this to work.
I’m gonna keep hacking at it.

Just wanted to say thanks.

I think if you just put the “{{ }}” brackets around your first post, it would have worked. In both the if section and the else section.

        {% macro battery_level() %}
        {% for entity_id in states.group.battery_status.attributes.entity_id if (
          {% if ( states.input_boolean.show_unavailable_batteries.state == true ) %}
   ---> here {{ not (
                  is_state_attr(entity_id, 'battery_alert_disabled', true)
                  or is_state_attr(entity_id, 'restored', true)
                )
   ---> here }}
          {% else %}
  --->here {{ not is_state_attr(entity_id, 'battery_alert_disabled', true) }} <--- here
          {% endif %}
        ) -%}
          {{ state_attr(entity_id, "friendly_name") }} ({{ states(entity_id) }})
        {% endfor -%}
        {% endmacro %}

My macro thing might work, but is a little tricky

I’m afraid I get a bunch of errors still:

Invalid config for [automation]: invalid template (TemplateSyntaxError: unexpected '%') for dictionary value @ data['action'][0]['value_template']. Got None. (See ?, line ?).
Invalid config for [automation]: invalid template (TemplateSyntaxError: unexpected '%') for dictionary value @ data['action'][0]['value_template']. Got None. (See ?, line ?).
Invalid config for [automation]: invalid template (TemplateSyntaxError: unexpected '%') for dictionary value @ data['action'][0]['value_template']. Got None. (See ?, line ?).

I’ve got the code here if you’d like to take a look:

I was originally trying to add it to this PR to fix issues of the package by @NotoriousBDG :

Oh. I see. Yeah, I was wrong.

Once you’re inside the template ({%), there’s no need to exit again. Just remove them all from your if/else inside the if.

      value_template: &low_battery_check >
        {% macro battery_level() %}
        {% for entity_id in states.group.battery_status.attributes.entity_id if (
             if ( states.input_boolean.show_unavailable_batteries.state == true )
               not (
                  is_state_attr(entity_id, 'battery_alert_disabled', true)
                  or is_state_attr(entity_id, 'restored', true)
                )
             else
               not is_state_attr(entity_id, 'battery_alert_disabled', true)
           and states(entity_id) is not none
           and (
            (
              (
                states(entity_id) is number
                or states(entity_id) | length == states(entity_id)| int | string | length
                or states(entity_id) | length == states(entity_id)| float | string | length
              )
              and states(entity_id) | int < states.input_number.battery_alert_threshold_max.state | int
              and states(entity_id) | int > states.input_number.battery_alert_threshold_min.state | int
            )
            or states(entity_id) | lower == 'low'
            or states(entity_id) | lower == 'unknown'
            or states(entity_id) | lower == 'unavailable'
          )
        ) -%}
          {{ state_attr(entity_id, "friendly_name") }} ({{ states(entity_id) }})
        {% endfor -%}
        {% endmacro %}
        {{ battery_level() | trim != "" }}

Just tried, different error.

Invalid config for [automation]: invalid template (TemplateSyntaxError: expected token ')', got 'not') for dictionary value @ data['action'][0]['value_template']. Got None. (See ?, line ?).
Invalid config for [automation]: invalid template (TemplateSyntaxError: expected token ')', got 'not') for dictionary value @ data['action'][0]['value_template']. Got None. (See ?, line ?).
Invalid config for [automation]: invalid template (TemplateSyntaxError: expected token ')', got 'not') for dictionary value @ data['action'][0]['value_template']. Got None. (See ?, line ?).

I’m not quite understanding all the layers of enumeration here, I admit.

Just read the error again, saw that it needed brackets:

      value_template: &low_battery_check >
        {% macro battery_level() %}
        {% for entity_id in states.group.battery_status.attributes.entity_id if (
             if ( states.input_boolean.show_unavailable_batteries.state == true )
               {{ not (
                  is_state_attr(entity_id, 'battery_alert_disabled', true)
                  or is_state_attr(entity_id, 'restored', true)
                ) }}
             else
               {{ not is_state_attr(entity_id, 'battery_alert_disabled', true) }}
             fi
           and states(entity_id) is not none
           and (
            (
              (
                states(entity_id) is number
                or states(entity_id) | length == states(entity_id)| int | string | length
                or states(entity_id) | length == states(entity_id)| float | string | length
              )
              and states(entity_id) | int < states.input_number.battery_alert_threshold_max.state | int
              and states(entity_id) | int > states.input_number.battery_alert_threshold_min.state | int
            )
            or states(entity_id) | lower == 'low'
            or states(entity_id) | lower == 'unknown'
            or states(entity_id) | lower == 'unavailable'
          )
        ) -%}
          {{ state_attr(entity_id, "friendly_name") }} ({{ states(entity_id) }})
        {% endfor -%}
        {% endmacro %}
        {{ battery_level() | trim != "" }}

After adding that, same error as originally:

Invalid config for [automation]: invalid template (TemplateSyntaxError: expected token ')', got '{') for dictionary value @ data['action'][0]['value_template']. Got None. (See ?, line ?).
Invalid config for [automation]: invalid template (TemplateSyntaxError: expected token ')', got '{') for dictionary value @ data['action'][0]['value_template']. Got None. (See ?, line ?).
Invalid config for [automation]: invalid template (TemplateSyntaxError: expected token ')', got '{') for dictionary value @ data['action'][0]['value_template']. Got None. (See ?, line ?).

This template wins the “Truly Byzantine” award. :thinking:

Are you familiar with these Jinja2 filters?
select
reject
selectattr
rejectattr

For example, this rejects anything whose state is one of three possible values:

rejectattr('state', 'in', ['low', 'unknown', 'unavailable'])
1 Like

Indeed, I’m not sure what kind of mind could architect this.

I’m not familiar with that jinja2 filter.

That sounds like it could be useful.

Yeah, you were supposed to get rid of the {{ }} that I made you add as well :stuck_out_tongue:

Yeah, I did, just like your example here.

That still didn’t work for some reason, so I added the {{ }} back, still no beans.
I’m not entirely sure what’s going wrong here tbh.

The key thing I’m trying to achieve with all of this, is to have an switch/boolean that I can enable/disable to include/exclude the entities with a attribute of restored: true.

In this case, I’m using input_boolean.show_unavailable_batteries.

This is so that I can filter out broken/unavailable battery entities by default, but show them if needed.

There is probably a different and better way to do this, and I continue to figure it out.

Thanks for your help.

All the code that has been posted has tons of errors in it.

  • Evaluating a input boolean against true/false. The true state of these devices is 'on'/'off'
  • You have an if statement that immediately goes into a not
    if ( states.input_boolean.show_unavailable_batteries.state == true )
      not (
    
    This is not valid. You’d need an and between them…
  • From the previous bullet onward, the code is unreadable because it just doesn’t make sense.
  • states(entity_id) is number number is not a valid test and will always return false. states return strings, so even if it’s a number as a string it will still fail.

I’m sure I could find other things but I decided to stop. Try this out, I think it does what you want… but as I said, it’s hard to read your intentions with all the mistakes.

{%- set show_unavailable = is_states('input_boolean.show_unavailable_batteries', 'on') %}
{%- set high = states('input_number.battery_alert_threshold_max') | float %}
{%- set low = states('input_number.battery_alert_threshold_min') | float %}
{%- set ns = namespace(results=[]) %}
{%- for s in expand('group.battery_status') %}
  {%- if s.state in ['unknown', 'unavailable'] and show_unavailable and not (s.attributes.battery_alert_disabled or s.attributes.restored) %}
    {%- ns.results = ns.results + [  s.name ~ ' ' ~ s.state ] %}
  {%- else %}
    {%- if s.state.lower() in ['low', 'medium', 'high'] %}
      {%- if s.state.lower() == 'low' %}
        {%- ns.results = ns.entites + [ s.name ~ ' ' ~ s.state ] %}
      {%- endif %}
    {%- elif low <  s.state | float < high %}
      {%- ns.results = ns.entites + [ s.name ~ ' ' ~ s.state ] %}
    {%- endif %}
  {%- endif %}
{%- endfor %}
{{ ns.results | join(', ') }}
1 Like

image

Unfortunately, I didn’t write this original code, and I admit I’m not the best at understanding everything that’s happening.

I guess I’m trying to increase my knowledge and help others using it with my PR, as it seems abandoned.

Saying that, I’m not sure where your code fits in.
Does it replace the entire macro, or just within it?

it should just replace the macro. I’m not sure if the output format is correct though. As I said, I had trouble understanding the overall goal. There’s alot of fluff in there that’s not needed. What does the original code look like?

You can access my PR here and here is a raw of the file in question.

My understanding of the goal/intention of much of the package is it’s essentially iterating over all the entity_ids, checks if they have a battery_level attribute, and creates new monitored dynamic entities for them.
They get added to a new group, also defined in the package, and sends out notifications when configurable thresholds are reached for battery levels of the new monitored entities.

I’ve been messing about with this for over a year, and I try to keep it updated, but it was written by someone who had a deeper understanding of this than me (And most of the people using it).

Unfortunately, I’m getting these errors when replacing your code into the package:

Invalid config for [automation]: invalid template (TemplateSyntaxError: Encountered unknown tag 'ns'. Jinja was looking for the following tags: 'elif' or 'else' or 'endif'. The innermost block that needs to be closed is 'if'.) for dictionary value @ data['action'][0]['value_template']. Got None. (See ?, line ?).
Invalid config for [automation]: invalid template (TemplateSyntaxError: Encountered unknown tag 'ns'. Jinja was looking for the following tags: 'elif' or 'else' or 'endif'. The innermost block that needs to be closed is 'if'.) for dictionary value @ data['action'][0]['value_template']. Got None. (See ?, line ?).
Invalid config for [automation]: invalid template (TemplateSyntaxError: Encountered unknown tag 'ns'. Jinja was looking for the following tags: 'elif' or 'else' or 'endif'. The innermost block that needs to be closed is 'if'.) for dictionary value @ data['action'][0]['value_template']. Got None. (See ?, line ?).

I’m still looking.

code works and passes validation on my end. How did you place it into the file?

I’ll make a branch, and post here in a sec.