Script to parse battery levels

Hi,

I’m trying to write a script that will extract the battery levels for the batteries that are defined in the script.
Here is the code of my script:

low_battery:
  alias: 'Script: Low Battery Alert'
  mode: single
  variables:
    a: !secret pankaj_email
    b: !secret pankaj_cell
    battery_sensors:
      - sensor.frontdoor_battery
      - sensor.slidingdoorbackyard_battery
      - sensor.prats01_battery
      - sensor.prats02_battery
      - sensor.sid01_battery
      - sensor.sid02_battery
      - sensor.frontdoorinside_battery
      - sensor.ewelink_th01_battery
      - sensor.motionsensorfrontporchhue_battery
      - sensor.motionsensordrivewayhue_battery
      - sensor.backdoor_battery
      - sensor.garage_motion_hue_battery
    output_message: |
      {% set ns = namespace( targets=[] ) %}
      {% for b in battery_sensors %}
      {% set ns.targets = ns.targets + state_attr(b, 'friendly_name') %}
      {% endfor %}{{ ns.targets }}
  sequence:
    - service: notify.smtp_1_gmail
      data:
        message: "{{ output_message }}"
        target:
          - "{{ a }}"
          - "{{ b }}"

I’ve two fold problems with the script:

  1. The script is not working and giving following error:
[140475850729024] UndefinedError: 'str object' has no attribute 'friendly_name'
[140476026635072] TypeError: can only concatenate list (not "str") to list
  1. I want the script to produce an output of name-value pair for each battery, I get the correct output in the template editor:
{{ state_attr('sensor.front_door_battery', 'friendly_name') }}, {{ states('sensor.front_door_battery') }}

Screenshot from 2023-11-16 19-39-20

But not sure how to make this line in the script produce the name-value output:

{% set ns.targets = ns.targets + state_attr(b, 'friendly_name') %}

Thanks.

ns.targets + [state_attr(b, 'friendly_name')]
1 Like

Thanks for the recommendations and made few tweaks to my script and here is the updated code:

low_battery:
  alias: 'Script: Low Battery Alert'
  mode: single
  variables:
    a: !secret pankaj_email
    b: !secret pankaj_cell
    battery_sensors:
      - sensor.frontdoor_battery
      - sensor.slidingdoorbackyard_battery
      - sensor.prats01_battery
      - sensor.prats02_battery
      - sensor.sid01_battery
      - sensor.sid02_battery
      - sensor.frontdoorinside_battery
      - sensor.ewelink_th01_battery
      - sensor.motionsensorfrontporchhue_battery
      - sensor.motionsensordrivewayhue_battery
      - sensor.backdoor_battery
      - sensor.garage_motion_hue_battery
    output_message: |
      {% set x = (states.battery_sensors | selectattr('entity_id', 'search', 'battery')  | map(attribute='name') ) %}
      {% set ns = namespace( targets=[] ) %}
      {% for b in battery_sensors %}
      {% set ns.targets = ns.targets + [state_attr(b, 'friendly_name'), states(b)] %} 
      {% endfor %}{{ ns.targets }}
  sequence:
    - service: notify.smtp_1_gmail
      data:
        message: Test
        title: "Battery Level Report"
        target:
          - "{{ a }}"
          - "{{ b }}"

So the good news is that template is parsing correct values, in the script trace I see all the friendly names of the batteries with their level (under changed variables section of the trace):

output_message:
  - - FrontDoor Battery
    - '100'
  - - SlidingDoorBackyard Battery
    - '72'
  - - prats01 Battery
    - '97'
  - - prats02 Battery
    - '95'
  - - sid01 Battery
    - '94'
  - - sid02 Battery
    - '91'
  - - FrontDoorInside Battery
    - '53'
  - - zTemperature Battery
    - '60'
  - - Battery Porch Hue
    - '100'
  - - Battery Driveway Hue
    - '91'
  - - BackDoor Battery
    - '100'
  - - Garage Motion Hue Battery
    - '100'

Now two problems:

  1. As you can see in the format above the output is coming as a list when I wanted it to be a value pair like (‘Battery Driveway Hue’, ‘91’).
  2. The smtp “message” is expecting a text or a string object and I cannot pass “output_message” list. Is there a way to format this list as a string that can passed to smtp message block?

Thanks.

The variables b is already defined.
Try this as variables output:

  output_message: |
    {% for batt in battery_sensors %}
      {{ state_attr(batt, 'friendly_name') }}: {{ states(batt) }}%
    {% endfor %}
1 Like

Great, it worked like a charm!
I was way over complicating it :grinning:

I do the same basic thing as this, except I adopted naming conventions to help me eliminate other battery entities, like my phones and tablets so I check for all battery entities and then eliminate those that don’t actually have “battery_level” in the name.

Basically, that is, what my battery sensor does. But PanMat wanted a specific list of sensors.

@CO_4X4 and @pedolsky I’m basically trying to do the same thing as you guys i.e. eliminate several other entities that have battery (phone, tablet, solar panels etc).

One problem with my code is that I’ll need to keep maintaining the list of named sensors which may not be practical on the long haul.

Please post your code as it sounds more efficient than mine.

My sensor only looks for battery levels lower or equal 10. Copy the code into Developer Tools/Template and change the level.


            {%- for state in states.sensor  
            |selectattr('attributes.device_class', '==', 'battery')
            -%}
              {%- if states(state.entity_id) |int(100) <= 10 %}
              {{ state.attributes.friendly_name |replace('Battery', '') |replace('Batterie', '') }} ({{ states(state.entity_id) }} %)
              {%- endif -%}
            {%- endfor -%}

To exclude entities you can use


            {%- for state in states.sensor  
            |selectattr('attributes.device_class', '==', 'battery')
            |reject('search', 'bewegung|tablet')
            -%}
              {%- if states(state.entity_id) |int(100) <= 10 %}
              {{ state.attributes.friendly_name |replace('Battery', '') |replace('Batterie', '') }} ({{ states(state.entity_id) }} %)
              {%- endif -%}
            {%- endfor -%}

|reject('search', 'bewegung|tablet') checks whether the entity id contains the search words and excludes them from the output.

Since you seem to be flexible with how you handle this, here how I deal with batteries:

Hope this helps.

You can assign custom attributes to any entity:

  customize:
    sensor.main_bedroom_ht_battery:
      friendly_name: Main Bedroom H&T Battery
      monitor: True

My automations:

  - alias: "Check For Low Batteries"
    initial_state: true
    variables:
      level: 5
      monitored_batteries: >-
        {{
          states.sensor
            | selectattr('attributes.monitor', 'defined')
            | selectattr('attributes.monitor', 'eq', True)
            | rejectattr('state', 'in', ['unavailable', 'unknown', 'none'])
            | selectattr('attributes.device_class', 'eq', 'battery')
            | map(attribute='entity_id')
            | list
        }}
    trigger:
      platform: time
      at: "09:00:00"
    condition: >-
      {{
        expand(monitored_batteries)
          | map(attribute='state')
          | map('int')
          | select('lt', level)
          | list
          | count
          > 0
      }}
    action:
      - service: notify.mobile_app_ceres
        data:
          message: >
            The following devices have less than {{ level }}% charge:
            {%- for b in monitored_batteries %}
              {%- if states(b) | int < level and not is_state(b, 'unavailable') %}
              - {{ state_attr(b, 'friendly_name') | replace(' Battery', '') }}: {{ states(b) | int }}%
              {%- endif -%}
            {%- endfor %}
          data:
            group: "batteries"
            url: homeassistant://navigate/lovelace/devices

  - alias: "Check For Flat Batteries"
    initial_state: true
    variables:
      level: 0
      monitored_batteries: >-
        {{
          states.sensor
            | selectattr('attributes.monitor', 'defined')
            | selectattr('attributes.monitor', 'eq', True)
            | rejectattr('state', 'in', ['unavailable', 'unknown', 'none'])
            | selectattr('attributes.device_class', 'eq', 'battery')
            | map(attribute='entity_id')
            | list
        }}
    trigger:
      - platform: state
        entity_id: sensor.number_of_flat_batteries
    condition: >-
      {{
        expand(monitored_batteries)
          | map(attribute='state')
          | map('int')
          | select('eq', level)
          | list
          | count
          > 0
      }}
    action:
      - service: notify.mobile_app_ceres
        data:
          title: "Batteries"
          message: >
            The following devices have no charge:
            {%- for b in monitored_batteries %}
              {%- if states(b) | int == level and not is_state(b, 'unavailable') %}
              - {{ state_attr(b, 'friendly_name') | replace(' Battery', '') }}
              {%- endif -%}
            {%- endfor %}
          data:
            group: "batteries"
            url: homeassistant://navigate/lovelace/devices

EDIT: Credit to Taras whom I first saw doing this custom attribute thing.

1 Like

@pedolsky @exx and @parautenbach

Thanks for your comments and really liked few things:

  1. Custom attribute is awesome and wish I had created one ( beta_testing: true) for all the sensors I created on the fly to test things. The housekeeping for my code would have been very different but it is never too late to start!
  2. Liked all the ways template logic can be addressed, very interesting and learned a lot!
1 Like

Here is the final solution I’m leaning towards and can use some help with it.

  1. There are over 30+ entities that have battery information in my house but there are only 10 that I need to keep track and I really want to avoid any manual code maintenance. But that seems unavoidable so trying to minimize the manual maintenance.
  2. Created a group that has all the 10 devices that I need to track, this is the only place to maintain the details of monitored batteries going forward.
  3. Created an input helper where I can set the threshold to monitor (currently set at 25%) below which the automation will send a notificaton for low battery level.
  4. In the automation the condition checks if the output of below template is greater than “0” and it is working well.
{{ expand('group.monitored_batteries') | map(attribute='state') | reject('in', ['unavailable', 'unknown']) | map('int') | select('lt', (states('input_number.battery_levels') | int(0)))  | list | count }}

The only thing I need some help with is tweaking the above template to output a list of names (not counts) that I can use in the message of SMTP notification but I cannot seem to make the tweak work.

1 Like
{{ expand('group.monitored_batteries') | map(attribute='attributes.friendly_name') | list }}
1 Like