Skip to content

Idle plan slots are unenforceable on self-consuming hybrids: the deadband watches grid error, so native charging looks like success #822

Description

@Leitet

@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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions