How do you sort entities by device ID in a template?

When using templates in Home Assistant, I have the following code, intended to find unavailable devices:

{% set entity_ids = states
  | selectattr('state', 'in', ['unavailable', 'unknown'])
  | selectattr('attributes.device_class', 'defined')
  | selectattr('attributes.device_class', 'ne', 'update')
  | selectattr('attributes.device_class', 'ne', 'restart')
  | selectattr('attributes.device_class', 'ne', 'identify')
  | selectattr('attributes.device_class', 'ne', 'wled__live_override')
  | selectattr('attributes.device_class', 'ne', 'firmware')
  | map(attribute='entity_id')
  | map("string")
  | unique
  | list
%}

This works but the resulting list is a list of entities, not devices (because it’s entities that can be unavailable). That’s fine, because I can then iterate over the resulting list, and display the output grouped by device:

{%- set ns = namespace(device_id='') -%}
{%- for entity_id in entity_ids -%}
  {%- if ns.device_id != device_id(entity_id) -%}
    {%- if ns.device_id != '' %}

{% endif -%}
    **[{{ device_attr(entity_id, "name_by_user") or device_attr(entity_id, "name") }}](/config/devices/device/{{ device_id(entity_id) }}) in {{area_name(entity_id)}}**
    {%- set ns.device_id = device_id(entity_id) %}
  {%- endif %}
  - {{ state_attr(entity_id, 'friendly_name') }}
{%- endfor -%}

However, because the list does not group entities belonging to the same device together, I end up with the same device sometimes listed several times, with different entities in each entry. How can I sort the initial list so that entities belonging to the same device are consecutive? Or otherwise how should I do this?

(I’m aware of previous efforts at this like GitHub - jazzyisj/unavailable-entities-sensor: Unavailable entities template sensor for Home Assistant., and none appear to be trying to group unavailable entities by device)

I can’t answer your specific question, but…

I initially went down the same path as you, but then found a lot of unavailable entities I didn’t really care about (eg. Christmas lights that are in the cupboard most of the year). So I created a “monitor-availability” label and then attached that label to everything I wanted to monitor. Since I only attached it to one entity per device, the problem you mentioned goes away.

Similarly I have labels for “monitor-low-percent” (eg. batteries) and “monitor-high-percent” (eg. disk space).

2 Likes

That’s really smart. I might well do that.

Meanwhile I made some progress:

{% set entity_ids = states
  | rejectattr('domain','in',['stt','tts','conversation','select','number', 'event','group','input_button','input_text','scene'])
  | rejectattr('attributes.device_class', 'in', ['update', 'restart', 'identify', 'wled__live_override', 'firmware'])
  | selectattr('state', 'in', ['unavailable', 'unknown'])
  | map(attribute='entity_id')
  | select("ne", None)
  | map("string")
  | unique
  | list
%}
{% set device_ids = entity_ids
  | map("device_id")
  | select("ne", None)
  | unique
  | list
%}

{%- for d_id in device_ids -%}
  **[{{ device_attr(d_id, "name_by_user") or device_attr(d_id, "name") }}](/config/devices/device/{{ d_id }}) in {{area_name(d_id)}}**
    {%- for e in device_entities(d_id) | select('in', entity_ids) %}
    - {{ state_attr(e, 'friendly_name') }} <span style='color: grey'>is {{ states(e) }}</span>
    {%- endfor %}
{% endfor %}

This actually feels a bit more straightforward than what I had before, and the output in the template debugger is perfect, but it just produces blank output when I put it in a markdown dashboard card :frowning:. Do markdown cards only support limited templates? It’s a bit weird.

OK, Solved it.

{% set entity_ids = states
  | selectattr('state', 'in', ['unavailable', 'unknown'])
  | selectattr('attributes.device_class', 'defined')
  | rejectattr('attributes.device_class', 'in', ['update', 'restart', 'identify', 'firmware', 'battery'])
  | rejectattr('domain','in',['stt','tts','conversation','select','number', 'event','group','input_button','input_text','scene'])
  | map(attribute='entity_id')
  | select("ne", None)
  | map("string")
  | unique
  | list
%}
{% set device_ids = entity_ids
  | map("device_id")
  | select("ne", None)
  | unique
  | list
%}

{%- for d_id in device_ids -%}
  **[{{ device_attr(d_id, "name_by_user") or device_attr(d_id, "name") }}](/config/devices/device/{{ d_id }}) in {{area_name(d_id)}}**
    {%- for e in device_entities(d_id) | select('in', entity_ids) %}
  - {{ state_attr(e, 'friendly_name') }} <span style='color: grey'>is {{ states(e) }}</span>
    {%- endfor %}
    
{% endfor %}

Produces:

The problem was that for some reason, in the Markdown card, it was struggling to do the rejectattr('attributes.device_class', 'in'... filter because the list clearly contained some objects that didn’t have a device class attribute. So I added a selectattr('attributes.device_class', 'defined') filter before that, and now it works great.

2 Likes

Even better version:

  • Include entities that don’t have a device_class
  • Only display area when the device has one
{% set entity_ids = (
  states
    | selectattr('state', 'in', ['unavailable', 'unknown'])
    | selectattr('attributes.device_class', 'defined')
    | rejectattr('attributes.device_class', 'in', ['update', 'restart', 'identify', 'firmware', 'battery'])
    | rejectattr('domain','in',['stt','tts','conversation','select','number', 'event','group','input_button','input_text','scene']
  ) | list + 
  states
    | selectattr('state', 'in', ['unavailable', 'unknown'])
    | rejectattr('attributes.device_class', 'defined')
    | rejectattr('domain','in',['stt','tts','conversation','select','number', 'event','group','input_button','input_text','scene']
  ) | list)
  | map(attribute='entity_id')
  | select("ne", None)
  | map("string")
  | unique
  | list
%}
{% set device_ids = entity_ids
  | map("device_id")
  | select("ne", None)
  | unique
  | list
%}

{%- for d_id in device_ids -%}
  **[{{ device_attr(d_id, "name_by_user") or device_attr(d_id, "name") }}](/config/devices/device/{{ d_id }})
    {%- if area_name(d_id) %} (in {{area_name(d_id)}}){%- endif %}**
    {%- for e in device_entities(d_id) | select('in', entity_ids) %}
  - {{ state_attr(e, 'friendly_name') }} <span style='color: grey'>is {{ states(e) }}</span>
    {%- endfor %}
    
{% endfor %}
1 Like