mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 16:51:12 -07:00
The fan floors proven on a pinned image; a fan fault ends with its session
The dated record of dev image 20260822135848: the campaign of every non-operator, non-live test at 18 of 18 PASS with the measured floors and the operating-point rule (cooling.fan-gate-trips and the hunt leg of cloud.mode-switch as recorded), and the unplugged-exhaust-fan drill: AIRFLOW at the grace plus three ticks with the exhaust dead, the other fans held, the reason relayed on the Grbl port, the replugged fan ok inside the next session's grace. The drill showed the fault riding into idle, where the hold canceled jogs and would have refused the cloud print that re-proves the fan. forgectrl pin d51dbdb: the fault ends with its run session, and the next session judges every fan afresh. cooling.fan-gate-trips checks the verdict is OK with no hold once the tripped session is over (the unit fake mirrors it); its covers, and the cooling tests' shared covers, gain src/coolfmt.* (the tree manifest carries the new files at the bumped pin, and the lint was right to ask). cloud.mode-switch samples the hunt's gate rows twice a second: a hunt's run phase is a few seconds long. Docs: COOLING 3a, BRINGUP item 19, CAMPAIGN-LOG.
This commit is contained in:
@@ -372,8 +372,8 @@ def wait_action_finished(ctx, offset, action, timeout, poll=0.5):
|
||||
|
||||
def watch_hunt_gates(ctx, offset, timeout):
|
||||
"""Wait for the action's terminal line like wait_action_finished,
|
||||
sampling /cool/status once a second meanwhile. Returns (line or
|
||||
None, samples)."""
|
||||
sampling /cool/status twice a second meanwhile (a hunt's run phase
|
||||
can be a few seconds). Returns (line or None, samples)."""
|
||||
fc = ctx.forgectrl
|
||||
samples = []
|
||||
t0 = time.time()
|
||||
@@ -386,7 +386,7 @@ def watch_hunt_gates(ctx, offset, timeout):
|
||||
i = action_finish_index(lines, "hunt")
|
||||
if i is not None:
|
||||
return lines[i], samples
|
||||
time.sleep(1)
|
||||
time.sleep(0.5)
|
||||
return None, samples
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import time
|
||||
from ..catalog import test
|
||||
from .. import hw
|
||||
|
||||
_COOL_COVERS = [("forgectrl", "src/cool.*"), ("forgectrl", "src/diag.*"),
|
||||
_COOL_COVERS = [("forgectrl", "src/cool.*"), ("forgectrl", "src/coolfmt.*"), ("forgectrl", "src/diag.*"),
|
||||
("forgectrl", "src/gates.*"), ("forgectrl", "src/airflow.*"),
|
||||
("forgectrl", "src/settings.*"),
|
||||
("forgectrl", "src/status.*"), ("forgectrl", "src/ui/**"),
|
||||
@@ -239,6 +239,18 @@ def _run_session(ctx, g, fc, until, what, wait=None):
|
||||
_session_ended(ctx, fc, what)
|
||||
|
||||
|
||||
def _after_session(ctx, fc, wait=5):
|
||||
"""The engine's state a few ticks after a session ended."""
|
||||
c = {}
|
||||
t0 = time.time()
|
||||
while time.time() - t0 < wait:
|
||||
ctx.sleep(1)
|
||||
c = _cool(fc)
|
||||
if c.get("phase") != "run" and c.get("verdict") == "OK":
|
||||
break
|
||||
return c
|
||||
|
||||
|
||||
def _tail_has(fc, needle):
|
||||
st, body = fc.get("/logs/tail", params={"name": "forgectrl", "lines": GATE_LOG_LINES})
|
||||
text = body.get("text", "") if st == 200 and isinstance(body, dict) else ""
|
||||
@@ -358,9 +370,10 @@ def _fan_gate(c, name):
|
||||
description="The airflow gates judge a fan commanded at the cut fan profile, which a bare M8 "
|
||||
"applies, armed or not. An exhaust floor no fan "
|
||||
"can meet must trip AIRFLOW after the grace plus three ticks (hold, fire blocked, no "
|
||||
"resume while the session lasts); a purge current floor at the ADC rail must trip the "
|
||||
"same way; a floor of zero must read off in gates_off and trip nothing; restored, the "
|
||||
"next session runs OK with every fan reading at or above its floor.")
|
||||
"resume while the session lasts) and the fault must end with the session (verdict OK, "
|
||||
"no hold, once the session is over); a purge current floor at the ADC rail must trip "
|
||||
"the same way; a floor of zero must read off in gates_off and trip nothing; restored, "
|
||||
"the next session runs OK with every fan reading at or above its floor.")
|
||||
def fan_gate_trips(ctx):
|
||||
fc = ctx.forgectrl
|
||||
ev = ctx.evidence
|
||||
@@ -397,6 +410,13 @@ def fan_gate_trips(ctx):
|
||||
ctx.check(_fan_gate(c, "exhaust").get("state") == "TRIPPED",
|
||||
"the exhaust gate does not read TRIPPED: %s", c.get("fan_gates"))
|
||||
ctx.check("exhaust" in (c.get("reason") or ""), "the reason does not name the fan: %r", c.get("reason"))
|
||||
# The fault is the session's: with the session over, the
|
||||
# verdict is back to OK and nothing holds (jogs and the next
|
||||
# job's pre-check must not see a fan fault at idle).
|
||||
c = _after_session(ctx, fc)
|
||||
ev["exhaust_trip_after"] = c
|
||||
ctx.check(c.get("verdict") == "OK" and c.get("hold") is False,
|
||||
"the fan fault outlived its run session: %s", c)
|
||||
|
||||
# Leg 2: the purge fan by current, floor at the ADC rail.
|
||||
_set_gates(ctx, fc, {"cool_tach_exhaust_min_rpm": orig["cool_tach_exhaust_min_rpm"],
|
||||
|
||||
@@ -448,6 +448,10 @@ class FanGateTests(unittest.TestCase):
|
||||
def end():
|
||||
time.sleep(0.3)
|
||||
cool["phase"] = "idle"
|
||||
# the fan fault is the session's: it ends with it
|
||||
cool.update(verdict="OK", fire_ok=True, hold=False, reason="")
|
||||
for g in cool.get("fan_gates", {}).values():
|
||||
g["state"] = "idle"
|
||||
threading.Thread(target=end, daemon=True).start()
|
||||
return
|
||||
if line != "M8":
|
||||
|
||||
Reference in New Issue
Block a user