mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-28 09:11:11 -07:00
forgetest: the fixture spaces its presses; an unattended refusal is an error; the baseline resets out of a hold
The first campaign with the bench actuator wired failed motion.button-hold-resume on the tool, not the machine: the second press was asked while the first 200 ms pulse was still on, the fixture answered 409, the runner handed the step to an operator who was not in the room, and the post pass could not jog a controller left in Hold. - fixture.py: a press waits for the last pulse to end (the fixture's pulse_ms) plus a 300 ms release, so the controller sees the edge; a 409 for a pulse in progress is waited out against button_pulsing and retried once. - runner.py: in an unattended run a fixture refusal ends the test at once as ERROR naming the refusal; the operator fallback stays for attended runs. - baseline.py: a controller in Hold or Door gets a soft reset before the return jog, position kept. - tests: the fake fixture refuses a press while one is in progress and reports button_pulsing; FakeGrbl records ^X and can land a reset in a chosen state; five new tests. - docs: ACCEPTANCE.md fixture rules, fixture/README.md tool's side. No catalog consequence: tool-side change, no covers map moves. Bench: campaign c-20260824174545-0bdc 25/25 with every action by the fixture; the hold reset proven by a dry drill.
This commit is contained in:
@@ -550,6 +550,19 @@ class Baseline:
|
||||
try:
|
||||
with hw.Grbl() as g:
|
||||
rep = g.status_report()
|
||||
if rep["state"].startswith(("Hold", "Door")):
|
||||
# a job left held (a pause test that failed there) refuses
|
||||
# a jog: the soft reset ends it where it stopped, position
|
||||
# kept, and hands the controller back
|
||||
held = rep["state"]
|
||||
g.realtime(0x18)
|
||||
deadline = time.time() + 5
|
||||
while time.time() < deadline:
|
||||
rep = g.status_report()
|
||||
if not rep["state"].startswith(("Hold", "Door")):
|
||||
break
|
||||
time.sleep(0.2)
|
||||
self.log("controller reset out of %s: now %s" % (held, rep["state"]))
|
||||
if rep["state"].startswith("Alarm"):
|
||||
g.command("$X")
|
||||
g.command("$J=G91X%.3fY%.3fF1200" % (-dx, -dy))
|
||||
|
||||
@@ -33,6 +33,8 @@ CHANNELS = ("lid", "interlock", "button")
|
||||
MDNS_GROUP = "224.0.0.251"
|
||||
MDNS_PORT = 5353
|
||||
RESOLVE_TTL_S = 300.0 # a resolved address is trusted this long
|
||||
BUTTON_GAP_S = 0.3 # the release between two presses: long enough for the controller to see it
|
||||
PULSE_WAIT_S = 1.5 # a pulse in progress ends within the firmware's 500 ms clamp
|
||||
|
||||
|
||||
class FixtureError(Exception):
|
||||
@@ -226,6 +228,7 @@ class Fixture:
|
||||
self._resolved_at = time.time() if self.ip_override else 0.0
|
||||
self._lock = threading.Lock()
|
||||
self.last_state = None
|
||||
self._pulse_end = 0.0 # when this client's last press ends (its pulse_ms)
|
||||
|
||||
# -- address -------------------------------------------------------
|
||||
def address(self, refresh=False):
|
||||
@@ -293,7 +296,7 @@ class Fixture:
|
||||
if channel == "button":
|
||||
if state != "press":
|
||||
raise FixtureError("the button is only ever pressed")
|
||||
st, body = self._request("POST", "/button", {})
|
||||
st, body = self._press()
|
||||
else:
|
||||
if state not in ("open", "close"):
|
||||
raise FixtureError("%s: unknown state %r" % (channel, state))
|
||||
@@ -304,6 +307,30 @@ class Fixture:
|
||||
self.last_state = body
|
||||
return body
|
||||
|
||||
def _press(self):
|
||||
"""One press, spaced from the last one. The controller detects
|
||||
the button's rising edge, so a press must follow a release it has
|
||||
seen: the next press waits for the last pulse to end (the
|
||||
fixture's own pulse_ms) plus BUTTON_GAP_S. A 409 for a pulse in
|
||||
progress (a press this client did not time) is waited out once
|
||||
against the fixture's state, then retried."""
|
||||
wait = self._pulse_end + BUTTON_GAP_S - time.time()
|
||||
if wait > 0:
|
||||
time.sleep(wait)
|
||||
st, body = self._request("POST", "/button", {})
|
||||
if st == 409 and "in progress" in str(body.get("error", "")):
|
||||
deadline = time.time() + PULSE_WAIT_S
|
||||
while time.time() < deadline:
|
||||
time.sleep(0.1)
|
||||
s, b = self._request("GET", "/")
|
||||
if s == 200 and not b.get("button_pulsing"):
|
||||
break
|
||||
time.sleep(BUTTON_GAP_S)
|
||||
st, body = self._request("POST", "/button", {})
|
||||
if st == 200:
|
||||
self._pulse_end = time.time() + float(body.get("pulse_ms") or 500) / 1000.0
|
||||
return st, body
|
||||
|
||||
def release(self):
|
||||
st, body = self._request("POST", "/release", {})
|
||||
if st != 200:
|
||||
|
||||
@@ -338,11 +338,16 @@ class Context:
|
||||
try:
|
||||
fixture.act(channel, state)
|
||||
except _fixture.FixtureError as e:
|
||||
rec["fixture_error"] = str(e)
|
||||
if self.run.unattended:
|
||||
# nobody is in the room to do it instead: the run ends
|
||||
# here, as the harness's failure, not the machine's
|
||||
self.log("ACT %s %s: fixture failed (%s) - unattended, nobody to ask", channel, state, e)
|
||||
raise
|
||||
# the box did not do it: the operator is asked instead,
|
||||
# and the record says so
|
||||
self.log("ACT %s %s: fixture failed (%s) - asking the operator", channel, state, e)
|
||||
rec["by"] = "operator"
|
||||
rec["fixture_error"] = str(e)
|
||||
self.notice(wording)
|
||||
else:
|
||||
self.notice(wording)
|
||||
@@ -900,6 +905,10 @@ class Runner:
|
||||
result, message = _campaign.ABORTED, str(e) or "aborted"
|
||||
except Failed as e:
|
||||
result, message = _campaign.FAIL, str(e)
|
||||
except _fixture.FixtureError as e:
|
||||
# the bench actuator could not perform a step: the test was
|
||||
# not judged, and the machine is not the one at fault
|
||||
result, message = _campaign.ERROR, "the fixture could not perform a step: %s" % e
|
||||
except Exception as e: # noqa: BLE001 - an erroring test is a failed test
|
||||
result, message = _campaign.ERROR, "%s: %s" % (type(e).__name__, e)
|
||||
run.log(traceback.format_exc().rstrip())
|
||||
|
||||
Reference in New Issue
Block a user