Your Powerwall is full at midnight. Why isn’t it full at 16:00?
If you’re on Octopus Flux with solar, you’ve probably seen this: the battery charges overnight, solar does its thing during the day, and yet by the time the peak export window opens at 16:00 the battery is sitting at 73% — or worse, it hit 100% at 10am and you’ve been curtailing solar ever since.
The problem is the overnight charge target. Too high and you waste money charging energy solar would have provided for free. Too low and you arrive at the export window short. The right answer changes every single day based on tomorrow’s forecast — and a fixed target gets it wrong more often than not.
This guide documents a Home Assistant system that solves this automatically. It fetches tomorrow’s solar forecast from two independent APIs, calibrates them against what actually happened today, calculates tonight’s charge target, charges to exactly that level during the 02:00–05:00 cheap rate window, and then learns from the outcome to do better tomorrow. After a few weeks it knows your roof better than either API does.
It’s Part 2 of 2. Part 1 covers the ILS Glide Slope system that manages the export window itself. Together they run the full 24-hour cycle without manual intervention.
ILS approach to solar/powerwall charging deep dive
A 24-Hour Battery Management System
Solar Forecasting, Headroom Calibration and Intelligent Overnight Charging
Tesla Powerwall | Octopus Flux | Home Assistant
February 2026 | Part 2 of 2
Overview
This is the second of two guides documenting a complete Home Assistant automation system for Tesla Powerwall optimisation on the Octopus Flux tariff. The first guide covered the ILS Glide Slope approach to export timing during the 16:00–19:00 peak window. This guide covers everything that happens in the other 21 hours — the system that decides how much to charge overnight, why that decision changes every day, and how it learns to get better over time.
The two systems are designed to work together as a single closed loop. The solar forecasting engine feeds the overnight charging decision. The overnight charging decision determines how much battery is available at 16:00. The export system then optimises discharge within that window. The result is a system that plans forward 24 hours continuously, not just a set of independent automations firing at fixed times.
The 24-hour loop at a glance
23:00 Solcast + Forecast.Solar APIs updated with latest forecast
01:00 APIs called again — freshest data before charging decision
01:35 Forecast consolidated, calibrated, charge target calculated
02:00 Night charging begins — grid charges battery to calculated target
05:00 Charging ends — Powerwall returns to self_consumption
05:01 EV protection check — holds battery if EV still charging
05:00–16:00 Solar generation fills remaining headroom
15:59 ILS pre-flight check
16:00–19:00 ILS export window (see Part 1)
16:15 Learning automation checks battery level, adjusts headroom factor
1. The Core Problem: How Much to Charge?
The Goldilocks problem
Every night on Octopus Flux you face the same question: how much should the Powerwall charge from the grid at 16.40p/kWh during the 02:00–05:00 super off-peak window?
- Charge too much: battery hits 100% before 16:00, solar generation after that point is curtailed, and you paid for grid energy you didn’t need.
- Charge too little: battery is only partially charged at 16:00, limiting how much you can export during the peak window and reducing revenue.
- Charge exactly right: battery arrives at 16:00 at 95–100%, having topped up whatever solar didn’t provide, maximising the energy available for peak export.
The correct overnight charge target changes every day based on tomorrow’s solar forecast. A forecast of 15 kWh on a sunny day means the battery will fill itself via solar — charge less overnight. A forecast of 2 kWh on a winter overcast day means the battery will barely move — charge more overnight.
Why a fixed charge target doesn’t work
The naive approach — always charge to 80%, or always charge to 100% — fails at both extremes. Charging to 100% every night wastes money on clear forecast days. Charging to a fixed lower target frequently leaves the battery short on bad solar days.
What’s needed is a dynamic target that reads tomorrow’s forecast each night and calculates exactly how much the solar is expected to contribute, charging only the deficit from the grid.
2. Solar Forecasting
Why two APIs?
The system uses two independent solar forecast APIs: Solcast and Forecast.Solar. Neither is reliably accurate in all conditions — Solcast tends to be better calibrated for a specific installation but uses up a limited daily API quota; Forecast.Solar is less precise but has no hard quota restrictions and provides useful corroboration.
Running both and combining them produces a more robust estimate than either alone, and provides a fallback if one API fails or returns zero.
API call schedule
Solcast allows a limited number of API calls per day. The system makes three calls at optimised times:
| Time | Purpose |
|---|---|
| 01:00 | Primary fetch — freshest overnight forecast data before the charging decision |
| 01:30 | Backup fetch — retries if 01:00 call failed |
| 23:00 | Evening update — captures any forecast revisions for the following day |
Forecast.Solar follows the same timing pattern (01:00:30, 01:30:30, 23:00:30 — offset by 30 seconds to avoid simultaneous API calls).
Forecast blending and weighting
At 01:35, the consolidated forecast automation combines the two API outputs into a single best estimate. The weighting adapts based on how many days of calibration data each API has accumulated:
if both APIs have < 3 days calibration: 50/50 average
if Solcast has < 3 days: 70% Forecast.Solar, 30% Solcast
if Forecast.Solar has < 3 days: 70% Solcast, 30% Forecast.Solar
if both have >= 3 days: 60% Solcast, 40% Forecast.Solar
Both raw API values are passed through individual calibration factors before blending. A safety cap of 20 kWh prevents unrealistic outlier forecasts from corrupting the charge target.
Per-API calibration
Each API maintains its own calibration factor, updated daily by comparing yesterday’s forecast against actual generation. Over time, each API’s output is corrected for systematic bias — if Solcast consistently over-forecasts by 15% for this installation, its factor drifts below 1.0 to compensate.
3. The Charge Target Calculation
The formula
Once the best estimate forecast is available, the overnight charge target is calculated by a template sensor read at 02:00 when charging starts:
surplus = forecast_kWh - expected_consumption_kWh
adjusted_surplus = surplus * headroom_calibration_factor
empty_percent = (adjusted_surplus / 13.5) * 100
charge_target = 100 - empty_percent
charge_target = clamp(charge_target, 10%, 92%)
In plain terms: if the forecast predicts 8 kWh of solar and expected consumption is 3.2 kWh, there is a 4.8 kWh surplus. That surplus should fill 35.5% of the 13.5 kWh battery via solar during the day. So the overnight target is 100 - 35.5 = 64.5%, rounded and clamped.
The headroom calibration factor
The headroom_calibration_factor is the key variable that makes the system adaptive. It starts at 1.0 (trust the forecast exactly) and is adjusted daily based on what actually happened:
| Battery at 16:00 | Conclusion | Adjustment |
|---|---|---|
| < 90% | Over-estimated solar surplus — charged too little overnight | DECREASE factor (charge more next time) |
| 90–100% | Optimal | No change |
| Hit 100% early | Under-estimated surplus — could have charged less | INCREASE factor (charge less next time) |
Adaptive learning rate
The magnitude of each adjustment depends on days of data accumulated. Early corrections are larger; later corrections are smaller — preventing overcorrection once the factor has converged:
# Decrease (battery too low at 16:00)
learning_rate = 0.20 if days < 7 else 0.12 if days < 30 else 0.06
adjustment = (error_from_target / 100) * learning_rate * 2
# Increase (battery hit 100% early)
learning_rate = 0.12 if days < 7 else 0.06 if days < 30 else 0.03
The factor is bounded between 0.3 and 2.0.
Day-type consumption estimates
Expected household consumption differs significantly between weekdays and weekends. The system maintains separate consumption figures for weekday, Saturday, and Sunday — used both in the charge target calculation and the ILS export intercept calculation (see Part 1).
4. Overnight Charging
The charging window: 02:00–05:00
The Octopus Flux super off-peak rate runs from 02:00 to 05:00 at 16.40p/kWh. The system charges exclusively within this window — charging at any other time imports at the standard off-peak rate (27.33p/kWh), eliminating most of the arbitrage margin.
Three hours at a sustained 3.3 kW charge rate delivers approximately 9.9 kWh — enough to take a depleted battery to around 73%. On days with a very low solar forecast requiring a near-full overnight charge, the full three hours may be needed.
How charging works
The system uses backup mode rather than self_consumption for overnight charging. In backup mode, the Powerwall treats the backup reserve as a hard floor and charges aggressively to reach it — producing a reliable, sustained 3.3 kW charge rate.
- At 02:00: read the calculated charge target from
sensor.solar_headroom_charge_target - Switch to backup mode, set backup reserve to the target percentage
- Enable grid charging
- Monitor every 20 minutes — log battery level, confirm settings
- Instant shutoff trigger: when battery >= target, disable grid charging immediately
- At 05:00: disable grid charging, reset reserve to normal level, switch to self_consumption
Manual override
A manual override boolean allows the calculated target to be bypassed when you know the forecast is wrong. When active, the system charges to a manually specified target instead. All overrides are logged — you can track when they were used and whether they were the right call.
EV charging coordination
If an EV is still charging after 05:00, the EV-Powerwall Protection automation activates at 05:01. It locks the backup reserve at the current battery level (or the overnight charge target, whichever is higher), preventing the Powerwall from discharging into the EV at daytime rates.
The EV continues charging from the grid at the standard off-peak rate. The Powerwall battery is preserved for the 16:00 export window. Once EV charging ends, protection releases automatically.
Why this matters: Without EV protection, a morning EV charging session could drain the Powerwall from 85% to near-zero before 16:00, eliminating the export window entirely. The protection ensures the EV is charged but not at the cost of the evening’s export revenue.
5. Daytime: Solar Filling the Headroom
What happens between 05:00 and 16:00
After charging ends at 05:00, the Powerwall returns to self_consumption mode. The battery level sits at the overnight charge target — deliberately below 100% — and solar generation fills the remaining headroom through the morning.
On a good forecast day (say, charge target 65%), solar needs to deliver approximately 4.7 kWh to fill the remaining 35% of the 13.5 kWh battery. On a typical UK solar day this happens by mid-morning to early afternoon, leaving the battery at 100% well before the 16:00 export window.
The headroom concept
Headroom is the gap left between the overnight charge target and 100%. This gap is the space reserved for solar. If sized correctly — matching the expected solar contribution — the battery arrives at 16:00 full, without grid import and without wasted solar.
Getting headroom right is an ongoing calibration problem. This is what the learning automation solves.
What can go wrong
- Forecast too optimistic: solar underdelivers, battery is below 95% at 16:00. Learning automation decreases the factor — charge more next time.
- Forecast too pessimistic: solar overdelivers, battery hits 100% early, solar is curtailed. Learning automation increases the factor — charge less next time.
- EV charging during daytime: large unplanned load not in the forecast. The EV consumption tracking sensor strips EV energy from the domestic consumption figure to avoid contaminating calibration data.
6. The Learning System
Daily calibration at 16:15
At 16:15 — 15 minutes after the export window opens — the headroom calibration automation runs. It checks battery_at_export (battery level recorded at 16:00) and applies the adjustment logic.
The calibration log (CSV) records every adjustment: date, battery level at 16:00, factor before, factor after, action taken, today’s forecast, actual solar generation, and the charge target that was used. This creates an auditable history of how the system has evolved.
Convergence
In practice the factor converges to a stable value within 2–4 weeks of real data. The system effectively learns the systematic bias of the forecasts for this specific installation, accounting for roof orientation, shading, and local weather patterns that neither API models perfectly.
A system health score sensor provides a live view of whether all components are functioning: both APIs returning data, forecast stored, calibration factor non-zero. A score below 60 warrants investigation before the next charging cycle.
Interaction with the ILS system
The headroom calibration factor indirectly affects the ILS export system. A well-calibrated factor means the battery reliably arrives at 16:00 close to full, giving the ILS the maximum possible energy to work with. A poorly calibrated factor (battery arriving at 70% at 16:00) constrains what the export window can deliver regardless of how well the ILS manages the discharge.
The two systems are coupled: the solar headroom system determines starting conditions, the ILS system optimises within them. Tuning one without the other gives incomplete results.
7. Lessons Learned
On forecasting:
- Two APIs are meaningfully better than one — they fail independently and provide mutual calibration.
- Raw API forecasts are systematically biased for any specific installation. Per-API calibration factors are essential.
- API call timing matters: fresh data just before the charging decision (01:00–01:35) is more valuable than stale data from the previous afternoon.
- A safety cap on the forecast prevents edge cases from corrupting the charge target.
On overnight charging:
- Backup mode produces more reliable charging than grid-charging in self_consumption mode.
- An instant shutoff trigger (template-based, not time-based) stops charging precisely at target — prevents costly overshoot.
- 05:00 is a hard boundary — never charge into the standard off-peak window.
- EV charging overlap is the single most disruptive edge case. Model it explicitly.
On learning systems:
- Start with a conservative (low) headroom factor — better to charge slightly too much initially than arrive at 16:00 short.
- Faster learning rates early, slower rates later — prevents overcorrection once the system has converged.
- Log everything. The CSV record of factor changes is invaluable for understanding any given day’s outcome.
- The system cannot learn correctly if consumption data is contaminated by EV charging — strip it out.
8. Getting Started
Prerequisites
- Part 1 (ILS export system) configured and working
- Solcast account with API key (free tier: 10 calls/day, system uses 3)
- Forecast.Solar integration in Home Assistant
- Shell command configured for CSV logging
Recommended setup order
- Set up both forecast APIs and verify they return non-zero values before building any automation around them.
- Set consumption estimates (weekday/Saturday/Sunday) based on your actual observed usage.
- Start the headroom calibration factor at 0.8 (slightly conservative — will charge a little more than needed initially, which is safe).
- Run the overnight charging automation for one week without the learning automation active. Review CSV logs to understand the forecast vs actual relationship for your installation.
- Enable the learning automation. Monitor the calibration factor over two weeks and verify it moves in the right direction.
- Once the factor stabilises, the system is self-maintaining. Review the calibration log weekly to catch any drift.
Key entities
| Entity | Purpose |
|---|---|
input_number.headroom_calibration_factor |
The core learning variable (0.3–2.0) |
sensor.solar_headroom_charge_target |
Tonight’s calculated charge target (%) |
sensor.solar_best_estimate_tomorrow |
Blended, calibrated forecast (kWh) |
input_number.solcast_calibration_factor |
Solcast bias correction |
input_number.forecast_solar_calibration_factor |
Forecast.Solar bias correction |
input_number.headroom_learning_days |
Days of calibration data accumulated |
sensor.solar_system_health_score |
System health 0–100 |
input_boolean.night_charge_enabled |
Master on/off for overnight charging |
input_boolean.use_manual_charge_override |
Override calculated target |
input_boolean.ev_powerwall_protection_bypass |
Disable EV protection if needed |
This is Part 2 of 2. Part 1 covers the ILS Glide Slope export system (16:00–19:00 window). Both guides together describe a complete closed-loop 24-hour battery management system.
Entity names use the Teslemetry naming convention — adjust to match your integration.