Script to track all devices with a battery_level

I thought this might be useful for others. I made it since I have quite a few z-wave devices, and I don’t want to create template sensors for each one of them.

# put this in a file named custom_components/battery_state.py
import logging
from homeassistant.helpers.event import track_state_change
from homeassistant.const import STATE_ON, STATE_OFF, STATE_HOME, STATE_NOT_HOME, MATCH_ALL

_LOGGER = logging.getLogger(__name__)

DOMAIN = 'battery'
DEPENDENCIES = []

def setup(hass, config=None):
    """Setup the Battery component. """
    _LOGGER.info("The 'battery' component is ready!")
    def state_changed(entity_id, old_state, new_state):
        if new_state is None:
            return
        if 'battery_level' in new_state.attributes:
            hass.states.set('%s_battery' % entity_id,
                            float(new_state.attributes['battery_level']),
                            {
                                'friendly_name': "%s Battery" % new_state.attributes['friendly_name'],
                                'unit_of_measurement': '%',
                                'icon': 'mdi:battery'
                            })

    track_state_change(hass, MATCH_ALL, state_changed)

    return True

Caveat: This creates a new battery sensor for every sensor it detects, so you may end up with multiple battery sensors per device if that device reports multiple sensors.

Edit for installation instructions:

  • See Creating Components for more information/example
  • Save the above script as custom_components/battery_state.py and create the custom_components directory if it doesn’t already exist
  • Add battery_state: to your configuration.yaml to enable the component
19 Likes

Very cool - I’ve been looking at something similar using the RESTFul API - some of my devices report an attribute “battery” not “battery_level” so you may want to add that to your code.

Awesome; I was thinking about this a while back when I installed my GoControl sensors and didn’t want all the work of creating the templates. Thanks!

This is awesome! Will have to get this up and running, cause I have a ton of template sensors created just for this purpose.

very cool
how can i add it to my HASS?

where can write the python file?
i dont have custom_components folder in my HA

Just create it if it isn’t there.

I edited the initial post to have some instructions for enabling a custom component.

Thanks for the idea @aimc, I’ll probably make the battery attribute a list of possible attributes instead of hardcoding battery_level.

2 Likes

Thank you for the custom component. I use the vera hub for my z-wave devices but had the same problem.
I modified version of the code a bit so that the battery values were all set as sensors and not as whatever the parent component was. What I found was that if my sensor was a binary_sensor the value not display properly.

I changed the line from:

hass.states.set('%s_battery' % entity_id,

To:

hass.states.set('sensor.%s_battery' % new_state.object_id,

I was then also able to use the same custom_component template and modify it to extract the power usage from my vera switches.

Edit:
I forgot to add that when geting the values from the vera component they already had a ‘%’ on the value. I removed theis by adding a ‘[:-1]’ to the line getting the attributes. This removes the last character of the string.

float(new_state.attributes['battery_level'][:-1]),
1 Like

Thanks! I was trying to figure out how to do this myself because it interfered with a script I was trying to loop through with binary sensors.

So, when I first started creating individual template sensors for each of my devices battery states, I was told that my sensors would be polling the battery constantly for changes to the battery state, which was causing more unnecessary drain on each devices battery as it was constantly asking the device for its battery state live, for every percent change. So I was told to add the additional entity_id: reference as the last line of my sensor blocks so that it would only check for the battery level when the state changed on the device listed in entity_id:, and thus causing no extra drain on the battery as it was only reporting back when a state changed on my conditional device instead of consistently polling for changes.

Since I don’t need to know the battery levels for every percent it drops, I created an input_boolean called input_boolean.update_batteries and I have an automation that turns the input_boolean on if its off, and off if its on, every day at 6pm, and since each of my battery sensors are only updating on a state change of the input_boolean.update_batteries, it only polls my battery levels once a day.

So I haven’t tested it with your component, but for those that don’t want this component to start draining their batteries faster, you may want to try adding the extra entity_id: option into this component to see if you can save yourself from buying new batteries before you need to.

Of course, I don’t know how if you can simply add that line underneath the line that says ‘icon’: ‘mdi:battery’ and have it work, since the extra entity_id option may only be a feature of a template sensor, but you can try and report back to let us know if it works.

If not, I personally won’t be using this component unless there is a way to limit when it is polling my batteries.

Here is an example of one of my battery sensors so you can see what I mean:

sensor:
  - platform: template
    sensors:
      batt_master_bedroom_window:
          value_template: '{{ states.sensor.ecolink_doorwindow_sensor_sourcenodeid_24.attributes.battery_level }}'
          friendly_name: 'Master Bedroom Window Battery'
          unit_of_measurement: '%'
          entity_id: input_boolean.update_batteries
1 Like

This may have something to do with whether you are using usb zwave direct connection or via a hub such as Vera.

I am using my zwave devices via a VeraLite hub and have not noticed any difference to the battery life, yet…
This would make sense in my case, as far as I know querying the Vera http API does not force polling of devices.

I am no zwave expert but from my experience using the VeraLite hub with my devices I cannot wake up a battery device by polling it. My devices will only send/receive information when woken by the appropriate physical input such as internal timer, motion or door opening. I can change the device parameters to make it stay awake and send information, this does have a significant impact on battery life. But to do this i need to set the new parameters in the hub and then manually wake the device up to update the device parameters.
But as I said, I am no zwave expert and only have my setup to go by.

1 Like

This is interesting. I am actually looking for some way to create a notification once a battery of any device drops below a certain threshold the. Otification then should just contain the name of the device.

Anyone has done this?

I like this as I wanted to see the battery levels of my door sensors and you did warn that it would create multiple sensors. However, for some reason I cannot hide the extra sensors. I created lines for each of them in my customize.yaml but they still appear. I’m not getting any errors or anything in the log either.

sensor.backd_access_control_7_9_battery:
  hidden: true
sensor.backd_alarm_level_7_1_battery:
  hidden: true
sensor.backd_alarm_type_7_0_battery:
  hidden: true
sensor.backd_appliance_7_15_battery:
  hidden: true
sensor.backd_burglar_7_10_battery:
  hidden: true
sensor.backd_emergency_7_13_battery:
  hidden: true
sensor.backd_homehealth_7_16_battery:
  hidden: true
sensor.backd_sourcenodeid_7_2_battery:
  hidden: true
sensor.backd_system_7_12_battery:
  hidden: true
sensor.frontd_access_control_5_9_battery:
  hidden: true
sensor.frontd_alarm_level_5_1_battery:
  hidden: true
sensor.frontd_alarm_type_5_0_battery:
  hidden: true
sensor.frontd_burglar_5_10_battery:
  hidden: true
sensor.frontd_clock_5_14_battery:
  hidden: true
sensor.frontd_sourcenodeid_5_2_battery:
  hidden: true
sensor.garaged_alarm_level_6_1_battery:
  hidden: true
sensor.garaged_alarm_type_6_0_battery:
  hidden: true
sensor.garaged_burglar_6_10_battery:
  hidden: true
sensor.garaged_sourcenodeid_6_2_battery:
  hidden: true

Sorry, I haven’t really been around lately to reply to everyone. I pasted a new version of this script below. I changed it to work like a normal component with a config, and included the advice of some of the replies to this thread.

Personally, I have been using a whitelist of sensors I want to get battery information from. Here’s my example config, with the script saved as custom_components/battery_state.py:

# Custom components
battery_state:
  include:
    - sensor.garage_door_burglar_3
    - sensor.office_multisensor_temperature_5
    - sensor.frontroom_multisensor_temperature_4

@Corey_Johnson Using something like this should be easier than maintaining a list of things to hide.

"""
See https://community.home-assistant.io/t/script-to-track-all-devices-with-a-battery-level/2596
for more details.

- justyns
"""
import logging
import voluptuous as vol

from homeassistant.helpers.event import track_state_change
from homeassistant.const import MATCH_ALL
from homeassistant.components.sensor import PLATFORM_SCHEMA
import homeassistant.helpers.config_validation as cv

_LOGGER = logging.getLogger(__name__)

DOMAIN = 'battery_state'
DEPENDENCIES = []
CONFIG_SCHEMA = vol.Schema({
    DOMAIN: vol.Schema({
        vol.Optional('include'): cv.ensure_list,
        vol.Optional('exclude'): cv.ensure_list,
        vol.Optional('attributes'): cv.ensure_list,
    }),
}, extra=vol.ALLOW_EXTRA)

def setup(hass, config):
    """Setup the Battery component. """
    excluded_items = config[DOMAIN].get('exclude', [])
    included_items = config[DOMAIN].get('include', MATCH_ALL)
    attribute_items = config[DOMAIN].get('attributes', ['battery_level', 'battery'])

    def state_changed(entity_id, old_state, new_state):
        if new_state is None \
            or entity_id in excluded_items \
            or (included_items != MATCH_ALL and entity_id not in included_items):
            return
        for attr in attribute_items:
            if attr in new_state.attributes:
                try:
                    new_value = str(new_state.attributes[attr]).replace('%', '')
                    hass.states.set('sensor.%s_battery' % new_state.object_id,
                                    float(new_value),
                                    {
                                        'friendly_name': "%s Battery" % new_state.attributes['friendly_name'],
                                        'unit_of_measurement': '%',
                                        'icon': 'mdi:battery'
                                    })
                except Exception as e:
                    _LOGGER.error("Error setting battery sensor value: %r", e)

    # Watch for changes to the devices we're interested in
    track_state_change(hass, included_items, state_changed)
    _LOGGER.info("The 'battery' component is ready!"
                 "Include list: %r. Exclude list: %r.  Attribute list: %r",
                 included_items, excluded_items, attribute_items)

    return True

7 Likes

Definitely more manageable! Thank you!

@justyns

This custom component wouldn’t take much to turn into a generic ‘attributes sensor’ that could be submitted as a PR.

I envisage the yaml config would look something like the following.


sensor:
    - platform: attributes
      attribute: 'battery_level'
      name: 'battery'
      include:
        - sensor.garage_door_burglar_3
        - sensor.office_multisensor_temperature_5
        - sensor.frontroom_multisensor_temperature_4

    - platform: attributes
      attribute: 'current_mwh'
      name: 'Power'
      include:
        - switch.fake1
        - switch.fake2

    - platform: attributes
      attribute: 'volume_level'
      name: 'Volume'
      exclude:
        - media_player.fake1
        - media_player.fake2

Most of the component is already written, it just needs to:

  • move it to the sensor platform,
  • add the custom attribute,
  • add the custom name.

For me, this would tidy up a few template sensors and custom components into one neat component.

I might have a look at doing this in the new year if I get time. If someone else wants to tackle it then I am willing to help test.

2 Likes

@tinglis1 thanks for the idea! I was thinking a ‘battery attribute sensor’ would be a weird/too specific of a component to be included, but I really like your idea of making a generic attribute sensor.

I’ll have some time next week that I could work on this idea and get something submitted.

@justyns Does the battery_state code:

battery_state:
  include:
    - sensor.garage_door_burglar_3
    - sensor.office_multisensor_temperature_5
    - sensor.frontroom_multisensor_temperature_4

Go in the configuration.yaml file like that? I tried it in mine just like that (well, with my own sensors obviously) and nothing is showing up. I am not getting any errors but am not seeing anything in HA or even in the entities list.

I added just one:
battery_state:
include:
- sensor.aeotec_zw100_multisensor_6_sourcenodeid_8_2

and now i have an entity:
sensor.aeotec_zw100_multisensor_6_sourcenodeid_8_2_battery