Planner: load management-aware multi-loadpoint planning - #31902
Draft
andig wants to merge 13 commits into
Draft
Conversation
Tracking doc for making tariff planning respect shared circuit capacity across multiple loadpoints, so per-loadpoint plans plus minSoc forced charging can no longer overcommit a circuit. Phased TODO checklist, no behaviour change.
Phase 0 observability for loadmanagement-aware planning: per-loadpoint plans ignore shared circuit capacity, so a planned or minSoc charge can be silently capped by the circuit and miss its target. Surface this as a warning in setLimit and cover it with a test.
Phase 0 observability, corrected: warn in the planner (not the loadpoint) and only on an actual goal miss - the required duration no longer fits before the target, so charging will overrun. Warned once per target to avoid per-cycle spam. Replaces the earlier loadpoint-side throttle warning, which fired on any transient circuit cap even when the goal was still met.
requiredDuration already clamps to the circuit's total max power via EffectiveMaxPower; that total-clamp is acceptable. The gap is shared, per-slot capacity: each loadpoint assumes it owns the whole circuit. Also correct the enforcement model to sequential lagging feedback.
Parallel planned sessions under load management must each be granted a whole effectiveMinPower chunk (charger runs >= min or off). At most floor(circuitBudget/effectiveMinPower) sessions fit a slot; the rest shift. Same semi-continuous rule as evcc-io/optimizer#91.
Under a shared circuit the reactive clamp grants budget in update order, so process fast/deadline-bound then higher-priority loadpoints first - they establish draw and the clamp throttles lower-priority ones, deterministic instead of config order. Foundation for loadmanagement-aware planning.
Reordering the round-robin update sequence does not help: updates are also event-driven and the circuit clamp reacts to measured power, so steady-state allocation is order-independent. Priority belongs in planner allocation (Phase 2), not the control loop. Round-robin sequence restored.
Tracks residual charging power per time slot on a shared circuit: Available/Reserve plus CanHost, the semi-continuous min-power gate (a charger runs >= min or off). Foundation for priority-ordered, circuit-aware planning. Single circuit + static budget for now; hierarchy TODO.
Generalise optimalPlan into planCapped: with no cap it is the old full-power duration accounting (optimalPlan delegates, behaviour unchanged); with a per-slot availability a slot delivers min(maxPower, avail) and is skipped below minPower (semi-continuous), so a power-limited slot counts less and the plan spills into more slots. Not yet wired into Plan - needs the ledger.
AllocateShared ties CapacityLedger + planCapped together: orders loadpoints forced-first then priority-desc, plans each against the residual budget with the semi-continuous min-power gate, and reserves its actual per-slot draw so later loadpoints see the remainder. Tests cover joint feasibility, slot sharing, and forced-beats-priority. Live site wiring still pending.
Without clamping, planCapped could pick globally-cheapest slots after the deadline. Add now and clamp per request; test that no slot is scheduled past the target.
computeSharedPlans groups planning loadpoints by circuit each cycle, calls AllocateShared, and stores each plan on the loadpoint; GetPlan returns it when set. Only power-limited circuits with 2+ contending loadpoints are allocated - single/no-circuit, precondition, continuous and no-tariff keep the independent path, and the reactive clamp stays the safety backstop.
The earlier revert (a42eeef) removed the test and doc but its site.go change was never committed, so the reorder stayed live. Remove the helper and restore the plain round-robin sequence; priority belongs in planner allocation, not the control loop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Planning is per-loadpoint and circuit-blind: each loadpoint independently picks the globally cheapest slots to meet its own target (
core/loadpoint_plan.go,core/planner/planner.go), unaware of the shared circuit budget or the other loadpoints. Circuit enforcement (Loadpoint.setLimit→circuit.ValidatePower/ValidateCurrent) is reactive and order-greedy: the first loadpoint in site iteration order grabs budget, later ones get the remainder. So two loadpoints that both planned the same cheap slot get clamped to half power, under-deliver, and miss their deadline — andminSocforced charging consumes budget another loadpoint's plan assumed it had, tripping load management (over current detected).Plan (phased TODOs)
planner.Plan/optimalPlanwith a per-slot power cap; site reserves forced load first, then plans loadpoints in priority order against the ledger.optimizerservice, only if greedy proves insufficient.Open decisions (defaults in the doc)
priority: yesNote that this somewhat doubles what the optimizer already does. It may still be helpful until actionable optimizer lands.
🤖 Generated with Claude Code