Script to warn on low battery

I couldn’t find a decent low battery warning notification for all my devices. This script checks all zwave and deconz devices and if any battery is below the threshold it will create a persistent notification.
I filter out mobile devices by checking for the is_charging attribute, otherwise these would be detected as deconz devices. Couldn’t find any other way to distinguish them.

If you want to add any other types of devices I would add the logic under get_battery_entities in a separate function.

Enable checking by adding an automation that calls this service every day or so. Provide any threshold by adding threshold: int to your service call.

def persistent_warning_message(devices):
    devices = ", ".join(devices)
    service_data = {
        "title": "Low Battery Warning",
        "notification_id": "low_battery",
        "message": f"Low battery for the following device(s): {devices}",
    }
    hass.services.call("persistent_notification", "create", service_data, False)
    logger.info(service_data["message"])


def get_battery_entities():
    def get_zwave_battery_entities():
        out = {}
        for entity_id in hass.states.entity_ids("zwave"):
            state = hass.states.get(entity_id)
            if "battery_level" in state.attributes:
                out.update({entity_id: state.attributes["battery_level"]})
        return out

    def get_deconz_battery_entities():
        out = {}
        for entity_id in hass.states.entity_ids("sensor"):
            state = hass.states.get(entity_id)
            if (
                state.attributes.get("device_class") is "battery"
                and "is_charging" not in state.attributes
            ):
                out.update({entity_id: int(state.state)})
        return out

    entities = {}
    entities.update(get_zwave_battery_entities())
    entities.update(get_deconz_battery_entities())
    return entities


def get_low_battery_entities(battery_entities, threshold):
    if len(battery_entities) is 0:
        return None
    return [
        entity_id for entity_id, value in battery_entities.items() if value < threshold
    ]


threshold = data.get("threshold", 10)
low_battery_devices = get_low_battery_entities(get_battery_entities(), threshold)
if len(low_battery_devices) > 0:
    persistent_warning_message(low_battery_devices)

4 Likes

Hi Sophof,
Hope you can help me, i get this error when i try to validate the script.

Error loading /config/configuration.yaml: mapping values are not allowed here
in “/config/script/low_battery.yaml”, line 4, column 16

It should be placed as something like “low_battery.py” in the “python_scripts” folder, did you do that? The error looks like you tried to place it as a yaml in a script folder.

See also https://www.home-assistant.io/integrations/python_script/

From there:

Add to configuration.yaml: python_script:
Create folder /python_scripts
Create a file hello_world.py in the folder and give it this content:

So in this case place low_battery.py there and then create an automation that calls the python_script.low_battery. Provide any threshold you want by adding threshold: int to your service call.

Retracted post.

Thanks for your reply, you were correct i did not created it as a pyton_script :grimacing:
I have done as you have describe but from the log i get this:

ERROR (SyncWorker_4) [homeassistant.components.python_script.low_battery.py] Error executing script: invalid literal for int() with base 10: ‘unavailable’
File “/usr/src/homeassistant/homeassistant/components/python_script/init.py”, line 205, in execute

Can you paste your service call? You don’t have to pass a threshold, but if you do, it should be something like: threshold: 10

Have done it like this, and i have a IKEA remote with battery level on 16 connected throw deconz.
image

Ok, I checked out the error and apparently somewhere python is implicitly casting the word ‘unavailable’ to int. My assumption is that your sensor is sending unavailable as the battery level, where I was expecting a number, so that’s what is causing the error.

I’ll try and make this ignore unavailable levels (it’s the only thing that makes sense), but I think you’ll want to have a look at the ikea remote.

I got myself some ikea devices (E1524 remotes) and am testing the script out. I get this error in log.

I have fixed the problem by adding and “unavailable” not in state.state to the script

def persistent_warning_message(devices):
    devices = ", ".join(devices)
    service_data = {
        "title": "Low Battery Warning",
        "notification_id": "low_battery",
        "message": f"Low battery for the following device(s): {devices}",
    }
    hass.services.call("persistent_notification", "create", service_data, False)
    logger.info(service_data["message"])


def get_battery_entities():
    def get_zwave_battery_entities():
        out = {}
        for entity_id in hass.states.entity_ids("zwave"):
            state = hass.states.get(entity_id)
            if "battery_level" in state.attributes:
                out.update({entity_id: state.attributes["battery_level"]})
        return out

    def get_deconz_battery_entities():
        out = {}
        for entity_id in hass.states.entity_ids("sensor"):
            state = hass.states.get(entity_id)
            if (
                state.attributes.get("device_class") is "battery"
                and "is_charging" not in state.attributes
                and "unavailable" not in state.state
            ):
                out.update({entity_id: int(state.state)})
        return out

    entities = {}
    entities.update(get_zwave_battery_entities())
    entities.update(get_deconz_battery_entities())
    return entities


def get_low_battery_entities(battery_entities, threshold):
    if len(battery_entities) is 0:
        return None
    return [
        entity_id for entity_id, value in battery_entities.items() if value < threshold
    ]


threshold = data.get("threshold", 10)
low_battery_devices = get_low_battery_entities(get_battery_entities(), threshold)
if len(low_battery_devices) > 0:
    persistent_warning_message(low_battery_devices)

I haven’t made this script very user friendly, that’s for sure :grimacing:

Could you try commenting out or removing the entities.update(get_zwave_battery_entities()) line? Afaik it should work fine with that there even if you have no zwave devices, but just to be sure.

Great! Now that I look at my code that is indeed the only place where there is an explicit int cast, don’t know why I didn’t catch that.

If I’m using zha and not deconz, can I just “Find/Replace” deconz with zha in the script?

I’m not sure, but I doubt it, the reason I made a python script is since I didn’t want to test everything :stuck_out_tongue:

In essence the different functions get_zwave_battery_entities and get_deconz_battery_entities are not necessarily zwave and deconz exclusive. For zwave I just check for a battery_level attribute in zwave entities and for the deconz devices I specifically scan for entities classed as battery and specifically excluded phone battery sensors.

So as long as ZHA exposes entities in one of those 2 ways, you shouldn’t have to do much. If it works as deconz you don’t have to do anything, if it works like zwave, you only need to change the for entity_id in hass.states.entity_ids("zwave"): part and replace it with the relevant prefix (possibly “ZHA”?).

But to be honest, based on the questions I get here, I think I should just invest the time in the future to make some sort of ‘proper’ integration if at all possible.

Hi,

What would be the best way to include battery entities from zigbee2mqtt?

I don’t know how the zigbee2mqtt sensors are reported in home assistant, but assuming it is similar to deconz (since it is only a bridge) it should work without any changes.

So if your device have a sensor with device_class: battery it should work.