The end result is a sensor that confirms the kwh capacity of your battery, and more importantly it helps answers the question ‘am I getting the kWh I paid for’ I used it to see the impact of temperature on capacity.
For this sensor to work effectively you must charge your battery each day on an eco tariff such as Octopus GO, as it works on the logic that the amount of power the battery discharges divided by the reduction in battery SoC will give the estimated capacity the battery should have.
Battery Capacity = battery discharge / (drop in SoC /100)
e.g. daily battery discharge is 5.2kwh, and the soc has dropped from 99 to 66% so : 5.2 / (.33) = 15.76 kWh
This code example is based on Octopus GO times with start charge at 12:30 and ending at 4:30 but it will work on any eco tariff that starts and ends in the same day i.e. Flux 2am-5am, or Eco7 12.30-7.30am
The sensor gets more accurate as the day progresses and helps to smooth out any SoC recalibrations, it is useful to compare changes on a day by day basis - for example it shows temperature effects reducing capacity.
The extra complexity comes when the battery gets charge during the day from solar when the SoC will increase - to deal with this it records the charge put into the battery at the end of the charge period (when soc is full) and subtracts it from the current daily battery charge - this gives the amount of solar charge which is then deducted in the battery discharge sum.
For accuracy it’s necessary to capture the battery discharge between midnight and 12:30 as when it finishes charging at 4.30am, the SoC will be full but the bat_discharge_daily sensor will already have had 30 minutes of house load, I keep a track of the discharge in the battery_off_peak_discharge helper and remove it in the calculation.
And so the final calculation becomes:-
Battery Capacity = (battery discharge - solar charge - battery off peak discharge) / (drop in SoC /100)
To calculate this I use a template sensor, an automation and 4 helpers.
EDIT: This latest version will reset the counters if the battery reaches 100% during the day from solar charge, this will improve it's accuracy during the summer months when grid charging is not normally used.
The following code is based on the latest BMS reporting SoC of 100%.
To start firstly create the helpers (HA Settings, Devices & Services, click ‘Helpers’ at the top and ‘+Create Helper’ in the bottom right corner, select ‘Number’) - the helpers should be named
battery charge off peak
The minimum value should be 0, maximum value should be 30, Display mode should be 'input field', Step Size should be 0.01, and Unit of Measurement should be kW then create the next helper
battery off peak discharge
The minimum value should be 0, maximum value should be 2, Display mode should be 'input field', Step Size should be 0.01, and Unit of Measurement should be kW Then create the helpers for the start and end of your ECO tariff (i've named them Octopus GO start/end which is 12:30 and 04:30 but if you are on Flux just set them to 2am and 5am)
Octopus GO Start
Octopus GO End Click on each helper and set the start and end times to your Tariff start and end times i.e. for Octopus Go it is 12:30 start, 04:30 end, for Flux it would be start 02:00, end 05:00
Finally create the helper that contains the actual storage size of your battery pack
Once created click on it and set the actual storage size of your battery pack, for example if you have 4 * HV2600 batteries it is 4*2.56kwh = 10.24kWh.
Then add the sensor code to your configuration.yaml in the Templates section (usually towards the bottom)
Code: Select all
- sensor:
- name: "Battery Track"
unit_of_measurement: "kW"
state: >
{% set kwhtotal = states('input_number.battery_kwh_capacity') | round(2,0) %}
{% if states("sensor.bat_charge_daily") not in ['unknown', 'unavailable', 'none'] %}
{% set batsoc = states("sensor.battery_soc")|float(0) %}
{% if batsoc > 99 %}
{% set calcbattery = kwhtotal | round(2) %}
{% else %}
{% set batUsed = ((100-batsoc)/100) %}
{% set dischgLessSolarChg = ((states('sensor.bat_charge_daily')|float(default=0) - states('input_number.battery_charge_off_peak')|float(default=0)) * 1.1) %}
{% set calcbattery = (((states("sensor.bat_discharge_daily")|float(default=0)) - states("input_number.battery_off_peak_discharge")|float(default=0) - dischgLessSolarChg) / batUsed) |round(2) %}
{% if calcbattery == 0 %}
{% set calcbattery = kwhtotal | round(2) %}
{% endif %}
{% endif %}
{% if states("sensor.bat_discharge_daily") not in ['unknown', 'unavailable', 'none'] %}
{% if states("input_number.battery_charge_off_peak")|float > 0 %}
{% if calcbattery > (kwhtotal*1.1) %}
{{ (kwhtotal*1.1) | round(2) }}
{% else %}
{% if calcbattery < (kwhtotal*(batsoc/130)) %}
{{ (kwhtotal*(batsoc/130)) | round(2) }}
{% else %}
{{ calcbattery |round(2) }}
{% endif %}
{% endif %}
{% else %}
{{ 0|float }}
{% endif %}
{% endif %}
{% endif %}
Code: Select all
alias: Battery Check
description: This updates the helpers for battery check charge sensor
trigger:
- platform: time_pattern
minutes: /5
- platform: time
at: input_datetime.octopus_go_end
condition: []
action:
- if:
- condition: template
value_template: |-
{% set start = states('input_datetime.octopus_go_end') %}
{{ now().strftime('%H:%M') == start[:5] }}
then:
- if:
- condition: numeric_state
entity_id: sensor.battery_soc
below: 100
then:
- service: input_number.set_value
target:
entity_id: input_number.battery_charge_off_peak
data:
value: >-
{% set bcd=(states('sensor.bat_charge_daily')|float(default=0))
%} {% set battkwh=( states('input_number.battery_kwh_capacity')
| round(2,0) ) %} {% set
batsocused=(100-(states('sensor.battery_soc')|float(default=0)
))/100 %} {{ ((bcd + (battkwh * batsocused))*1)|round(3) }}
else:
- service: input_number.set_value
target:
entity_id: input_number.battery_charge_off_peak
data:
value: >-
{{ (states('sensor.bat_charge_daily')|float(default=0)
)|round(2) }}
- service: logbook.log
data:
message: Set charge off peak at Eco Tariff End
name: Battery Check
- service: input_number.set_value
target:
entity_id: input_number.battery_off_peak_discharge
data:
value: >-
{{ (states('sensor.bat_discharge_daily')|float) | round(2,default=0)
}}
- service: logbook.log
data:
message: Set off peak discharge at Eco Tariff End
name: Battery Check
else:
- if:
- condition: time
before: input_datetime.octopus_go_end
after: input_datetime.octopus_go_start
then:
- service: input_number.set_value
target:
entity_id: input_number.battery_charge_off_peak
data:
value: 0
- service: logbook.log
data:
message: "Set off peak charge to zero "
name: Battery Check
- if:
- condition: time
after: input_datetime.octopus_go_end
before: input_datetime.octopus_go_start
then:
- if:
- condition: numeric_state
entity_id: sensor.battery_soc
above: 99
then:
- service: logbook.log
data:
message: SoC reached 100% in day, reset counters
name: Battery Check
- service: input_number.set_value
target:
entity_id: input_number.battery_charge_off_peak
data:
value: >-
{{ (states('sensor.bat_charge_daily')|float(default=0)
)|round(2) }}
- service: input_number.set_value
target:
entity_id: input_number.battery_off_peak_discharge
data:
value: >-
{{ (states('sensor.bat_discharge_daily')|float(default=0)
)|round(2) }}
mode: single
Code: Select all
sensor:
- platform: filter
name: "Battery Track Smooth"
entity_id: sensor.battery_track
filters:
- filter: time_simple_moving_average
window_size: "00:15"
- filter: lowpass
time_constant: 10
precision: 2
The new sensors will now be available to add to a dashboard, they are called
sensor.battery_track
and
sensor.battery_track_smooth
The 'smooth' sensor is simply a filtered version of the sensor.battery_track which smooths out the step changes as SoC changes, it is much easier to visualise.
When you first setup this code the helpers won’t contain any values as they are set by the automation at 4.30am so the sensor.battery_track value will be wrong, you can either wait for the next day when the automation will set them or just add an entity card on a dashboard with the following entities:-
Input.number_battery_charge_off_peak
and
Input.number_battery_off_peak_discharge
They helpers can be modified in the dashboard display, to get the sensor working set input.number_battery_charge_off_peak to the same value that is shown in sensor.bat_charge_daily and set input.number_battery_off_peak_discharge to 0.25 (this assumes your average load is 500Wh)
Please note this code relies on having two utility meter sensors already working, and depending on how you interfaced the Inverter you may (or may not have these), the sensors are sensor.bat_charge_daily and sensor.bat_discharge_daily.
If you do not have these sensors please add the following code to your configuration.yaml -
These are the Riemann sums that feed the utility meters
Code: Select all
- method: left
name: bat_charge_sum
platform: integration
round: 2
source: sensor.battery_charge
unit_time: h
- method: left
name: bat_discharge_sum
platform: integration
round: 2
source: sensor.battery_discharge
unit_time: h
Code: Select all
bat_charge_daily:
source: sensor.bat_charge_sum
cycle: daily
bat_discharge_daily:
source: sensor.bat_discharge_sum
cycle: daily
And that's it, please feel free to use or modify it to suit your eco tariff times and if you can suggest any improvements, please let me know.
I have attached a couple of images of the sensor from my system (an 18.2kWh battery).