Can this code be optimized? Reducing the sensors that are looped

I have configured a helper of the type template to monitor, what type of filament my Bambu Lab P1S is currently printing.

The template goes like this:

{% set global = namespace(active_filament="N/A") %}
{% for spool in states.sensor -%}
  {%- if (spool.name).split(' ')[0] == 'P1S_123456789ABCDEF_AMS_1' and (spool.name).split(' ')[1] == 'Slot' %}
    {%- if (state_attr((spool.entity_id), "active")) %}
      {% set global.active_filament = state_attr((spool.entity_id), "type") %}
    {% endif -%}
  {% endif -%}
{% endfor %}

{{ global.active_filament }}

The template/helper works as expected, but the downside is that the for loop runs for nearly 650 sensors! And only within the loop do the if-statements filter the four relevant sensors. So the names of all 650 sensors are split and matched. That doesn’t feel lean.

So I wondering if there is a more efficient way to code the template. The relevant sensors have these attributes:

active: false
color: #D6001CFF
empty: false
k_value: 0.019999999552965164
name: Bambu PETG Basic
nozzle_temp_min: 220
nozzle_temp_max: 270
remain: 53
tag_uid: 0ABCD12345678900
type: PETG
icon: mdi:printer-3d-nozzle
friendly_name: P1S_123456789ABCDEF_AMS_1 Slot 2

Looking forward to your suggestions.

Why can’t you just hard code those 4 sensors instead of doing a loop to find them?

Reliability!
If for whatever reason the name/id of the AMS sensors will change sometime, it should still work.

How much more reliability?
You need to search for a pattern in your loop, which seems to be a big part of the name, so if the name change then the search will likely fail too.

{{ states.sensor 
    | selectattr('name', 'match', '^P1S_123456789ABCDEF_AMS_1 Slot')
    | selectattr('attributes.active', 'defined')
    | selectattr('attributes.active', 'eq', True)
    | map(attribute='attributes.type') | list }}

see below

Thanks a lot for the code. It works.
To make it more reliable, I made the regular expression more common. Also I want one string instead of a list (there always can be 0 or 1 active filaments, not more) and if no filament is active, the string is set to “N/A”.

{% set filament_list = states.sensor 
    | selectattr('name', 'match', '^P1S_(.)+_AMS_\d Slot \d')
    | selectattr('attributes.active', 'defined')
    | selectattr('attributes.active', 'eq', True)
    | map(attribute='attributes.type') 
    | list %}
{%- if ((filament_list | count) > 0 ) %}
  {% for spool in filament_list -%}
    {{ spool }}
  {% endfor %}
{%- else %}
  N/A
{%- endif %}

If there’s only 1 on at a time…

{{ states.sensor 
    | selectattr('name', 'match', '^P1S_123456789ABCDEF_AMS_1 Slot')
    | selectattr('attributes.active', 'defined')
    | selectattr('attributes.active', 'eq', True)
    | map(attribute='attributes.type') | list | first | default or "N/A" }}
2 Likes

The question still stands. How much more reliability do you get.
Petros script actually removes a bit of the flexibility too, because your script searched for “P1S_123456789ABCDEF_AMS_1” and “Slot”, while his only search for “P1S_123456789ABCDEF_AMS_1 Slot”.

That’s not how the code works. The regex does not remove any flexibility. When you take a string and split it based on spaces, then check the first and second item. it’s identical to a regex that has the space checking both.

i.e.

match('^P1S_123456789ABCDEF_AMS_1 Slot', value)

is functionally identical to

a, b = value.split(' ')
a == 'P1S_123456789ABCDEF_AMS_1' and b == 'Slot'

Anyways, the regex he’s using is now more flexible than what I wrote initially anyways.

^P1S_(.)+_AMS_\d Slot \d

Looks for strings starting with P1S_ that contain any number of characters and numbers until _AMS_. It then checks for a valid number, then a space, then the word Slot then a space, lastly a valid number.

All in all, his method is robust and requires little maintenance. It’s a route I would take.

Petros already made clear, how his regex works.
As mentioned in my last post, I customized the regex to be more common. That’s what the template in my helper actually looks like:

{{ states.sensor 
    | selectattr('name', 'match', '^P1S_(.)+_AMS_\d Slot \d')
    | selectattr('attributes.active', 'defined')
    | selectattr('attributes.active', 'eq', True)
    | map(attribute='attributes.type') 
    | list 
    | first 
    | default or "N/A" }}

You could also take it a bit further. Maybe some time I get a X1C :slight_smile:

selectattr('name', 'match', '^(P1S|X1C)_(.)+_AMS_\d Slot \d)

Okay, I am not really on good terms with regex, but the first few iterations did not really look like regex.
The ones with ^P1S_(.)+_AMS_\d Slot \d makes sense to me flexibility-wise.

it was though.

^P1S_123456789ABCDEF_AMS_1 Slot

^ = starts with.

I just replicated what his code was with if statements, but using regex. I.e. they are functionally the same as his original code.

The first post did not have that.
Your reply had it, but I can not see how a regex expression of ^P1S_123456789ABCDEF_AMS_1 Slot is different from a substring match with P1S_123456789ABCDEF_AMS_1 Slot.
It might be my lack of regex knowledge, but that clouds it here though.

He isn’t looking at a substring though. He splits the name and takes the first item.

This implies the string starts with P1S_123456789ABCDEF_AMS_1

He then splits the name and takes the second item.

This implies the second item is 'Slot' and it implies that it’s separated by a space.

I.e. starts with P1S_123456789ABCDEF_AMS_1, has a space, then Slot.

ergo: ^P1S_123456789ABCDEF_AMS_1 Slot as the regex.

I understand what is being done.
Just not why.

To me it was a lot of jumping around that was not needed.
First take a line, then split it up and then compare each element of the split with a fixed value.
Instead of just combining the split and compare with that.

And because the elements were pretty fixed, then it made hardly no sense to search for them, because the compare pattern would be almost the complete string to compare against.

^(P1S|X1C)_(.)+_AMS_\d Slot \d makes sense to me and if you used splits then there would probably be elements skipped, which makes it flexible.

I did not try to optimize the original code, but questioned its premise.

Would using labels work? Would labels be more efficient? In particular does HA have an index of where each label is used.

You can use labels if you want but it doesn’t change the template much.

{{ label_entities('some_label') 
    | select('is_state_attr', 'active', True)
    | map('state_attr', 'type') 
    | list 
    | first 
    | default or "N/A" }}

And that’s the problem.