Hi,
Does anyone have any studies, reports on how the longevity of LiFePo4 batteries is affected by different ranges being used?
e.g. for longevity, is
0-100%
20-100%
0-80%
20-80%
best, and by how much?
A lot of people seem to have opinions on this but was wondering does anyone know of any proper research here?
Many thanks,
Mark
There are a lot of research papers about for LFP batteries but they tend to focus on specific things like ageing, or temperature - the main problem being that LFP batteries do not have enough historical data to study as the chemistry is fairly new.
This is a very good video that explains some of the realities
The main thing to remember is that Fox have already limited the batteries to 90% DOD and they provide warranties for their batteries being charged by their BMS systems which do require regular 100% charge cycles to maintain SoC accuracy. Best advice is to use them within the Fox specifications temperature, currents and their battery warranty will provide all the guarantees you need.
This is a very good video that explains some of the realities
The main thing to remember is that Fox have already limited the batteries to 90% DOD and they provide warranties for their batteries being charged by their BMS systems which do require regular 100% charge cycles to maintain SoC accuracy. Best advice is to use them within the Fox specifications temperature, currents and their battery warranty will provide all the guarantees you need.
Thanks Dave!
That makes sense!
when you say:
"which do require regular 100% charge cycles to maintain SoC accuracy"
Any ideas how often 'regular' is?
1x per month
1x per week
1x per day?
That makes sense!
when you say:
"which do require regular 100% charge cycles to maintain SoC accuracy"
Any ideas how often 'regular' is?
1x per month
1x per week
1x per day?
It’s best to do it monthly, it won’t damage them if you don’t but you’ll see the accuracy of the SoC gets worse with occasional jumps as it re-evaluates the Soc from the cell voltages.
I found this one quite interesting
https://iopscience.iop.org/article/10.1 ... 111/ad6cbd
There's also https://www.sciencedirect.com/science/a ... 1722000283 and https://www.mdpi.com/1996-1073/14/6/1732 which cover aspects like calendar degradation.
Personally I do 100% once a week, 10% once a month via an automation. It'll do it more often in certain circumstances (for example, if forecast PV for the day is very low, it'll use the full range instead for a bit of extra arbitrage to make up for the lost generation).
It's not worth doing the calibration cycles manually, you might as well just stick to the full range rather than trying to keep track. Also, underutilising the battery is just as much of a waste, due to calendar degradation (for example, sticking to 20-80 all the time, 0.6 full cycles daily, would mean you'd run out of warranty years, and the battery capacity would have decreased anyway due to the passing of time). It's a bit of a balancing act.
Calendar degradation is (relative to cycling) more important on LFP, whereas with NMC batteries the cycling is a much bigger factor.
https://iopscience.iop.org/article/10.1 ... 111/ad6cbd
There's also https://www.sciencedirect.com/science/a ... 1722000283 and https://www.mdpi.com/1996-1073/14/6/1732 which cover aspects like calendar degradation.
Personally I do 100% once a week, 10% once a month via an automation. It'll do it more often in certain circumstances (for example, if forecast PV for the day is very low, it'll use the full range instead for a bit of extra arbitrage to make up for the lost generation).
It's not worth doing the calibration cycles manually, you might as well just stick to the full range rather than trying to keep track. Also, underutilising the battery is just as much of a waste, due to calendar degradation (for example, sticking to 20-80 all the time, 0.6 full cycles daily, would mean you'd run out of warranty years, and the battery capacity would have decreased anyway due to the passing of time). It's a bit of a balancing act.
Calendar degradation is (relative to cycling) more important on LFP, whereas with NMC batteries the cycling is a much bigger factor.
Thanks WyndStryke for your thoughtful reply (and for the papers), much appreciated! 
What SoC range do you typically use for your batteries then?
What SoC range do you typically use for your batteries then?
It changes on a day to day basis, anywhere from 14-92 to 20-84 (my automations will tweak it based on the PV forecast / weather forecast / etc), but I will also be cycling in the middle of that range as well (again, dependant on things like cell temperature / PV / weather / etc). I also adjust charge and discharge rates based on those criteria. On very hot days etc it will be using the slowest possible charge and discharge rate and no mid-range cycling (a high cell temperature causes increased cell degradation). When it is cold, it will be more aggressive, to generate warmth.What SoC range do you typically use for your batteries then?
So I'm using more like 0.8-1.0 full cycles daily even if I don't hit the extremes. That's only possible on particular tariffs.
I would honestly expect FoxESS to manage this properly. The only thing as a enduser I should set is the MinimumSoC and the rest the system should "automagically" manage.
That's how 95% of people do it. I tend to go above&beyond. The BMS will already reduce the charge rate as you near 100%, or if the battery is very hot or cold. I just put stronger limits on things.
Last edited by WyndStryke on Thu Aug 07, 2025 11:55 am, edited 1 time in total.
Hi All.
I have an EP11, installed during February as part of a new solar install (Fox 6kW hybrid inverter, for what it's worth).
I have set the SoC to 15% (I think it was at 20% by default) and allow it to charge up to 100%, which over the first six months it has done so more than 50% (maybe as much as 80% plus) of days - though winter is of course coming
I don't do forced charging or discharging - I just let it fluctuate up and down according to solar availability and our usage. During the hot nights, it goes down to 15% due to air con and fan usage.
Is this basically fine and an OK operating mode for this battery?
Ian C
I have an EP11, installed during February as part of a new solar install (Fox 6kW hybrid inverter, for what it's worth).
I have set the SoC to 15% (I think it was at 20% by default) and allow it to charge up to 100%, which over the first six months it has done so more than 50% (maybe as much as 80% plus) of days - though winter is of course coming

I don't do forced charging or discharging - I just let it fluctuate up and down according to solar availability and our usage. During the hot nights, it goes down to 15% due to air con and fan usage.
Is this basically fine and an OK operating mode for this battery?
Ian C
Yes it's fine, the important thing is that it gets to 100% from time to time as it's in the last few % the batteries balance all of which will improve the accuracy of the batteries SoC reading.
In winter it's likely you won't get anywhere near 100% some people use the eco tariff's (EV or eco 7) to charge during the night on the low tariff and then use the power during the day when the tariffs are high. If you don't do that you might want to give it a monthly charge from grid to 100% just to keep it reporting accurately.
Equally you can set the battery minSoC to 10%, it is rated for that and it won't effect the warranty or guarantees plus you'll get all the capacity you have paid for - the only time it might be better to be at 15% is when it is very cold over winter, particularly if the batteries are stored externally (and don't have the internal heater option).
In winter it's likely you won't get anywhere near 100% some people use the eco tariff's (EV or eco 7) to charge during the night on the low tariff and then use the power during the day when the tariffs are high. If you don't do that you might want to give it a monthly charge from grid to 100% just to keep it reporting accurately.
Equally you can set the battery minSoC to 10%, it is rated for that and it won't effect the warranty or guarantees plus you'll get all the capacity you have paid for - the only time it might be better to be at 15% is when it is very cold over winter, particularly if the batteries are stored externally (and don't have the internal heater option).
Thanks, Dave.
Interestingly, I had a look at the behaviour in February.
Feed in to the grid occurred at some point on the following dates after the mid-Feb install (implying the battery was full): 14th, 15th, 17th, 18th, 19th, 21st, 22nd, 23rd, 24th, 25th, 26th, 27th, 28th (or perhaps more simply, not the 16th and the 20th). I've got an array of panels matching the hybrid inverter capacity facing 5-10 degrees west of south without obstruction, so even in winter, it was producing decently.
Half or more of these were only for short periods around the middle of the day. I suppose this might mean that the battery couldn't handle all the hybrid inverter's spot output on a bright day, so the excess was diverted to the grid - in which case the battery was probably not full. However, a number of days did have sustained periods of feed in, with the usual arc starting in the middle of the day then decreasing through to sunset - in which case the battery probably was full.
So, even with a few of those during bright winter days pushing the battery to 100%, all should be good without me needing to do anything, if I've understood you correctly.
Ian C
Interestingly, I had a look at the behaviour in February.
Feed in to the grid occurred at some point on the following dates after the mid-Feb install (implying the battery was full): 14th, 15th, 17th, 18th, 19th, 21st, 22nd, 23rd, 24th, 25th, 26th, 27th, 28th (or perhaps more simply, not the 16th and the 20th). I've got an array of panels matching the hybrid inverter capacity facing 5-10 degrees west of south without obstruction, so even in winter, it was producing decently.
Half or more of these were only for short periods around the middle of the day. I suppose this might mean that the battery couldn't handle all the hybrid inverter's spot output on a bright day, so the excess was diverted to the grid - in which case the battery was probably not full. However, a number of days did have sustained periods of feed in, with the usual arc starting in the middle of the day then decreasing through to sunset - in which case the battery probably was full.
So, even with a few of those during bright winter days pushing the battery to 100%, all should be good without me needing to do anything, if I've understood you correctly.
Ian C
Ok no problem, keep an eye on it - it’s not essential and if it doesn’t hit 100% for a few months you just might start to see jumps in SoC and drifts in accuracy but theres no damage as a result.
Just as a quick FYI December is by far the worst solar month, followed by January, February is a big improvement
Just as a quick FYI December is by far the worst solar month, followed by January, February is a big improvement
July felt like it was bad (compared to the sunny weather earlier in the year), but I was still 9% above the PVGis and installer estimates despite the seemingly endless cloud cover. That really surprised me.
Hi all,
I just got my system installed, and I have the opposite problem as everyone on this post here so I'm looking for some advice.
I have the H1(g2) 6kw inverter installed with 6.6kw PV system with 2x ep11 (20.72kw) battery. I live in australia so doesn't matter if it's winter or summer, my battery will sit at 100% for almost the entirety of the day anywhere from about 11am till about 7pm (when i return from work). I'm at the point where I'm constantly trying to force the use of central cooling/heating at night just to use the stored energy.
What would the best recommendation be in terms of battery longetivity? I've been reading mixed reviews about batteries in general and how it shouldn't be left at 100% for long periods. Since installation, my SoC has never even seen anything under 50%.
Would setting something like this work? Since my battery is 100% around 11am, i'm thinking of setting a forced discharge to 85% from 11am - 7pm (which is when i return from work)?
Am I overthinking?
I just got my system installed, and I have the opposite problem as everyone on this post here so I'm looking for some advice.
I have the H1(g2) 6kw inverter installed with 6.6kw PV system with 2x ep11 (20.72kw) battery. I live in australia so doesn't matter if it's winter or summer, my battery will sit at 100% for almost the entirety of the day anywhere from about 11am till about 7pm (when i return from work). I'm at the point where I'm constantly trying to force the use of central cooling/heating at night just to use the stored energy.
What would the best recommendation be in terms of battery longetivity? I've been reading mixed reviews about batteries in general and how it shouldn't be left at 100% for long periods. Since installation, my SoC has never even seen anything under 50%.
Would setting something like this work? Since my battery is 100% around 11am, i'm thinking of setting a forced discharge to 85% from 11am - 7pm (which is when i return from work)?
Am I overthinking?
I would once a month do a force discharge down to 10% SoC and allow the system to slowly charge again to full. Don't worry about holding it at 100% as these are LFP cells and are happy and stable to sit at 100% for months. Unlike NMC or NCA cells which are damaged with prolonged max soc holding.
knightwalkr wrote: ↑Sat Sep 06, 2025 10:54 am Hi all,
I just got my system installed, and I have the opposite problem as everyone on this post here so I'm looking for some advice.
I have the H1(g2) 6kw inverter installed with 6.6kw PV system with 2x ep11 (20.72kw) battery. I live in australia so doesn't matter if it's winter or summer, my battery will sit at 100% for almost the entirety of the day anywhere from about 11am till about 7pm (when i return from work). I'm at the point where I'm constantly trying to force the use of central cooling/heating at night just to use the stored energy.
What would the best recommendation be in terms of battery longetivity? I've been reading mixed reviews about batteries in general and how it shouldn't be left at 100% for long periods. Since installation, my SoC has never even seen anything under 50%.
Would setting something like this work? Since my battery is 100% around 11am, i'm thinking of setting a forced discharge to 85% from 11am - 7pm (which is when i return from work)?
Am I overthinking?
Community Admin / FoxESS Elite Professional
Did I help you? Feel free to leave me a tip
Book a zoom meeting for remote consultancy or help
Switch to Octopus and earn £50
Get a heatpump from Octopus and earn £100
Subscribe to my YouTube channel
Fox ESS Tri Inverter Installation
2 x KH Hybird Inverters (Parallel Mode)
1 x H1 Gen1
24 x HV2600 (62.4kWh)
32 x 490w across 4 arrays
Dual Tesla Household
Heatpump & Low Carbon Housebuild





Fox ESS Tri Inverter Installation
2 x KH Hybird Inverters (Parallel Mode)
1 x H1 Gen1
24 x HV2600 (62.4kWh)
32 x 490w across 4 arrays
Dual Tesla Household
Heatpump & Low Carbon Housebuild
Hi WyndStrykeWyndStryke wrote: ↑Mon Aug 04, 2025 5:56 pm
Personally I do 100% once a week, 10% once a month via an automation.
It looks like you are using home assistant for the automation. If that is the case, do you mind sharing your code for your automations? I just got me solar/battery installed and hooked it up to my HA server via modbus. I'm keen to do some good battery management.
Thanks
Alan
You're probably best off writing your own, I was just learning how to write automations, wouldn't do it the same way now. There are over 20 interacting automations, and maybe 140 helpers, but these are the main ones which would be involved with calibration.
Code: Select all
alias: FoxESS calibrate set bottom SoC reached
description: >-
When the battery SoC drops to the minimum, it means that the BMS will be able
to calibrate the low end of the SoC voltage range. Store the date so that we
can use it to schedule the next calibration cycle.
triggers:
- trigger: template
value_template: >-
{{(states('sensor.battery_soc')| float) <=
(states('input_number.foxess_calibrate_bottom_soc')| float)}}
- trigger: state
entity_id:
- sensor.battery_soc
- trigger: time_pattern
minutes: /10
seconds: "15"
conditions:
- condition: template
value_template: >-
{{(states('sensor.battery_soc')| float) <=
(states('input_number.foxess_calibrate_bottom_soc')| float)}}
actions:
- action: input_datetime.set_datetime
metadata: {}
data:
datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}"
target:
entity_id: input_datetime.foxess_calibrate_bottom_reached_date
- action: input_boolean.turn_on
metadata: {}
data: {}
target:
entity_id: input_boolean.foxess_calibrate_bottom_reached
- action: input_text.set_value
metadata: {}
data:
value: Bottom SoC reached
target:
entity_id: input_text.foxess_schedule_mode
- choose:
- conditions:
- condition: state
entity_id: input_boolean.foxess_calibrate_top_reached
state: "on"
- condition: state
entity_id: input_boolean.foxess_calibrate_bottom_reached
state: "on"
- condition: state
entity_id: input_boolean.foxess_calibrate_today
state: "on"
sequence:
- action: input_boolean.turn_off
metadata: {}
data: {}
target:
entity_id: input_boolean.foxess_calibrate_today
mode: single
Code: Select all
alias: FoxESS calibrate set top SoC reached
description: >-
When the battery SoC hits the max, it means that the BMS will be able to
calibrate the high end of the SoC voltage range. Store the date so that we
can use it to schedule the next calibration cycle. Note that it may take
multiple calibration cycles to resolve if SoC calibration has been fully lost.
triggers:
- trigger: state
entity_id:
- sensor.battery_soc
- trigger: template
value_template: >-
{{ (states('sensor.battery_soc') | float) >=
(states('input_number.FoxESS_calibrate_top_SoC') | float) }}
- trigger: time_pattern
minutes: /10
seconds: "15"
conditions:
- condition: template
value_template: >-
{{ (states('sensor.battery_soc') | float) >=
(states('input_number.FoxESS_calibrate_top_SoC') | float) }}
actions:
- if:
- condition: or
conditions:
- condition: state
entity_id: input_boolean.foxess_calibrate_top_reached
state: "off"
- condition: template
value_template: >-
{{
(states('input_datetime.foxess_calibrate_top_reached_date')|as_datetime).date()
<= (now()-timedelta(days=1)).date() }}
then:
- action: input_datetime.set_datetime
metadata: {}
data:
datetime: "{{ now().strftime('%Y-%m-%d %H:%M:%S') }}"
target:
entity_id: input_datetime.foxess_calibrate_top_reached_date
- action: input_boolean.turn_on
metadata: {}
data: {}
target:
entity_id: input_boolean.foxess_calibrate_top_reached
- action: input_number.set_value
metadata: {}
data:
value: 1
target:
entity_id: input_number.foxess_overnight_calibration_modifier
- action: input_text.set_value
metadata: {}
data:
value: Top SoC reached
target:
entity_id: input_text.foxess_schedule_mode
- choose:
- conditions:
- condition: state
entity_id: input_boolean.foxess_calibrate_top_reached
state: "on"
- condition: state
state: "on"
entity_id: input_boolean.foxess_calibrate_bottom_reached
- condition: state
entity_id: input_boolean.foxess_calibrate_today
state: "on"
sequence:
- action: input_boolean.turn_off
metadata: {}
data: {}
target:
entity_id: input_boolean.foxess_calibrate_today
mode: single
Code: Select all
alias: FoxESS Overnight charge/discharge speed
description: ""
triggers:
- trigger: state
entity_id:
- input_boolean.foxess_calibrate_top_reached
- sensor.battery_soc
- trigger: state
entity_id:
- sensor.FoxESS_overnight_charge_rate_kW
- sensor.FoxESS_overnight_discharge_rate_kW
- select.work_mode
- input_boolean.eon_next_drive_cheap_period
- input_number.foxess_overnight_calibration_modifier
for:
hours: 0
minutes: 0
seconds: 1
- trigger: time
at: "00:00:05"
- trigger: time
at: "00:02:05"
- trigger: time
at: "06:30:05"
- trigger: time
at: "06:45:05"
- trigger: time
at: "06:58:05"
conditions:
- condition: state
entity_id: input_boolean.eon_next_drive_cheap_period
state: "on"
actions:
- if:
- condition: state
entity_id: input_boolean.foxess_calibrate_top_reached
state: "off"
then:
- action: input_number.set_value
metadata: {}
data:
value: 1.1
target:
entity_id: input_number.foxess_overnight_calibration_modifier
else:
- action: input_number.set_value
metadata: {}
data:
value: 1
target:
entity_id: input_number.foxess_overnight_calibration_modifier
enabled: false
- choose:
- conditions:
- condition: or
conditions:
- condition: time
after: "00:00:00"
before: "00:01:59"
- condition: time
after: "06:58:00"
before: "07:00:00"
- condition: template
value_template: >-
{% set target_soc =
states('input_number.foxess_calibrate_top_soc') | float %} {%
if states('input_boolean.foxess_calibrate_top_reached') | bool
%}
{% set target_soc = states('input_number.foxess_overnight_charge_top_soc') | float %}
{% endif %} {{states('sensor.battery_soc') | float >=
target_soc}}
sequence:
- action: number.set_value
metadata: {}
data:
value: "0.4"
target:
entity_id: number.force_charge_power
- action: number.set_value
metadata: {}
data:
value: "{{(states('sensor.pv_power')| float) + 0.1}}"
target:
entity_id: number.force_discharge_power
- conditions:
- condition: time
after: "00:02:00"
before: "06:58:00"
- condition: numeric_state
entity_id: sensor.battery_soc
above: 90
sequence:
- if:
- condition: state
entity_id: select.work_mode
state: Force Discharge
then:
- action: number.set_value
metadata: {}
data:
value: >-
{% set force_discharge_power =
states('number.force_discharge_power') | float %} {% set
charge_speed_for_7am =
states('sensor.foxess_charge_speed_for_7am') | float %} {%
if charge_speed_for_7am > 0 %}
{% set power = states('sensor.FoxESS_overnight_discharge_rate_kW') | float(4) | round(2) %}
{% else %}
{# We have reached the target SoC, and we are close in time to the end, so stay at this level #}
{% set power = (states('sensor.pv_power')| float) + 0.1 %}
{% endif %} {% if force_discharge_power > power * 0.95
and force_discharge_power < power * 1.05 %}
{% set power = force_discharge_power %}
{% endif %} {{ power }}
target:
entity_id: number.force_discharge_power
- choose:
- conditions:
- condition: numeric_state
entity_id: sensor.battery_soc
above: 97
sequence:
- action: number.set_value
metadata: {}
data:
value: >-
{% set force_charge_power =
states('number.force_charge_power') | float %} {% set
power = max(0.5 - (states('sensor.pv_power')| float),
(0.5 / 2)) %} {{ power }}
target:
entity_id: number.force_charge_power
- conditions:
- condition: numeric_state
entity_id: sensor.battery_soc
above: 95
sequence:
- action: number.set_value
metadata: {}
data:
value: >-
{% set force_charge_power =
states('number.force_charge_power') | float %} {% set
power = max(0.7 - (states('sensor.pv_power')| float),
(0.7 / 2)) %} {{ power }}
target:
entity_id: number.force_charge_power
- conditions:
- condition: numeric_state
entity_id: sensor.battery_soc
above: 94
sequence:
- action: number.set_value
metadata: {}
data:
value: >-
{% set force_charge_power =
states('number.force_charge_power') | float %} {% set
power = max(1 - (states('sensor.pv_power')| float), (1 /
2)) %} {% if force_charge_power > power * 0.95 and
force_charge_power < power * 1.05 %}
{% set power = force_charge_power %}
{% endif %} {{ power }}
target:
entity_id: number.force_charge_power
- conditions:
- condition: numeric_state
entity_id: sensor.battery_soc
above: 93
sequence:
- action: number.set_value
metadata: {}
data:
value: >-
{% set force_charge_power =
states('number.force_charge_power') | float %} {% set
power = max(1.25 - (states('sensor.pv_power')| float),
(1.25 / 2)) %} {% if force_charge_power > power * 0.95
and force_charge_power < power * 1.05 %}
{% set power = force_charge_power %}
{% endif %} {{ power }}
target:
entity_id: number.force_charge_power
- conditions:
- condition: numeric_state
entity_id: sensor.battery_soc
above: 92
sequence:
- action: number.set_value
metadata: {}
data:
value: >-
{% set force_charge_power =
states('number.force_charge_power') | float %} {% set
power = max(1.5- (states('sensor.pv_power')| float),
(1.5 / 2)) %} {% if force_charge_power > power * 0.95
and force_charge_power < power * 1.05 %}
{% set power = force_charge_power %}
{% endif %} {{ power }}
target:
entity_id: number.force_charge_power
- conditions:
- condition: numeric_state
entity_id: sensor.battery_soc
above: 91
sequence:
- action: number.set_value
metadata: {}
data:
value: >-
{% set force_charge_power =
states('number.force_charge_power') | float %} {% set
power = max(1.75 - (states('sensor.pv_power')| float),
(1.75 / 2)) %} {% if force_charge_power > power * 0.95
and force_charge_power < power * 1.05 %}
{% set power = force_charge_power %}
{% endif %} {{ power }}
target:
entity_id: number.force_charge_power
- conditions:
- condition: numeric_state
entity_id: sensor.battery_soc
above: 90
sequence:
- action: number.set_value
metadata: {}
data:
value: >-
{% set force_charge_power =
states('number.force_charge_power') | float %} {% set
power = max(2 - (states('sensor.pv_power')| float), (2 /
2)) %} {% if force_charge_power > power * 0.95 and
force_charge_power < power * 1.05 %}
{% set power = force_charge_power %}
{% endif %} {{ power }}
target:
entity_id: number.force_charge_power
- conditions:
- condition: time
after: "06:30:00"
- condition: state
entity_id: automation.foxess_calibrate_set_top_soc_reached
state: "on"
sequence:
- if:
- condition: state
entity_id: select.work_mode
state: Force Charge
then:
- action: number.set_value
metadata: {}
data:
value: >-
{% set force_charge_power =
states('number.force_charge_power') | float %} {% set power
= states('sensor.foxess_charge_speed_for_7am') | float |
round(2) %} {% if power < 0.1 %}
{# We have reached the target SoC, and we are close in time to the end, so stay at this level #}
{% set power = 0.1 %}
{% elif force_charge_power > power * 0.95 and
force_charge_power < power * 1.05 %}
{% set power = force_charge_power %}
{% endif %} {{ power }}
target:
entity_id: number.force_charge_power
else:
- action: number.set_value
metadata: {}
data:
value: >-
{% set force_discharge_power =
states('number.force_discharge_power') | float %} {% set
charge_speed_for_7am =
states('sensor.foxess_charge_speed_for_7am') | float %} {%
if charge_speed_for_7am > 0 %}
{% set power = states('sensor.FoxESS_overnight_discharge_rate_kW') | float(4) | round(2) %}
{% else %}
{# We have reached the target SoC, and we are close in time to the end, so stay at this level #}
{% set power = (states('sensor.pv_power')| float) + 0.1 %}
{% endif %} {% if force_discharge_power > power * 0.95
and force_discharge_power < power * 1.05 %}
{% set power = force_discharge_power %}
{% endif %} {{ power }}
target:
entity_id: number.force_discharge_power
- conditions:
- condition: time
after: "00:02:00"
before: "06:57:59"
sequence:
- if:
- condition: state
entity_id: select.work_mode
state: Force Charge
then:
- action: number.set_value
metadata: {}
data:
value: >-
{% set charge_speed_for_7am =
states('sensor.foxess_charge_speed_for_7am') | float %} {%
set overnight_charge_rate_kW =
states('sensor.FoxESS_overnight_charge_rate_kW') | float %}
{% set force_charge_power =
(states('number.force_charge_power') | float) %} {% set
new_charge_power = max(overnight_charge_rate_kW,
charge_speed_for_7am)| round(2) %} {% if (new_charge_power
<= (force_charge_power * 1.05)) and (new_charge_power >=
(force_charge_power * 0.95)) %}
{# If we are already close to the force charge power, don't change it #}
{% set new_charge_power = force_charge_power %}
{% endif %} {{ new_charge_power }}
target:
entity_id: number.force_charge_power
else:
- action: number.set_value
metadata: {}
data:
value: >-
{% set force_discharge_power =
states('number.force_discharge_power') | float %} {% set
power = states('sensor.FoxESS_overnight_discharge_rate_kW')
| float(4) | round(2) %} {% if force_discharge_power > power
* 0.95 and force_discharge_power < power * 1.05 %}
{% set power = force_discharge_power %}
{% endif %} {{ power }}
target:
entity_id: number.force_discharge_power
mode: single
Code: Select all
alias: FoxESS_does_bottom_calibrate_need_to_run_today
description: >-
Check whether bottom calibration needs to be run for the day. This will
happen if calibration has not happened for more than a certain number of days,
or if the normal bottom_soc is set to the extremes which will happen if the
solar forecast is particularly low. It is recommended that this be done on a
monthly schedule. Set the trigger to run at the start of the cheap import
period.
triggers:
- trigger: time_pattern
hours: "15"
minutes: "55"
seconds: "55"
- trigger: time_pattern
hours: "16"
minutes: "55"
seconds: "55"
- trigger: state
entity_id:
- select.work_mode
from: Feed-in First
to: Force Discharge
- trigger: state
entity_id:
- select.work_mode
to: Force Discharge
from: Self Use
conditions:
- condition: state
entity_id: input_boolean.foxess_calibrate_bottom_reached
state: "on"
- condition: time
after: "15:00:00"
before: "20:00:00"
- condition: or
conditions:
- condition: template
value_template: >-
{{
(states('input_datetime.foxess_calibrate_bottom_reached_date')|as_datetime).date()
<=
(now()-timedelta(days=(states('input_number.foxess_calibrate_days_between_bottom_calibration')
| int))).date() }}
- condition: numeric_state
entity_id: input_number.foxess_daytime_discharge_bottom_soc
below: 12
- condition: template
value_template: >-
{{
(states('sensor.foxess_highest_vs_forecast_today_spread')|float(100)
<= 35) }}
- condition: template
value_template: >-
{{
(states('sensor.foxess_highest_vs_forecast_today_spread')|float(100)
<= 40) and
(states('sensor.foxess_highest_vs_forecast_today_spread')|float(100)
<=
states('sensor.foxess_highest_vs_forecast_tomorrow_spread')|float(100))
}}
- condition: and
conditions:
- condition: or
conditions:
- condition: template
value_template: >-
{{
(states('input_datetime.foxess_calibrate_top_reached_date')|as_datetime).date()
>= (now()-timedelta(days=1)).date() }}
- condition: numeric_state
entity_id: input_number.foxess_daytime_discharge_bottom_soc
below: 14
- condition: template
value_template: >-
{{
(states('sensor.foxess_highest_vs_forecast_today_spread')|float(100)
<= 40) }}
- condition: template
value_template: >-
{{
(states('sensor.foxess_highest_vs_forecast_today_spread')|float(100)
<= 50) and
(states('sensor.foxess_highest_vs_forecast_today_spread')|float(100)
<=
states('sensor.foxess_highest_vs_forecast_tomorrow_spread')|float(100))
}}
- condition: template
value_template: >-
{{
(states('input_datetime.foxess_calibrate_bottom_reached_date')|as_datetime).date()
<=
(now()-timedelta(days=(states('input_number.foxess_calibrate_days_between_bottom_calibration')
| int - 6))).date() }}
- condition: numeric_state
entity_id: sensor.battery_soc
above: sensor.min_soc_on_grid
actions:
- action: input_boolean.turn_off
metadata: {}
data: {}
target:
entity_id: input_boolean.foxess_calibrate_bottom_reached
- action: input_boolean.turn_on
metadata: {}
data: {}
target:
entity_id: input_boolean.foxess_calibrate_today
- action: input_number.set_value
metadata: {}
data:
value: 1
target:
entity_id: input_number.foxess_overnight_calibration_modifier
mode: single
Code: Select all
alias: FoxESS_does_top_calibrate_need_to_run_today
description: >-
Check whether either top calibration needs to be run for the day. This will
happen if calibration has not happened for more than a certain number of days,
or if the normal top_soc is set to the max, which will happen if the solar
forecast is particularly low). It is recommended that these be done on a
weekly schedule. Set the trigger to run at the start of the cheap import
period.
triggers:
- trigger: time_pattern
hours: "0"
minutes: "0"
seconds: "43"
conditions:
- condition: or
conditions:
- condition: template
value_template: >-
{{ states('sensor.foxess_highest_vs_forecast_today_spread')|float(100)
<= 35 }}
- condition: template
value_template: >-
{{
(states('input_datetime.foxess_calibrate_top_reached_date')|as_datetime).date()
<=
(now()-timedelta(days=(states('input_number.foxess_calibrate_days_between_top_calibration')
| int))).date() }}
- condition: numeric_state
entity_id: input_number.foxess_overnight_charge_top_soc
above: 95
actions:
- if:
- condition: or
conditions:
- condition: template
value_template: >-
{{
states('sensor.foxess_highest_vs_forecast_today_spread')|float(100)
<= 35 }}
- condition: numeric_state
entity_id: input_number.foxess_overnight_charge_top_soc
above: 95
- condition: template
value_template: >-
{{
(states('input_datetime.foxess_calibrate_top_reached_date')|as_datetime).date()
<=
(now()-timedelta(days=(states('input_number.foxess_calibrate_days_between_top_calibration')
| int))).date() }}
then:
- action: input_boolean.turn_off
metadata: {}
data: {}
target:
entity_id: input_boolean.foxess_calibrate_top_reached
- action: input_number.set_value
metadata: {}
data:
value: 1.2
target:
entity_id: input_number.foxess_overnight_calibration_modifier
enabled: true
else:
- action: input_boolean.turn_on
metadata: {}
data: {}
target:
entity_id: input_boolean.foxess_calibrate_top_reached
- action: input_number.set_value
metadata: {}
data:
value: 1
target:
entity_id: input_number.foxess_overnight_calibration_modifier
enabled: true
- if:
- condition: or
conditions:
- condition: state
entity_id: input_boolean.foxess_calibrate_top_reached
state: "off"
- condition: state
entity_id: input_boolean.foxess_calibrate_bottom_reached
state: "off"
then:
- action: input_boolean.turn_on
metadata: {}
data: {}
target:
entity_id: input_boolean.foxess_calibrate_today
else:
- action: input_boolean.turn_off
metadata: {}
data: {}
target:
entity_id: input_boolean.foxess_calibrate_today
- action: input_number.set_value
metadata: {}
data:
value: 1
target:
entity_id: input_number.foxess_overnight_calibration_modifier
enabled: true
mode: single
Code: Select all
alias: FoxESS Force charge overnight
description: ""
triggers:
- trigger: state
entity_id:
- input_boolean.eon_next_drive_cheap_period
to: "on"
- trigger: state
entity_id:
- sensor.battery_soc
- trigger: time
at: "00:00:05"
- trigger: time
at: "00:02:05"
- trigger: time
at: "00:20:05"
- trigger: time
at: "00:40:05"
- trigger: time
at: "01:00:05"
- trigger: time
at: "01:20:05"
- trigger: time
at: "01:40:05"
- trigger: time
at: "02:00:05"
- trigger: time
at: "02:20:05"
- trigger: time
at: "02:40:05"
- trigger: time
at: "03:00:05"
- trigger: time
at: "03:10:05"
- trigger: time
at: "03:20:05"
- trigger: time
at: "03:30:05"
- trigger: time
at: "03:40:05"
- trigger: time
at: "03:50:05"
- trigger: time
at: "04:00:05"
- trigger: time
at: "04:06:05"
- trigger: time
at: "04:12:05"
- trigger: time
at: "04:18:05"
- trigger: time
at: "04:24:05"
- trigger: time
at: "04:30:05"
- trigger: time
at: "04:36:05"
- trigger: time
at: "04:42:05"
- trigger: time
at: "04:48:05"
- trigger: time
at: "04:54:05"
- trigger: time
at: "05:00:05"
- trigger: time
at: "05:06:05"
- trigger: time
at: "05:12:05"
- trigger: time
at: "05:18:05"
- trigger: time
at: "05:24:05"
- trigger: time
at: "05:30:05"
- trigger: time
at: "05:36:05"
- trigger: time
at: "05:42:05"
- trigger: time
at: "05:48:05"
- trigger: time
at: "05:54:05"
- trigger: time
at: "06:00:05"
- trigger: time
at: "06:03:05"
- trigger: time
at: "06:06:05"
- trigger: time
at: "06:09:05"
- trigger: time
at: "06:12:05"
- trigger: time
at: "06:15:05"
- trigger: time
at: "06:18:05"
- trigger: time
at: "06:21:05"
- trigger: time
at: "06:24:05"
- trigger: time
at: "06:27:05"
- trigger: time
at: "06:30:05"
- trigger: time
at: "06:33:05"
- trigger: time
at: "06:36:05"
- trigger: time
at: "06:39:05"
- trigger: time
at: "06:42:05"
- trigger: time
at: "06:45:05"
- trigger: time
at: "06:48:05"
- trigger: time
at: "06:51:05"
- trigger: time
at: "06:54:05"
- trigger: time
at: "06:57:05"
- trigger: time
at: "06:58:05"
conditions:
- condition: state
entity_id: input_boolean.eon_next_drive_cheap_period
state: "on"
- condition: time
after: "00:00:00"
before: "06:58:00"
- condition: or
conditions:
- condition: numeric_state
entity_id: sensor.battery_soc
below: 55
- condition: and
conditions:
- condition: template
value_template: >-
{% if states('input_boolean.foxess_calibrate_top_reached') | bool
%}
{% set perc_time = (((now().strftime("%H") | int) * 60 + (now().strftime("%M") | int) )) / (6 * 60 + 55) %}
{% set top_soc = states('input_number.foxess_overnight_charge_top_soc') | float %}
{% else %}
{% set perc_time = (((now().strftime("%H") | int) * 60 + (now().strftime("%M") | int) )) / (6 * 60 + 20) %}
{% set top_soc = states('input_number.foxess_calibrate_top_soc') | float %}
{% endif %} {{ ((states('sensor.foxess_charge_speed_for_7am') |
float) >= (states('number.force_charge_power') | float * perc_time
* 0.9)) and ((states('sensor.battery_soc') | float) < (top_soc *
perc_time)) }}
- condition: numeric_state
entity_id: sensor.foxess_charge_speed_for_7am
above: 0
- condition: time
before: "06:00:00"
- condition: and
conditions:
- condition: template
value_template: >-
{{ (states('sensor.foxess_charge_speed_for_7am') | float) >=
(states('number.force_charge_power') | float * 0.95) }}
- condition: numeric_state
entity_id: sensor.foxess_charge_speed_for_7am
above: 0
- condition: or
conditions:
- condition: and
conditions:
- condition: state
entity_id: select.work_mode
state: Force Discharge
- condition: template
value_template: >-
{{ (states('sensor.foxess_charge_speed_for_7am') | float) >=
(states('number.force_charge_power') | float * 0.93) }}
- condition: numeric_state
entity_id: sensor.foxess_charge_speed_for_7am
above: 0
- condition: state
entity_id: select.work_mode
state: Force Discharge
for:
hours: 0
minutes: 25
seconds: 0
- condition: and
conditions:
- condition: state
entity_id: select.work_mode
state: Force Discharge
for:
hours: 0
minutes: 5
seconds: 0
- condition: template
value_template: >-
{{ (states('sensor.load_power') | float) >=
(states('number.force_discharge_power') | float * 0.5) }}
- condition: state
entity_id: select.work_mode
state: Self Use
- condition: state
entity_id: select.work_mode
state: Feed-in First
- condition: state
entity_id: select.work_mode
state: Back-up
- condition: state
entity_id: select.work_mode
state: unavailable
- condition: state
entity_id: select.work_mode
state: unknown
- condition: and
conditions:
- condition: state
entity_id: select.work_mode
state: Force Discharge
- condition: numeric_state
entity_id: sensor.battery_soc
below: 25
actions:
- choose:
- conditions:
- condition: or
conditions:
- condition: time
after: "00:00:01"
before: "00:01:59"
- condition: time
after: "06:58:00"
before: "07:00:00"
- condition: numeric_state
entity_id: sensor.foxess_charge_speed_for_7am
below: 0.1
sequence:
- action: number.set_value
metadata: {}
data:
value: "0.4"
target:
entity_id: number.force_charge_power
- action: select.select_option
metadata: {}
data:
option: Force Charge
target:
entity_id: select.work_mode
- if:
- condition: numeric_state
entity_id: sensor.foxess_charge_speed_for_7am
below: 0.1
then:
- action: input_text.set_value
metadata: {}
data:
value: Reached target SoC - minimal charge
target:
entity_id: input_text.foxess_schedule_mode
else:
- if:
- condition: time
after: "06:58:00"
before: "07:00:00"
then:
- action: input_text.set_value
metadata: {}
data:
value: Transition to day - minimal charge
target:
entity_id: input_text.foxess_schedule_mode
else:
- action: input_text.set_value
metadata: {}
data:
value: Transition to overnight - minimal charge
target:
entity_id: input_text.foxess_schedule_mode
- choose:
- conditions:
- condition: or
conditions:
- condition: time
after: "06:30:00"
before: "06:57:59"
- condition: template
value_template: >-
{{ (states('sensor.foxess_charge_speed_for_7am') | float) >=
(states('sensor.foxess_overnight_charge_rate_kw') | float |
round(2)) }}
sequence:
- if:
- condition: numeric_state
entity_id: sensor.battery_soc
below: 90
then:
- action: number.set_value
metadata: {}
data:
value: >-
{{ states('sensor.foxess_charge_speed_for_7am') | float |
round(2) }}
target:
entity_id: number.force_charge_power
- action: select.select_option
metadata: {}
data:
option: Force Charge
target:
entity_id: select.work_mode
- action: input_text.set_value
metadata: {}
data:
value: Final overnight charge
target:
entity_id: input_text.foxess_schedule_mode
- choose:
- conditions:
- condition: time
after: "00:02:00"
before: "06:29:59"
sequence:
- if:
- condition: numeric_state
entity_id: sensor.battery_soc
below: 90
then:
- action: number.set_value
metadata: {}
data:
value: >-
{{ states('sensor.foxess_overnight_charge_rate_kw') | float
| round(2) }}
target:
entity_id: number.force_charge_power
- action: select.select_option
metadata: {}
data:
option: Force Charge
target:
entity_id: select.work_mode
- action: input_text.set_value
metadata: {}
data:
value: Overnight charge
target:
entity_id: input_text.foxess_schedule_mode
- action: select.select_option
metadata: {}
data:
option: Force Charge
target:
entity_id: select.work_mode
- action: input_text.set_value
metadata: {}
data:
value: Overnight charge (default)
target:
entity_id: input_text.foxess_schedule_mode
mode: single
Code: Select all
alias: FoxESS Force Discharge Overnight
description: ""
triggers:
- trigger: state
entity_id:
- sensor.battery_soc
- trigger: state
entity_id:
- input_boolean.foxess_calibrate_top_reached
to: "on"
for:
hours: 0
minutes: 0
seconds: 5
- trigger: time
at: "01:40:00"
- trigger: time
at: "02:05:00"
- trigger: time
at: "02:30:00"
- trigger: time
at: "02:55:00"
- trigger: time
at: "03:20:00"
- trigger: time
at: "03:45:00"
- trigger: time
at: "04:10:00"
- trigger: time
at: "04:35:00"
- trigger: time
at: "05:00:00"
- trigger: time
at: "05:20:00"
- trigger: time
at: "05:30:00"
- trigger: time
at: "05:40:00"
- trigger: time
at: "05:50:00"
- trigger: time
at: "06:15:00"
- trigger: time
at: "06:35:00"
- trigger: time
at: "06:59:00"
conditions:
- condition: or
conditions:
- condition: and
conditions:
- condition: time
after: "00:02:00"
before: "06:30:00"
- condition: template
value_template: >-
{% if states('input_boolean.foxess_calibrate_top_reached') | bool
%}
{% set perc_time = (((now().strftime("%H") | int) * 60 + (now().strftime("%M") | int) )) / (6 * 60 + 55) %}
{% set top_soc = states('input_number.foxess_overnight_charge_top_soc') | float %}
{% else %}
{% set perc_time = (((now().strftime("%H") | int) * 60 + (now().strftime("%M") | int) )) / (6 * 60 + 20) %}
{% set top_soc = states('input_number.foxess_calibrate_top_soc') | float %}
{% endif %} {{ (states('sensor.foxess_battery_soc_exact') |
float) > (40 + ((top_soc-40) * perc_time * 0.6))}}
- condition: or
conditions:
- condition: and
conditions:
- condition: template
value_template: >-
{% if states('input_boolean.foxess_calibrate_top_reached')
| bool %}
{% set perc_time = (((now().strftime("%H") | int) * 60 + (now().strftime("%M") | int) )) / (6 * 60 + 55) %}
{% set top_soc = states('input_number.foxess_overnight_charge_top_soc') | float %}
{% else %}
{% set perc_time = (((now().strftime("%H") | int) * 60 + (now().strftime("%M") | int) )) / (6 * 60 + 20) %}
{% set top_soc = states('input_number.foxess_calibrate_top_soc') | float %}
{% endif %} {{
(((states('sensor.foxess_charge_speed_for_7am') | float)
<= (states('number.force_charge_power') | float *
perc_time * 0.9)) or ((states('sensor.battery_soc') |
float) > (top_soc * perc_time))) and
((states('sensor.foxess_charge_speed_for_7am') | float) <
(states('number.force_charge_power') | float * 0.9)) }}
- condition: numeric_state
entity_id: sensor.foxess_charge_speed_for_7am
above: 0
- condition: time
before: "06:30:00"
- condition: and
conditions:
- condition: template
value_template: >-
{% if states('input_boolean.foxess_calibrate_top_reached')
| bool %}
{% set top_soc = states('input_number.foxess_overnight_charge_top_soc') | float %}
{% else %}
{% set top_soc = states('input_number.foxess_calibrate_top_soc') | float %}
{% endif %} {{ (states('sensor.battery_soc') | float) >=
top_soc }}
- condition: or
conditions:
- condition: time
after: "06:59:00"
- condition: and
conditions:
- condition: time
before: "05:45:00"
- condition: or
conditions:
- condition: state
entity_id: select.work_mode
state: Force Charge
for:
hours: 0
minutes: 35
seconds: 0
- condition: numeric_state
entity_id: sensor.battery_soc
above: 99
- condition: and
conditions:
- condition: time
after: "05:45:00"
- condition: or
conditions:
- condition: state
entity_id: select.work_mode
state: Force Charge
for:
hours: 0
minutes: 20
seconds: 0
- condition: numeric_state
entity_id: sensor.battery_soc
above: 99
- condition: and
conditions:
- condition: state
entity_id: select.work_mode
state: Force Charge
- condition: template
value_template: >-
{% if states('input_boolean.foxess_calibrate_top_reached')
| bool %}
{% set top_soc = states('input_number.foxess_overnight_charge_top_soc') | float %}
{% else %}
{% set top_soc = states('input_number.foxess_calibrate_top_soc') | float %}
{% endif %} {{
(states('sensor.battery_soc') | float) >= top_soc }}
- condition: time
before: "06:30:00"
- condition: and
conditions:
- condition: time
after: "06:57:59"
before: "07:00:00"
- condition: or
conditions:
- condition: and
conditions:
- condition: time
after: "06:00:00"
- condition: template
value_template: >-
{{ ((states('sensor.foxess_charge_speed_for_7am') | float) <
(states('number.force_charge_power') | float * 0.75)) and
(states('sensor.foxess_overnight_discharge_rate_kw')|float >
(states('sensor.load_power')|float * 2.0)) }}
- condition: and
conditions:
- condition: time
after: "06:30:00"
- condition: template
value_template: >-
{{ ((states('sensor.foxess_charge_speed_for_7am') | float) <
(states('number.force_charge_power') | float * 0.65)) and
(states('sensor.foxess_overnight_discharge_rate_kw')|float >
(states('sensor.load_power')|float * 2.0)) }}
- condition: and
conditions:
- condition: time
after: "05:30:00"
- condition: template
value_template: >-
{{ ((states('sensor.foxess_charge_speed_for_7am') | float) <
(states('number.force_charge_power') | float * 0.80)) and
(states('sensor.foxess_overnight_discharge_rate_kw')|float >
(states('sensor.load_power')|float * 2.0)) }}
- condition: and
conditions:
- condition: time
before: "05:30:00"
- condition: template
value_template: >-
{{ ((states('sensor.foxess_charge_speed_for_7am') | float) <
(states('number.force_charge_power') | float * 0.85)) and
(states('sensor.foxess_overnight_discharge_rate_kw')|float >
(states('sensor.load_power')|float * 2.0)) }}
- condition: time
after: "06:59:00"
actions:
- action: select.select_option
metadata: {}
data:
option: Force Discharge
target:
entity_id: select.work_mode
- choose:
- conditions:
- condition: or
conditions:
- condition: time
after: "06:58:00"
- condition: template
value_template: >-
{% set target_soc =
states('input_number.foxess_calibrate_top_soc') | float %}
{% if states('input_boolean.foxess_calibrate_top_reached') |
bool %}
{% set target_soc = states('input_number.foxess_overnight_charge_top_soc') | float %}
{% endif %}
{{states('sensor.battery_soc') | float >= target_soc}}
sequence:
- action: number.set_value
metadata: {}
data:
value: "{{(states('sensor.pv_power')| float) + 0.4}}"
target:
entity_id: number.force_discharge_power
- if:
- condition: template
value_template: >-
{% set target_soc =
states('input_number.foxess_calibrate_top_soc') | float %}
{% if states('input_boolean.foxess_calibrate_top_reached') |
bool %}
{% set target_soc = states('input_number.foxess_overnight_charge_top_soc') | float %}
{% endif %}
{{states('sensor.battery_soc') | float >= target_soc}}
then:
- action: input_text.set_value
metadata: {}
data:
value: Target SoC reached - minimal export
target:
entity_id: input_text.foxess_schedule_mode
else:
- action: input_text.set_value
metadata: {}
data:
value: Transition to day - minimal export
target:
entity_id: input_text.foxess_schedule_mode
- choose:
- conditions:
- condition: time
before: "06:58:00"
sequence:
- action: number.set_value
metadata: {}
data:
value: >-
{{(states('sensor.foxess_overnight_discharge_rate_kw') | float |
round(2)) }}
target:
entity_id: number.force_discharge_power
- action: input_text.set_value
metadata: {}
data:
value: Overnight arbitrage export
target:
entity_id: input_text.foxess_schedule_mode
mode: single
/code]
Thanks a lot. Even with adjusting, it gets me off to a great start.