mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-10-19 02:51:42 -07:00
laser_lifecycle_test: a ^X during the web-service homing keeps the lens hold
Rule 18 (home-reset) sends a ^X during the web-service homing session
and reads what the reset leaves. The session runs with homing_mode =
gfcloud and a session command that sleeps (sleep 47.25, a command no
other process on the host runs), so the device is the session's for
as long as the rule wants it; Session drops GFSINK from the inherited
environment, which is what lets gfcloud_home_cmd run.
A reset during the session marks the position lost: the device is
the session's, and nothing is read back. Left set, the core's re-init
clears the homed mask, and the driver's Z-only hold with it, so a jog
after $X would move an unreferenced lens. The rule reads:
- before the session, a 1 mm Z jog is refused with error 15: the lens
has no reference in this session, so it is held where it stands;
- $H reports Home and the session's command is running;
- the ^X 0.5 s later lands in Alarm with ALARM:6, the session's
command is gone within 3 s, and the attribute log holds no new
"cnc/stop 1": the device was the session's, so the stream owed the
kernel no stop;
- $X recovers to Idle; the Z jog is refused with error 15 again and
MPos Z has not moved;
- a program move G91 G1 Z1 F100 raises ALARM:2, the soft limit of the
pinned lens; a ^X and $X then leave the controller Idle.
The verdicts are the error code, the alarm codes, MPos Z, the
attribute count and the process count; no log wording is judged.
Session gets mpos_z(), the Z of a status report, beside mpos_x().
The soft-limit alarm asks for a reset before $X unlocks it, and the
rule sends that ^X only once a status report has come from the
alarm's wait: a ^X on the heels of the ALARM line lands while the
soft limit's own reset is still pending and is folded into it.
Measured on the null sink, a ^X 0 ms or 20 ms after the ALARM line is
lost (the alarm's wait goes on, $X answers error 79) and one 100 ms
after it is taken. The '?' polls that wait for the reset banner keep
the controller's output flowing: in Alarm its protocol thread waits
up to 10 s for traffic before it drains its output.
Proof: python3 -m py_compile and pyflakes 4.0.3 are clean on
scripts/bench/laser_lifecycle_test.py. Against the null-sink build of
the grblHAL-glowforge default branch head, which carries "Stream
engine: a reset whose position stays lost pins the lens before the
core's re-init", configured as its CI configures it
(-DGFMCODE_WAIT_S=5.0), rule 18 passes 10 runs in 10 and the harness
passes whole, with no session command left running. Against the
build of that change's parent ("Stream engine: the step a reset owes
is taken only when the idle lands between interrupt callbacks") rule
18 fails 3 runs in 3, each at the Z jog after $X: the parent answers
ok and the lens moves to Z 1.027. The harness runs in
grblHAL-glowforge CI, step "Armed-window lifecycle" (python3
forgefirm/scripts/bench/laser_lifecycle_test.py
build/grblHAL_glowforge), from the head of this repository's default
branch: this commit is pushed before that driver change.
Acceptance: none. scripts/bench/ is outside every covers map.
This commit is contained in:
@@ -73,6 +73,11 @@ reported messages:
|
|||||||
shipped stream (GFSINK_DUMP) moved, an unreferenced lens is still
|
shipped stream (GFSINK_DUMP) moved, an unreferenced lens is still
|
||||||
held after $X, and the head does not return to the job start; $X
|
held after $X, and the head does not return to the job start; $X
|
||||||
recovers to Idle and the next jog ships steps
|
recovers to Idle and the next jog ships steps
|
||||||
|
18. a ^X during the web-service homing session keeps the lens hold: the
|
||||||
|
session's command is ended, the stream owes no stop, the reset lands
|
||||||
|
in Alarm, and after $X a Z jog is still refused (error 15) with MPos
|
||||||
|
Z unmoved, while a program Z move hits the pinned lens's soft limit
|
||||||
|
(ALARM:2)
|
||||||
|
|
||||||
The disarm grace is shortened via a temp config (GFHOME_CONF), the
|
The disarm grace is shortened via a temp config (GFHOME_CONF), the
|
||||||
cooling verdict is published hermetically (GF_VERDICT_FILE), the same
|
cooling verdict is published hermetically (GF_VERDICT_FILE), the same
|
||||||
@@ -337,6 +342,12 @@ class Session:
|
|||||||
m = re.findall(r"MPos:(-?[\d.]+)", "".join(self.log[-4:]))
|
m = re.findall(r"MPos:(-?[\d.]+)", "".join(self.log[-4:]))
|
||||||
return float(m[-1]) if m else None
|
return float(m[-1]) if m else None
|
||||||
|
|
||||||
|
def mpos_z(self):
|
||||||
|
self.sock.sendall(b"?")
|
||||||
|
read_avail(self.sock, self.log, 0.3)
|
||||||
|
m = re.findall(r"MPos:-?[\d.]+,-?[\d.]+,(-?[\d.]+)", "".join(self.log[-4:]))
|
||||||
|
return float(m[-1]) if m else None
|
||||||
|
|
||||||
def setting(self, n):
|
def setting(self, n):
|
||||||
"""A $-setting's value, from a $$ dump."""
|
"""A $-setting's value, from a $$ dump."""
|
||||||
mark = len(self.log)
|
mark = len(self.log)
|
||||||
@@ -993,6 +1004,100 @@ def test_cancel_and_abort():
|
|||||||
s.close()
|
s.close()
|
||||||
|
|
||||||
|
|
||||||
|
HOME_SESSION_CMD = "sleep 47.25"
|
||||||
|
|
||||||
|
|
||||||
|
def home_sessions():
|
||||||
|
"""How many processes of the web-service homing session's command are
|
||||||
|
running (the command is one no other process on the host uses)."""
|
||||||
|
r = subprocess.run(["pgrep", "-f", HOME_SESSION_CMD], capture_output=True, text=True)
|
||||||
|
return len(r.stdout.split())
|
||||||
|
|
||||||
|
|
||||||
|
def test_homing_abort_keeps_z_hold():
|
||||||
|
"""Rule 18: a ^X during the web-service homing session keeps the lens
|
||||||
|
hold. The session owns the device while it runs, so the reset reads
|
||||||
|
nothing back and the position stays lost; the lens, unreferenced in
|
||||||
|
this session, must still be held where it stands once $X has cleared
|
||||||
|
the alarm. The session's command is ended by the reset, and the stream
|
||||||
|
owes the kernel no stop. A program Z move then hits the pinned lens's
|
||||||
|
soft limit."""
|
||||||
|
s = Session("home-reset", disarm_s=60, attr_log=True,
|
||||||
|
conf_extra="homing_mode = gfcloud\ngfcloud_home_cmd = %s\n" % HOME_SESSION_CMD)
|
||||||
|
try:
|
||||||
|
if home_sessions():
|
||||||
|
fail("[home-reset] a homing session's command is already running on the host")
|
||||||
|
mark = len(s.log)
|
||||||
|
if send_line(s.sock, "$J=G91Z1F100", s.log, tolerate_error=True) != "error" or \
|
||||||
|
"error:15" not in "".join(s.log[mark:]):
|
||||||
|
fail("[home-reset] the lens is not held before the session: a Z jog was not refused")
|
||||||
|
send_line(s.sock, "", s.log)
|
||||||
|
z0 = s.mpos_z()
|
||||||
|
if z0 is None:
|
||||||
|
fail("[home-reset] no position in the status report")
|
||||||
|
stops = s.attr_count("cnc/stop 1")
|
||||||
|
s.send_raw("$H")
|
||||||
|
if not s.wait_state("Home", 5):
|
||||||
|
fail("[home-reset] $H never reported Home (state %s)" % s.state())
|
||||||
|
time.sleep(0.5)
|
||||||
|
if not home_sessions():
|
||||||
|
fail("[home-reset] the homing session's command is not running")
|
||||||
|
mark = len(s.log)
|
||||||
|
s.sock.sendall(b"\x18")
|
||||||
|
if not s.wait_state("Alarm", 8):
|
||||||
|
fail("[home-reset] ^X during the homing session did not land in Alarm (state %s)"
|
||||||
|
% s.state())
|
||||||
|
read_avail(s.sock, s.log, 0.5)
|
||||||
|
if "ALARM:6" not in "".join(s.log[mark:]):
|
||||||
|
fail("[home-reset] the reset during homing did not raise ALARM:6")
|
||||||
|
end = time.time() + 3
|
||||||
|
while home_sessions() and time.time() < end:
|
||||||
|
time.sleep(0.1)
|
||||||
|
if home_sessions():
|
||||||
|
fail("[home-reset] the homing session's command outlived the reset")
|
||||||
|
if s.attr_count("cnc/stop 1") != stops:
|
||||||
|
fail("[home-reset] the reset during the session wrote cnc/stop: the stream owed no stop")
|
||||||
|
send_line(s.sock, "$X", s.log)
|
||||||
|
if not s.wait_state("Idle", 5):
|
||||||
|
fail("[home-reset] $X did not recover to Idle (state %s)" % s.state())
|
||||||
|
mark = len(s.log)
|
||||||
|
if send_line(s.sock, "$J=G91Z1F100", s.log, tolerate_error=True) != "error" or \
|
||||||
|
"error:15" not in "".join(s.log[mark:]):
|
||||||
|
fail("[home-reset] a Z jog after the reset was not refused: the lens hold was dropped")
|
||||||
|
time.sleep(0.6)
|
||||||
|
z1 = s.mpos_z()
|
||||||
|
if z1 is None or abs(z1 - z0) > 0.001:
|
||||||
|
fail("[home-reset] the lens moved after the reset (Z %s, was %.3f)" % (z1, z0))
|
||||||
|
send_line(s.sock, "", s.log)
|
||||||
|
mark = len(s.log)
|
||||||
|
s.send_raw("G91 G1 Z1 F100")
|
||||||
|
if read_avail(s.sock, s.log, 5.0, until=("ALARM:", "ok")) != "ALARM:" or \
|
||||||
|
"ALARM:2" not in "".join(s.log[mark:]):
|
||||||
|
fail("[home-reset] a program Z move did not hit the pinned lens's soft limit (ALARM:2)")
|
||||||
|
# The soft-limit alarm asks for a reset before $X unlocks it: one
|
||||||
|
# sent once a status report has come from the alarm's wait. A ^X
|
||||||
|
# on the heels of the ALARM line lands while the soft limit's own
|
||||||
|
# reset is still pending and is folded into it. The '?' polls keep
|
||||||
|
# the controller's output flowing while it waits for the banner.
|
||||||
|
if not s.wait_state("Alarm", 5):
|
||||||
|
fail("[home-reset] no status report from the soft-limit alarm (state %s)" % s.state())
|
||||||
|
banners = s.count("GrblHAL")
|
||||||
|
s.sock.sendall(b"\x18")
|
||||||
|
end = time.time() + 5
|
||||||
|
while s.count("GrblHAL") <= banners and time.time() < end:
|
||||||
|
s.state()
|
||||||
|
if s.count("GrblHAL") <= banners:
|
||||||
|
fail("[home-reset] no reset banner after the ^X that clears the soft limit")
|
||||||
|
send_line(s.sock, "$X", s.log)
|
||||||
|
if not s.wait_state("Idle", 5):
|
||||||
|
fail("[home-reset] $X after the soft limit did not recover to Idle (state %s)" % s.state())
|
||||||
|
print("PASS [home-reset]: ^X during the homing session ended its command, wrote no "
|
||||||
|
"cnc/stop, landed in ALARM:6; after $X the Z jog was refused with Z at %.3f and "
|
||||||
|
"a program Z move raised ALARM:2" % z1)
|
||||||
|
finally:
|
||||||
|
s.close()
|
||||||
|
|
||||||
|
|
||||||
def test_lid_cancel_from_hold():
|
def test_lid_cancel_from_hold():
|
||||||
"""Rule 16: a job paused on the button is not resumable past a lid
|
"""Rule 16: a job paused on the button is not resumable past a lid
|
||||||
open. The lid cancels it from the hold as it does from Run - the
|
open. The lid cancels it from the hold as it does from Run - the
|
||||||
@@ -1134,6 +1239,7 @@ def main():
|
|||||||
test_lid_cancels_and_returns()
|
test_lid_cancels_and_returns()
|
||||||
test_pause_then_lid_cancel_returns_to_the_job_start()
|
test_pause_then_lid_cancel_returns_to_the_job_start()
|
||||||
test_cancel_and_abort()
|
test_cancel_and_abort()
|
||||||
|
test_homing_abort_keeps_z_hold()
|
||||||
test_lid_cancel_from_hold()
|
test_lid_cancel_from_hold()
|
||||||
test_lid_policy_hold()
|
test_lid_policy_hold()
|
||||||
test_sigterm_mid_job()
|
test_sigterm_mid_job()
|
||||||
|
|||||||
Reference in New Issue
Block a user