@frahlg — flagging this from the FoxESS 1K5 site; it's the design question behind everything we chased this week, and it likely affects every hybrid inverter driver, not just this one.
The problem, short
When the plan says idle — export PV surplus, FTW doesn't command the battery to 0. It runs reactive PI on grid = 0 and only acts when the meter leaves the ±grid_tolerance_w band.
But a hybrid's native mode (FoxESS self-use, Sungrow SH, …) also holds the meter at ~0 — by charging the battery with the surplus. So FTW sees "target met" and stays silent while the battery fills instead of the surplus exporting. The controller's deadband is on grid error; slot intent for the battery is never checked, so it cannot distinguish "battery idled, surplus exported" from "inverter absorbed the surplus itself".
Live capture (2026-08-05, planner_passive_arbitrage, idle slot modeled as battery 0 / grid 3.4 kW export):
bat +1820 W grid −6 W pv 2455 W dispatch [] — battery filling, plan wanted export
Consequences
Evidence the machinery half-exists already
When the absorb-ceiling logic does notice (SoC near the plan's cap), FTW starts commanding the battery down — we watched it fight native self-use in a ~90 s limit cycle this afternoon (steady 3 kW PV, battery saw-toothing 250–2300 W). The driver-side half of that cycle is fixed (a zero command now holds AC = PV rather than releasing to native mode — device-drivers#71, v0.5.1, verified live: battery pinned within ±100 W of a commanded zero). What's missing is the core-side half: nothing ever sends that zero when the slot says idle and the battery deviates.
Suggested direction
On idle/export slots, check battery deviation alongside grid error: if the aggregate battery is charging beyond a threshold while the slot models ~0, dispatch an explicit 0. Drivers whose uncommanded state is not idle can then hold it (the FoxESS driver already can); drivers that are naturally idle see no change. Possibly gated per-driver via catalog metadata ("uncommanded state: self-consume vs idle") so pure meters/read-only gateways are untouched.
Related: #816 (slew/MaxCommandW trap the same site exposed), #768, device-drivers#70/#71. Happy to build whichever direction you pick — this site is a willing HIL rig.
🤖 Written with Claude Code from live captures on the 1K5 site.
@frahlg — flagging this from the FoxESS 1K5 site; it's the design question behind everything we chased this week, and it likely affects every hybrid inverter driver, not just this one.
The problem, short
When the plan says idle — export PV surplus, FTW doesn't command the battery to 0. It runs reactive PI on grid = 0 and only acts when the meter leaves the ±
grid_tolerance_wband.But a hybrid's native mode (FoxESS self-use, Sungrow SH, …) also holds the meter at ~0 — by charging the battery with the surplus. So FTW sees "target met" and stays silent while the battery fills instead of the surplus exporting. The controller's deadband is on grid error; slot intent for the battery is never checked, so it cannot distinguish "battery idled, surplus exported" from "inverter absorbed the surplus itself".
Live capture (2026-08-05,
planner_passive_arbitrage, idle slot modeled asbattery 0 / grid 3.4 kW export):Consequences
planner.soc_max_pct(which re-armed the Optimizer burns stored energy via simultaneous charge+discharge when SoC starts above soc_max — every plan rejected until the battery drains #768 rejections until the cap went to 100).twin-driftreplans fold the SoC in) — but it's an accident, not a decision, and the PV-surplus absorber's SoC cap is unenforceable the same way.Evidence the machinery half-exists already
When the absorb-ceiling logic does notice (SoC near the plan's cap), FTW starts commanding the battery down — we watched it fight native self-use in a ~90 s limit cycle this afternoon (steady 3 kW PV, battery saw-toothing 250–2300 W). The driver-side half of that cycle is fixed (a zero command now holds AC = PV rather than releasing to native mode — device-drivers#71, v0.5.1, verified live: battery pinned within ±100 W of a commanded zero). What's missing is the core-side half: nothing ever sends that zero when the slot says idle and the battery deviates.
Suggested direction
On idle/export slots, check battery deviation alongside grid error: if the aggregate battery is charging beyond a threshold while the slot models ~0, dispatch an explicit 0. Drivers whose uncommanded state is not idle can then hold it (the FoxESS driver already can); drivers that are naturally idle see no change. Possibly gated per-driver via catalog metadata ("uncommanded state: self-consume vs idle") so pure meters/read-only gateways are untouched.
Related: #816 (slew/
MaxCommandWtrap the same site exposed), #768, device-drivers#70/#71. Happy to build whichever direction you pick — this site is a willing HIL rig.🤖 Written with Claude Code from live captures on the 1K5 site.