Thank you, I have a similar installation plus an ev charger. Will try to set this up and test it !
Itβs probably so specific to my particular situation that it will be of little value to anyone else.
Can you show us the other tabs for your energy monitoring please. Iβm intriqued
For energy monitoring I just have a screen full of multiple entity cards showing power, energy today, energy this month, and percentages of the total for each. The energy totals are from an Emporia Vue and a Shelly EM. I also have power flow and energy flow dashboards but I never look at them. Theyβre just the standard cards.
The more interesting dashboards (and automations) have to do with importing grid power. I monitor grid voltage and adjust how much power I am importing. This is necessary because our grid is incredibly fragile. Itβs very easy to cause the voltage to drop so much that it causes problems.
Hi, I have been working on something very similar to your system. Dynamic level of battery charging and dynamic charge rate so any charging is less stressfull on the battery.
This automation manages battery charging during night rate electricity periods by dynamically calculating the required charge power and target state-of-charge (SOC) based on the solar forecast and time remaining until day rate.
Key features:
- Uses Solcast solar forecasts to set different SOC targets (e.g. charge less if lots of solar is forecast for tomorrow, charge more if little is forecast).
- Adjusts force charge power dynamically to ensure the target SOC is reached just before the end of the cheap night rate.
- Switches inverter modes automatically between Force Charge and Feed-in First depending on the tarrif rates.
- Sends email notifications with detailed status, forecasts, SOC targets, and inverter readings at key moments (start, update, end, or target reached).
- Handles edge cases (e.g. backup mode, SOC already above target).
This is version v5 of the automation, de-personalised for sharing. Replace placeholders such as [email protected] and notify.your_email_service with your own details.
My system comprises of the following:-
FoxESS 5kWh Inverter
20.72kWh of Fox battery
17 Solar panels (9 North facing and 8 soth facing)
Modbus integration to HA from the inverter
Solcast solar forcast updated 4 times a day
EON EV charger electricity tarrif that gives 7 hours cheap electricity overnight (night rate is 6.7pKwh)
I receive SEG payments of 16.5pKwh for export
I charge my batteries at night and export all the solar I can during the day after any house use.
I use a calendar in HA to store the tarrif day and night rate times
alias: Battery Charging - Dynamic Night Rate Charge Power v5
description: >
Automates battery charging during night rate periods based on solar forecast
and current SOC, using next Day rate from calendar.get_events.
# βββββββββββββββββββββββββββββββββββββββ
# TRIGGERS - When should this automation run?
# βββββββββββββββββββββββββββββββββββββββ
triggers:
# Trigger when Solcast solar forecast is updated
- id: solcast_update
entity_id: sensor.solcast_pv_forecast_api_last_polled
trigger: state
# Trigger at the END of the Night rate (switch to Feed-in First mode)
- id: night_rate_end
entity_id: sensor.electricity_current_rate
from: Night rate
trigger: state
# Trigger at the START of the Night rate (start Force Charge mode)
- id: night_rate_start
entity_id: sensor.electricity_current_rate
to: Night rate
trigger: state
# Trigger if inverter unexpectedly enters "Back-up" mode (after 1 min)
- id: backup_work_mode
trigger: state
entity_id:
- select.foxinvertermodbus_work_mode
to: Back-up
for:
minutes: 1
# Trigger whenever Battery SOC changes
- id: soc_reached
trigger: state
entity_id: sensor.foxinvertermodbus_battery_soc
conditions: [] # No global conditions
# βββββββββββββββββββββββββββββββββββββββ
# ACTIONS - What should happen when triggered?
# βββββββββββββββββββββββββββββββββββββββ
actions:
# 1. Get calendar events for the next 2 days (to find next Day rate start)
- action: calendar.get_events
target:
entity_id: calendar.electricity_prices_daily
data:
start_date_time: "{{ now().isoformat() }}"
end_date_time: "{{ (now() + timedelta(days=2)).isoformat() }}"
response_variable: events_data
# 2. Define variables for SOC targets, charging power calculations, etc.
- variables:
# Mapping forecast ranges (kWh) β target SOC %
soc_levels:
- min: 0
max: 7
soc: 100
- min: 7.001
max: 11
soc: 96
- min: 11.001
max: 15
soc: 92
- min: 15.001
max: 18
soc: 88
- min: 18.001
max: 21
soc: 84
- min: 21.001
max: 25
soc: 80
- min: 25.001
max: 28
soc: 76
- min: 28.001
max: 30
soc: 72
- min: 30.001
max: 68
soc: 70
# Min/max allowed charge power (kW)
min_power_kw: 0.1
max_power_kw: 5
# Efficiency uplift factor (accounts for losses)
efficiency_uplift: 1.3
# Inputs from sensors
forecast_today: "{{ states('sensor.solcast_pv_forecast_forecast_today') | float(0) }}"
current_soc: "{{ states('sensor.foxinvertermodbus_battery_soc') | float(0) }}"
total_capacity_kwh: 20.72
now_time: "{{ now() }}"
# Desired SOC (Force Charge mode)
desired_soc: >
{% set forecast = forecast_today %}
{% set soc = namespace(val=100) %}
{% for level in soc_levels %}
{% if forecast >= level.min and forecast <= level.max %}
{% set soc.val = level.soc %}
{% endif %}
{% endfor %}
{{ soc.val }}
# Desired SOC (after Night rate ends / normal use)
desired_soc_self_use: >
{% set forecast = forecast_today %}
{% set soc = namespace(val=100) %}
{% for level in soc_levels %}
{% if forecast >= level.min and forecast <= level.max %}
{% set soc.val = level.soc %}
{% endif %}
{% endfor %}
{{ soc.val }}
# Next "Day rate" time (from calendar) or default 06:01
target_time: >
{% set cal_events = events_data['calendar.electricity_prices_daily'].events %}
{% set matches = cal_events | selectattr('summary', 'search', '(?i)^Day rate') | list %}
{% set next_event = matches[0] if matches else None %}
{% if next_event %}
{{ as_datetime(next_event.start) }}
{% else %}
{{ now().replace(hour=6, minute=1, second=0, microsecond=0) }}
{% endif %}
# Ensure target time is always in the future
adjusted_target_time: >
{% set now_dt = now() %}
{% set target_dt = as_datetime(target_time) %}
{% if now_dt > target_dt %}
{{ target_dt + timedelta(days=1) }}
{% else %}
{{ target_dt }}
{% endif %}
# Time remaining (hours) until target
time_remaining_hours: >
{% set now_dt = now() %}
{% set adjusted = as_datetime(adjusted_target_time) %}
{{ ((adjusted - now_dt).total_seconds() / 3600) | round(2) }}
# SOC difference to reach
soc_delta: "{{ [desired_soc - current_soc, 0] | max }}"
# Energy needed (kWh) to reach desired SOC
energy_needed_kwh: "{{ ((soc_delta / 100) * total_capacity_kwh) | round(2) }}"
# Required average charge power (kW) within time remaining
required_power_kw: |
{% if time_remaining_hours > 0 %}
{% set raw_power = ((energy_needed_kwh * efficiency_uplift) / time_remaining_hours) | round(2) %}
{% else %}
{% set raw_power = 0 %}
{% endif %}
{{ [max_power_kw, [min_power_kw, raw_power] | max] | min }}
# 3. Email configuration (replace with your own notify service)
- variables:
email_target: [email protected]
email_action: notify.your_email_service
email_message: >-
Timestamp (UK): {{ now().strftime('%d/%m/%Y %H:%M:%S') }}<br><br>
Solcast forecast: {{ forecast_today }} kWh<br>
Current SOC: {{ current_soc }}%<br>
Desired SOC for Force Charge: {{ desired_soc }}%<br>
Desired SOC after Night (Feed-in First): {{ desired_soc_self_use }}%<br>
Inverter Work Mode: {{ states('select.foxinvertermodbus_work_mode') }}<br>
Battery Temperature: {{ states('sensor.foxinvertermodbus_battery_temp') }}Β°C<br>
BMS Cell mV Low: {{ states('sensor.foxinvertermodbus_bms_cell_mv_low') }}mV<br>
BMS Cell mV High: {{ states('sensor.foxinvertermodbus_bms_cell_mv_high') }}mV<br>
Battery Voltage: {{ states('sensor.foxinvertermodbus_batvolt') }}V<br>
Battery Current: {{ states('sensor.foxinvertermodbus_bat_current') }}A<br>
Max Charge Current: {{ states('sensor.foxinvertermodbus_max_charge_current') }}A<br>
Max Discharge Current: {{ states('sensor.foxinvertermodbus_max_discharge_current') }}A<br>
Charge Power: {{ states('sensor.foxinvertermodbus_battery_charge') }}W<br>
Discharge Power: {{ states('sensor.foxinvertermodbus_battery_discharge') }}W<br><br>
Total capacity: {{ total_capacity_kwh }} kWh<br>
Target time: {{ as_datetime(target_time).strftime('%A, %d %B %Y %H:%M:%S') }}<br>
Adjusted target time: {{ as_datetime(adjusted_target_time).strftime('%A, %d %B %Y %H:%M:%S') }}<br>
Time remaining hours: {{ time_remaining_hours }}<br>
SOC delta: {{ soc_delta }}%<br>
Energy needed: {{ energy_needed_kwh }} kWh<br>
Required power: {{ required_power_kw }} kW<br>
Current Force Charge Power: {{ states('number.foxinvertermodbus_force_charge_power') | float }} kW<br>
Min Power Limit: {{ min_power_kw }} kW<br>
Max Power Limit: {{ max_power_kw }} kW<br>
Efficiency uplift factor: {{ efficiency_uplift }}<br>
email_title_start: >
Battery Force STARTED Charging to {{ desired_soc }}% at {{ required_power_kw }}kW
email_title_solcast_update: >
Battery Max SOC changed to {{ desired_soc_self_use }}% at {{ required_power_kw }}kW
email_title_end: >
Battery Max SOC changed to {{ desired_soc_self_use }}% at {{ required_power_kw }}kW
# 4. Choose block - actions depend on the trigger
- choose:
# CASE A: Night rate starts β begin Force Charge
- conditions:
- condition: trigger
id: night_rate_start
- condition: state
entity_id: sensor.electricity_current_rate
state: Night rate
sequence:
- action: number.set_value
target:
entity_id: number.foxinvertermodbus_max_soc
data:
value: "{{ current_soc }}" # Prevents instant stop
- delay: 5
- action: select.select_option
target:
entity_id: select.foxinvertermodbus_work_mode
data:
option: Force Charge
- action: "{{ email_action }}"
data:
title: "{{ email_title_start }}"
message: "{{ email_message }}"
target: "{{ email_target }}"
# CASE B: Solcast update or backup mode β update force charge parameters
- conditions:
- condition: trigger
id:
- solcast_update
- backup_work_mode
- condition: state
entity_id: sensor.electricity_current_rate
state: Night rate
- condition: template
value_template: "{{ required_power_kw > 0 }}"
sequence:
- action: number.set_value
target:
entity_id: number.foxinvertermodbus_force_charge_power
data:
value: "{{ required_power_kw }}"
- delay: 5
- action: number.set_value
target:
entity_id: number.foxinvertermodbus_max_soc
data:
value: "{{ desired_soc }}"
- action: select.select_option
target:
entity_id: select.foxinvertermodbus_work_mode
data:
option: Force Charge
- action: "{{ email_action }}"
data:
title: "{{ email_title_solcast_update }}"
message: "{{ email_message }}"
target: "{{ email_target }}"
# CASE C: Night rate ends β switch back to Feed-in First
- conditions:
- condition: trigger
id: night_rate_end
sequence:
- action: select.select_option
target:
entity_id: select.foxinvertermodbus_work_mode
data:
option: Feed-in First
- action: "{{ email_action }}"
data:
title: "{{ email_title_end }}"
message: "{{ email_message }}"
target: "{{ email_target }}"
# CASE D: SOC reaches the desired target
- conditions:
- condition: trigger
id: soc_reached
- condition: template
value_template: >
{% set prev = trigger.from_state.state | int(0) %}
{% set new = trigger.to_state.state | int(0) %}
{{ new == desired_soc | int(0) and prev == (desired_soc | int(0) - 1) }}
sequence:
- action: "{{ email_action }}"
data:
title: Battery SOC reached {{ desired_soc }}%
message: "{{ email_message }}"
target: "{{ email_target }}"
# DEFAULT CASE: If desired SOC < current SOC β no charging required
default:
- condition: template
value_template: "{{ desired_soc < current_soc }}"
- action: "{{ email_action }}"
data:
title: Desired SOC less than current SOC
message: "{{ email_message }}"
target: "{{ email_target }}"
# βββββββββββββββββββββββββββββββββββββββ
# MODE - Prevent overlapping runs
# βββββββββββββββββββββββββββββββββββββββ
mode: single
