Creating alert with data about disconnected/non responding battery devices

Hi there,
I am trying to create an alert that pops up when a battery powered sensor needs attention.
I used a naming convention with all my zigbee sensors:
example: the kitchen motion sensor has 3 entities, 1 for each sub-sensor:
binary_sensor.kitchen_motion_sensor
sensor.kitchen_motion_sensor_battery_level
sensor.kitchen_motion_sensor_temperature

The strict naming convention enabled me to create many useful automations but I am struggling with creating an alert that pops up whenever a sensor becomes unavailable or not responsive.
I managed to create a template with all the relevant information calculated:

{% set vars = namespace(
                numberOfBatteryDevices = 0,
                numberOfEmptyBatteryDevices = 0,
                emptyBatteryDevices = '',
                numberOfNonResponsiveDevices = 0,
                nonResponsiveDevices = '',
                numberOfLowBatteryDevices = 0,
                lowBatteryDevices = '',
                goodBatteryDevices = ''
                ) -%}
{%- for curr_entity in (states |selectattr('entity_id')| list) -%}
{%-   if curr_entity.entity_id.endswith('_battery_level') -%}
    {%- set _batteryDevice=curr_entity.entity_id -%}
    {%- set _sensorName= curr_entity.entity_id.split('.')[1].split('_battery_level')[0] -%}
    {%- set _batteryLevel = states[_batteryDevice].state  -%}
    {%- set _temperatureSensor = 'sensor.'~_sensorName~'_temperature'  -%}
    {%- set _lastChanged = states[_temperatureSensor].last_changed  -%}
    {%- if _lastChanged!=null -%} {#probably a phone, not a sensor!#}
      {%- set vars.numberOfBatteryDevices = vars.numberOfBatteryDevices+ 1-%}
      {%- set _hoursPassed = (((as_timestamp(now()) - as_timestamp(_lastChanged)) | int) % 86400)//3600  %}
      {%- if _batteryLevel=='unavailable' -%}
        {%- set vars.numberOfEmptyBatteryDevices = vars.numberOfEmptyBatteryDevices+ 1 -%}
        {%- set vars.emptyBatteryDevices = vars.emptyBatteryDevices~',' if vars.emptyBatteryDevices!='' -%}
        {%- set vars.emptyBatteryDevices = vars.emptyBatteryDevices~_sensorName -%}
      {%- elif _hoursPassed>=2 -%}
        {%- set vars.numberOfNonResponsiveDevices = vars.numberOfNonResponsiveDevices+ 1 -%}
        {%- set vars.nonResponsiveDevices = vars.nonResponsiveDevices~',' if vars.nonResponsiveDevices!='' -%}
        {%- set vars.nonResponsiveDevices = vars.nonResponsiveDevices~_sensorName -%}
      {%- elif _batteryLevel|int <40 -%}
        {%- set vars.numberOfLowBatteryDevices = vars.numberOfLowBatteryDevices+ 1 -%}
        {%- set vars.lowBatteryDevices = vars.lowBatteryDevices~',' if vars.lowBatteryDevices!='' -%}
        {%- set vars.lowBatteryDevices = vars.lowBatteryDevices~_sensorName -%}
      {%- else -%}
        {%- set vars.goodBatteryDevices = vars.goodBatteryDevices~',' if vars.goodBatteryDevices!='' -%}
        {%- set vars.goodBatteryDevices = vars.goodBatteryDevices~_sensorName -%}
      {%- endif -%}
{#'battery device:'~ _batteryDevice}}
{{'sensor name:'~ _sensorName }}
{{'battery level:'~ _batteryLevel }}
{{'temperature sensor:'~ _temperatureSensor}}
{{'last changed:'~ _hoursPassed~'|'~_lastChanged #}
    {%- endif -%}
  {%- endif %}
{%- endfor -%}
{%- set _totalBatteryIssues = vars.numberOfEmptyBatteryDevices + vars.numberOfNonResponsiveDevices %}
Number Of Battery Devices: {{vars.numberOfBatteryDevices}}
Number Of Empty Battery Devices: {{vars.numberOfEmptyBatteryDevices}}
Empty Battery Devices: {{vars.emptyBatteryDevices}}
Number Of Non Responsive Devices: {{vars.numberOfNonResponsiveDevices}}
Non Responsive Devices: {{vars.nonResponsiveDevices}}
Number Of Low Battery Devices: {{vars.numberOfLowBatteryDevices}}
Low Battery Devices: {{vars.lowBatteryDevices}}
Total Battery Issues: {{ _totalBatteryIssues }}
Good Battery Devices: {{vars.goodBatteryDevices}}

The output is as follows:

Number Of Battery Devices: 17
Number Of Empty Battery Devices: 4
Empty Battery Devices: bedroom_bath_button,closet_room_motion_sensor,kids_room_motion_sensor,main_yard_button
Number Of Non Responsive Devices: 2
Non Responsive Devices: living_room_button,main_bath_motion_sensor
Number Of Low Battery Devices: 1
Low Battery Devices: bedroom_button
Total Battery Issues: 6
Good Battery Devices: bedroom_bath_motion_sensor,bedroom_motion_sensor,den_motion_sensor,front_door_sensor,kitchen_motion_sensor,kitchen_yard_left_door_sensor,kitchen_yard_right_door_sensor,living_room_back_motion_sensor,main_yard_door_sensor,vault_sensor

Can someone help me to create a binary_sensor using this template that is on if _totalBatteryIssues>0 but also stores a list of the sensors that I created in the template somehow.

If we had support for global variables, this would have been easy, but since we don’t, I can’t think of a good way to manage this information.

Any ideas?

Update, I created the following sensor:

binary_sensor:
  - platform: template
    sensors:     
      battery_devices_issues:
        friendly_name: 'Battery Devices Issues'
        device_class: problem
        value_template: >-
          {% set vars = namespace(
                numberOfBatteryDevices = 0,
                numberOfEmptyBatteryDevices = 0,
                emptyBatteryDevices = '',
                numberOfNonResponsiveDevices = 0,
                nonResponsiveDevices = '',
                numberOfLowBatteryDevices = 0,
                lowBatteryDevices = '',
                goodBatteryDevices = ''
                ) -%}
          {%- for curr_entity in (states |selectattr('entity_id')| list) -%}
          {%-   if curr_entity.entity_id.endswith('_battery_level') -%}
              {%- set _batteryDevice=curr_entity.entity_id -%}
              {%- set _sensorName= curr_entity.entity_id.split('.')[1].split('_battery_level')[0] -%}
              {%- set _batteryLevel = states[_batteryDevice].state  -%}
              {%- set _temperatureSensor = 'sensor.'~_sensorName~'_temperature'  -%}
              {%- set _lastChanged = states[_temperatureSensor].last_changed  -%}
              {%- if _lastChanged!=null -%} {#probably a phone, not a sensor!#}
                {%- set vars.numberOfBatteryDevices = vars.numberOfBatteryDevices+ 1-%}
                {%- set _hoursPassed = (((as_timestamp(now()) - as_timestamp(_lastChanged)) | int) % 86400)//3600  %}
                {%- if _batteryLevel=='unavailable' -%}
                  {%- set vars.numberOfEmptyBatteryDevices = vars.numberOfEmptyBatteryDevices+ 1 -%}
                  {%- set vars.emptyBatteryDevices = vars.emptyBatteryDevices~',' if vars.emptyBatteryDevices!='' -%}
                  {%- set vars.emptyBatteryDevices = vars.emptyBatteryDevices~_sensorName -%}
                {%- elif _hoursPassed>=2 -%}
                  {%- set vars.numberOfNonResponsiveDevices = vars.numberOfNonResponsiveDevices+ 1 -%}
                  {%- set vars.nonResponsiveDevices = vars.nonResponsiveDevices~',' if vars.nonResponsiveDevices!='' -%}
                  {%- set vars.nonResponsiveDevices = vars.nonResponsiveDevices~_sensorName -%}
                {%- elif _batteryLevel|int <40 -%}
                  {%- set vars.numberOfLowBatteryDevices = vars.numberOfLowBatteryDevices+ 1 -%}
                  {%- set vars.lowBatteryDevices = vars.lowBatteryDevices~',' if vars.lowBatteryDevices!='' -%}
                  {%- set vars.lowBatteryDevices = vars.lowBatteryDevices~_sensorName -%}
                {%- else -%}
                  {%- set vars.goodBatteryDevices = vars.goodBatteryDevices~',' if vars.goodBatteryDevices!='' -%}
                  {%- set vars.goodBatteryDevices = vars.goodBatteryDevices~_sensorName -%}
                {%- endif -%}
              {%- endif -%}
            {%- endif %}
          {%- endfor -%}
          {%- set _totalBatteryIssues = vars.numberOfEmptyBatteryDevices + vars.numberOfNonResponsiveDevices %}
          {{ _totalBatteryIssues>0 }}

It works well but I can’t figure out where I should store the sensors list to display later.
It would be highly inefficient to run this template again multiple times just to extract the values that I already have.

What you want to achieve is easily done if you use custom attributes instead of packing metadata into an entity’s name. It’s trivial to create a template to select entities based on the values of one or more custom attributes. The template can be the basis of a Template Binary Sensor or Template Sensor.

For more information:

Let me know if this interests you and I can help you implement it.


NOTE

You can also store computed data (i.e. the result of a template) in an entity’s custom attribute.

You should also be aware that an entity’s state value cannot hold more than 255 characters and only of type string. Its attributes have no such limitation and can also store data with other types.

Hi Taras,
I am not using the entity name for anything but the name.
I am using deCONZ and all my ZigBee sensors reports both the battery status and the temperature as separate sensors. The naming convention helps me to figure out which battery sensor and temperature sensor belongs to each physical sensor.
I am using the battery sensor to figure out when a battery is low or unavailable
I am using the temperature sensor to figure out if a sensor stopped reporting for more than 2 hours (it takes time for deCONZ to report a sensor as unavailable, monitoring the last_changed of the temperature sensor triggers it a lot faster (2 hours in my template vs. 2 days in deCONZ)

My question was how to create a reporting sensor using my template that reports IF there are battery devices that needs changing and also provides access to the list of the devices.

You can also store computed data (i.e. the result of a template) in an entity’s custom attribute.

This suggestion is interesting:

You should also be aware that an entity’s state value cannot hold more than 255 characters and only of type string. Its attributes have no such limitation and can also store data with other types

Can you give me a pointer on how to do this?

Does deCONZ automatically generate the entity’s name using that convention?

It mostly does, sometimes Home Assistant messes it up a bit with “auto-discovery” but it is really easy to correct and you only have to do it once when adding a physical sensor to deCONZ

My point is you are manually changing the entity’s name and including additional information (metadata) following a naming convention you have devised.

I explain (in the linked post) that this investment of your time and effort returns greater benefits if you create custom attributes to store the metadata (as opposed to within the entity’s name).

Anyway, it’s a suggestion and you’re free to choose whatever strategy you prefer. All I can say is the technique I’m proposing employs far more compact templates than the one you posted. That becomes an advantage when composing a Template Sensor containing multiple attribute_templates.

Is there a way to use a template to attach custom attribute values to an entity?
I only used custom attributes in automations but I have no idea where to define a custom attribute template without an automation trigger.

How do you define a custom attribute template with an automation trigger? Can you provide an example of what you mean?

I define it with a default value in my configuration.yaml and I use automation triggers to set the values.

That statement is unclear to me because triggers don’t set values. Services set values and services are called within a script or in an automation’s action. Can you clarify what you mean exactly? Perhaps with an example?

The value of a Template Sensor’s attribute can be set by the Template Sensor’s attribute_templates.

So I think I got it working.
I don’t like the code and the fact that I can’t reuse my set variables throughout the template, I wish we had a way to use scope variables when using templates…

This is how I set the binary_sensor with a list of batteries that I need to replace:

binary_sensor:
  - platform: template
    sensors:       
      battery_devices_issues:
        friendly_name: 'Battery Devices Issues'
        device_class: problem
        value_template: >-
          {% set vars = namespace(
                numberOfBatteryDevices = 0,
                numberOfEmptyBatteryDevices = 0,
                emptyBatteryDevices = '',
                numberOfNonResponsiveDevices = 0,
                nonResponsiveDevices = '',
                numberOfLowBatteryDevices = 0,
                lowBatteryDevices = '',
                goodBatteryDevices = ''
                ) -%}
          {%- for curr_entity in (states |selectattr('entity_id')| list) -%}
          {%-   if curr_entity.entity_id.endswith('_battery_level') -%}
              {%- set _batteryDevice=curr_entity.entity_id -%}
              {%- set _sensorName= curr_entity.entity_id.split('.')[1].split('_battery_level')[0] -%}
              {%- set _batteryLevel = states[_batteryDevice].state  -%}
              {%- set _temperatureSensor = 'sensor.'~_sensorName~'_temperature'  -%}
              {%- set _lastChanged = states[_temperatureSensor].last_changed  -%}
              {%- if _lastChanged!=null -%} {#probably a phone, not a sensor!#}
                {%- set vars.numberOfBatteryDevices = vars.numberOfBatteryDevices+ 1-%}
                {%- set _hoursPassed = (((as_timestamp(now()) - as_timestamp(_lastChanged)) | int) % 86400)//3600  %}
                {%- if _batteryLevel=='unavailable' -%}
                  {%- set vars.numberOfEmptyBatteryDevices = vars.numberOfEmptyBatteryDevices+ 1 -%}
                  {%- set vars.emptyBatteryDevices = vars.emptyBatteryDevices~',' if vars.emptyBatteryDevices!='' -%}
                  {%- set vars.emptyBatteryDevices = vars.emptyBatteryDevices~_sensorName -%}
                {%- elif _hoursPassed>=2 -%}
                  {%- set vars.numberOfNonResponsiveDevices = vars.numberOfNonResponsiveDevices+ 1 -%}
                  {%- set vars.nonResponsiveDevices = vars.nonResponsiveDevices~',' if vars.nonResponsiveDevices!='' -%}
                  {%- set vars.nonResponsiveDevices = vars.nonResponsiveDevices~_sensorName -%}
                {%- elif _batteryLevel|int <40 -%}
                  {%- set vars.numberOfLowBatteryDevices = vars.numberOfLowBatteryDevices+ 1 -%}
                  {%- set vars.lowBatteryDevices = vars.lowBatteryDevices~',' if vars.lowBatteryDevices!='' -%}
                  {%- set vars.lowBatteryDevices = vars.lowBatteryDevices~_sensorName -%}
                {%- else -%}
                  {%- set vars.goodBatteryDevices = vars.goodBatteryDevices~',' if vars.goodBatteryDevices!='' -%}
                  {%- set vars.goodBatteryDevices = vars.goodBatteryDevices~_sensorName -%}
                {%- endif -%}
              {%- endif -%}
            {%- endif %}
          {%- endfor -%}
          {%- set _totalBatteryIssues = vars.numberOfEmptyBatteryDevices + vars.numberOfNonResponsiveDevices %}
          {{ _totalBatteryIssues>0 }}
        attribute_templates:
          empty_batteries: >-
            {% set vars = namespace(
                  numberOfBatteryDevices = 0,
                  numberOfEmptyBatteryDevices = 0,
                  emptyBatteryDevices = '',
                  numberOfNonResponsiveDevices = 0,
                  nonResponsiveDevices = '',
                  numberOfLowBatteryDevices = 0,
                  lowBatteryDevices = '',
                  goodBatteryDevices = ''
                  ) -%}
            {%- for curr_entity in (states |selectattr('entity_id')| list) -%}
            {%-   if curr_entity.entity_id.endswith('_battery_level') -%}
                {%- set _batteryDevice=curr_entity.entity_id -%}
                {%- set _sensorName= curr_entity.entity_id.split('.')[1].split('_battery_level')[0] -%}
                {%- set _batteryLevel = states[_batteryDevice].state  -%}
                {%- set _temperatureSensor = 'sensor.'~_sensorName~'_temperature'  -%}
                {%- set _lastChanged = states[_temperatureSensor].last_changed  -%}
                {%- if _lastChanged!=null -%} {#probably a phone, not a sensor!#}
                  {%- set vars.numberOfBatteryDevices = vars.numberOfBatteryDevices+ 1-%}
                  {%- set _hoursPassed = (((as_timestamp(now()) - as_timestamp(_lastChanged)) | int) % 86400)//3600  %}
                  {%- if _batteryLevel=='unavailable' -%}
                    {%- set vars.numberOfEmptyBatteryDevices = vars.numberOfEmptyBatteryDevices+ 1 -%}
                    {%- set vars.emptyBatteryDevices = vars.emptyBatteryDevices~',' if vars.emptyBatteryDevices!='' -%}
                    {%- set vars.emptyBatteryDevices = vars.emptyBatteryDevices~_sensorName -%}
                  {%- elif _hoursPassed>=2 -%}
                    {%- set vars.numberOfNonResponsiveDevices = vars.numberOfNonResponsiveDevices+ 1 -%}
                    {%- set vars.nonResponsiveDevices = vars.nonResponsiveDevices~',' if vars.nonResponsiveDevices!='' -%}
                    {%- set vars.nonResponsiveDevices = vars.nonResponsiveDevices~_sensorName -%}
                  {%- elif _batteryLevel|int <40 -%}
                    {%- set vars.numberOfLowBatteryDevices = vars.numberOfLowBatteryDevices+ 1 -%}
                    {%- set vars.lowBatteryDevices = vars.lowBatteryDevices~',' if vars.lowBatteryDevices!='' -%}
                    {%- set vars.lowBatteryDevices = vars.lowBatteryDevices~_sensorName -%}
                  {%- else -%}
                    {%- set vars.goodBatteryDevices = vars.goodBatteryDevices~',' if vars.goodBatteryDevices!='' -%}
                    {%- set vars.goodBatteryDevices = vars.goodBatteryDevices~_sensorName -%}
                  {%- endif -%}
                {%- endif -%}
              {%- endif %}
            {%- endfor -%}
            {{vars.emptyBatteryDevices ~','~ vars.nonResponsiveDevices }}
  • I still need to work on this, the code doesn’t meet my coding standards but it is working.

@123 Thanks for the lead with custom attributes.

Hi buddy,
I am sorry if I confused you.
I figured out how to get the binary_sensor to work but to answer your question:

defining a custom attribute in configuration.yaml for all the entities that are tracked by my alarm logic

  customize_glob:
    "light.*shade":
      icon: mdi:window-shutter
    "light.*":
      alarm_tracking: true
    "binary_sensor.*_motion_sensor":
      alarm_tracking: true
    "binary_sensor.*_contact_sensor":
      alarm_tracking: true
    "binary_sensor.*_door_sensor":
      alarm_tracking: true
    "lock.*":
      alarm_tracking: true
  customize:
    binary_sensor.vault_sensor:
      alarm_tracking: true
    switch.outside_fan:
      alarm_tracking: true

example for using the custom attribute in an automation:

- id: '1111111111111'
  alias: Security - Alarm Armed Away
  description: ''
  trigger:
  - platform: device
    device_id: c2a59ce4973c2a59ce4973c2a59ce4973
    domain: alarm_control_panel
    entity_id: alarm_control_panel.security_mode
    type: armed_away
  condition: []
  action:
  - service: notify.all_devices
    data:
      title: 🔔Alarm Was Armed- Securing Home!
      message: You will recieve another notification when the shut down completes.
      data:
        subtitle: Home is shutting down
        url: /home-security/security_panel
        push:
          sound:
            name: default
            critical: 1
            volume: 1
          thread-id: presence-notification-group
          category: alarm_armed
        action_data:
        - identifier: AUTHORIZE_PRESENCE
          title: Authorized person is home
        apns_headers:
          apns-collapse-id: alarm-armed-notification
        presentation_options:
        - alert
  - service: script.home_off
    data: {}
  - delay:
      hours: 0
      minutes: 1
      seconds: 0
      milliseconds: 0
  - service: notify.all_devices
    data:
      title: Alarm Was Armed- Home Secured!
      message: "{%- set tracked_devices = states | selectattr('attributes.alarm_tracking',\
        \ 'defined')|selectattr('state','in', ['on', 'unlocked', 'unavailable'])|\
        \ list -%} {%- for device in tracked_devices -%}\n  {{device.name ~ \" is\
        \ \" ~ device.state ~ \",\\n\" }}\n{%- endfor -%}"
      data:
        subtitle: '{%- set counter= states | selectattr(''attributes.alarm_tracking'',
          ''defined'')|selectattr(''state'',''in'', [''on'', ''unlocked'', ''unavailable''])|
          list| count -%} {%- if counter>0 -%} {{ ''Pay attention to '' ~ counter
          ~ '' security incidents:''}} {% else %} {{ ''No Security incidents''}} {%
          endif %}'
        url: /home-security/security_panel
        push:
          sound:
            name: default
            critical: 1
            volume: 1
          thread-id: presence-notification-group
          category: alarm_armed
        action_data:
        - identifier: AUTHORIZE_PRESENCE
          title: Authorized person is home
        apns_headers:
          apns-collapse-id: alarm-armed-notification
        presentation_options:
        - alert

Your example of ’ using the custom attribute in an automation’ does not show what you claimed earlier:

I define it with a default value in my configuration.yaml and I use automation triggers to set the values

I see where you defined it but I don’t see where in that automation it sets the value of a custom attribute; it reads the value of a custom attribute.

As for the binary_sensor example, it demonstrate the point I made earlier regarding the advantage of using custom attributes to produce compact templates. The binary_sensor relies on a lengthy template (due to the need to parse metadata out of an entity’s name) and then is obligated to duplicate the entire lengthy template in attributes_template.

FWIW, the following template casts a very wide net because it uses the State Objects of all entities in your system:

states | selectattr('entity_id') | list

If you know that only certain domains have battery-operated devices then you can narrow the field to include only those domains:

expand(states.light, states.sensor, states.binary_sensor, states.lock) | selectattr('entity_id') | list

For future reference, here’s an alternate way of reporting entities with low battery levels. The user had the additional need to provide each battery-operated device with its own low-battery threshold (as opposed to using the same threshold for all entities). If you only require a single threshold value then it can be specified with customize_glob (as opposed to a per entity basis) given that your entities adhere to a naming convention.

Thanks buddy, You gave me some useful tips here.
I will definitely implement them.

The link you attached would not work for my needs since I don’t want a time triggered notification Nor adding state triggers for each sensor.
My example is using a custom template, once it works, it will report any battery sensor as soon as it becomes unresponsive or unavailable or crosses the low-battery threshold (40%)
It works just fine now but I will be implementing your suggestion to narrow the for-loop scope to the desired domain.
You have been most helpful, not just here, I read a lot of your posts here and they are gold!

@123
I finally found a solution that I am comfortable with.

this is how I set my binary_sensor, without repeating templates and with a bit of clever coding:

 binary_sensor:
  - platform: template
    sensors:   
      battery_devices_issues:
        friendly_name: 'Battery Devices Issues'
        device_class: problem
        value_template: >-
          {% set batteryDevicesStatus = (state_attr('binary_sensor.battery_devices_issues', 'all_battery_devices_status')) %}
          {{batteryDevicesStatus.empty_battery_devices|count+batteryDevicesStatus.non_responsive_devices|count>0}}
        attribute_templates:
          all_battery_devices_status: >-
            {% set vars = namespace(
                  emptyBatteryDevices = '',
                  nonResponsiveDevices = '',
                  lowBatteryDevices = '',
                  goodBatteryDevices = ''
                  ) -%}
            {%- for curr_entity in (expand(states.sensor) | selectattr('entity_id') | list) -%}
              {%- if curr_entity.entity_id.endswith('_battery_level') -%}
                {%- set _batteryDevice=curr_entity.entity_id -%}
                {%- set _sensorName= curr_entity.entity_id.split('.')[1].split('_battery_level')[0] -%}
                {%- set _batteryLevel = states[_batteryDevice].state  -%}
                {%- set _temperatureSensor = 'sensor.'~_sensorName~'_temperature'  -%}
                {%- set _lastChanged = states[_temperatureSensor].last_changed  -%}
                {%- if _lastChanged!=null -%} {#probably a phone, not a sensor!#}
                  {%- set _hoursPassed = (((as_timestamp(now()) - as_timestamp(_lastChanged)) | int) % 86400)//3600  %}
                  {%- if _batteryLevel=='unavailable' -%}
                    {%- set vars.emptyBatteryDevices = vars.emptyBatteryDevices~"," if vars.emptyBatteryDevices!="" -%}
                    {%- set vars.emptyBatteryDevices = vars.emptyBatteryDevices~ "'" ~ _sensorName ~ "'" -%}
                  {%- elif _hoursPassed>=2 -%}
                    {%- set vars.nonResponsiveDevices = vars.nonResponsiveDevices~"," if vars.nonResponsiveDevices!="" -%}
                    {%- set vars.nonResponsiveDevices = vars.nonResponsiveDevices~ "'" ~ _sensorName ~ "'" -%}
                  {%- elif _batteryLevel|int <40 -%}
                    {%- set vars.lowBatteryDevices = vars.lowBatteryDevices~"," if vars.lowBatteryDevices!="" -%}
                    {%- set vars.lowBatteryDevices = vars.lowBatteryDevices~ "'" ~ _sensorName ~ "'"-%}
                  {%- else -%}
                    {%- set vars.goodBatteryDevices = vars.goodBatteryDevices~"," if vars.goodBatteryDevices!="" -%}
                    {%- set vars.goodBatteryDevices = vars.goodBatteryDevices~ "'" ~ _sensorName ~ "'" -%}
                  {%- endif -%}
                {%- endif -%}
              {%- endif %}
            {%- endfor -%}
            {%- set batteryDevicesStatusJSON = "{'empty_battery_devices':["~vars.emptyBatteryDevices~"],
                                                 'non_responsive_devices':["~vars.nonResponsiveDevices~"],
                                                 'low_battery_devices':["~vars.lowBatteryDevices~"],
                                                 'good_battery_devices':["~vars.goodBatteryDevices~"],
                                                 }" -%}
            {{batteryDevicesStatusJSON}}

I am setting a JSON in a custom attribute that I can access from anywhere in home assistant + the sensor accesses it’s own custom attribute to determine it’s state.
Now I am getting notifications as I wanted, no need to update any scripts when I add sensors.

1 Like

If you’re interested, I refactored the template using several techniques that help to reduce its length and complexity.

I have created several battery_level and temperature sensors (as Template Sensors) in order to test the streamlined version of the template. It appears to work as desired but the acid-test will, of course, be your system’s sensors.

Screenshot from 2021-02-16 11-55-42

      battery_devices_issues:
        friendly_name: 'Battery Devices Issues'
        device_class: problem
        value_template: >-
          {% set status = (state_attr('binary_sensor.battery_devices_issues', 'status')) %}
          {{ status.empty|count + status.dead|count > 0 }}
        attribute_templates:
          status: >-
            {% set ns = namespace(empty = [], dead = [], low = [], good = []) %}
            {% for entity in expand(states.sensor) if entity.entity_id.endswith('_battery_level') %}
              {% set name = entity.object_id[:-14] %}
              {% set level = states(entity.entity_id) %}
              {% set changed = states['sensor.'~name~'_temperature'].last_changed %}
              {% if changed != null %}
                {% if level == 'unavailable' %} {% set ns.empty = ns.empty + [name] %}
                {% elif now() - changed >= timedelta(hours=2) %} {% set ns.dead = ns.dead + [name] %}
                {% elif level|int < 40 %} {% set ns.low = ns.low + [name] %}
                {% else %} {% set ns.good = ns.good + [name] %}
                {% endif %}
              {% endif %}
            {% endfor %}
            {{ "{{\"empty\": {}, \"dead\": {}, \"low\": {}, \"good\": {} }}".format(ns.empty, ns.dead, ns.low, ns.good) }}

What has changed

Although I changed the names of many variables, that’s purely cosmetic (feel free to replace them with your original names ). The important changes are:

  • The type of the namespace variables is list instead of string. This alone eliminates a lot of “list-building” code.
  • The for-loop incorporates the initial if-statement in order to reduce iterations.
  • Parsing the sensor’s root-name is simplified by referencing the entity’s object_id and slicing off the last 14 characters.
  • Eliminating variables for expressions that are only used once.
  • Simplifying the time calculation by using native datetime and timedelta objects.
  • Generate the final JSON string using the format method.
2 Likes

Awesome!
I started coding for home assistant a couple of weeks ago, I am still learning the framework (and jinja).
The HA documentation is lacking, especially in the more complex scenarios, I appreciate your help.
I will test it and report back.

[UPDATE]
Works as expected!

Glad to hear it passed the test.

I made one more simplification. This line contains unnecessary filtering:

 {% for entity in (expand(states.sensor)|selectattr('entity_id')|list) if entity.entity_id.endswith('_battery_level') %}

and is reduced to this:

 {% for entity in expand(states.sensor) if entity.entity_id.endswith('_battery_level') %}

You should know that the use of states.sensor means this template will be evaluated every time any of your sensors changes state. If battery-powered devices comprise the majority of your sensors then using states.sensor is fine. If they represent the minority then states.sensor casts much too wide a net.

The simplest technique to improve efficiency is to create a group containing all battery-powered entities. The for-loop statement is reduced to:

 {% for entity in expand('group.battery_powered') %}

and the template is evaluated only when one of the group’s members changes state (as opposed to any and all sensors in your system).

If manually creating the group seems like an onerous task then one could use an automation, triggered at startup, to automatically create the group and populate it with sensors whose names end with the phrase “battery_level”. The only potential ‘gotcha’ with this technique is if, on startup, Home Assistant creates the Template Binary Sensor before the automation creates the group. :thinking: The sequence of what gets created on startup becomes important here.