A Home Assistant custom integration that intelligently controls EV charging based on solar surplus power. It maximises self-consumption of solar energy by dynamically adjusting the charge current and automatically starting/stopping charging when surplus is available.
- Surplus charging: automatically charges your EV using excess solar production
- Force charge: override to charge at maximum current immediately
- Charge Tonight: overnight scheduling using a desired range target, with auto-off on cable unplug or solar recovery
- Invert net power: toggle for sensors with reversed sign convention
- Charge limit control: optional number entity to set/reset the vehicle's charge limit %
- kW / W auto-detection: works with sensors reporting in either unit
- EMA smoothing: exponential moving-average filter for stable current decisions
- Debounced start/stop/modulate: prevents relay chatter with configurable delays
- Solar Done detection: detects when solar generation has finished for the day
- Enhanced import guard: multi-stage protection (debounce → reduce → stop) with hysteresis
- Dynamic measurement alignment: adaptive skew detection and coherence scoring
- Low-power protection: force-charges if battery SoC drops below threshold, with solar forecast awareness
- Persistent storage: energy counters survive restarts, reloads, and reboots without ever briefly dropping to zero
- Utility meters: optional daily/monthly/yearly energy tracking sensors (standard HA helper, opt-in)
- Mode tracking: reason, source, timestamps, and transition history on every mode change
- Solar-to-EV ratio: lifetime percentage of solar energy that reached the EV vs. total produced (displayed as
%with 2 decimal places) - Battery energy tracking: live battery-side energy delta and AC→DC charging overhead per session (requires EV battery energy sensor)
- Charging priority: five selectable modes control whether surplus, export, or import is preferred — without affecting any monitoring sensor values
- Expert mode: unlocks advanced controller-tuning sensors, parameters, and priority bias
- Full debug attributes: every sensor exposes source values, mode and last action
- Open HACS in Home Assistant.
- Go to Integrations → three-dot menu → Custom repositories.
- Add
https://github.com/Patrick1610/AdaptiveChargewith category Integration. - Click Download on the AdaptiveCharge card.
- Restart Home Assistant.
Navigate to Settings → Devices & Services → Add Integration and search for AdaptiveCharge.
Choose how your household net power is measured:
| Option | Description |
|---|---|
Single net power sensor |
One sensor gives import (+) / export (−) in W or kW |
Separate consumption & production sensors |
Two sensors: house load and solar yield |
Select a sensor entity. Sign convention: positive = importing from grid, negative = exporting.
If your sensor uses the opposite convention (positive = export), enable Invert Net Power to flip the sign automatically.
- Consumption Sensor: total house load (W or kW, always positive)
- Production Sensor: solar generation (W or kW, always positive)
The integration computes net = consumption − production.
- EV Power Sensor: the power currently drawn by the EVSE/charger (W or kW)
- Voltage Sensor: the mains voltage (V) used to convert surplus W → A
- Vehicle Presence:
device_trackerentity (home= present) - Cable Sensor:
binary_sensor— on when cable is plugged in - Current Range Sensor:
sensorreporting current battery range in km - Battery Level Sensor (optional):
sensorreporting battery SoC % - EV Battery Energy Sensor (optional):
sensorreporting energy remaining in the battery (kWh). Enables live battery-side energy delta and AC→DC overhead tracking. - EV Energy Added Sensor (optional):
sensorreporting session energy added by the charger (kWh). Used together with the battery energy sensor for capacity estimation.
| Parameter | Default | Description |
|---|---|---|
| Charge Buffer | 0 % | Extra buffer above desired range |
| Range Hysteresis | 3.0 % | Dead zone to prevent start/stop oscillation near target |
| Default Charge Limit | 80 % | Vehicle charge limit % to reset to when cable is disconnected |
| Parameter | Default | Description |
|---|---|---|
| Surplus Start Threshold | 2 A | Minimum EMA current to start surplus charging |
| Surplus Stop Threshold | 1 A | EMA current below which surplus charging stops |
| Min Current | 0 A | Minimum charge current allowed |
| Max Current | 16 A | Maximum charge current allowed |
Set the start and end times (HH:MM) for the night charging window. The Charge Tonight switch activates charging at the start time and auto-resets at the end time.
| Parameter | Default | Description |
|---|---|---|
| Night Charging Start | 22:00 | Earliest time to start overnight charging |
| Night Charging End | 05:00 | Time at which Charge Tonight auto-disables |
One or more sensor entities for total solar yield (W or kW). Used to detect Solar Done state and accumulate the solar-to-EV ratio.
Optionally add one or more Remaining Forecast Today sensors (e.g. Solcast remaining_today) — multiple values are summed.
- Charge Switch:
switchentity to enable/disable the EVSE - Charge Current Number:
numberentity to set the charge current (A) - Charge Limit Number (optional):
numberentity to set the vehicle's charge limit (%)
If left empty the integration tracks state internally but does not issue actual commands.
| Parameter | Default | Description |
|---|---|---|
| Smoothing Window | 120 s | Length of the rolling window for current averaging |
| Sample Interval | 10 s | How often sensor values are read and logic executed |
| Solar Done Threshold | 50 W | Solar production below which solar done timer starts |
| Solar Done Duration | 600 s | How long production must stay below threshold before solar_done triggers |
| Start Delay | 30 s | Debounce before starting surplus charging |
| Stop Delay | 30 s | Debounce before stopping surplus charging |
| Modulate Min Interval | 30 s | Minimum time between current modulation calls |
| Import Guard Threshold | 200 W | Grid import above which the import guard activates |
| Import Guard Duration | 30 s | How long import must exceed threshold before action |
| Enable Utility Meters | off | Opt-in for daily/monthly/yearly period tracking sensors |
Enable Expert Mode to access advanced controller-tuning parameters:
| Parameter | Default | Description |
|---|---|---|
| Max Step (A) | 1 A | Maximum current change per modulation step |
| Hysteresis Up (A) | 0.2 A | Minimum EMA increase required before stepping up |
| Hysteresis Down (A) | 1.0 A | Minimum EMA decrease required before stepping down |
| Settling Duration (s) | 10 s | Post-commit window during which upward steps are suppressed |
Expert mode also automatically enables two normally-hidden diagnostic sensors:
- Alignment Diagnostics — full alignment engine state
- Input Skew — real-time timestamp skew between net and EV power streams
These sensors are only enabled when expert mode is on. Turning expert mode off leaves them enabled so no data or dashboards are disrupted.
The controller uses a multi-layered approach to prevent flapping, overreaction, and stale-data faults:
Every poll cycle updates the last_seen timestamp of each sensor — even when the value hasn't changed. This prevents false staleness detection when power is steady.
When the charger setpoint changes by a significant amount (>400W), the controller enters an alignment phase. During this phase:
- Upward current adjustments are blocked.
- Downward safety adjustments remain responsive.
- The phase ends when the net power sensor reacts in the expected direction, or when a dynamic timeout expires.
The timeout is learned from observed reaction lag: timeout = min(max(2 × median_lag, 8s), 60s).
After every setpoint commit, a short settling window (10s) suppresses further upward steps. This prevents "self-induced dip" flapping where the controller reacts to the transient caused by its own action.
The controller computes a coherence score (0–1) from the timestamp skew between net and EV power streams, and their individual reliabilities. When coherence is low, upward changes are gated.
Internal calculations use float current values. Integer rounding only happens at the final actuator call. A configurable hysteresis dead zone prevents rapid toggling at integer boundaries.
- Max 1A change per step (configurable in Expert Mode).
- 45s minimum between upward steps.
- Downward steps and safety actions are immediate.
Each tick computes a confidence level (HIGH/MEDIUM/LOW) from data staleness, alignment state, settling state, and target stability. Upward changes require at least MEDIUM confidence.
The import guard prevents grid import while surplus charging. It uses a multi-stage approach:
Debounce: Import must exceed the threshold (default 200W) for a sustained period (default 30s) before any action is taken. Short transient spikes are ignored.
Escalation Ladder:
- Reduce current — decrease by 1A (soft mitigation)
- Settle window — wait 30s to observe if net import improves
- Reduce to 0A — if import persists, reduce to 0A (charger stays connected)
- Hard stop — only if 0A doesn't resolve import, disable the charger relay
Hysteresis: Import must drop below threshold − margin (default 150W) for 20s before the guard clears. This prevents rapid flip-flop at the threshold boundary.
Guard States: ok → reducing → stopped (visible in the Import Guard State sensor)
Every mode transition records:
mode_reason: why the mode is currently activemode_source: what triggered it (e.g.auto_rule,charge_now_switch,user_toggle,import_guard)mode_since: ISO timestamp of when the current mode was enteredlast_transition:previous_mode → current_mode: reason
The Charge Tonight switch automatically turns off when:
- The EV cable is unplugged
- The
solar_donecondition transitions from active to inactive (solar production recovers)
Energy counters are persisted to .storage/adaptive_charge.counters.<entry_id> using HA's Store helper and are restored before the first coordinator tick on each reload. This means:
Energy Charged(aTOTAL_INCREASINGsensor) never briefly drops to 0 on reload — utility meters tracking it will not incorrectly count the recovery as new energy.Solar to EV Ratiocaches its last known value and restores it immediately on reload, preventing a brief unavailable/zero window.- Throttled writes (max once per 30s) avoid disk I/O spam.
Solar to EV Ratio (sensor) is a lifetime KPI — it tracks what fraction of all solar energy produced has actually reached the EV battery, across every charging session:
ratio = min(energy_solar_wh / solar_production_total_wh, 1.0)
This is displayed as a percentage (0–100 %) and serves as a dashboard metric for long-term self-consumption tracking.
Solar Capture Factor (attribute on the ratio sensor) is a rolling operational metric — it is an EMA of the per-session solar capture efficiency, updated at the end of each charging session. It is currently provided for diagnostics/trending and is not used in control decisions.
Low-power forecast logic uses a deterministic lifetime-ratio check:
expected_ev_kwh = forecast_kwh × solar_to_ev_ratio
Net power sensor invalid/unavailable: when the net power sensor returns unknown, unavailable, or no value:
- The surplus calculation falls back to 0 W (safe default)
- Surplus start and modulate-up are blocked — the integration will not begin or increase charging based on an unreliable reading
- Safety actions (stop, modulate-down, import guard) continue to work normally
- A
net_power_validflag is exposed in the data dict and logged for diagnostics
Cable sensor unknown: surplus charging only starts when cable_connected is explicitly True. Unknown (None) or disconnected (False) both block surplus start. Force charge still works when triggered by Charge Now, Charge Tonight, or import_priority.
Battery capacity estimation, overhead calculation, and solar capture factor are now finalized via a central session finalizer triggered by:
- Cable disconnection
- Stale charge detection (car stopped independently for >60 s)
- HA shutdown/unload (best-effort)
This ensures session data is persisted even when the cable remains connected (e.g. car reaches its own charge limit).
When the EV Battery Energy Sensor is configured:
- Battery Energy Delta: live reading of how much energy has entered the battery this session (
current_battery_kwh − session_start_battery_kwh), updated every coordinator tick. - Charging Overhead: rolling AC→DC conversion loss percentage, updated live during charging by blending the current session's partial data with the lifetime totals. Falls back to lifetime history-only when the cable is disconnected.
overhead% = (1 − battery_received_kwh / wall_energy_kwh) × 100
Live blending only activates once at least 0.5 kWh has been charged in the current session to avoid noisy readings at session start.
The live blend uses a wall-energy snapshot — the wall value captured at the exact moment the battery sensor last changed — rather than the continuously-growing live accumulator. This keeps the displayed overhead stable between car API polls (typically every ~3 minutes) and prevents a sawtooth pattern where the overhead would drift up between updates and snap back down on each new battery reading.
Integer setpoint quantization (important):
- EVSE current is integer amps. The controller computes a float target first and then quantizes.
- For upward modulation the target is now rounded-to-nearest (half-up) and executes at least +1A when an up-step is approved.
- For downward modulation the target is floored (conservative).
- This prevents a stall where
available_currentcan stay abovecurrent_setting(e.g. 4.7A vs 4A) without ever ramping up.
The Charging Priority select entity (select.adaptivecharge_charging_priority) controls how the float EMA current is rounded to the integer amp value sent to the EVSE. All monitoring sensors (surplus_w, ema_current_a, etc.) are always computed from real measurements and are never affected by the priority setting.
| Mode | Quantization | Effect |
|---|---|---|
balance (default) |
Round (half-up) | Steps up when EMA is ≥ 0.5 A above current setpoint. Symmetric around 0 W net — pure surplus behaviour. |
zero_prefer_export |
Floor | Steps up only when EMA is ≥ 1 A above current setpoint. Naturally biases toward exporting rather than consuming. |
zero_prefer_import |
Ceil | Steps up as soon as EMA is any positive fraction above current setpoint. Biases toward charging while staying near zero net. |
export_priority |
— | Stops any active surplus session and prevents new ones. All solar is exported. Overridden by Charge Now, Charge Tonight, and low-power protection. |
import_priority |
— | Permanent force-charge whenever the cable is connected — equivalent to leaving Charge Now on. Import guard is automatically bypassed. |
The three surplus modes are exact mirrors of each other around the half-integer point: prefer_export is the most conservative (floor), balance is neutral (round), and prefer_import is the most liberal (ceil). No additional configuration is needed.
export_priority is a soft block — it only prevents surplus charging. The following always override it (highest to lowest priority):
- Charge Now switch — force charge immediately
- Charge Tonight switch — overnight range-target charging
- Low-power protection — force charge when battery SoC is below the threshold and the solar forecast is genuinely insufficient to cover the deficit (the precise-mode forecast check determines this)
The Alignment Diagnostics sensor (enabled automatically in Expert Mode) exposes:
| Attribute | Description |
|---|---|
alignment_active |
True during alignment phase |
settling_active |
True during settling window |
confidence_level |
LOW / MEDIUM / HIGH |
measurement_coherence |
0..1 coherence score |
estimated_skew_seconds |
Current timestamp skew between net and EV |
estimated_lag_seconds |
Learned median reaction lag |
net_update_interval_s |
EWMA of net power update interval |
ev_update_interval_s |
EWMA of EV power update interval |
last_sample_age_net_s |
Seconds since last net power poll |
last_sample_age_ev_s |
Seconds since last EV power poll |
last_applied_current_a |
Last integer setpoint sent to charger |
last_control_reason |
Why the last decision was made |
The Import Guard State sensor exposes:
| Attribute | Description |
|---|---|
import_guard_reason |
Reason string (e.g. "transient spike ignored", "sustained import 35s > 200W") |
time_in_import_state |
Seconds in current guard state |
import_watts |
Current grid import power (W) |
The Mode sensor exposes:
| Attribute | Description |
|---|---|
mode_reason |
Why the current mode is active |
mode_source |
What triggered it (auto_rule, charge_now_switch, user_toggle, import_guard) |
mode_since |
ISO timestamp of when current mode was entered |
last_transition |
Previous → current mode with reason |
net_w > 0 → importing from grid (house load > solar)
net_w < 0 → exporting to grid (solar > house load)
Surplus available for EV:
surplus_w = (0 − net_w) + ev_w
The EV power is added back because it is already included in the net measurement; we want to know the surplus excluding what the EV is already using.
For every power sensor the integration checks the unit_of_measurement attribute:
- If the attribute contains
kW, the value is multiplied by 1000. - If no unit is present and the absolute value is less than 20, the value is assumed to be kW and multiplied by 1000.
- Otherwise the value is used as-is (W).
The voltage sensor is always used as-is.
Every sample_interval seconds a new (timestamp, raw_current_a) sample is appended to a deque. Samples older than smoothing_window seconds are pruned. smoothed_a is the arithmetic mean of all remaining samples.
raw_current_a = surplus_w / (voltage × 3)
smoothed_a = mean(samples within smoothing_window)
smoothed_floored = clamp(floor(smoothed_a), 0, max_current_limit)
Using a longer smoothing window reduces current oscillations caused by cloud cover fluctuations.
Condition: smoothed_floored > 0 and not currently charging
→ After start_delay seconds: set current to smoothed_floored A, wait 5 s, enable charging.
Condition: smoothed_floored < 1 and currently charging in surplus mode
→ After stop_delay seconds: disable charging, wait 10 s, reset current to 16 A.
Condition: charging in surplus mode and raw_floored changed
→ After modulate_min_interval seconds: set current to raw_floored A (if > 0).
Condition: Charge Now switch turned on
→ After 5 s debounce: set current to 16 A, wait 5 s, enable charging.
Condition: Charge Now switch turned off
→ After 3 s debounce: disable charging, wait 10 s, reset current to 16 A.
When the cable sensor transitions off → on:
- if force charge active → start_force
- elif smoothed_floored > 0 → start_surplus
- else → stop_surplus (stay off, current reset)
| Entity | Unit | Description |
|---|---|---|
sensor.adaptivecharge_net_surplus_excl_ev_w |
W | Surplus available for EV charging |
sensor.adaptivecharge_mode |
— | Current control mode (force, surplus, stopped, night_target, off) |
sensor.adaptivecharge_current_setting (diagnostic) |
A | Last current value sent to charger |
sensor.adaptivecharge_available_current_decision (diagnostic) |
A | EMA-smoothed available current used for decisions |
sensor.adaptivecharge_last_action (diagnostic) |
— | Most recent control action |
sensor.adaptivecharge_last_reason (diagnostic) |
— | Reason for last action |
sensor.adaptivecharge_import_guard_state (diagnostic) |
— | Import guard state: ok, reducing, stopped |
sensor.adaptivecharge_version (diagnostic) |
— | Integration version from manifest |
sensor.adaptivecharge_energy_charged_kwh |
kWh | Cumulative energy charged (TOTAL_INCREASING, utility-meter safe) |
sensor.adaptivecharge_solar_to_ev_ratio |
— | Lifetime fraction of solar production that reached the EV (0–1) |
sensor.adaptivecharge_range_upper_limit_km |
km | Range upper threshold — charging stops here |
sensor.adaptivecharge_range_lower_limit_km |
km | Range lower threshold — charging starts when below this |
sensor.adaptivecharge_alignment_diagnostics (diagnostic, expert) |
— | Alignment engine internals; auto-enabled in Expert Mode |
sensor.adaptivecharge_input_skew (diagnostic, expert) |
s | Timestamp skew between net and EV sensors; auto-enabled in Expert Mode |
sensor.adaptivecharge_charging_overhead_pct (optional) |
% | Rolling AC→DC conversion loss %; live during charging session |
sensor.adaptivecharge_battery_energy_delta_kwh (optional) |
kWh | Energy received by the battery this session (live, resets on cable plug-in) |
sensor.adaptivecharge_energy_needed_full_kwh (optional) |
kWh | Estimated wall energy still needed to reach 100% SoC (includes charging overhead) |
Optional sensors are only created when the EV Battery Energy Sensor is configured.
| Entity | Description |
|---|---|
binary_sensor.adaptivecharge_force_charge |
True when Charge Now switch is on |
binary_sensor.adaptivecharge_charging_active |
True when actively controlling charging |
binary_sensor.adaptivecharge_low_power_active |
True when low-power protection is forcing a charge |
| Entity | Range | Description |
|---|---|---|
number.adaptivecharge_desired_range_km |
0–1000 km | Target range for overnight charging |
| Entity | Description |
|---|---|
switch.adaptivecharge_controller_enabled |
Master switch — enables/disables the charge controller |
switch.adaptivecharge_charge_now |
Force charge at maximum current immediately |
switch.adaptivecharge_charge_tonight |
Enable overnight charge-to-range scheduling |
| Entity | Options | Description |
|---|---|---|
select.adaptivecharge_charging_priority |
balance, zero_prefer_export, zero_prefer_import, export_priority, import_priority |
Charging priority mode (see §14) |
| Service | Description |
|---|---|
adaptive_charge.force_start |
Enable Charge Now and start immediately |
adaptive_charge.force_stop |
Disable Charge Now and stop |
adaptive_charge.set_desired_range |
Set desired range (km) |
adaptive_charge.enable_tonight |
Turn on Charge Tonight |
adaptive_charge.disable_tonight |
Turn off Charge Tonight |
When Enable Utility Meters is turned on in Advanced Settings, these additional HA utility meter helpers are created, tracking the Energy Charged sensor:
| Entity | Period |
|---|---|
sensor.adaptivecharge_energy_charged_daily |
Daily |
sensor.adaptivecharge_energy_charged_monthly |
Monthly |
sensor.adaptivecharge_energy_charged_yearly |
Yearly |
Note: Because
Energy Chargedis aTOTAL_INCREASINGsensor and its value is now fully persisted before the first tick on every reload, these utility meters will never incorrectly accumulate energy during an integration restart.
If you use Tessie or the Tesla integration, map entities like this:
| AdaptiveCharge field | Tesla / Tessie entity |
|---|---|
| EV Power Sensor | sensor.my_car_charger_power |
| Voltage Sensor | sensor.my_car_charger_voltage |
| Vehicle Presence | device_tracker.my_car |
| Cable Sensor | binary_sensor.my_car_charging_cable_connected |
| Current Range Sensor | sensor.my_car_battery_range |
| Battery Level Sensor | sensor.my_car_battery_level |
| EV Battery Energy Sensor | sensor.my_car_energy_remaining |
| EV Energy Added Sensor | sensor.my_car_energy_added |
| Charge Switch | switch.my_car_charger |
| Charge Current Number | number.my_car_charging_amps |
| Charge Limit Number | number.my_car_charge_limit |
Every sensor exposes the following extra attributes:
| Attribute | Description |
|---|---|
mode |
Current control mode (force, surplus, stopped) |
last_action |
Description of the last control action taken |
last_updated |
ISO timestamp of the last data update |
charging_on |
Whether the integration believes charging is active |
sample_count |
Number of samples in the smoothing deque |
Charging never starts
- Check that
sensor.net_surplus_excl_ev_wshows a positive value during peak solar hours. - Verify sign convention: net power must be negative (exporting) to show surplus.
- Increase
smoothing_windowif values are unstable.
Charging keeps starting and stopping
- Increase
start_delayandstop_delayto add more hysteresis. - Increase
smoothing_windowto smooth out short-term fluctuations.
kW sensors not converting correctly
- Ensure the sensor has
unit_of_measurement: kWin its attributes. - If missing, the heuristic (value < 20 → treat as kW) may misfire; add a unit attribute template sensor.
Force charge not working
- Ensure
Charge Switchis configured and the entity is available. - Check Home Assistant logs for service call errors.
Utility meters jumped after an integration reload
- This was a bug fixed in v4.1.5. The
Energy Chargedsensor now restores its value from the persistent store before the very first coordinator tick, so it never briefly shows 0 on reload.
Charging Overhead or Battery Energy Delta showing Unknown after integration reload
- Fixed in v4.1.6. The Battery Energy Delta sensor now lazily captures a new start snapshot the moment its source sensor becomes available — even if the integration was reloaded or restarted while the cable was already connected.
Charging Overhead or Battery Energy Delta not updating during a session
- The Battery Energy Delta is already updated live each coordinator tick.
- The Charging Overhead becomes live once ≥0.5 kWh has been charged in the current session (threshold avoids noisy early-session readings).
- Both sensors depend on the EV Battery Energy Sensor being configured and its update frequency — they are only as fresh as the source sensor.
Charging Overhead shows a sawtooth / flickering pattern
- Fixed in v4.2.2. The live overhead blend now uses a wall-energy snapshot (taken at the moment the battery sensor last changed) rather than the live wall accumulator. This prevents the ~2 pp sawtooth that previously appeared every time the car API delivered a new battery reading (~3 min apart).
Alignment Diagnostics / Input Skew sensor not visible
- These sensors are hidden by default. Enable Expert Mode in the integration options to make them appear automatically. Alternatively, enable them manually via Settings → Devices & Services → AdaptiveCharge → Entities.
Charging Priority redesign (simpler, no configuration needed):
zero_prefer_exportandzero_prefer_importnow use floor and ceil quantization respectively, instead of a configurable W-bias offset. This makes the distinction intuitive:balance(default) — round (half-up): neutral, steps at ±0.5 A around the setpoint.zero_prefer_export— floor: requires a full integer amp of surplus above the current setpoint before stepping up; conservatively biases toward export.zero_prefer_import— ceil: any positive fraction above the setpoint triggers a step up; liberally biases toward charging.
- Removed Priority Bias (W) setting from Expert Mode. The mode itself now fully encodes the intent, with no numeric parameter to tune.
surplus_wandema_current_aremain unaffected — monitoring is always clean.
Import guard oscillation fix (EV startup transient):
- Import guard debounce is now frozen while
alignment.activeoralignment.settlingisTrue. This prevents the guard from reacting to the expected power transient during EV charger startup (Tesla draws at max current for up to 80 s before applying the commanded limit). - Session start (
_action_start_surplus) explicitly opens a 30 s settling window and resets the debounce, covering the full startup transient.
Surplus ramp-up fix (balanced mode):
- Fixed integer setpoint quantization so approved upward modulation no longer stalls below the next whole amp.
- Example: previous behaviour could hold at 4A when decision hovered around 4.7–4.9A; now it ramps to 5A once up-modulation is allowed.
committed_currentnow always reflects the actual integer current sent to the charger.
Rate-limiter consistency:
- Upward cooldown now uses the configured
modulate_min_intervalinstead of a hardcoded constant, so the setting in options directly controls ramp-up speed.
Control behaviour correction:
- Removed the v4.3.1 upward EMA "snap". In balanced mode this could bias behaviour toward earlier import-like ramp-up. Balanced now again follows pure filtered surplus without extra upward bias.
Sensor reliability (no graph gaps):
Solar to EV Rationow always reports a continuous value: restored last known value on restart/reload, and0.0fallback when no history exists yet.Charging Overheadnow also restores/holds the last known value and uses a0.0fallback before first valid sample, preventing temporaryunknownholes during startup or source outages.
Docs consistency:
- Clarified that
solar_capture_factoris currently diagnostic only; low-power forecast control usessolar_to_ev_ratiodirectly.
Control & logic improvements:
- Faster up-modulation response: EMA now applies an upward snap when raw surplus clearly exceeds filtered current, reducing delayed ramp-up during sudden export spikes.
- Simplified low-power forecast rule: low-power protection now uses the lifetime
solar_to_ev_ratiodirectly for expected EV energy (forecast × ratio) to keep behaviour transparent and predictable. - Solar noise gate: solar production accumulation now ignores very low readings (
<= 50 W) to reduce ratio drift from standby/noise values.
New battery planning sensor:
- Added
Energy Needed Full(kWh): estimated wall energy required to go from current SoC to 100%, including charging overhead when available.
Critical fixes:
- Net sensor fail-safe: invalid/unavailable net power sensor no longer treated as 0 W for control decisions. Surplus start and upward modulation are blocked; safety/downward actions remain active.
- Cable guard strictened: surplus start now requires
cable_connected = True. Unknown (None) cable status blocks charging start (previously allowed as permissive fallback). - Solar ratio semantics split: the lifetime Solar-to-EV Ratio remains a dashboard KPI. A new rolling Solar Capture Factor (EMA per session) is used for low-power forecast decisions, reflecting short-term conditions faster.
- Session finalizer: central
_finalize_session_if_needed()ensures capacity estimates, overhead, and capture factor are persisted on cable disconnect, stale charge reset, and HA shutdown — not only on cable-disconnect events. - Battery delta deduplication:
_compute_session_battery_delta()is called once per tick; the result is reused for both the data dict and overhead calculation, eliminating double sensor reads with side-effects.
Improvements:
- Storage flush deduplication: in-flight async flush task guard prevents parallel save scheduling.
- Sensor clarity: SolarToEvRatioSensor docstring and attributes updated;
solar_capture_factorexposed as attribute. - 38 new edge-case tests covering net/cable unknown/unavailable, capture factor logic, session finalizer, and storage.
Backward compatibility:
- All existing entity IDs remain unchanged.
solar_capture_factoris added as a new storage key (defaults to 0.0, merged on load from older stores).net_power_validis a new key in the data dict (informational only — no entity change).- Low-power forecast logic now prefers
solar_capture_factor; it falls back to the lifetime ratio when the factor has not yet been populated (first session after upgrade).
Migration notes:
- No manual migration required. Upgrade and restart.
- The
solar_capture_factorwill be populated after the first completed charging session. - Cable sensor: if you previously relied on surplus starting with an unknown cable state, this is now blocked. Ensure your cable sensor is configured and reporting correctly.
Known limitations / follow-ups:
solar_capture_factoruses a session-level EMA — it does not weight by season or time-of-day. Future versions may add a sliding-window approach.- The session finalizer is best-effort on shutdown; if HA crashes without a graceful shutdown, the most recent session data may be lost (same as before, but now also includes capture factor updates).