mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 08:41:13 -07:00
Pin forgectrl on the gate that waits for the enclosure; add its test
forgectrl now holds the motion check while a lid or the interlock is open instead of starting the controller unverified: GET /mode reports controller "waiting" with why, the button blinks amber, and the check runs when the enclosure closes. motion.gate-waits-for-lid drives the fixture's lid channel: the lid opens, forgectrl restarts, /mode must read waiting with why naming the lid, no pid, motion unverified, the button amber (sampled over a blink period: the smooth trigger's target reads 0 through the off half) and no probe line in the log; the lid closes, and the controller must come up verified with MOTION OK on the first probe. Proven on the bench reference: PASS, the controller verified 6.5 s after the lid closed.
This commit is contained in:
@@ -546,6 +546,120 @@ def _liveness_masked_restart(ctx, fc, ev):
|
||||
ctx.log("PASS: masked restart probed MOTION OK on the first try, mask cleared, controller up")
|
||||
|
||||
|
||||
# ------------------------------------------------- the gate and the lid
|
||||
|
||||
def _wait_controller(ctx, fc, states, timeout, motion=None):
|
||||
"""GET /mode until the controller state is one of `states` (and, for a
|
||||
running one, the motion verdict is `motion` when given). Tolerates a
|
||||
daemon that is still coming up. Returns the last document read."""
|
||||
m = {}
|
||||
t0 = time.time()
|
||||
while time.time() - t0 < timeout:
|
||||
ctx.checkpoint()
|
||||
try:
|
||||
st, m = fc.get("/mode")
|
||||
except hw.HwError:
|
||||
m = None
|
||||
if isinstance(m, dict) and m.get("controller") in states:
|
||||
if motion is None or m.get("controller") != "running" or m.get("motion") == motion:
|
||||
return m
|
||||
ctx.sleep(1)
|
||||
return m if isinstance(m, dict) else {}
|
||||
|
||||
|
||||
def _button_led(ctx, window_s=1.2):
|
||||
"""The commanded level of the button's red, green, and blue channels
|
||||
(ledtrig_smooth's target), sampled over a blink period or two and
|
||||
reduced to the maximum per channel: with a pulse set, target reads 0
|
||||
through the off half of every blink. None when the LEDs are not
|
||||
readable."""
|
||||
best = None
|
||||
t0 = time.time()
|
||||
while time.time() - t0 < window_s:
|
||||
out = []
|
||||
for ch in (1, 2, 3):
|
||||
try:
|
||||
with open("/sys/class/leds/button_led_%d/target" % ch, "r", encoding="utf-8") as f:
|
||||
out.append(int(f.read().strip()))
|
||||
except (OSError, ValueError):
|
||||
return None
|
||||
best = out if best is None else [max(a, b) for a, b in zip(best, out)]
|
||||
ctx.sleep(0.1)
|
||||
return best
|
||||
|
||||
|
||||
@test("motion.gate-waits-for-lid", title="The motion gate waits for the lid", subsystem="motion",
|
||||
kind="operator", est_min=2,
|
||||
covers=[("forgectrl", "src/super.*"), ("forgectrl", "src/liveness.*"), ("forgectrl", "src/led.*"),
|
||||
("forgectrl", "src/ui/panel.js")],
|
||||
requires=["motion.liveness-probe"], actions=["lid"],
|
||||
steps=["The lid opens, forgectrl is restarted (the gate runs at the first spawn of a "
|
||||
"session: power-on, a daemon restart), the lid closes: the head does not move "
|
||||
"while the lid is open, then makes the 15 mm probe move (+X first)."],
|
||||
description="With the lid open when forgectrl starts (power-on with the lid up, or a "
|
||||
"daemon restart) the supervisor starts nothing: GET /mode reports controller "
|
||||
"waiting with why naming the lid, motion unverified, no pid, the button blinks "
|
||||
"amber, and no probe line reaches the log. When the lid closes the probe runs "
|
||||
"(MOTION OK), the lens takes its reference, and the controller comes up "
|
||||
"verified with the button handed back.")
|
||||
def gate_waits_for_lid(ctx):
|
||||
fc = ctx.forgectrl
|
||||
ev = ctx.evidence
|
||||
ctx.check(fc.wait_idle(15, abort=ctx.aborted), "machine not idle at the start")
|
||||
try:
|
||||
ctx.act("lid", "open")
|
||||
off = _log_offset(FORGECTRL_LOG)
|
||||
rc, out = hw.initd("forgectrl", "restart")
|
||||
ctx.check(rc == 0, "forgectrl restart -> rc %s", rc)
|
||||
m = _wait_controller(ctx, fc, ("waiting", "running", "motion-fault"), 60)
|
||||
ev["waiting"] = m
|
||||
ctx.log("mode with the lid open: %s", m)
|
||||
ctx.check(m.get("controller") == "waiting",
|
||||
"controller is %r with the lid open, expected waiting", m.get("controller"))
|
||||
ctx.check("lid" in (m.get("why") or ""), "why does not name the lid: %r", m.get("why"))
|
||||
ctx.check(m.get("motion") == "unverified", "motion reads %r while waiting", m.get("motion"))
|
||||
ctx.check(not m.get("pid"), "a controller (pid %s) runs while the gate waits", m.get("pid"))
|
||||
led = _button_led(ctx)
|
||||
ev["led_waiting"] = led
|
||||
ctx.log("button LED targets while waiting (r, g, b): %s", led)
|
||||
ctx.check(led is not None and led[0] > 0 and led[1] > 0 and led[2] == 0,
|
||||
"the button does not blink amber while the gate waits: %s", led)
|
||||
ctx.sleep(3)
|
||||
st, m2 = fc.get("/mode")
|
||||
ctx.check(isinstance(m2, dict) and m2.get("controller") == "waiting", "the wait did not hold: %s", m2)
|
||||
lines = _probe_lines(FORGECTRL_LOG, off)
|
||||
ctx.check(not lines, "the probe ran with the lid open: %s", lines[:1])
|
||||
|
||||
t_close = time.time()
|
||||
ctx.act("lid", "close")
|
||||
m = _wait_controller(ctx, fc, ("running", "motion-fault"), 150, motion="verified")
|
||||
ev["after_close"] = m
|
||||
ev["close_to_running_s"] = round(time.time() - t_close, 1)
|
||||
ctx.log("mode %.1f s after the lid closed: %s", time.time() - t_close, m)
|
||||
ctx.check(m.get("controller") == "running" and m.get("motion") == "verified",
|
||||
"the controller did not come up verified after the lid closed: %s", m)
|
||||
t1 = time.time()
|
||||
lines = _probe_lines(FORGECTRL_LOG, off)
|
||||
while not lines and time.time() - t1 < 10:
|
||||
ctx.sleep(0.5)
|
||||
lines = _probe_lines(FORGECTRL_LOG, off)
|
||||
ev["probe_lines"] = lines[-3:]
|
||||
ctx.check(lines and "MOTION OK" in lines[0],
|
||||
"the probe after the lid closed was not MOTION OK: %s", lines[:1])
|
||||
led = _button_led(ctx)
|
||||
ev["led_running"] = led
|
||||
ctx.check(led is None or led[0] == 0 or led[2] > 0,
|
||||
"the amber wait pattern is still on the button after the start: %s", led)
|
||||
finally:
|
||||
if ctx.switch("lid") is False:
|
||||
ctx.act("lid", "close", fail=False)
|
||||
m = _wait_controller(ctx, fc, ("running", "motion-fault"), 150)
|
||||
if m.get("controller") != "running":
|
||||
fc.post("/controller/start")
|
||||
_wait_controller(ctx, fc, ("running", "motion-fault"), 150)
|
||||
ctx.check(fc.wait_idle(15, abort=ctx.aborted), "machine not idle after the drill")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- cancel / abort
|
||||
|
||||
@test("motion.cancel-abort", title="Jog cancel and controlled abort recover cleanly", subsystem="motion",
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# only SRCREV and PV here - the image manifest leaves *-pin.inc out of the
|
||||
# layer content hash because the component entry already identifies the
|
||||
# pinned source (forgefirm-image-manifest.bbclass).
|
||||
SRCREV = "6040e64a388005761d7598a5edbc46e55d38af76"
|
||||
PV = "0.1.9"
|
||||
SRCREV = "7e03073863c63b93da6281d318eb25dc3e822f2c"
|
||||
PV = "0.1.10"
|
||||
|
||||
Reference in New Issue
Block a user