I have two EcoFlow DELTA Pro Ultra battery systems behind a Smart Home Panel 2. The useful part is not just seeing battery percentage in Home Assistant; it is making the battery follow the time-of-use (TOU) rate schedule without blindly charging to 100% every day.
My current schedule treats 16:00–21:00 as the expensive window. Home Assistant tracks both DELTA Pro Ultra arrays, the SHP2 load, and solar input. A small Python job combines those readings with an Open-Meteo forecast and adjusts the overnight grid-charge ceiling.
1. Start with the entities that matter
These are the entities I use for the two battery arrays and the SHP2:
sensor.delta_pro_ultra_1069_2_battery_level_socsensor.delta_pro_ultra__battery_level_soc sensor.smart_home_panel_2_battery_levelsensor.smart_home_panel_2_ac_out_powersensor.delta_pro_ultra_1069_2_solar_1_in_power
I check the live values from the Home Assistant REST API before changing an automation:
export HASS_URL="http://homeassistant.local:8123"
export HASS_TOKEN="<long-lived-access-token>"
curl -sS \
-H "Authorization: Bearer $HASS_TOKEN" \
-H "Content-Type: application/json" \
"$HASS_URL/api/states/sensor.smart_home_panel_2_battery_level" | jq
curl -sS \
-H "Authorization: Bearer $HASS_TOKEN" \
"$HASS_URL/api/states/sensor.delta_pro_ultra_1069_2_battery_level_soc" | jq
The token belongs in the shell environment or a protected secrets file. I do not put it in an automation, blog post, or command history.
2. Define the TOU and battery assumptions
My configuration uses a 24 kWh bank, 90% usable capacity, 92% charge efficiency, and 92% discharge efficiency. The peak window starts at 16:00, and the forecast model targets 11.5 kWh of load during that window. The relevant portion of config.json is:
{
"bank_kwh": 24.0,
"usable_fraction": 0.9,
"charge_eff": 0.92,
"discharge_eff": 0.92,
"peak_rate": 0.59,
"off_peak_rate": 0.26,
"tou_start_hour": 16,
"tou_end_hour": 21,
"tou_load_kwh_p50": 11.5,
"tou_load_kwh_p90": 14.8,
"grid_charge_kw_total": 1.8
}
The numbers are deliberately conservative. A battery percentage is not the same thing as energy delivered to the loads: charging and inverter losses have to be included.
3. Use the solar forecast to leave room for PV
The daily script reads the average SOC from both arrays, forecasts plane-of-array irradiance, and estimates how much solar energy should arrive before 16:00. It then calculates the grid-charge limit for the smaller solar-hybrid battery so overnight charging does not consume tomorrow’s solar headroom.
python3 /home/hermes/.hermes/scripts/solar/charge_plan.py
For a plan without changing anything, force plan mode:
SOLAR_MODE=plan \
python3 /home/hermes/.hermes/scripts/solar/charge_plan.py
On September 28, 2026, the real output showed an 85% average bank SOC, about 5.0 kWh of forecast PV before 16:00, and a recommended Battery 2 grid-charge limit of 53%. The same run estimated about $4.66 per day, or roughly $140 per 30-day month, in steady-state peak-window savings. The value changes with weather, load, and utility rates; it is not a guaranteed bill reduction.
4. Have Home Assistant perform the scheduled changes
I keep the schedule in Home Assistant rather than relying on a workstation being online. The active automations are:
- EcoFlow SHP2 grid threshold (auto) — changes the threshold used by the SHP2 mode logic.
- EcoFlow SHP2 switch to battery mode 4pm — enters battery mode at the start of the expensive window.
- EcoFlow Battery 2 complete charging 10am — finishes the morning charge cycle.
- EcoFlow Battery 3 top up 3pm — tops up before the peak window when required.
- EcoFlow SHP2 charging 9pm — starts the overnight charging window.
The Python job updates the threshold through the Home Assistant configuration API. It first reads the existing automation, changes only the numeric threshold, writes a timestamped JSON backup, and reads the automation back to verify the value. The configured automation ID is 1787983746425.
Before allowing the nightly job to write, I run its dry-run path:
SOLAR_MODE=nightly SOLAR_DRY_RUN=1 \
python3 /home/hermes/.hermes/scripts/solar/charge_plan.py
On the real system this produced a proposed change from 54% to 53% and confirmed DRY RUN — no change written. That is the check I want before putting a new forecast or rate configuration into production.
5. Add a pre-peak reality check
Forecasts are not measurements. After 13:00 I run the check mode, which integrates today’s solar-power history and compares the projected bank energy with the peak-window target:
SOLAR_MODE=check \
python3 /home/hermes/.hermes/scripts/solar/charge_plan.py
The current run reported 20.4 kWh in the 24 kWh bank, 5.1 kWh of solar already received, and no top-off required. If the projected bank is short, the output calculates whether the 15:00–16:00 top-off is sufficient or gives the latest safe grid-charge start time.
6. Keep the failure modes boring
- Use a minimum reserve instead of allowing the battery to discharge to zero.
- Cap the grid-charge threshold so a bad forecast cannot cause an unlimited charge cycle.
- Keep a local backup before writing an automation configuration.
- Do not make the battery control loop depend on a single unavailable sensor.
- Use a dry run and read-back verification for every automated configuration change.
The result is simple: Home Assistant handles the schedule, the DELTA Pro Ultra integration supplies the measurements, and the forecast script decides how much grid charging is justified. The battery is used for the expensive hours, while forecast solar is given first claim on the remaining capacity.