SAFETY-119: no "not keeping up" when the loop takes the wire back
When the peak guard or a maintenance charge hands the wire back, the loop's integrator is re-seeded from that owner's command and kicked by a meter that does not show it yet. It crossed its rail for a cycle or two and raised goodwe_integrator_bound while the battery gave what it was told. The loop's integrator bound is not counted until the plant's settle (SHORT_CYCLES, SHORT_SPAN_S) has passed since the hand-back. An over-rating always counts; a bound that lasts warns after the settle. No command changes and no text changes. test_safety119.py; DECISIONS entry. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
75f0804f9d
commit
c4e15ec4cd
@@ -1272,6 +1272,10 @@ class Controller:
|
||||
self._loop_decision = None
|
||||
self._asked_hist: dict = {}
|
||||
self._short_run = 0
|
||||
# SAFETY-119: did another owner set the wire last cycle, and since
|
||||
# when (monotonic s, owned cycles) has the loop had it back.
|
||||
self._wire_lost = False
|
||||
self._handback = None
|
||||
self.stopping = False
|
||||
# SAFETY-61. time.monotonic() at the end of the last run_control() pass.
|
||||
self.loop_pass_mono = None
|
||||
@@ -5813,6 +5817,7 @@ class Controller:
|
||||
if d is None:
|
||||
self._short_run = 0
|
||||
self._asked_hist = {}
|
||||
self._wire_lost, self._handback = False, None
|
||||
return
|
||||
unmet = self.allocation.unmet_w if self.allocation is not None else 0
|
||||
cut = (resolution.bound_by or "").split("(")[0]
|
||||
@@ -5838,7 +5843,9 @@ class Controller:
|
||||
and delivery_short(asked[inv.name], inv.batt,
|
||||
self.tuning.saturation_w)]
|
||||
self._short_run = self._short_run + 1 if short else 0
|
||||
if owns and self.loop_clamped[0]:
|
||||
settling = self._settling(owns, now)
|
||||
if owns and self.loop_clamped[0] and not (
|
||||
settling and not self.loop_clamped[2]):
|
||||
return
|
||||
engaged = self._short_run >= self.tuning.saturation_cycles
|
||||
named = len(self.inverters) > 1
|
||||
@@ -5847,6 +5854,28 @@ class Controller:
|
||||
if engaged else "", False)
|
||||
self.loop_clamp_facts = ()
|
||||
|
||||
def _settling(self, owns, now) -> bool:
|
||||
"""SAFETY-119: is the loop within the plant's settle (SHORT_CYCLES
|
||||
cycles and SHORT_SPAN_S) of taking the wire back from another owner?
|
||||
Its integrator is re-seeded from that owner's command and kicked by a
|
||||
meter that does not show it yet, so its bound there is not counted.
|
||||
DECISIONS/SAFETY-119-bound-after-hand-back.md."""
|
||||
if not owns:
|
||||
self._wire_lost, self._handback = True, None
|
||||
return False
|
||||
if self._wire_lost:
|
||||
self._wire_lost, self._handback = False, (now, 0)
|
||||
if self._handback is None:
|
||||
return False
|
||||
t0, n = self._handback
|
||||
n += 1
|
||||
self._handback = (t0, n)
|
||||
if n >= SHORT_CYCLES and (now is None or t0 is None
|
||||
or now - t0 >= SHORT_SPAN_S):
|
||||
self._handback = None
|
||||
return False
|
||||
return True
|
||||
|
||||
async def check_output_clamp(self) -> None:
|
||||
"""SAFETY-03. Alarm whenever a clamp actually engaged this cycle.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user