Hi @studioIngrid
Thirst thanks for sharing. I recently wrote a template macro for the same purpose.
The endeavor was not easy, as I encountered various things that should be taken into account.
So reading your code, some improvements to consider:
- Some devices have a binary connection sensor (device_class connectivity). These remain available,when the device is off line. Your code misses them
- Some integrations extend devices of other integrations with additional entities. Most of these extended entities will also become unavailable ( I.e a cast device gets extended by e media player, etc). BUT not all!, Example is powercalc, adding power/energy sensors to devices. So I had to exclude powercalc entities from the evaluation.
So I now share my template macro, that takes these exceptions into account. The flow is simple to avoid to much iterations over entities:
- First I create an list of suspicious device_ids with unavailable entitie(s). (This limits the scope for the remaining code)
- create an ignore list of entries not to use (powercalc only for now)
- then I evaluate only the suspicious devices:
- lookup the device entities and reject ignores
- check there is at least one unavailable
- check there are no availables, excluding connection types
- for every unavailable device, a struct with information is added to a list to return.
I think this is a reasonable efficient approach:
{%- macro unavailable_devices() -%}
{#- list all devices that have at least one unavailable entity -#}
{% set suspicious_devices = states | rejectattr('state','ne','unavailable') | map(attribute='entity_id') | map('device_id') | unique | reject('eq',None) | list -%}
{%- set ns = namespace(unavailables = []) -%}
{#- create a list of entities to ignore.
i.e. powercalc extends devices with entities. -#}
{%- set ignore_lst=integration_entities('powercalc') -%}
{%- for device_id in suspicious_devices -%}
{%- set ids = device_attr(device_id, 'identifiers') -%}
{%- set integration = 'unknown' if (not ids or ids|list|length == 0 or ids|list|first|length!=2) else ids|list|first|first -%}
{#- exlude entities of i.e. powercalc which extends devices with power entities -#}
{%- set entities= device_entities(device_id) | reject('in', ignore_lst) | list -%}
{%- set unavailable = expand(entities) | selectattr('state','eq','unavailable') |list|count -%}
{#- don't count available entities of connection type -#}
{%- set available = expand(entities) | selectattr('state','ne','unavailable') | list | count -%}
{#- so count connection entities in error state -#}
{%- set contypes = expand(entities) | selectattr('state','eq','off') | selectattr('attributes.device_class', 'defined') |selectattr('attributes.device_class','eq','connectivity') | list | count -%}
{%- if unavailable!=0 and available-contypes==0 -%}
{#- preferably use named_by_user -#}
{%- set name = device_attr(device_id, 'name_by_user') -%}
{%- set name = device_attr(device_id, 'name') if name==None else name -%}
{%- set area = device_attr(device_id, 'area_id') or 'n/a' -%}
{%- set model = device_attr(device_id, 'model') or 'n/a' -%}
{%- set devices_info = {"name":name,"area":area,"model":model,"id":device_id,"available":available,"unavailable":unavailable,"integration":integration} -%}
{%- set ns.unavailables = ns.unavailables + [ devices_info ] -%}
{%- endif -%}
{%- endfor -%}
{{ ns.unavailables | to_json }}
{%- endmacro -%}
Usage, note the from_json:
{{ unavailable_devices()|from_json|count }}
{{ unavailable_devices()|from_json }}
Result snippet:
2
[
{'name': 'Keukenkast',
'area': 'keuken',
'model': 'Dimmable light (PL 110)',
'id': '720b81c19d128a2d4d9e7fa7d3383f61',
'available': 0,
'unavailable': 1,
'integration': 'hue'},
{'name': 'Athom Plug V3 5090e8',
'area': 'other',
'model': 'Smart_Plug_V3',
'id': 'a05cbf2d7536349b1caefb4f8e65d4bf',
'available': 1,
'unavailable': 14,
'integration': 'unknown'},
]
The second device in this snippet has a connectivity sensor. Therefor available is 1.
The macro can also be used in the markdown card and auto-entities. Example for auto entities & template-entity-row:
filter:
template: |
{%- from 'config.jinja' import unavailable_devices -%}
{%- set devices = unavailable_devices()|from_json -%}
{%- set ns=namespace(rows=[]) -%}
{%- for info in devices -%}
{%- set sec_info = 'Area:'~info.area~', Mdl:'~info.model -%}
{%- set entry = {
'type': 'custom:template-entity-row',
'name': info.integration~': '~info.name,
'state': info.available~'-'~info.unavailable,
'secondary': sec_info,
'tap_action': {
'action': 'navigate',
'navigation_path': '/config/devices/device/'~info.id },
} -%}
{%- set ns.rows= ns.rows + [entry] -%}
{%- endfor -%}
{{ ns.rows }}
include: []
exclude: []
Result, when you tap it navigates to the device info page:
@AndySymons
For me there are no issues with MQTT or ZHA devices.