diff --git a/goodwe_controller/DECISIONS/MAINT-28-hold-the-pause-in-a-peak.md b/goodwe_controller/DECISIONS/MAINT-28-hold-the-pause-in-a-peak.md index 5ddf269..5cbf248 100644 --- a/goodwe_controller/DECISIONS/MAINT-28-hold-the-pause-in-a-peak.md +++ b/goodwe_controller/DECISIONS/MAINT-28-hold-the-pause-in-a-peak.md @@ -14,7 +14,7 @@ Why 300 W: the house wanders +-50 W; once the charge resumes it settles at (aim The flag lives only while the charge-only branch is taken: every cycle that reaches the maintenance loop starts it at False, so a maintenance that ended, a mode change or a missing aim cannot carry a stale True into the next charge (test_maint28.py part 1b). `cycle()` has two returns before that loop, and the flag is kept through them on purpose: "inputs missing" and "readings late" (SAFETY-128) are holds of under `stale_after` seconds that write nothing and decide nothing, so the next full cycle sees the same charge and the same peak. A missed poll must not end a peak pause; a reset there would restart the charge on it (`_maint_shape_suspended` is kept through them the same way). -It only ever keeps the maintenance charge off. No cut-back is delayed or weakened: the guard's claim is filed as before. While the pause is held the loop works as at 02eac3b, so A also gives for the house (quarters then sit under the aim, A's energy goes to the load: 1.67 kWh in the 45 min below, 0.45 kWh on 1b5f7c9). A cap on that give would conflict with the guard's `lo` (`lo > hi` resolves to 0 W), so it is not tried here. +It only ever keeps the maintenance charge off. No cut-back is delayed or weakened: the guard's claim is filed as before. While the pause is held the loop works as at 02eac3b, so A also gives for the house (quarters then sit under the aim, A's energy goes to the load: 1.67 kWh in the 45 min below, 0.45 kWh on 1b5f7c9). Measured alternative, A capped at 0 W while held and the guard idle (review scratch, same 45 min run): A gives 0.44 kWh in the load instead of 1.66, quarters 5919/5915 W, but A switches give/idle 32 times in 40 min. The uncapped give costs ~1.2 kWh round trip per 45-min peak (a few cents) and ~50 min more maintenance charge, far inside the 36 h timeout, so the steady give is kept. ## Numbers (sim, real `cycle()`, A rated 2500 W) House 3000 -> 6500 W for 45 min -> 3000 W, +-50 W every 5 s, banked month peak 6000 W (aim 5950 W), forecast = quarter average so far plus the rest at the present grid (`scratch/MAINT-28/dev/m28_repro.py`; `test_maint28.py` runs the same shape for 15 min to keep the suite short). diff --git a/goodwe_controller/app/ctl_cycle.py b/goodwe_controller/app/ctl_cycle.py index 09723ca..a8928a7 100644 --- a/goodwe_controller/app/ctl_cycle.py +++ b/goodwe_controller/app/ctl_cycle.py @@ -568,10 +568,8 @@ class CycleMixin: maint_aim = peakguard.quarter_target_w( float(self.o.get("peak_cap_w", 3500)), self.peak_margin_w, _reading) - # MAINT-28: stays paused until the house is clear of the aim. - # Only where MAINT-24 moved the aim (a banked peak above the - # cap) and at N=2 and up: elsewhere the old rule, byte for byte - # (SAFETY-87 / SAFETY-119 pins). + # MAINT-28: hold the pause in a real peak. + # DECISIONS/MAINT-28-hold-the-pause-in-a-peak.md house = (None if self.grid is None or self.site_batt_w is None else float(self.grid) + float(self.site_batt_w)) _susp = peakguard.shaping_suspended(