How can I determine if my battery will run out before power is restored

Hi folks,

Just to give you a bit of background before I ask my question. Here in South Africa, we have rolling power outages which we call load shedding. There are 8 stages and the higher the stage, the more
MW needs to be shed off the gird and thus more areas are affected and more regularly power needs to be cut. The power cuts last around 2 hours. For instance, yesterday my area was shed 3 times as we were on stage 4.
A few months ago before all hell broke loose with the constant load shedding, we installed an inverter/battery system to keep the power on during load shedding. Works great! I have everything integrated with Home Assistant, so I can keep track of state of charge, when the power is out or not, etc. I have a plethora of data at my fingertips.

Of course there are times, like yesterday, where I wasn’t aware that we were load shed … I had did some pool maintenance so I had the pool pump going, my wife had the dishwasher on and basically 10 min before power was to be restored, the battery ran out of juice.

So I immediately added some automations to notify me if the battery is below 50%, 35% and 30% … it is usually around 20% or so that my inverter shuts the power off.

The notifications work great and last night I received the one whereby my battery was below 50% … which was about 10min before power was restored.

I want to make these notifications a bit more intelligent. That one mentioned above I should not have received because the battery would have kept us going for those last 10 min without any issues.

So here is my question.

How can I predict if the battery will die so that I can get these types of notifications out without them being a “cry wolf”?
I have the following at my disposal.

Date and time when the power went out.
Date and time when the power will be restored
State of charge (of course)
The load on the battery i.e. the watts drawn.

I know this is not going to be accurate as power draw is not constant during those 2 or so hours - we might boil the kettle, or watch TV, turn on a computer, etc, etc … we go about our day / night as if we are not load shed - that is the beauty of this, but I am hoping to be able to get notified when the system assesses and sees that we are not going to make it and thus based on these notifications, will know to reduce our usage - like in yesterday’s case, turn off the dishwasher and pool pump.

My thought is predict the state of charge of the battery when power is restored. Since we know when the power went out, the current time and we know when the power will be restored, so my thought is to calculate the number of minutes between those two times and hopefully calculate the predicted state of charge.

eg:

Power went off at 10:00am, will be restored at 12:00pm and it is 10:30am, so 90 min until power is restored. At 10:30, the state of charge is 90%, so between 10:00am and 10:30am, 10% was depleted in 30min. Based on current usage, the battery should be at 60% when the power is restored. As such, the notification won’t be sent out.

At 11:00am, the battery is at 70% which means that more power was used, so in an hour 30% was used and since there is an hour left of load shedding, it will predict that the batter will be 40% and as such, the notification will go out.

I hope that my thinking isn’t flawed, but it is a good start.

The other use case for this is that we have prepaid electricity and there has been a time when I forgot to load more kw/h into the meter and the power went off while I was at work. Thankfully nobody is home, so the power draw is low, but it would be cool to let HA predict as to what time the battery will run out.

I figured a template sensor to contain the predicted state of charge would be what I’d need. So how would I do this using templating? I really would appreciate your help on this please. If you have any other ideas, please let me know. This would be really cool.

Next year I plan to do my on-wall dashboard, so having some info on there to glance at while in the kitchen would be good.

1 Like

I think the first thing I would do is try to get a signal for when I’m using batteries and automatically shed non-critical loads like the pool pump. And/or I would think about increasing my battery capacity.

What I would do is to each 30 minutes calculate if you are above or below the the curve.

You should have a number of watts you can get out of the battery.
If you use a statistics sensor you should be able to get the consumption of a historic given time (30 minutes) as an example.
If this sensor has a higher value than what you total capacity/(hours or load shedding *2) then you have an issue.

You could then create a sensor with 1 hours usage and one with 1.5 hours and so on.

This will require a lot of “helper” statistic sensors.

If you use node red then you could skip the statistics sensors and use the get history node to get a (I believe dynamic) time and thus simplify the automation a bit.

Would this help:

The idea is to calculate a predicted estimated battery percentage at the end of a given time and as a separate sensor, calculate the estimated time the battery will die. I need help with the templating side of things and Jinja is not my strongpoint.

I would hope that somebody has done something like this before.

As per your first comment, yes, non critical devices are turned off when power goes out … adding more battery capacity would be nice … but at about $1800±a battery … not a priority for now.

@Hellis81 thanks for your comment. Will assess too.

I do something very similar to what you want in calculating the estimated time that my food will be done when I use my smoker.

I know the current temperature of the food and from that I can calculate a rate of change of the temperature every 5 minutes.

then I can calculate based on a pre-defined setpoint at what time the food will hit that “done” setpoint.

you can use the same basic concepts to find out at what estimated time the battery will be at it’s “done” setpoint (battery empty) using the rate of change of the battery percentage.

Here is the code I use (I hope I got it all…):

input_datetime:
  smoker_food_done:
    name: Estimated Time Smoker Food Will Be Done
    has_date: true
    has_time: true

input_number:
  smoker_food_setpoint:
    name: "Smoker Food Setpoint"
    initial: 200
    min: 100
    max: 225
    step: 5
    mode: box

sensor:
  - platform: derivative
    source: sensor.smoker_food_temperature
    name: Smoker Food Temperature Derivative
    round: 3
    unit_time: min
    time_window: "00:05:00"
    unit: '°F/Min'

- platform: template
    sensors:
      smoker_food_temp_rate:
        friendly_name: Smoker Food Temp Rate
        value_template: "{{ ((states('variable.smoker_food_temp_history') | float(0) - state_attr('variable.smoker_food_temp_history', 'history_01') | float(0)) / 5)| round(1) }}"
        icon_template: mdi:chart-line
        unit_of_measurement: "°F/min"
      smoker_food_time_done:
        friendly_name: "Estimated Food Completion Time"
        value_template: "{{ as_timestamp(states('input_datetime.smoker_food_done')) | timestamp_custom('%-I:%M %p')}}"
        icon_template: mdi:clock
      smoker_food_done_time_countdown:
        friendly_name: "Estimated Food Completion Countdown"
        value_template: "{{ (as_timestamp(states('input_datetime.smoker_food_done')) - as_timestamp(now())) | timestamp_custom('%H hr %M min', false)}}"
        icon_template: mdi:timer-sand

# this uses a custom integration called hass-variables available thru HACS
variable:
  smoker_food_temp_history:
    value: 'Not set'
    restore: true
    attributes:
      name: 'Smoker Food Temp History'

automation:
  - alias: Initialize Smoker Food Temp Sensors
    trigger:
      - platform: state
        entity_id: input_boolean.smoker_sensor_active
        to: 'off'
    action:
      - service: input_number.set_value
        entity_id: input_number.smoker_graph_display_hours
        data:
          value: 1
      - service: variable.set_variable
        data:
          variable: smoker_food_temp_history
          attributes: 
            history_01: 0.0
      - service: variable.set_variable
        data:
          variable: smoker_food_temp_history
          value: 0.0
      - service: input_datetime.set_datetime
        entity_id: input_datetime.smoker_food_done
        data:
          #timestamp: "0.0"
          datetime: '1970-01-01 00:00:00'
      - service: input_boolean.turn_off
        entity_id: input_boolean.smoker_oven_mode
  
  - alias: Calc Rate of Change Smoker Food Temp
    trigger:
      - platform: time_pattern
        minutes: "/5"
    condition:
      - condition: state
        entity_id: input_boolean.smoker_sensor_active
        state: 'on'
    action:
      - service: variable.set_variable
        data:
          variable: smoker_food_temp_history
          attributes: 
            history_01: "{{ states('variable.smoker_food_temp_history') | float }}"
      - service: variable.set_variable
        data:
          variable: smoker_food_temp_history
          value: "{{ states('sensor.smoker_food_temperature') | float}}"
          
  - alias: Set Time Smoker Food Done
    trigger:
      - platform: time_pattern
        minutes: "/5"
    condition:
      - condition: state
        entity_id: input_boolean.smoker_sensor_active
        state: 'on'
    action:
      - service: input_datetime.set_datetime
        entity_id: input_datetime.smoker_food_done
        data:
          timestamp: >
            {% if states('sensor.smoker_food_temp_rate') | float > 0.0 %}
              {{ (((states('input_number.smoker_food_setpoint') | float(0) - states('sensor.smoker_food_temperature') | float(0)) / states('sensor.smoker_food_temp_rate') | float(0)) * 60) + as_timestamp(now()) }}
            {% else %}
              {{ as_timestamp(now()) + 86400 }}
            {% endif %}

or you can use the load in amps (convert watts to amps) vs remaining battery capacity (in amp-hours) to tell you how many hours of battery capacity you have at the current amp draw of the battery.

assume the following:

12 volt system
150 amp-hours @ 100%
20% is empty so 30 amp-hours is empty
current state of charge = 70% (105 amp-hours)
5 amps current load (watts / battery voltage)

{% set volts = 12 %}
{% set max_cap = 150 %}
{% set amps = states('sensor.watts') | float(0.0001) / volts  %}
{% set empty = 30 %}
{% set percent_remaining = states('sensor.state_of_charge') | float(0) %}
{% set current_cap = (max_cap * percent_remaining) - empty %}
{{ current_cap / amps }} hours till empty

I think I’ve got everything.

Obviously you’ll need to change things to your actual values/entities.

Also obviously the second method will be easier.

Wow @finity I think this is going to be my Sunday project tomorrow. I’ll dig into this code then. Thanks very much! Hope I can simulate as much as I can as we are only going to be shed at 10pm tomorrow night. Will post how it all goes.

1 Like

@finity managed to grab your scripts, modify them and get them running in HA, but I am not getting an expected predicated battery depletion time.

For instance, the expected depletion time is always 2 min from current date/time which is incorrect.

Here is what I’ve done. Any ideas?

Under Helpers, I created

The derivative sensor

sensor.battery_state_of_charge_derivative

  • precision: 3
  • time window: 00:05:00
  • Time unit: minute

An input date/time

input_datetime.estimated_battery_depletion_time

  • date and time

variable:
  battery_state_of_charge_history:
    value: 'Not set'
    restore: true
    attributes:
      name: 'Battery State of Charge History'

template:
  sensor:
      - name: "Battery State of Charge Rate"
        state: "{{ ((states('variable.battery_state_of_charge_history') | float(0) - state_attr('variable.battery_state_of_charge_history', 'history_01') | float(0)) / 5)| round(1) }}"
        icon: mdi:chart-line
        unit_of_measurement: "%/min"
        
      - name: "Estimated Battery Depletion Time"
        state: "{{ as_timestamp(states('input_datetime.estimated_battery_depletion_time')) | timestamp_custom('%-I:%M %p')}}"
        icon: mdi:clock
        
      - name: "Estimated Battery Depletion Time Countdown"
        state: "{{ (as_timestamp(states('input_datetime.estimated_battery_depletion_time')) - as_timestamp(now())) | timestamp_custom('%H hr %M min', false)}}"
        icon: mdi:timer-sand

  - alias: Initialize Battery Depletion Sensors
    trigger:
      - platform: state
        entity_id: binary_sensor.house_power_status
        to: 'off'
    action:
      - service: input_number.set_value
        entity_id: input_number.battery_setpoint
        data:
          value: 100
      - service: variable.set_variable
        data:
          variable: battery_state_of_charge_history
          attributes: 
            history_01: 0.0
      - service: variable.set_variable
        data:
          variable: battery_state_of_charge_history
          value: 0.0
      - service: input_datetime.set_datetime
        entity_id: input_datetime.estimated_battery_depletion_time
        data:
          #timestamp: "0.0"
          datetime: '1970-01-01 00:00:00'
  
  - alias: Calc Rate of Change Battery State of Charge
    trigger:
      - platform: time_pattern
        minutes: "/5"
    condition:
      - condition: state
        entity_id: binary_sensor.house_power_status
        state: 'off'
    action:
      - service: variable.set_variable
        data:
          variable: battery_state_of_charge_history
          attributes: 
            history_01: "{{ states('variable.battery_state_of_charge_history') | float }}"
      - service: variable.set_variable
        data:
          variable: battery_state_of_charge_history
          value: "{{ states('sensor.battery_state_of_charge_derivative') | float}}"
          
  - alias: Set Time Battery Depleted
    trigger:
      - platform: time_pattern
        minutes: "/5"
    condition:
      - condition: state
        entity_id: binary_sensor.house_power_status
        state: 'off'
    action:
      - service: input_datetime.set_datetime
        entity_id: input_datetime.estimated_battery_depletion_time
        data:
          timestamp: >
            {% if states('sensor.battery_state_of_charge_rate') | float > 0.0 %}
              {{ (((states('input_number.battery_setpoint') | float(0) - states('sensor.battery_state_of_charge_derivative') | float(0)) / states('sensor.battery_state_of_charge_rate') | float(0)) * 60) + as_timestamp(now()) }}
            {% else %}
              {{ as_timestamp(now()) + 86400 }}
            {% endif %}

I need to apologize a bit…

added some things in my examples above that weren’t needed in yours because I had some development/double-checking stuff in my code. I think that added some confusion.

here should be everything you need to get this working. I tested it using my cell phone battery and I think it seemed to get accurate results in the short testing I did.

the only additional thing you will need is to use your sensor for the current state of battery charge (in %) where it says “sensor.your_battery_current_charge”.

sensor:
  - platform: derivative
    source: sensor.your_battery_current_charge
    name: battery_state_of_charge_derivative
    round: 3
    unit_time: min
    time_window: "00:05:00"
    unit: '%/Min'

input_number:
  battery_setpoint:
    min: 0
    max: 100
    step: 0.1
    mode: box

input_datetime:
  estimated_battery_depletion_time:
    has_date: true
    has_time: true
            
template:
  sensor:
      - name: "Estimated Battery Depletion Time"
        state: "{{ as_timestamp(states('input_datetime.estimated_battery_depletion_time')) | timestamp_custom('%-I:%M %p')}}"
        icon: mdi:clock
        
      - name: "Estimated Battery Depletion Time Countdown"
        state: "{{ (as_timestamp(states('input_datetime.estimated_battery_depletion_time')) - as_timestamp(now())) | timestamp_custom('%H hr %M min', false)}}"
        icon: mdi:timer-sand

automation:
  - alias: Initialize Battery Depletion Time
    trigger:
      - platform: state
        entity_id: binary_sensor.house_power_status
        to: 'off'
    action:
      - service: input_datetime.set_datetime
        entity_id: input_datetime.estimated_battery_depletion_time
        data:
          datetime: '1970-01-01 00:00:00'
  
  - alias: Set Time Battery Depleted
    trigger:
      - platform: time_pattern
        minutes: "/5"
    condition:
      - condition: state
        entity_id: binary_sensor.house_power_status
        state: 'off'
    action:
      - service: input_datetime.set_datetime
        entity_id: input_datetime.estimated_battery_depletion_time
        data:
          timestamp: >
            {% if states('sensor.battery_state_of_charge_rate') | float < 0.0 %}
              {{ (((states('sensor.your_battery_current_charge') | float(0) - states('input_number.battery_setpoint') | float(0)) / states('sensor.battery_state_of_charge_derivative') | float(0) | abs) * 60) + as_timestamp(now())}}
            {% else %}
              {{ as_timestamp(now()) + 86400 }}
            {% endif %}

and TBH I don’t think you need the automation to initialize the time either. Everything else should take care of that as well.

try it and let me know if you are still having troubles.

sistem
What you are doing is very valuable, I am interested in this. This prediction thing really means intelligent systems that can be applied with everything. I’m trying to implement it, but my system is not working properly. Can you share the system you made with your own phone battery here. I have implemented the system but I don’t know how it works. Automation does not work as far as I understand. I want to experiment with the phone battery and then apply it in different things.

I tried to re-create the situation of the OP’s battery using my own cell battery as the source.

the “battery setpoint” should be better called “battery empty setpoint” but I’ll leave it as is for now.

here is the exact code I’m using (my cell app info redacted):

sensor:
  - platform: derivative
    source: sensor.my_mobile_app_battery_level
    name: tb_battery_state_of_charge_derivative
    round: 3
    unit_time: min
    time_window: "00:05:00"
    unit: '%/Min'

input_number:
  tb_battery_setpoint:
    min: 0
    max: 100
    step: 0.1
    mode: box

input_datetime:
  tb_estimated_battery_depletion_time:
    has_date: true
    has_time: true

template:
  sensor:
      - name: "TB Estimated Battery Depletion Time"
        state: "{{ as_timestamp(states('input_datetime.tb_estimated_battery_depletion_time')) | timestamp_custom('%-I:%M %p')}}"
        icon: mdi:clock
        
      - name: "TB Estimated Battery Depletion Time Countdown"
        state: "{{ (as_timestamp(states('input_datetime.tb_estimated_battery_depletion_time')) - as_timestamp(now())) | timestamp_custom('%H hr %M min', false)}}"
        icon: mdi:timer-sand

automation:
  - alias: TB Initialize Battery Depletion Time
    trigger:
      - platform: state
        # entity_id: binary_sensor.house_power_status
        entity_id: input_boolean.bool_11
        to: 'off'
    action:
      - service: input_datetime.set_datetime
        entity_id: input_datetime.tb_estimated_battery_depletion_time
        data:
          datetime: '1970-01-01 00:00:00'
  
  - alias: TB Set Time Battery Depleted
    trigger:
      - platform: time_pattern
        minutes: "/5"
    condition:
      - condition: state
        # entity_id: binary_sensor.house_power_status
        entity_id: input_boolean.bool_11
        state: 'off'
    action:
      - service: input_datetime.set_datetime
        entity_id: input_datetime.tb_estimated_battery_depletion_time
        data:
          timestamp: >
            {% if states('sensor.tb_battery_state_of_charge_derivative') | float < 0.0 %}
              {{ (((states('sensor.my_mobile_app_battery_level') | float(0) - states('input_number.tb_battery_setpoint') | float(0)) / states('sensor.tb_battery_state_of_charge_derivative') | float(0) | abs) * 60) + as_timestamp(now())}}
            {% else %}
              {{ as_timestamp(now()) + 86400 }}
            {% endif %}

(NOTE: I put “tb_” in front things to be easily sorted later in dev tools.)

Thank you very much, I will apply it when I go home in the evening. So for this simplified last post, do you still need to install the variables plugin via hacs?

No. not required. Just the stuff in the post should do it.

I built the system the way you described. I have the same problem. The time always shows 2 or 3 minutes before the current time.

alias: Initialize Battery Depletion Time
description: ""
trigger:
 - platform: state
   entity_id:
     - input_boolean.house_power_status
   to: "off"
condition: []
action:
 - service: input_datetime.set_datetime
   data:
     datetime: "'1970-01-01 00:00:00'"
   target:
     entity_id: input_datetime.estimated_battery_depletion_time
mode: single






alias: Set Time Battery Depleted
description: ""
trigger:
 - platform: time_pattern
   minutes: /5
condition:
 - condition: state
   entity_id: input_boolean.house_power_status
   state: "off"
action:
 - service: input_datetime.set_datetime
   data:
     timestamp: |
       {% if states('sensor.a70_battery_level') | float < 0.0 %}
         {{ (((states('sensor.a70_battery_level') | float(0) - states('input_number.battery_setpoint') | float(0)) / states('sensor.a70batterycharge') | float(0) | abs) * 60) + as_timestamp(now())}}
       {% else %}
         {{ as_timestamp(now()) + 86400 }}
       {% endif %}
   target:
     entity_id: input_datetime.estimated_battery_depletion_time
mode: single

Looking at your screen shot the battery is actually charging (derivative is positive) so the estimated depletion time is set to 24 hours from now (whenever “now” is at any point in time).

So if you look at the date associated with the estimated depletion time you should hopefully see that it is tomorrow at about the same time as it is “now” (adjusted for the automation update interval of every 5 minutes).

At least I think that’s what’s happening.

you shouldn’t see a valid depletion time until the battery starts discharging and the derivative sensor returns as negative value.

that and you “battery setpoint” is 100%. Which seems to me that as soon as the battery reaches that percentage of charge it should be depleted. 100% doesn’t seem correct for that value. above you talked about it being around 20% when the battery is dead.

Also if you don’t reply directly to my post or mention me in the post I don’t get notified that you replied to me.

I am very new to the forums, I am trying to learn .I will tag you from now on. What does battery setpoint mean? I’m trying to understand it frankly. Is it the percentage of battery depletion? In other words, does the system calculate how long it will take until the number defined there? As it is now, the phone is not charged. The battery is draining, but the data I’m getting still doesn’t seem healthy… Am I wrong?

yes.

what is the current battery percent remaining? without that info it’s hard to tell if it’s reasonable or not.

But what it is telling you is that at a discharge rate of 0.088%/minute the battery should be at 20% capacity at 11:40 on december 1st.

does that not seem right?

I think the measurements are wrong… Even if I set different battery setpoint levels and the discharge time of the battery is very short, it still shows an average of one full day and two to three minutes back. I think there is a problem with the calculation. I am sharing two separate screenshots below. Look at the time of the phone and pay attention to the time given by the system. Two separate scenarios give the same times. It always gives 2 or 3 minutes behind the current time and one day later. In the last screenshot, the battery is 50%, I set the setpoint to 40%. It states that it will take a day for 10% discharge. The other screenshot is the same, the battery is 64%, the setpoint is 20%, again the same one day later 1 min behind… Maybe I may have done something wrong because I am a beginner, but if you wish, I will share those details. This prediction thing really attracts my attention. I really want to learn and do this. This is a system that can be applied in many subjects … Thank you for your support.


Yeah, I’ll need to see the code you are using.

I’m using the code from the posts above and I think mine seems to be reasonable tho I guess I haven’t tested it fully.

But I’m definitely not getting a ~1 day estimated time for every scenario.

Well, I will share the codes with you when I can