A sensor to list unavailable devices

So, in case anyone else wants it, here is the final full code – tested, tidied up and annotated.

Thanks to all of you for your help and to others on the forum and elsewhere on the internet who made contributions to this subject.

##----------------------------------------------------------------------------------------------------------------------
##
## Unavailable Entities Sensor
##
## 05-Feb-2024 | Andy Symons | created
##
## Credit: based loosely on https://github.com/jazzyisj/unavailable-entities-sensor/blob/main/README.md
##
## The sensor provides lists related to real devices, not internal entities, helpers, automations etc.,
## Entities with state 'unknown' are not counted, because it is possible for a device to have a sub-entity that is
##    unknown while the device itself is available.
##
## The STATE simply gives the count of unavailable entities.
## The long results have to be attributes because the state cannot contain more than 255 characters:
##   ATTRIBUTE 'entity_id_list' contains a list of unavailable entities using their entity ids, which may or may not have been set by the user.
##   ATTRIBUTE 'entity_name_list' contains a list of unavailable entities using their friendly names as assigned by the user.
##   ATTRIBUTE 'device_name_list contains a list of the devices that are unavailable, which is to say having one or more entities that are unavailable,
##      using their friendly names as assigned by the user.
##
##----------------------------------------------------------------------------------------------------------------------


template:
  - sensor:
      name: "Unavailable Entities"
      unique_id: unavailable_entities
      icon: "{{ iif(states(this.entity_id)|int(-1) > 0,'mdi:alert-circle','mdi:check-circle') }}"
      state_class: measurement
      unit_of_measurement: entities

      # The entity state is the count of unavailable entites
      state: >
        {{ states
        | selectattr('domain','in',['binary_sensor', 'climate', 'light', 'sensor', 'switch'])
        | selectattr('state', 'in', ['unavailable'])
        | map(attribute='entity_id')
        | unique
        | list 
        | count
        }}

      # The long results have to be attributes because the state cannot contain more than 255 characters.
      attributes:
        ## A list of unavailable entities using their entity ids (which mnay or may not have been set by the user).
        entity_id_list: >-
          {{ states
          | selectattr('domain','in',['binary_sensor', 'climate', 'light', 'sensor', 'switch'])
          | selectattr('state', 'in', ['unavailable'])
          | map(attribute='entity_id')
          | reject('match', 'None')
          | list 
          | sort 
          | join('\n')
          }}

        ## A list of unavailable entities using their friendly names as assigned by the user.
        entity_name_list: >-
          {{ states
          | selectattr('domain','in',['binary_sensor', 'climate', 'light', 'sensor', 'switch']) 
          | selectattr('state', 'in', ['unavailable'])
          | map(attribute='entity_id')
          | map('state_attr', 'friendly_name') 
          | reject('match', 'None')
          | list 
          | sort 
          | join('\n')
          }}

        ## A list of the devices that are unavailable, using their friendly names as assigned by the user.
        device_name_list: >-
          {{ states
          | selectattr('domain','in',['binary_sensor', 'climate', 'light', 'sensor', 'switch'])
          | selectattr('state', 'in', ['unavailable'])
          | map(attribute='entity_id')
          | map('device_attr', 'name_by_user') 
          | reject('match', 'None')
          | unique
          | list 
          | sort 
          | join('\n')
          }}

# HOW THE ATTRIBUTE TEMPLATES WORK
#  -- Taking device_name_list as an example...
#
# {{ states                                                        -- all the states (entities) in the system
#    | selectattr('domain','in',['binary_sensor', 'climate', etc.  -- filter only the entities for real devices
#    | selectattr('state', 'in', ['unavailable'])                  -- filter only entities that are unavailable
#    | map(attribute='entity_id')                                  -- get the entity id from the record
#    | map('device_attr', 'name_by_user')                          -- map the entity id onto the device name
#    | reject('match', 'None')                                     -- take out names 'None' (meaning there is no name, so not a device)
#    | unique                                                      -- take out duplicates (devices usually have several entities)
#    | list                                                        -- make into a list (in the template sense)
#    | sort                                                        -- put them in alphabetical order
#    | join('\n')                                                  -- take out extraneous punctuation for a tidy output
#  }}

This web page is colouring the last few lines incorrectly; it’s just comment and will be fine when you paste it into a YAML file.

4 Likes

FWIW, there are one or two other templating shortcuts that are possible but, seeing that you have already have your own Solution, it’s good enough.

I would like to see them anyway!
I readily confess that my solution was achieved more by trial-and-error than by a deep understanding of how templates work :person_shrugging:

I’m running into an issue using this because I have a separate sensors.yaml and when I paste this in, I think the indentation is wrong. Below are the non-critical errors that only exist when I paste the code in to my YAML file and attempt to check the HA config.

  - platform: template
    sensors:
        unavailable_entities:
            name: "Unavailable Entities"
            unique_id: unavailable_entities
            icon: "{{ iif(states(this.entity_id)|int(-1) > 0,'mdi:alert-circle','mdi:check-circle') }}"
            state_class: measurement
            unit_of_measurement: entities

      # The entity state is the count of unavailable entites
    state: >
        {{ states
        | selectattr('domain','in',['binary_sensor', 'climate', 'light', 'sensor', 'switch'])
        | selectattr('state', 'in', ['unavailable'])
        | map(attribute='entity_id')
        | unique
        | list 
        | count
        }}

      # The long results have to be attributes because the state cannot contain more than 255 characters.
    attributes:
        ## A list of unavailable entities using their entity ids (which mnay or may not have been set by the user).
        entity_id_list: >-
          {{ states
          | selectattr('domain','in',['binary_sensor', 'climate', 'light', 'sensor', 'switch'])
          | selectattr('state', 'in', ['unavailable'])
          | map(attribute='entity_id')
          | reject('match', 'None')
          | list 
          | sort 
          | join('\n')
          }}

        ## A list of unavailable entities using their friendly names as assigned by the user.
    entity_name_list: >-
          {{ states
          | selectattr('domain','in',['binary_sensor', 'climate', 'light', 'sensor', 'switch']) 
          | selectattr('state', 'in', ['unavailable'])
          | map(attribute='entity_id')
          | map('state_attr', 'friendly_name') 
          | reject('match', 'None')
          | list 
          | sort 
          | join('\n')
          }}

        ## A list of the devices that are unavailable, using their friendly names as assigned by the user.
    device_name_list: >-
          {{ states
          | selectattr('domain','in',['binary_sensor', 'climate', 'light', 'sensor', 'switch'])
          | selectattr('state', 'in', ['unavailable'])
          | map(attribute='entity_id')
          | map('device_attr', 'name_by_user') 
          | reject('match', 'None')
          | unique
          | list 
          | sort 
          | join('\n')
          }}


I don’t see your log with errors — only YAML config.

state and attributes should be indented more, but if you learn to interpret the errors and check the docs, you can fix it yourself.

If you have a separate sensors.yaml, then the key sensor is already in your configurations.yaml, presumably (as sensor: !include sensor.yaml) so you should not have the sensors keyword again in sensors.yaml

I have labeled mine, so it is written like this:

sensor sun2: !include sun2.yaml
sensor climate: !include climate.yaml
...

I managed to get it down to one error.

Invalid config for 'template' from integration 'sensor' at sensors.yaml, line 394: 'attributes' is an invalid option for 'sensor.template', check: sensors->unavailable_entities->attributes

I had to replace a bunch of the items like “state” with “value_template”, etc. I’m not sure what to do with the attributes section. I tried various indents. See below for updated YAML. I know the beginning is correct because all the other items in my sensors.yaml are formatted the same way.

  - platform: template
    sensors:
        unavailable_entities:
            friendly_name: "Unavailable Entities"
            unique_id: unavailable_entities
            icon_template: "{{ iif(states(this.entity_id)|int(-1) > 0,'mdi:alert-circle','mdi:check-circle') }}"
            unit_of_measurement: entities

# The entity state is the count of unavailable entites
            value_template: >-
                {{ states
                | selectattr('domain','in',['binary_sensor', 'climate', 'light', 'sensor', 'switch'])
                | selectattr('state', 'in', ['unavailable'])
                | map(attribute='entity_id')
                | unique
                | list 
                | count
                }}

# The long results have to be attributes because the state cannot contain more than 255 characters.
            attributes:
# A list of unavailable entities using their entity ids (which mnay or may not have been set by the user).
                entity_id_list: >-
                    {{ states
                    | selectattr('domain','in',['binary_sensor', 'climate', 'light', 'sensor', 'switch'])
                    | selectattr('state', 'in', ['unavailable'])
                    | map(attribute='entity_id')
                    | reject('match', 'None')
                    | list 
                    | sort 
                    | join('\n')
                    }}

# A list of unavailable entities using their friendly names as assigned by the user.
                entity_name_list: >-
                    {{ states
                    | selectattr('domain','in',['binary_sensor', 'climate', 'light', 'sensor', 'switch']) 
                    | selectattr('state', 'in', ['unavailable'])
                    | map(attribute='entity_id')
                    | map('state_attr', 'friendly_name') 
                    | reject('match', 'None')
                    | list 
                    | sort 
                    | join('\n')
                    }}

# A list of the devices that are unavailable, using their friendly names as assigned by the user.
                device_name_list: >-
                    {{ states
                    | selectattr('domain','in',['binary_sensor', 'climate', 'light', 'sensor', 'switch'])
                    | selectattr('state', 'in', ['unavailable'])
                    | map(attribute='entity_id')
                    | map('device_attr', 'name_by_user') 
                    | reject('match', 'None')
                    | unique
                    | list 
                    | sort 
                    | join('\n')
                    }}

Thanks a million for sharing your final code! Really helpful!

This is for those who are having the same problem with an empty device_name_list:
Both attributes entity_id_list and entity_name_list work fine at my end.
However, I had a problem that the attribute device_name_list produced just an empty value. Reason was that the list is based on name_by_user which needs to be manually defined for each device which I never did.

My workaround was to replace name_by_user by just name.
Accordingly, my corresponding line in the device_name_list section is the following:
map('device_attr', 'name') instead of map('device_attr', 'name_by_user')

I edited @AndySymons sensor to output unavailable device count rather than entities.

It also inverts the order of the sensor details so that the list of unavailable devices is at the top and the entity IDs are at the bottom.

##----------------------------------------------------------------------------------------------------------------------
##
## Unavailable Devices Sensor
##
## 05-Feb-2024 | Andy Symons | created
## 16-May-2024 | mr_roboto | updated to track unavailable device count rather than entities
##
## Credit: based loosely on https://github.com/jazzyisj/unavailable-entities-sensor/blob/main/README.md
##
## The sensor provides lists related to real devices, not internal entities, helpers, automations etc.,
## Entities with state 'unknown' are not counted, because it is possible for a device to have a sub-entity that is
##    unknown while the device itself is available.
##
## The STATE simply gives the count of unavailable entities.
## The long results have to be attributes because the state cannot contain more than 255 characters:
##   ATTRIBUTE 'entity_id_list' contains a list of unavailable entities using their entity ids, which may or may not have been set by the user.
##   ATTRIBUTE 'entity_name_list' contains a list of unavailable entities using their friendly names as assigned by the user.
##   ATTRIBUTE 'device_name_list contains a list of the devices that are unavailable, which is to say having one or more entities that are unavailable,
##      using their friendly names as assigned by the user.
##
##----------------------------------------------------------------------------------------------------------------------


template:
  - sensor:
      name: "Unavailable Devices"
      unique_id: unavailable_devices
      icon: "{{ iif(states(this.entity_id)|int(-1) > 0,'mdi:alert-circle','mdi:check-circle') }}"
      state_class: measurement
      unit_of_measurement: devices

      # The entity state is the count of unavailable devices.
      state: >
        {{ states
        | selectattr('domain','in',['binary_sensor', 'climate', 'light', 'sensor', 'switch'])
        | selectattr('state', 'in', ['unavailable'])
        | map(attribute='entity_id')
        | map('device_attr', 'name_by_user')
        | reject('match', 'None')
        | unique
        | list
        | count
        }}

      # The long results have to be attributes because the state cannot contain more than 255 characters.
      attributes:
        ## A list of the devices that are unavailable, using their friendly names as assigned by the user.
        device_name_list: >-
          {{ states
          | selectattr('domain','in',['binary_sensor', 'climate', 'light', 'sensor', 'switch'])
          | selectattr('state', 'in', ['unavailable'])
          | map(attribute='entity_id')
          | map('device_attr', 'name_by_user')
          | reject('match', 'None')
          | unique
          | list
          | sort
          | join('\n')
          }}

        ## A list of unavailable entities using their friendly names as assigned by the user.
        entity_name_list: >-
          {{ states
          | selectattr('domain','in',['binary_sensor', 'climate', 'light', 'sensor', 'switch'])
          | selectattr('state', 'in', ['unavailable'])
          | map(attribute='entity_id')
          | map('state_attr', 'friendly_name')
          | reject('match', 'None')
          | list
          | sort
          | join('\n')
          }}

        ## A list of unavailable entities using their entity ids (which may or may not have been set by the user).
        entity_id_list: >-
          {{ states
          | selectattr('domain','in',['binary_sensor', 'climate', 'light', 'sensor', 'switch'])
          | selectattr('state', 'in', ['unavailable'])
          | map(attribute='entity_id')
          | reject('match', 'None')
          | list
          | sort
          | join('\n')
          }}


# HOW THE ATTRIBUTE TEMPLATES WORK
#  -- Taking device_name_list as an example...
#
# {{ states                                                        -- all the states (entities) in the system
#    | selectattr('domain','in',['binary_sensor', 'climate', etc.  -- filter only the entities for real devices
#    | selectattr('state', 'in', ['unavailable'])                  -- filter only entities that are unavailable
#    | map(attribute='entity_id')                                  -- get the entity id from the record
#    | map('device_attr', 'name_by_user')                          -- map the entity id onto the device name
#    | reject('match', 'None')                                     -- take out names 'None' (meaning there is no name, so not a device)
#    | unique                                                      -- take out duplicates (devices usually have several entities)
#    | list                                                        -- make into a list (in the template sense)
#    | sort                                                        -- put them in alphabetical order
#    | join('\n')                                                  -- take out extraneous punctuation for a tidy output
#  }}

Great work :ok_hand: I learned a lot.

Some thoughts to share…
Now you have a sensor, but what doing with it?
Showing on a dashboard?

This may lead to the situation, that you (or whoever) have to look regularly on it.
And in the most cases you hopefully see: Everything okay.

But are you really interested in the information that everything is okay? Isn’t it a „waste of time“ to check again and again a status which is fine?

I would guess you are interested in the situation where something is wrong.
Personally I don’t like to get bothered with informations I don’t need or which does not trigger any kind of action.

So for me the logical next step would be to use the sensor you created to trigger an automation which informs me about a problem.
And in case there is no problem… why should I care for a „no-problem“? :wink:

I originally wrote it for a ‘service’ dashboard on which I also display devices with low battery level, the system status and the status of Zigbee repeaters. It is intended for general maintenance and first place to look when funny things happen.