Solar battery run time till empty

With a battery solar system, has anyone worked out a calculation that can estimate the available power left in a solar battery?
Im using Charles excellent code for the system here: GitHub - CharlesGillanders/homeassistant-alphaESS: Monitor your energy generation, storage, and usage data using an unofficial API from Alpha ESS
Its not a requirement that his code needs to fill, but certainly carries some of the sensors we need.
I can imagine its a useful value template that anyone with a solar battery could use.

Its never going to be an exact or lineal value, but a ballpark figure would be useful.

Current battery capacity is known in %
Load is constantly reported

I would imagine that you would only need to retain and compare values from the previous hour to get a trend and a rudimentary run time remaining.

Thoughts?

image

You could use utility meter on a quarter hr cycle and calculate the time based of capacity times % / divided by the last quarter hr usage. So my two (2) batteries are each 13.5 kWh at 100%. So if my % battery was 75.5% at the end of the 15 minutes and I used 0.5 kWh in the last 15 minutes calculation would be (2 * 13.5 * (75.5/100)/0.5) /4 = 10.2 hours remaining if the load remains the same.

Thanks for the reply, would you mind clarifying the math for me? what does 213.5 represent?

The system did not recognize the * without a space between. I edited and fixed the formula. It represents 2 batteries.

This is my current way of thinking (out loud).
If I add in 2 sensors for stats, that gives me 30 minutes of data to play with:

One being the current load (av 30 mins) to remove peaks and troughs
The other being the battery output / input (charging / not charging)

  - platform: statistics
    name: "House Load Av thirty mins"
    entity_id: sensor.solar_load_total
    state_characteristic: mean
    max_age:
      minutes: 30

  - platform: statistics
    name: "House Battery i/o thirty mins"
    entity_id: sensor.solar_battery_i_o
    state_characteristic: mean
    max_age:
      minutes: 30

I can then use that data to see whether the battery is charged, charging or discharging based on the battery i/o

{% set state = states('sensor.house_battery_i_o_thirty_mins') %}
{% set load = states('sensor.house_load_av_thirty_mins') %}
{% set battery = states('sensor.solar_battery_soc') %}
{% if float(state) == 0 or float(battery) == 100 %}
Battery is full. Current average House load {{load}}
{% elif float(state) < 0  %}
Battery is charging. Current average House load {{load}}
{% elif float(state) > 0  %}
Battery is discharging. Current average House load {{load}}
{% endif %}
Battery run time at current load: {{ 9 * (float(battery)/100)/(float(load) /1000)  }}

Any ideas expanding on that?

I think you need to up the sampling_size from the default 20.

sampling_size integer (Optional, default: 20)

Maximum number of source sensor measurements stored. Be sure to choose a reasonably high number if the limit should be driven by max_age instead.

  - platform: statistics
    name: "House Battery i/o thirty mins"
    entity_id: sensor.solar_battery_i_o
    state_characteristic: mean
    max_age:
      minutes: 30
    sampling_size: 100

Can’t you have a condition where the battery is not charging and the battery is not full. I would change the ‘or’ to ‘and’ so both conditions are met. This assumes you are tied into the grid as that is the scenario that could cause this to happen.

There are times when both conditions arent met, eg the battery can read 100% yet the i/o will still be trying to charge or discharge to the battery up to max. So yes, at 100% there can still be charging occurring.
That code is just more of a test to see what logic could be applied to get a reasonable hours remaining figure.

With your help, Ive pretty much ended up here.
If solar is producing, we just output full battery runtime, else expected runtime at (30 min) average.

Seems to be ok :slight_smile:

template:
  - sensor:
      - name: "Solar Battery Runtime"
        state: >
            {% if float(states('sensor.house_load_av_15_mins')) - float(states('sensor.house_solar_gen_15_mins')) <= 0 %}
            {% set runtime =  (9 * (float(states('sensor.solar_battery_soc'))/100)) %}
            {% else %}
            {% set runtime =  (9 * (float(states('sensor.solar_battery_soc'))/100)/(float(states('sensor.house_load_av_thirty_mins')) /1000)) %}
            {% endif %}
            {% set hours = runtime | float %}
            {% set minutes = ((hours % 1) * 60) | int %}
            {% set hours = (hours - (hours % 1)) | int %}
            {{ '%02i:%02i'%(hours, minutes) }}

Silly question, where do you place this in your config? I tried putting it under the sensors yaml but I get an error. Is it supposed to reside under platform within the sensor.yaml? If so, which platform?

As written it goes in configuration.yaml. If you want a separate file you need a

template: !include templates.yaml

in configuration.yaml.

and then this in the templates.yaml file

 - sensor:
     - name: "Solar Battery Runtime"
       state: >
         {% if float(states('sensor.house_load_av_15_mins')) - float(states('sensor.house_solar_gen_15_mins')) <= 0 %}
         {% set runtime =  (9 * (float(states('sensor.solar_battery_soc'))/100)) %}
         {% else %}
         {% set runtime =  (9 * (float(states('sensor.solar_battery_soc'))/100)/(float(states('sensor.house_load_av_thirty_mins')) /1000)) %}
         {% endif %}
         {% set hours = runtime | float %}
         {% set minutes = ((hours % 1) * 60) | int %}
         {% set hours = (hours - (hours % 1)) | int %}
         {{ '%02i:%02i'%(hours, minutes) }}

can you share code for this visualization. perhaps love lace code ?

The card is

GitHub - JonahKr/power-distribution-card: A Lovelace Card for visualizing power distributions.

do you have Tesla power wall? is this your exact code or do you have the name of the sensors changed?
I tried this code but the numbers don’t seem to match my calculations

ps. I have 2 Powerwall, total 27kwh

I also have 2 powerwalls and I created this in developer tools to demonstrate how to calculate hours left.

You would have to make this a sensor. Code is given below:

{% set percent = states('sensor.powerwall_charge') | float(0) / 100.0 %}
{% set number = 2 %}
{% set energy = 13.5 %}
{% set charge = percent * number * energy %}
{% set a = states('sensor.powerwall_solar_now') | float(0) %}
{% set b = states('sensor.powerwall_site_now') | float(0) %}
{% set c = states('sensor.powerwall_battery_now') | float(0) %}
{% set current_power = a + b + c %}
{% set decimal_hours = charge / current_power %}
{% set minutes = (decimal_hours % 1 * 60) | round(0) %}
{{ percent * 100 }}
{{ charge }}
{{ current_power }}
{{ "Battery Remaining @ current power: " ~ decimal_hours | int(0) ~ ":" ~ minutes}}

Here is the screenshot of the output:

As you can see I have 100% charge, 27 kWh in the battery, My current power draw is 4.495 kW.

At this power draw my life is 6 hrs 0 minutes.

I created this in Desktop on my home page

It also just hit me that I didn’t include the reserve. Here is my sensor code with reserve added.

template:
  - sensor:
    - name: "solar_battery_life"
      unique_id: "solar_battery_life"
      state: >-
        {% set percent = states('sensor.powerwall_charge') | float(0) / 100.0 %}
        {% set number = 2 %}
        {% set energy = 13.5 %}
        {% set reserve = 0.2 %}
        {% set charge = (percent - reserve) * number * energy %}
        {% set a = states('sensor.powerwall_solar_now') | float(0) %}
        {% set b = states('sensor.powerwall_site_now') | float(0) %}
        {% set c = states('sensor.powerwall_battery_now') | float(0) %}
        {% set current_power = a + b + c %}
        {% set decimal_hours = charge / current_power %}
        {% set minutes = (decimal_hours % 1 * 60) | round(0) %}
          {{ decimal_hours | int(0) ~ ' hrs ' ~ minutes ~ ' minutes' }} 
3 Likes

the code in developer tool seems to be working,
what is your exact template code in yaml?

Just posted as an edit in my response. Also added the reserve to the mix.

1 Like

thanks man!
so that will give you an estimate saving 20% for reserve?
the code in developers tool and the template still show different numbers, the one in the template is lower

EDIT:
just adjusted and remove the 0.2 from reserve and it shows same amount of the code in developer tools.

Try this. I have changed to make all of the entities float(0). I tested it and it seems to be working. The battery will cut off at 5% to prevent damage. You should at least use 0.05 as the reserve.

{% set percent = states('sensor.powerwall_charge') | float(0) / 100.0 %}
{% set number = 2 | float(0) %}
{% set energy = 13.5 | float(0) %}
{% set reserve = 0.3| float(0) %}
{% set charge = ((percent-reserve) * number * energy) | float(0) %}
{% set a = states('sensor.powerwall_solar_now') | float(0) %}
{% set b = states('sensor.powerwall_site_now') | float(0) %}
{% set c = states('sensor.powerwall_battery_now') | float(0) %}
{% set current_power = a + b + c %}
{% set decimal_hours = charge / current_power %}
{% set minutes = (decimal_hours % 1 * 60) | round(0) %}
{{ percent * 100 }}
{{ (percent - reserve) * 100 }}
{{ charge }}
{{ current_power }}
{{ decimal_hours | int(0) ~ ' hrs ' ~ minutes ~ ' minutes' }}

Here is the screenshot

Here is my code copied from my templates.yaml

#--------------------------------------------------------------------------------------------------
# Battery Life at Current Power Draw
#--------------------------------------------------------------------------------------------------
- sensor:
  - name: "solar_battery_life"
    unique_id: "solar_battery_life"
    state: >-
      {% set percent = states('sensor.powerwall_charge') | float(0) / 100.0 %}
      {% set number = 2 %}
      {% set energy = 13.5 %}
      {% set reserve = 0.2 %}
      {% set charge = (percent - reserve) * number * energy %}
      {% set a = states('sensor.powerwall_solar_now') | float(0) %}
      {% set b = states('sensor.powerwall_site_now') | float(0) %}
      {% set c = states('sensor.powerwall_battery_now') | float(0) %}
      {% set current_power = a + b + c %}
      {% set decimal_hours = charge / current_power %}
      {% set minutes = (decimal_hours % 1 * 60) | round(0) %}
        {{ decimal_hours | int(0) ~ ' hrs ' ~ minutes ~ ' minutes' }} 

#--------------------------------------------------------------------------------------------------
# Battery Life at Average 1 hr Power Draw
#--------------------------------------------------------------------------------------------------
- sensor:
  - name: "solar_battery_life_1_hr"
    unique_id: "solar_battery_life_1_hr"
    state: >-
      {% set percent = states('sensor.powerwall_charge') | float(0) / 100.0 %}
      {% set number = 2 | float(0) %}
      {% set energy = 13.5 | float(0) %}
      {% set reserve = 0.2 | float(0) %}
      {% set charge = ((percent - reserve) * number * energy) | float(0) %}
      {% set current_power =  states('sensor.average_kw_used_last_hour') | float(0) %}
      {% set decimal_hours = charge / current_power %}
      {% set minutes = (decimal_hours % 1 * 60) | round(0) %}
        {{ decimal_hours | int(0) ~ ' hrs ' ~ minutes ~ ' minutes' }}

#--------------------------------------------------------------------------------------------------
# Battery Life at Average 12 hr Power Draw
#--------------------------------------------------------------------------------------------------
- sensor:
  - name: "solar_battery_life_12_hr"
    unique_id: "solar_battery_life_12_hr"
    state: >-
      {% set percent = states('sensor.powerwall_charge') | float(0) / 100.0 %}
      {% set number = 2 | float(0) %}
      {% set energy = 13.5 | float(0) %}
      {% set reserve = 0.2 | float(0) %}
      {% set charge = ((percent - reserve) * number * energy) | float(0) %}
      {% set current_power =  states('sensor.average_kw_used_last_12_hour') | float(0) %}
      {% set decimal_hours = charge / current_power %}
      {% set minutes = (decimal_hours % 1 * 60) | round(0) %}
        {{ decimal_hours | int(0) ~ ' hrs ' ~ minutes ~ ' minutes' }}

#--------------------------------------------------------------------------------------------------
# Battery Life at Average 24 hr Power Draw
#--------------------------------------------------------------------------------------------------
- sensor:
  - name: "solar_battery_life_24_hr"
    unique_id: "solar_battery_life_24_hr"
    state: >-
      {% set percent = states('sensor.powerwall_charge') | float(0) / 100.0 %}
      {% set number = 2 | float(0) %}
      {% set energy = 13.5 | float(0) %}
      {% set reserve = 0.2 | float(0) %}
      {% set charge = ((percent - reserve) * number * energy) | float(0) %}
      {% set current_power =  states('sensor.average_kw_used_last_24_hour') | float(0) %}
      {% set decimal_hours = charge / current_power %}
      {% set minutes = (decimal_hours % 1 * 60) | round(0) %}
        {{ decimal_hours | int(0) ~ ' hrs ' ~ minutes ~ ' minutes' }}

Here are my sensors from sensors.yaml.

# ---------------------------------------------------------------------------------------------
# Average over the last hour
# ---------------------------------------------------------------------------------------------
- platform: statistics
  name: "Average kW Used Last Hour"
  entity_id: sensor.powerwall_load_now
  state_characteristic: mean
  max_age:
    hours: 1
  sampling_size: 300
  precision: 3

# ---------------------------------------------------------------------------------------------
# Average over the last 12 hours
# ---------------------------------------------------------------------------------------------
- platform: statistics
  name: "Average kW Used Last 12 Hour"
  entity_id: sensor.powerwall_load_now
  state_characteristic: mean
  max_age:
    hours: 12
  sampling_size: 3600
  precision: 3
  
# ---------------------------------------------------------------------------------------------
# Average over the last 24 hours
# ---------------------------------------------------------------------------------------------
- platform: statistics
  name: "Average kW Used Last 24 Hour"
  entity_id: sensor.powerwall_load_now
  state_characteristic: mean
  max_age:
    hours: 24
  sampling_size: 7200
  precision: 3

I set this in my configuration.yaml for the files and my default directory is \config

sensor: !include sensors.yaml
template: !include templates.yaml

Sorry for any confusion!

4 Likes

Hello, i am currently trying to get this work with lifepo4 battery and a JK-BMS . Can you please explain your data input? Why is your energy a % value?

I can get the state of charge from my bms and the current consumption in ampere. So e.g i have total battery capacity of 302A and the current consumption is 2 A/h at a state of charge of 75%. How do i calculate the remaining runtime of the battery?

Thanks for any help!

Best regards
Hellfish

I don’t know if the question was to me.

.

Blockquote

Can you please explain your data input? Why is your energy a % value?

Blockquote

All of my values are in kW or kWh except the battery charge %.

Percent is % of total charge
I have two batteries so number is 2
Each battery holds 13.5 kWh of energy at 100%
The reserve set for outages is 20%

If I was using 2kW from my battery and the battery was 90% charged, the time left on the batteries would be:

(0.9-0.2) * 2 * 13.5 / 2 = 0.7 * 13.5 * 2/2 = 9 hours 27 minutes at the 2kW draw.

I hope this helps.

1 Like

@AllHailJ This is awesome! I’ve been doing mental calculations of this since I got my solar installed and this just tells me what I’m always thinking about. Thank you for sharing this!

A few questions for you:

1. Do you see the variance in Tesla Powerwall integration reporting a different battery percentage when below 100% than the Tesla Mobile App?

e.g. when at or near the reserve amount (20% in my case), my Tesla Mobile App shows 20% battery level remaining, the web UI via IP for Powerwall shows 24%, and the HA sensor shows 24%. So as of right now, my Powerwall in the Tesla app is at 21%, but HA is reporting 25% and the sensor is showing the battery life based on the 5% remaining it thinks I have instead of the 1% that’s really there. It’s usually a 4% diff.

This isn’t an issue at 100% since all sensors report the same, but when at the reserve level it’s misleading. I’ve raised this to Tesla support, but their Tier 2 support indicated the difference between the Tesla App and Web UI (I assume the HA integration is pulling from the same location which is why they’re the same) is by design…do you see this?

Also, the Tesla custom integration via HACS appears to have a sensor that reports the correct value, however I’ve found that integration to periodically toggle between 0% and the correct percentage. So, that’s not a reliable option.

UPDATE - after more research and filing an issue with the official HA Tesla Powerwall Integration, I found a reddit post that provided the formula that appears to correct for the 5% backup reserve. I’ve updated the solar_battery_life sensor below using that logic and it looks right so far on my end. Here is how I’ve modified your code to attempt to adjust for this difference, but I am now thinking it’s proportional and not a straight 4% difference. How would you approach this?

- sensor:
  - name: "solar_battery_life_actual"
    unique_id: "solar_battery_life_actual"
    state: >-
      {% set percent = ((states('sensor.powerwall_charge') | float(0) / 100.0) - (0.05)) / (0.95) %}
      {% set number = 2 %}
      {% set energy = 13.5 %}
      {% set reserve = 0.2 %}
      {% set charge = (percent - reserve) * number * energy %}
      {% set a = states('sensor.powerwall_solar_now') | float(0) %}
      {% set b = states('sensor.powerwall_site_now') | float(0) %}
      {% set c = states('sensor.powerwall_battery_now') | float(0) %}
      {% set current_power = a + b + c %}
      {% set decimal_hours = charge / current_power %}
      {% set minutes = (decimal_hours % 1 * 60) | round(0) %}
        {{ decimal_hours | int(0) ~ ' hrs ' ~ minutes ~ ' minutes' }} 

2. If you wanted to create a sensor to show Battery Life at Current Power Draw Including Reserve, would you just pull out the mentions of “reserve” ? Here’s what I’ve put together…anything you would do differently?

- sensor:
  - name: "solar_battery_life_total"
    unique_id: "solar_battery_life_total"
    state: >-
      {% set percent = states('sensor.powerwall_charge') | float(0) / 100.0 %}
      {% set number = 2 %}
      {% set energy = 13.5 %}
      {% set charge = (percent) * number * energy %}
      {% set a = states('sensor.powerwall_solar_now') | float(0) %}
      {% set b = states('sensor.powerwall_site_now') | float(0) %}
      {% set c = states('sensor.powerwall_battery_now') | float(0) %}
      {% set current_power = a + b + c %}
      {% set decimal_hours = charge / current_power %}
      {% set minutes = (decimal_hours % 1 * 60) | round(0) %}
        {{ decimal_hours | int(0) ~ ' hrs ' ~ minutes ~ ' minutes' }} 

3. Do you get these errors on boot for these sensors? I’m seeing this at ever reboot in the logs, so I’m wondering if it’s just because the system rebooted.

Logger: homeassistant.helpers.template_entity
Source: helpers/template_entity.py:356
First occurred: 22:01:28 (3 occurrences)
Last logged: 22:01:28

TemplateError('ZeroDivisionError: float division by zero') while processing template 'Template("{% set percent = states('sensor.powerwall_charge') | float(0) / 100.0 %} {% set number = 2 | float(0) %} {% set energy = 13.5 | float(0) %} {% set reserve = 0.2 | float(0) %} {% set charge = ((percent - reserve) * number * energy) | float(0) %} {% set current_power = states('sensor.average_kw_used_last_hour') | float(0) %} {% set decimal_hours = charge / current_power %} {% set minutes = (decimal_hours % 1 * 60) | round(0) %} {{ decimal_hours | int(0) ~ ' hrs ' ~ minutes ~ ' minutes' }}")' for attribute '_attr_native_value' in entity 'sensor.solar_battery_life_1_hr'
TemplateError('ZeroDivisionError: float division by zero') while processing template 'Template("{% set percent = states('sensor.powerwall_charge') | float(0) / 100.0 %} {% set number = 2 | float(0) %} {% set energy = 13.5 | float(0) %} {% set reserve = 0.2 | float(0) %} {% set charge = ((percent - reserve) * number * energy) | float(0) %} {% set current_power = states('sensor.average_kw_used_last_12_hour') | float(0) %} {% set decimal_hours = charge / current_power %} {% set minutes = (decimal_hours % 1 * 60) | round(0) %} {{ decimal_hours | int(0) ~ ' hrs ' ~ minutes ~ ' minutes' }}")' for attribute '_attr_native_value' in entity 'sensor.solar_battery_life_12_hr'
TemplateError('ZeroDivisionError: float division by zero') while processing template 'Template("{% set percent = states('sensor.powerwall_charge') | float(0) / 100.0 %} {% set number = 2 | float(0) %} {% set energy = 13.5 | float(0) %} {% set reserve = 0.2 | float(0) %} {% set charge = ((percent - reserve) * number * energy) | float(0) %} {% set current_power = states('sensor.average_kw_used_last_24_hour') | float(0) %} {% set decimal_hours = charge / current_power %} {% set minutes = (decimal_hours % 1 * 60) | round(0) %} {{ decimal_hours | int(0) ~ ' hrs ' ~ minutes ~ ' minutes' }}")' for attribute '_attr_native_value' in entity 'sensor.solar_battery_life_24_hr'```