Jinja / Template Sensor Question

I was looking to do something similar to this… After using this post for inspiration, I’ve ended up with a generic solution using device_class=battery, so there was no need to hardcode group/entity names.

This template outputs a comma delimited list of entity names where battery level is under 30%:

{% set separator = joiner(", ") %}
{% for battery in (states | selectattr('attributes.device_class', 'eq', 'battery'))  if battery.state | int < 30 %}
{{- separator() + battery.entity_id -}} 
{% endfor %}

I don’t have enough devices to test this properly (and the ones I do have, have fully-charged batteries). Can you do me a favor and try this out on your system and let me know if it produces correct results?

{{ states | selectattr('attributes.device_class', 'eq', 'battery')
    | rejectattr('state', 'eq', '100')
    | selectattr('state', 'lt', '30')
    | map(attribute='entity_id')
    | list | join(', ') }}

It performs a string comparison of the battery-level (which explains why it rejects 100 because in a string comparison, 100 is not less than 30). It assumes all battery-level values are strings. If there’s even one that is int or float, this template will fail.

This is just an exercise to see if useful results can be produced without employing a for-loop.

1 Like

It looks like your code works. I like it because it doesn’t require me to update all the groups that have batteries. This is what I get with your code snippet:

1 Like

Just be aware that’s there’s a subtle difference in how often this template will be updated compared to the other version.

In 0.117, a template containing states, and no other identifiable entities, will be evaluated every minute. That’s more than adequate to report a low battery but arguably much more than needed given that batteries last months.

In contrast, a template that expands a group (and contains no reference to states or states.DOMAIN) will be evaluated only when one of the group’s entities changes state. In other words, the template is updated only when something changes (which is more efficient than polling according to a fixed time interval).

You should know that it’s possible (and fairly easy) to create an automation that is triggered at startup and dynamically creates a group containing the entities you desire (like all entities whose device_class is battery). This eliminates the overhead of manually maintaining the group’s membership. The template can then refer to the dynamically-created group.

1 Like

Your code also works. This is what I get:

1 Like

I’ll have to look into that. Below is what I implemented a while back. Lot’s of room for improvement.

######## Low Battery Detection ########
  - id: low_battery_detection
    alias: Low Battery Detection
      - platform: numeric_state
        - sensor.garage_door_lock_battery
        - sensor.front_door_deadbolt_battery
        - sensor.deck_door_deadbolt_battery
        - sensor.basement_door_deadbolt_battery
        - sensor.garage_door_battery
        - sensor.garage_car_door_sensor_battery
        - sensor.laundry_entry_door_battery
        - sensor.front_door_battery
        - sensor.deck_door_battery
        - sensor.basement_stairs_door_battery
        - sensor.basement_door_battery
        - sensor.shop_door_battery
        - sensor.jeremys_closet_door_battery
        - sensor.nicholes_closet_door_battery
        - sensor.finishing_room_door_sensor_battery
        - sensor.master_bedroom_door_sensor_battery
        - sensor.pantry_door_sensor_battery
        - sensor.refrigerator_door_sensor_battery
        - sensor.dust_collector_blast_gate_sensor_battery
        - sensor.finishing_room_blast_gate_sensor_battery
        - sensor.bedroom_hall_motion_detector_battery
        - sensor.computer_room_motion_sensor_battery
        - sensor.computer_room_motion_sensor_desk_battery
        - sensor.living_room_motion_sensor_battery
        - sensor.kitchen_motion_sensor_battery
        - sensor.shop_motion_sensor_battery
        - sensor.shop_entry_motion_sensor_battery
        - sensor.shop_motion_beam_battery
        - sensor.shop_motion_over_saw_battery
        - sensor.dining_room_motion_sensor_battery
        - sensor.dining_room_motion_sensor_battery2
        - sensor.master_bedroom_motion_sensor_battery
        - sensor.master_bedroom_motion_sensor2_battery
        - sensor.basement_lumber_rack_motion_sensor_battery
        - sensor.basement_workout_motion_sensor_battery
        - sensor.basement_play_area_motion_sensor_battery
        - sensor.basement_stairs_motion_sensor_battery
        - sensor.basement_beam_motion_sensor_battery
        - sensor.craft_room_motion_sensor_battery
        - sensor.craft_room_motion_desk_battery
        - sensor.half_bathroom_motion_sensor_battery
        - sensor.master_bath_motion_sensor_battery
        - sensor.master_bath_motion_sensor_battery2
        - sensor.master_bath_shower_motion_sensor_battery
        - sensor.poop_closet_motion_sensor_battery
        - sensor.guest_bathroom_motion_sensor_battery
        - sensor.finishing_room_motion_sensor_battery
        - sensor.finishing_room_door_motion_sensor_battery
        - sensor.laundry_room_motion_sensor_battery
        - sensor.garage_motion_sensor2_battery
        - sensor.main_hall_temp_sensor_battery
        - sensor.guest_bath_temp_sensor_battery
        - sensor.basement_temp_sensor_battery
        - sensor.basement_temp_sensor_dust_collector_battery
        - sensor.shop_cart_temp_sensor_battery
        - sensor.shop_entry_temp_sensor_battery
        - sensor.shop_jointer_temp_sensor_battery
        - sensor.deck_temp_sensor_battery
        - sensor.finishing_room_temp_sensor_battery
        - sensor.finishing_room_temp_sensor2_battery
        - sensor.basement_stairs_temp_sensor_battery
        - sensor.master_bath_temp_sensor_battery
        - sensor.fireplace_temp_sensor_battery
        - sensor.grill_temp_sensor_battery
        - sensor.grill_temp_sensor2_battery
        - sensor.garage_temp_sensor_battery
        - sensor.roomba_battery
        - sensor.fireplace_gas_sensor_battery
        - sensor.water_heater_gas_sensor_battery
        - sensor.shop_smoke_detector_battery
        - sensor.finishing_room_smoke_detector_battery
        - sensor.shop_cube_battery
        - sensor.shop_cube2_battery
        - sensor.jeremys_cube_battery
        - sensor.nicholes_cube_battery
        - sensor.basement_cube_battery
        - sensor.hannahs_cube_battery
        - sensor.shop_button1_battery
        - sensor.shop_button2_battery
        - sensor.shop_button3_battery
        - sensor.shop_button4_battery
        - sensor.shop_button5_battery
        - sensor.shop_button6_battery
        - sensor.shop_button7_battery
        - sensor.shop_button8_battery
        - sensor.shop_button9_battery
        - sensor.living_room_button_battery
        - sensor.master_bath_button_battery
        - sensor.basement_button_battery
        - sensor.laundry_room_leak_sensor_battery
        - sensor.shop_leak_sensor_battery
        - sensor.poop_closet_leak_sensor_battery
        - sensor.backyard_hose_leak_sensor_battery
        - sensor.driveway_hose_leak_sensor_battery
        - sensor.half_bath_leak_sensor_battery
        - sensor.half_bath_sink_leak_sensor_battery
        - sensor.guest_bath_left_sink_leak_sensor_battery
        - sensor.guest_bath_right_sink_leak_sensor_battery
        - sensor.master_bath_jeremy_sink_leak_sensor_battery
        - sensor.master_bath_nichole_sink_leak_sensor_battery
        - sensor.kitchen_sink_leak_sensor_battery
        - sensor.guest_bath_sink_leak_sensor_battery
        - sensor.water_heater_leak_sensor_battery
        - sensor.finishing_room_water_leak_sensor_battery
        - sensor.basement_sink_water_leak_sensor_battery
        below: 25
      - platform: state
        entity_id: input_boolean.ha_initialized
        to: 'on'
      - condition: state
        entity_id: input_boolean.ha_initialized
        state: 'on'
      - service: group.set
          name: Low Battery Levels
          object_id: low_battery_levels
          entities: >
            {% set entities = expand('group.door_lock_batteries') +
                              expand('group.door_sensor_batteries') +
                              expand('group.motion_sensor_batteries') +
                              expand('group.temp_sensor_batteries') +
                              expand('group.other_batteries') +
                              expand('group.leak_sensor_batteries') +
                              expand('group.button_cube_batteries') %}
            {% for x in entities if x.state|int < 25 %}
              {%- if not loop.first %}, {% endif -%}
              {{- x.entity_id -}}
            {% endfor %}
      - condition: template
        value_template: >
          {% if trigger.to_state.name == "Home Assistant Initialized" %}
          {% else %}
          {% endif %}
      - service: notify.text_jeremy
          message: >
            {{ trigger.to_state.name }} level is less than 25 percent
      - service: persistent_notification.create
          title: "Low Battery Warning"
          notification_id: low_battery
          message: >
            {{ trigger.to_state.name }} level is less than 25 percent

I spent ages trying to do this without a generator/for loop, as I wanted to have the sensor as “on/off” and have an array/list attribute with the names of the sensors.

As you say, select/rejectattr treats the state as a string, and there doesn’t appear to be any test in jinja/HA templating that will do the lt/gt and cast the state to an integer. I think this would need either custom tests in HA or a custom filter that allows us to treat attributes as numbers instead of strings

I don’t think that 2nd one does work properly as you get different results. The states are being compared as strings instead of numbers so it’s not returning a correct list.

That’s correct. I’m investigating adding this as we speak.


As mentioned, the template I suggested provides different results due to the string comparison.

What is the battery_level of one of the entities that it fails to include? For example, deck_temp_sensor_battery and phone_battery_jeremy? I’m curious to know what failed the string comparison. For example, ‘8’ fails to be less than ‘30’ in a string comparison.

deck_temp_sensor_battery is ‘unavailable’ (I have issues with that sensor)
phone_battery_jeremy is ‘unknown’ (I am going through a re-install currently, so that is probably messed up at the moment)

Yup, that will definitely goof it up.

Anyway, just curious; a string comparison is simply unreliable for this application.

It’s possible to use map('int') to convert values but you can only pass it values, not objects, so you lose all other information about the entity like it’s entity_id.

I’m hoping Petro can enhance the templating engine so that we can be more selective in how a filter is applied.

1 Like

I know this is a long dead thread, but I’m just wondering if anything ever came of this. I’m running into the same issue now, but am not seeing any mentions of new solutions.

what are you trying to do, many template changes have been made

Sorry, thought the thread would provide enough context.

I’m wondering if there are any new ways to cast values in something like selectattr() before the comparison is done, as you’ve mentioned you were looking into above. More specifically, I want to do something like the following template, but ensure that the comparison is treating all values as ints, not strings:

{{ states.sensor | selectattr('entity_id', 'search', 'shelly') | selectattr('state', 'le', '20') | join(' and ', attribute='attributes.friendly_name') }}

I did not add them, at the time it wasn’t possible to add tests into the jinja environment. I’ll see what it will take to get these added.


Awesome, I’ll keep my eye out. Thanks!

I’m also struggling with something similar: I want to select all battery levels <= treshold

{% for item in states.sensor
| selectattr('attributes.device_class', '==', 'battery')
| selectattr('attributes.unit_of_measurement', '==', '%')
| selectattr('state', '<=', '50') %}


You do not need a for loop:

{{ states.sensor
| selectattr('attributes.device_class', '==', 'battery')
| selectattr('attributes.unit_of_measurement', '==', '%')
| selectattr('state', '<=', '50')
| map(attribute='entity_id')
| list }}

@Didgeridrew This is not working because you do the check on a string in your case while you should do the check on an int imho.

1 Like