Skip to content

[#101] Add phase protection logic + offline test suites#147

Open
nickles-lee wants to merge 15 commits into
gitcodebob:mainfrom
nickles-lee:101-phase-protection-peak-shaving
Open

[#101] Add phase protection logic + offline test suites#147
nickles-lee wants to merge 15 commits into
gitcodebob:mainfrom
nickles-lee:101-phase-protection-peak-shaving

Conversation

@nickles-lee

@nickles-lee nickles-lee commented Jun 20, 2026

Copy link
Copy Markdown

Summary

Closes #101.

This PR adds optional per-phase peak-shaving protection on top of the existing aggregate import/export peak-shaving behavior.

When enabled and usable phase data is available, the controller can react when an individual phase exceeds the configured per-phase power limit, even if the aggregate grid value still looks safe.

What changed

  • Adds per-phase command-limit calculation from grid_power_phase.L1/L2/L3.
  • Adds a global phase-protection guard before strategy execution so direct strategies like Charge, Sell, and Dynamic 2 can be preempted into Standby / peak shave.
  • Extends peak-shaving logic so per-phase overloads can trigger import/export shaving.
  • Applies phase-aware command limits in Charge, Sell, and Self-consumption.
  • Adds water-fill allocation so batteries on the same phase share constrained power more fairly.
  • Preserves priority-battery behavior during phase-throttled charging.
  • Redistributes unused phase charge allowance when a battery cannot use its full share.
  • Adds an offline Node-RED flow test harness and integration/unit tests for phase-protection behavior.

Stacked PR note

This PR is stacked on top of #139.

#139 contains the phase assignment / phase exposure groundwork. This PR intentionally focuses only on the #101 phase-protection logic and offline test coverage. Please avoid reviewing #102-scoped changes here; review this PR as the delta from the head of 102-phase-assignments-for-batteries to the head of 101-phase-protection-peak-shaving.

Suggested review compare:

git diff origin/102-phase-assignments-for-batteries..origin/101-phase-protection-peak-shaving

Range-diff from the #139 stack branch to this branch:

git range-diff origin/main..origin/102-phase-assignments-for-batteries origin/main..origin/101-phase-protection-peak-shaving

 1:  6ced552 =  1:  6ced552 Feat: Add battery phase assignment support
 2:  ba1fad7 =  2:  ba1fad7 Expose per-phase info from meter
 3:  cb7e37a =  3:  cb7e37a Hide phase interaction when no phases assigned
 4:  b37c8da =  4:  b37c8da Remove tracked macOS metadata
 -:  ------- >  5:  c66abcd Add per-phase peak shaving protection
 -:  ------- >  6:  8e81b23 Apply phase protection before strategies
 -:  ------- >  7:  da9c313 Stabilize phase command limits
 -:  ------- >  8:  1606757 Add phase water-fill allocator
 -:  ------- >  9:  d5ce48f Deduplicate phase allocator logic
 -:  ------- > 10:  9154abf Preserve charge priority under phase throttling
 -:  ------- > 11:  caf4f55 Redistribute unused phase charge allowance
 -:  ------- > 12:  faab292 test: add Node-RED flow harness and strategy integration tests
 5:  5b85367 = 13:  5b85367 improve .gitignore + switch power reference
 -:  ------- > 14:  852f1a1 test: harden phase-protection tests across personas; fix harness init & command_limits_available handling

Compatibility

Existing behavior remains unchanged when phase protection is disabled, phase sensors are unavailable, or no batteries are assigned to phases.

Per-phase protection only acts when all of the following are true:

  • Phase protection is enabled.
  • L1/L2/L3 readings are available.
  • A valid per-phase limit is configured.
  • At least one battery is assigned to the overloaded phase.

Full stop is intentionally not preempted.

Testing

Offline Node-RED flow test coverage was added for phase-protection behavior.

Validated locally on this branch:

npm test
60 tests
60 pass

Also verified that the phase-protection tests fail when run against main, where the phase-protection logic does not exist:

60 tests
16 pass
44 fail

This confirms the tests are not simply passing vacuously on branches without the feature.

Regression checks were also run against earlier draft commits in this branch to make sure the test suite catches prior unwanted behavior, including missing guard logic, unfair first-fit allocation, missing priority preservation, and missing allocator helpers.

Real-world testing / safety notes

This is an experimental control feature. It does not come with any warranty, guarantee, or promise that your phase fuse will not blow. If you're worried about this, set a wider safety margin on the per-phase limit to account for any spikes that will take some time for the batteries to respond to.

This logic is software-based peak shaving, not a replacement for correct electrical design, breaker sizing, phase-aware charger limiting, inverter limits, or professional installation checks.

If you test this branch on a real installation:

  • Understand that peak shaving reduces and shortens excessive load but it does not eliminate or prevent it. In testing, charging took roughly 10 seconds to fully throttle to a safe power level when overcurrent was observed. Wear/progressive failure can occur if the spikes are large enough, and instantaneous failure can happen if the spikes are massive.
  • Start with conservative phase limits to test behavior and responsiveness when your fuse is not at risk.
  • Actively monitor L1/L2/L3 power and current while testing.
  • Test alongside a controllable, interruptible & instantly stoppable load such as ovens or induction stoves.
  • Keep the Home Battery Control master switch accessible as well.
  • Be prepared to disable control immediately if behavior looks wrong.
  • Do not rely on this as the only protection for your fuses.
  • If your phase fuse blows, your grid operator may charge you to replace it; for example, Liander charges around €100.

This improves the safety of your system & lets you run "hotter", but it does not make the system safe in the way a 25A circuit breaker on each phase would.

Real-World Testing

I have been running this branch in my house for around 1 to 2 weeks without incident, with no PV + heavier loading on Phase 3. If I ran the Charge strategy with the power limits I now have set, one of the main fuses would have cooked.

Some graphs:

Approx. 1 day view:
Image

A more zoomed-in view of a period where phase 3 is running at full load:
Image

Start of a brief overload to 32A, caused by an appliance switching on while the batteries had Phase 3 fully loaded:
Image

End of the overload, 10s later
Image

Note that I've validated phase protection during the time period where the batteries are charging. I haven't tested phase protection when the batteries are on standby / not actively charging.

@nickles-lee nickles-lee changed the title [#101] Add phase protection logic [#101] Add phase protection logic + test suites Jun 20, 2026
@nickles-lee nickles-lee changed the title [#101] Add phase protection logic + test suites [#101] Add phase protection logic + offline test suites Jun 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Improvement] Extend import/export peak-shaving logic to support per-phase peak shaving

1 participant