forgetest: the setup suite, and the sensors check asks nothing

The commission.* tests are setup.* (subsystem "setup"): suite/setup.py,
setup_dark.py, setup_sheet.py, and the host tests test_setup_*.py. The
coverage maps name src/setup.* in place of src/commission.*, the record
is setup.json, and the bench seed in forgetest.init creates
/run/forgefirm/setup-override.

setup.check-sensors follows the check as it is now: no question is
asked and no setting is written. The test reads the settings before
and after and fails on any change, and fails at once if the check
opens a prompt. The host test drives the fake daemon with no prompt
and proves both outcomes.

Proof: test_setup_dark.py, test_setup_sheet.py, and
test_setup_suite.py pass (46 tests). The coverage lint names
src/commission.c and .h as uncovered until the forgectrl pin moves to
the revision that carries the rename.
This commit is contained in:
ScottW514
2026-09-14 10:37:55 -04:00
parent c0d4302d73
commit d8b8adaef4
12 changed files with 243 additions and 213 deletions
+1 -1
View File
@@ -321,7 +321,7 @@ class Baseline:
def wait_settled(self, timeout=SETTLE_S, unreachable_s=10):
"""Block until forgectrl reports a settled supervisor: motion
verified (the probe passed), motion-fault (the ladder exhausted),
standby (the manual stop lever), or gated (the commissioning gate
standby (the manual stop lever), or gated (the setup gate
is closed: no controller spawns until it opens). Gives up after
unreachable_s without an answer. Returns the last /mode body (None
if unreachable)."""
+3 -3
View File
@@ -4,9 +4,9 @@ order. Each module registers its tests with @catalog.test."""
from . import image # noqa: F401,E402
from . import kernel # noqa: F401,E402
from . import forgectrl # noqa: F401,E402
from . import commission # noqa: F401,E402
from . import commission_dark # noqa: F401,E402
from . import commission_sheet # noqa: F401,E402
from . import setup # noqa: F401,E402
from . import setup_dark # noqa: F401,E402
from . import setup_sheet # noqa: F401,E402
from . import logs # noqa: F401,E402
from . import motion # noqa: F401,E402
from . import cooling # noqa: F401,E402
+1 -1
View File
@@ -396,7 +396,7 @@ def lid_privacy(ctx):
"authorizes a write; a rotation from the panel route stops the old key and "
"the new one reads. The setting is put back after.")
def key_read(ctx):
from .commission import request
from .setup import request
from .forgectrl import lan_ip
fc = ctx.forgectrl
ev = ctx.evidence
+1 -1
View File
@@ -891,7 +891,7 @@ CRASH_GATES = ["crash_abort", "crash_x_alert", "crash_y_alert"]
"read as the three crash gates off by value (gates_off names them) with the "
"session OK; a threshold past the register range is refused; restored, the "
"gates read ok. The tiers themselves (BUMP, CRASH) trip only on a physical "
"knock inside an armed window and are commissioned at the bench, not here.")
"knock inside an armed window and are proven at the bench, not here.")
def crash_watch_plumbing(ctx):
fc = ctx.forgectrl
ev = ctx.evidence
+1 -1
View File
@@ -152,7 +152,7 @@ def auth(ctx):
# HTTPS the route itself refuses the peer (403 loopback only). Neither
# request follows the redirect, and the self-signed certificate is
# not verified.
from .commission import request, decode
from .setup import request, decode
ip = lan_ip()
ev["lan_ip"] = ip
ctx.check(ip, "cannot determine the board's LAN address")
@@ -1,9 +1,9 @@
"""commission.* - the first-run commissioning: the record and the
"""setup.* - the first-run setup: the record and the
controller gate, the advisories, the account and its login, the HTTPS
boundary, SSH, the cloud switch, the machine's name, and the factory
return.
The daemon reads the commissioning record (commissioning.json in the
The daemon reads the setup record (setup.json in the
data directory) and the account record (users) once, at its start, and
keeps them in memory; only its own routes write them. A test that needs
a different record installs the file under a forgectrl restart (a
@@ -34,13 +34,13 @@ from ..catalog import test
from .. import hw
from .forgectrl import lan_ip
RECORD = "commissioning.json"
RECORD = "setup.json"
USERS = "users"
OVERRIDE = "commissioning-override"
OVERRIDE = "setup-override"
SSH_FLAG = "ssh-enabled"
TLS_BASE = "https://127.0.0.1" # FORGECTRL_TLS_URL overrides (host tests)
# The image's required wizards, all at version 1 (commission.c).
# The image's required wizards, all at version 1 (setup.c).
REQUIRED_WIZARDS = ("advisories", "account", "preferences", "machine", "cloud")
ADVISORY_DOCS = ("safety-and-risk", "licenses", "privacy", "cloud-service")
SAFETY_PHRASE = "I UNDERSTAND"
@@ -176,8 +176,8 @@ def record_without_wizards(record):
def complete_record(status, base=None, account_name="bench"):
"""A complete commissioning record for a machine that must count as
commissioned: every advisory at the hash GET /wiz reports, the
"""A complete setup record for a machine that must count as
set up: every advisory at the hash GET /wiz reports, the
press recorded, an account (the one of `base` when it has one),
every wizard of the catalog at its version, and the run complete.
`status` is a GET /wiz body; `base` is an existing record to keep
@@ -352,12 +352,12 @@ def gate_is_open():
# -------------------------------------------------------------- the gate
_GATE_COVERS = [("forgectrl", "src/commission.*"), ("forgectrl", "src/super.c"),
_GATE_COVERS = [("forgectrl", "src/setup.*"), ("forgectrl", "src/super.c"),
("forgectrl", "src/main.c"), ("forgectrl", "src/paths.h")]
@test("commission.gate-blocks-controllers", title="The commissioning gate spawns no controller",
subsystem="commission", kind="auto", hardware="takeover", mode="grbl", est_min=5,
@test("setup.gate-blocks-controllers", title="The setup gate spawns no controller",
subsystem="setup", kind="auto", hardware="takeover", mode="grbl", est_min=5,
covers=_GATE_COVERS, requires=["forgectrl.auth"], precheck=gate_is_open,
description="With the override removed and a record that lacks every required wizard "
"(installed under a forgectrl restart, the consent and the account kept), "
@@ -374,11 +374,11 @@ def gate_blocks_controllers(ctx):
ctx.log("before: gate %s, override %s, required %s", before.get("gate"), before.get("override"),
before.get("required"))
raw = read_file(record_path())
ctx.check(raw, "no commissioning record at %s", record_path())
ctx.check(raw, "no setup record at %s", record_path())
try:
record = json.loads(raw.decode("utf-8"))
except ValueError as e:
ctx.fail("the commissioning record is not JSON: %s", e)
ctx.fail("the setup record is not JSON: %s", e)
lacking = json.dumps(record_without_wizards(record), indent=1, sort_keys=True).encode() + b"\n"
had_override = override_present()
ev["override_before"] = had_override
@@ -391,7 +391,7 @@ def gate_blocks_controllers(ctx):
ctx.log("/mode after the restart: %s (after %s s)", m, took)
ctx.check(m.get("controller") == "gated" and m.get("gated") is True,
"the supervisor did not report the gate: %s", m)
ctx.check("commissioning required" in (m.get("why") or ""),
ctx.check("setup required" in (m.get("why") or ""),
"the reason does not name the missing wizards: %r", m.get("why"))
w = wiz(fc)
ev["gated_wiz"] = wiz_summary(w)
@@ -439,11 +439,11 @@ def gate_blocks_controllers(ctx):
ctx.log("restored: gate %s, controller %s", after.get("gate"), m.get("controller"))
@test("commission.override-until-reboot", title="The override stands until the next reboot",
subsystem="commission", kind="auto", est_min=1,
@test("setup.override-until-reboot", title="The override stands until the next reboot",
subsystem="setup", kind="auto", est_min=1,
covers=_GATE_COVERS, requires=["forgectrl.auth"],
description="The bench seed: the dev image's forgetest init script writes "
"/run/forgefirm/commissioning-override at boot. With it present, GET /wiz "
"/run/forgefirm/setup-override at boot. With it present, GET /wiz "
"reports the override and an open gate, the reason names the wizards it lifts "
"when any is required, and GET /mode reports the controller running and not "
"gated. The file lives on tmpfs (/proc/mounts), so a reboot removes it by "
@@ -486,9 +486,9 @@ def override_until_reboot(ctx):
# ------------------------------------------------------------ advisories
@test("commission.advisories-rehash", title="An advisory is accepted at its current hash only",
subsystem="commission", kind="auto", hardware="takeover", est_min=3,
covers=[("forgectrl", "src/advisories.*"), ("forgectrl", "src/commission.*"),
@test("setup.advisories-rehash", title="An advisory is accepted at its current hash only",
subsystem="setup", kind="auto", hardware="takeover", est_min=3,
covers=[("forgectrl", "src/advisories.*"), ("forgectrl", "src/setup.*"),
("forgectrl", "src/wiz.*"), ("forgectrl", "src/sha256.*"), ("forgectrl", "src/main.c"),
("forgectrl", "src/ui/md.js"), ("forgectrl", "src/ui/embed_docs.cmake")],
requires=["forgectrl.auth"],
@@ -504,7 +504,7 @@ def advisories_rehash(ctx):
fc = ctx.forgectrl
ev = ctx.evidence
raw = read_file(record_path())
ctx.check(raw, "no commissioning record at %s", record_path())
ctx.check(raw, "no setup record at %s", record_path())
before = wiz(fc)
ev["before"] = wiz_summary(before)
docs = {d.get("id"): d for d in before.get("documents") or []}
@@ -579,7 +579,7 @@ def advisories_rehash(ctx):
# -------------------------------------------------------------- the login
@test("commission.account-login", title="The panel login over HTTPS", subsystem="commission",
@test("setup.account-login", title="The panel login over HTTPS", subsystem="setup",
kind="auto", est_min=2,
covers=[("forgectrl", "src/auth.*"), ("forgectrl", "src/session.*"), ("forgectrl", "src/users.*"),
("forgectrl", "src/tls.*"), ("forgectrl", "src/peer.*"), ("forgectrl", "src/main.c"),
@@ -702,7 +702,7 @@ def login_checks(ctx, name, pw):
# ------------------------------------------------------- HTTPS boundary
@test("commission.https-only-writes", title="Writes from the LAN go to HTTPS", subsystem="commission",
@test("setup.https-only-writes", title="Writes from the LAN go to HTTPS", subsystem="setup",
kind="auto", est_min=1,
covers=[("forgectrl", "src/auth.*"), ("forgectrl", "src/tls.*"), ("forgectrl", "src/peer.*"),
("forgectrl", "src/main.c")],
@@ -802,8 +802,8 @@ def restore_ssh(fc, before):
write_file(flag, b"" if before.get("enabled") else None)
@test("commission.ssh-until-reboot", title="SSH is switched from the panel until the next reboot",
subsystem="commission", kind="auto", est_min=1,
@test("setup.ssh-until-reboot", title="SSH is switched from the panel until the next reboot",
subsystem="setup", kind="auto", est_min=1,
covers=[("forgectrl", "src/main.c"), ("forgectrl", "src/paths.h")],
requires=["forgectrl.auth"],
description="GET /system/ssh reports enabled, running, and dev_image, and agrees with the "
@@ -888,8 +888,8 @@ def ssh_until_reboot(ctx):
# ---------------------------------------------------------------- cloud
@test("commission.cloud-disabled-surface", title="Nothing points at the cloud while it is off",
subsystem="commission", kind="auto", mode="grbl", est_min=1,
@test("setup.cloud-disabled-surface", title="Nothing points at the cloud while it is off",
subsystem="setup", kind="auto", mode="grbl", est_min=1,
covers=[("forgectrl", "src/main.c"), ("forgectrl", "src/super.c"), ("forgectrl", "src/settings.*"),
("forgectrl", "src/wiz.c"), ("forgectrl", "src/hooks.h")],
requires=["forgectrl.settings-bounds"],
@@ -971,8 +971,8 @@ def cloud_disabled_surface(ctx):
# ---------------------------------------------------------- the operator
@test("commission.factory-return", title="The return to the factory firmware is guarded",
subsystem="commission", kind="auto", est_min=1,
@test("setup.factory-return", title="The return to the factory firmware is guarded",
subsystem="setup", kind="auto", est_min=1,
covers=[("forgectrl", "src/update.*"), ("forgectrl", "src/main.c"), ("forgectrl", "src/ui/wizard.js")],
requires=["update.slots-and-signature"],
description="The return itself reboots the machine as a Glowforge, so it never runs from the "
@@ -1034,8 +1034,8 @@ def cmdlines():
return out
@test("commission.machine-name", title="The machine names itself from its MAC address",
subsystem="commission", kind="auto", est_min=1,
@test("setup.machine-name", title="The machine names itself from its MAC address",
subsystem="setup", kind="auto", est_min=1,
requires=["forgectrl.auth"],
description="The machine calls itself forgefirm-<xxxx>, where xxxx is the last four hex "
"digits of its WiFi MAC address, so two machines on one network answer to "
@@ -1213,12 +1213,12 @@ def finished_first_run(ctx):
"the controller did not come back after the setup: %s", m)
_FIRST_RUN_REQUIRES = ["commission.gate-blocks-controllers", "commission.advisories-rehash"]
_FIRST_RUN_REQUIRES = ["setup.gate-blocks-controllers", "setup.advisories-rehash"]
@test("commission.first-run-flow", title="The first run, driven as the page drives it", subsystem="commission",
@test("setup.first-run-flow", title="The first run, driven as the page drives it", subsystem="setup",
kind="operator", hardware="takeover", est_min=3,
covers=[("forgectrl", "src/wiz.*"), ("forgectrl", "src/commission.*"), ("forgectrl", "src/users.*"),
covers=[("forgectrl", "src/wiz.*"), ("forgectrl", "src/setup.*"), ("forgectrl", "src/users.*"),
("forgectrl", "src/session.*"), ("forgectrl", "src/button.*"), ("forgectrl", "src/led.*"),
("forgectrl", "src/sheetid.*"), ("forgectrl", "src/advisories.*"), ("forgectrl", "src/hooks.h"),
("forgectrl", "src/main.c")],
@@ -1309,11 +1309,11 @@ def first_run_flow(ctx):
ctx.log("the run completed: gate open, the controller back")
@test("commission.first-run-page", title="The first-run page, walked once", subsystem="commission",
@test("setup.first-run-page", title="The first-run page, walked once", subsystem="setup",
kind="operator", hardware="takeover", est_min=6,
covers=[("forgectrl", "src/ui/wizard.*"), ("forgectrl", "src/ui/forms.js"), ("forgectrl", "src/ui/md.js"),
("forgectrl", "src/ui/login.*"), ("forgectrl", "src/ui/embed_docs.cmake"), ("forgectrl", "src/tls.*")],
requires=_FIRST_RUN_REQUIRES + ["commission.first-run-flow"],
requires=_FIRST_RUN_REQUIRES + ["setup.first-run-flow"],
actions=["button"], hands=["workstation"],
steps=["From the workstation open https://<address>/ and accept the self-signed certificate.",
"Click through the setup: scroll each document to its end and accept it (type I "
@@ -1328,7 +1328,7 @@ def first_run_flow(ctx):
"clicks through (the documents accepted, the press step with the LEDs "
"breathing teal, the account created, the run complete, the gate open, the "
"controller back). The operator confirms the panel opened. Everything the run "
"wrote is restored as commission.first-run-flow restores it.")
"wrote is restored as setup.first-run-flow restores it.")
def first_run_page(ctx):
fc = ctx.forgectrl
ev = ctx.evidence
@@ -1364,8 +1364,8 @@ def first_run_page(ctx):
ctx.confirm("Did the control panel open after Finish?")
@test("commission.cert-page", title="The certificate is checkable before the warning",
subsystem="commission", kind="auto", est_min=1,
@test("setup.cert-page", title="The certificate is checkable before the warning",
subsystem="setup", kind="auto", est_min=1,
covers=[("forgectrl", "src/tls.*"), ("forgectrl", "src/main.c"),
("forgectrl", "src/ui/wizard.html"), ("forgectrl", "src/ui/wizard.js")],
requires=["forgectrl.auth"],
@@ -1423,9 +1423,9 @@ def flag_of(w, level, wid):
return None
@test("commission.what-changed", title="A replaced part asks for its checks again",
subsystem="commission", kind="auto", est_min=2, hardware="takeover",
covers=[("forgectrl", "src/commission.*"), ("forgectrl", "src/wiz.*"), ("forgectrl", "src/main.c"),
@test("setup.what-changed", title="A replaced part asks for its checks again",
subsystem="setup", kind="auto", est_min=2, hardware="takeover",
covers=[("forgectrl", "src/setup.*"), ("forgectrl", "src/wiz.*"), ("forgectrl", "src/main.c"),
("forgectrl", "src/ui/panel.js"), ("forgectrl", "src/ui/index.html"),
("forgectrl", "src/ui/help.js")],
requires=["forgectrl.auth"],
@@ -1493,9 +1493,9 @@ def what_changed(ctx):
ctx.check(bool(mode(fc).get("gated")) == gated_before, "the gate does not read as before")
@test("commission.record-export", title="The record: JSON, the printable page, and the log bundle",
subsystem="commission", kind="auto", est_min=2,
covers=[("forgectrl", "src/commission.*"), ("forgectrl", "src/recordhtml.*"), ("forgectrl", "src/wiz.*"),
@test("setup.record-export", title="The record: JSON, the printable page, and the log bundle",
subsystem="setup", kind="auto", est_min=2,
covers=[("forgectrl", "src/setup.*"), ("forgectrl", "src/recordhtml.*"), ("forgectrl", "src/wiz.*"),
("forgectrl", "src/logs.*"), ("forgectrl", "src/main.c"), ("forgectrl", "src/ui/index.html"),
("forgectrl", "src/ui/panel.js"), ("forgectrl", "src/ui/wizard.html")],
requires=["forgectrl.auth", "logs.tree-tail-export"],
@@ -1504,7 +1504,7 @@ def what_changed(ctx):
"named after the sheet id; GET /wiz/record.html is a page with no script that "
"carries the sheet id and every completed wizard; without the token and without "
"a login both are refused (403); and the sanitized log export carries "
"system/commissioning.json, parseable, with the same sheet id and no panel token.")
"system/setup.json, parseable, with the same sheet id and no panel token.")
def record_export(ctx):
import gzip
import io
@@ -1544,7 +1544,7 @@ def record_export(ctx):
ctx.check(st == 200 and "text/html" in (hdrs.get("content-type") or ""), "GET /wiz/record.html -> %s %s",
st, hdrs.get("content-type"))
ctx.check("<script" not in page.lower(), "the printable page carries a script")
ctx.check(sid in page and "commissioning record" in page, "the page lacks the sheet id or its title")
ctx.check(sid in page and "setup record" in page, "the page lacks the sheet id or its title")
titles = {z["id"]: z["title"] for z in w.get("wizards") or []}
missing = [d for d in done if titles.get(d) and ("<h3>" + titles[d]) not in page]
ev["missing_on_page"] = missing
@@ -1565,11 +1565,11 @@ def record_export(ctx):
try:
tf = tarfile.open(fileobj=io.BytesIO(gzip.decompress(data)))
for m in tf.getmembers():
if m.isfile() and m.name.endswith("system/commissioning.json"):
if m.isfile() and m.name.endswith("system/setup.json"):
member = tf.extractfile(m).read()
except (OSError, tarfile.TarError, EOFError) as e:
ctx.fail("export is not a readable tar.gz: %s", e)
ctx.check(member is not None, "the bundle lacks system/commissioning.json")
ctx.check(member is not None, "the bundle lacks system/setup.json")
if member is not None:
try:
inside = json.loads(member.decode("utf-8"))
@@ -1587,12 +1587,12 @@ def record_export(ctx):
ctx.log("record %d wizards; page %d bytes; bundle carries the record", len(done), ev["page_bytes"])
@test("commission.mirror", title="One browser drives a check; a second one follows",
subsystem="commission", kind="auto", est_min=2, hardware="takeover",
@test("setup.mirror", title="One browser drives a check; a second one follows",
subsystem="setup", kind="auto", est_min=2, hardware="takeover",
covers=[("forgectrl", "src/wizdark.*"), ("forgectrl", "src/wizcalc.*"), ("forgectrl", "src/wiz.*"),
("forgectrl", "src/session.*"), ("forgectrl", "src/ui/wizard.js"),
("forgectrl", "src/ui/wizard.html"), ("forgectrl", "src/ui/wizard.css")],
requires=["forgectrl.auth", "commission.account-login", "commission.check-sensors"],
requires=["forgectrl.auth", "setup.account-login", "setup.check-sensors"],
description="With a temporary account (made and removed as the login test does) and two "
"login sessions: the first session starts the sensors check; GET /wiz/dark "
"reports the run as owned and its own to the first session, not to the second, "
@@ -1,4 +1,4 @@
"""commission.check-* - the setup's checks (the dark wizards), driven the
"""setup.check-* - the setup's checks (the dark wizards), driven the
way the page drives them: POST /wiz/<id>/start, GET /wiz/dark polled,
the prompts answered from here (the bench fixture works the lid and the
button; a confirmation is answered yes once the snapshot exists), the
@@ -12,7 +12,7 @@ import time
from ..catalog import test
from .. import hw
from ..baseline import read_position
from .commission import wiz, Restore # noqa: F401 - Restore is re-exported for the sheet
from .setup import wiz, Restore # noqa: F401 - Restore is re-exported for the sheet
POLL_S = 1.0
# What the machine's own press prompt says, used when it sends no text.
@@ -22,7 +22,7 @@ PRESS_TEXT = "The button is lit white. Press it now: the laser fires after your
# (74) for the first seconds and settles near 630.
PURGE_SPINUP_S = 30
DARK_COVERS = [("forgectrl", "src/wizdark.*"), ("forgectrl", "src/wizcalc.*"),
("forgectrl", "src/wiz.*"), ("forgectrl", "src/commission.*"),
("forgectrl", "src/wiz.*"), ("forgectrl", "src/setup.*"),
("forgectrl", "src/main.c"), ("forgectrl", "src/ui/wizard.*")]
@@ -113,8 +113,8 @@ def run_check(ctx, wid, on_prompt, timeout_s):
return last
@test("commission.check-switches", title="The switches check follows the lid and the button",
subsystem="commission", kind="operator", est_min=3,
@test("setup.check-switches", title="The switches check follows the lid and the button",
subsystem="setup", kind="operator", est_min=3,
covers=DARK_COVERS + [("forgectrl", "src/status.c")],
requires=["forgectrl.auth"], actions=["lid", "button"],
description="POST /wiz/switches/start; the check asks for the lid to open and close and for "
@@ -148,30 +148,40 @@ def check_switches(ctx):
ctx.log("switches: %s", json.dumps(r))
@test("commission.check-sensors", title="The sensors check reads a plausible machine at rest",
subsystem="commission", kind="auto", est_min=2,
@test("setup.check-sensors", title="The sensors check reads a plausible machine at rest",
subsystem="setup", kind="auto", est_min=2,
covers=DARK_COVERS + [("forgectrl", "src/status.c"), ("forgectrl", "src/accel.c"),
("forgectrl", "src/cool.c")],
requires=["forgectrl.auth"],
description="POST /wiz/sensors/start; ten seconds of readings, then the room-temperature "
"prompt, answered Skip so no offset is written. The result carries both coolant "
"temperatures, the chassis and SoC, the lid IR maxima, the accelerometer event "
"count, the supply power-good, the HV current, and the idle fan speeds; the "
"record carries sensors at version 1 and cool_temp_offset_c reads as before.")
description="POST /wiz/sensors/start; ten seconds of readings and no question asked. The "
"result carries both coolant temperatures, the chassis and SoC, the lid IR "
"maxima, the accelerometer event count, the supply power-good, the HV current, "
"and the idle fan speeds; the record carries sensors at version 1 and the "
"settings read as before (the check writes none).")
def check_sensors(ctx):
with Restore(ctx, ["cool_temp_offset_c"]):
last = run_check(ctx, "sensors", lambda p: "Skip" if p.get("id") == "room-temp" else None, 120)
r = last.get("result") or {}
for k in ("coolant_down_c", "coolant_up_c", "lid_ir_max", "laser_pgood", "hv_current_max",
"exhaust_rpm_idle", "intake_rpm_idle"):
ctx.check(k in r, "the result lacks %s: %s", k, r)
ctx.check(r.get("laser_pgood") == 1, "power-good read %s", r.get("laser_pgood"))
ctx.log("sensors: coolant %s/%s C, IR %s, HV %s", r.get("coolant_down_c"), r.get("coolant_up_c"),
r.get("lid_ir_max"), r.get("hv_current_max"))
fc = ctx.forgectrl
ev = ctx.evidence
before = fc.settings() or {}
ev["settings_before"] = dict(before)
def on_prompt(p):
ctx.fail("the sensors check asked a question: %s", p)
last = run_check(ctx, "sensors", on_prompt, 120)
r = last.get("result") or {}
for k in ("coolant_down_c", "coolant_up_c", "lid_ir_max", "laser_pgood", "hv_current_max",
"exhaust_rpm_idle", "intake_rpm_idle"):
ctx.check(k in r, "the result lacks %s: %s", k, r)
ctx.check(r.get("laser_pgood") == 1, "power-good read %s", r.get("laser_pgood"))
after = fc.settings() or {}
changed = {k: (before.get(k), after.get(k)) for k in set(before) | set(after)
if before.get(k) != after.get(k)}
ctx.check(not changed, "the sensors check wrote a setting: %s", changed)
ctx.log("sensors: coolant %s/%s C, IR %s, HV %s", r.get("coolant_down_c"), r.get("coolant_up_c"),
r.get("lid_ir_max"), r.get("hv_current_max"))
@test("commission.check-airflow", title="The airflow check measures the fans and sets the floors",
subsystem="commission", kind="auto", hardware="takeover", est_min=3,
@test("setup.check-airflow", title="The airflow check measures the fans and sets the floors",
subsystem="setup", kind="auto", hardware="takeover", est_min=3,
covers=DARK_COVERS + [("forgectrl", "src/cool.c"), ("forgectrl", "src/airflow.*"),
("forgectrl", "src/gates.c"), ("forgectrl", "src/super.c")],
requires=["forgectrl.auth", "cooling.fan-gate-trips"],
@@ -229,8 +239,8 @@ def check_airflow(ctx):
ctx.check(ok is not None, "the controller did not come back after the check")
@test("commission.check-cameras", title="The cameras check captures both cameras",
subsystem="commission", kind="operator", est_min=2,
@test("setup.check-cameras", title="The cameras check captures both cameras",
subsystem="setup", kind="operator", est_min=2,
covers=DARK_COVERS + [("forgectrl", "src/cam.c")],
requires=["forgectrl.auth", "camera.snapshot"], actions=["lid"],
description="POST /wiz/cameras/start with the lid closed: a lid snapshot, the question, a "
@@ -264,8 +274,8 @@ def check_cameras(ctx):
shots.get("lid", {}).get("bytes", 0), shots.get("head", {}).get("bytes", 0))
@test("commission.check-motion", title="The motion check proves the rail, the lens reference, and the jogs",
subsystem="commission", kind="auto", hardware="takeover", est_min=5,
@test("setup.check-motion", title="The motion check proves the rail, the lens reference, and the jogs",
subsystem="setup", kind="auto", hardware="takeover", est_min=5,
covers=DARK_COVERS + [("forgectrl", "src/super.c"), ("forgectrl", "src/liveness.c"),
("forgectrl", "src/lenshome.*"),
("forgectrl", "src/accel.c"), ("forgectrl", "src/cool.c")],
@@ -305,8 +315,8 @@ def check_motion(ctx):
r.get("rest"), {k: (v.get("p2p_lp_x"), v.get("p2p_lp_y")) for k, v in moves.items()})
@test("commission.check-flow-verify", title="The flow check runs as a setup check",
subsystem="commission", kind="auto", hardware="takeover", est_min=5,
@test("setup.check-flow-verify", title="The flow check runs as a setup check",
subsystem="setup", kind="auto", hardware="takeover", est_min=5,
covers=DARK_COVERS + [("forgectrl", "src/diag.c")],
requires=["forgectrl.auth", "cooling.aa-offset-calibrate", "cooling.flow-verify"],
description="POST /wiz/cooling.flow-verify/start drives the flow-verify diagnostic through the "
@@ -323,8 +333,8 @@ def check_flow_verify(ctx):
r.get("flow_rise"), r.get("noflow_rise"), r.get("thin_margin"))
@test("commission.cloud-header-capture", title="The cloud header check takes one print's envelope",
subsystem="commission", kind="operator", hardware="takeover", mode="grbl", est_min=8,
@test("setup.cloud-header-capture", title="The cloud header check takes one print's envelope",
subsystem="setup", kind="operator", hardware="takeover", mode="grbl", est_min=8,
covers=DARK_COVERS + [("forgectrl", "src/super.c"),
("python3-gfhardware", "gfhardware/machine.py"),
("python3-gfhardware", "forgefirm-app/gfcloud.py"),
@@ -1,4 +1,4 @@
"""commission.sheet - the setup's sheet (the live wizards) in one run,
"""setup.sheet - the setup's sheet (the live wizards) in one run,
driven the way the page drives them: POST /wiz/<id>/start, GET /wiz/dark
polled, the prompts answered from here, each burn armed by a press, the
result judged, and every setting a wizard wrote put back as found. One
@@ -18,13 +18,13 @@ restored. The page is where the real numbers go in.
import re
from ..catalog import test
from .commission_dark import run_check, Restore
from .setup_dark import run_check, Restore
SHEET_COVERS = [("forgectrl", "src/wizlive.*"), ("forgectrl", "src/wizrun.h"),
("forgectrl", "src/lens.*"), ("forgectrl", "src/sheet.*"),
("forgectrl", "src/font_hershey.*"), ("forgectrl", "src/jobstream.*"),
("forgectrl", "src/curverec.*"), ("forgectrl", "src/wizdark.*"),
("forgectrl", "src/wiz.*"), ("forgectrl", "src/commission.*"),
("forgectrl", "src/wiz.*"), ("forgectrl", "src/setup.*"),
("forgectrl", "src/main.c"), ("forgectrl", "src/ui/wizard.*"),
("forgectrl", "src/super.c"),
("forgectrl", "src/status.c"), ("forgectrl", "src/cool.c"),
@@ -222,10 +222,10 @@ def burn(ctx, wid, answers, want):
file_card(ctx, wid)
@test("commission.sheet", title="The commissioning sheet: the placement, the frame, and the five cards",
subsystem="commission", kind="live", hardware="takeover", mode="grbl", est_min=35,
@test("setup.sheet", title="The setup sheet: the placement, the frame, and the five cards",
subsystem="setup", kind="live", hardware="takeover", mode="grbl", est_min=35,
covers=SHEET_COVERS,
requires=["forgectrl.auth", "commission.check-motion", "laser.emission-witness", "cooling.flow-verify"],
requires=["forgectrl.auth", "setup.check-motion", "laser.emission-witness", "cooling.flow-verify"],
actions=["button"], hands=["scrap"],
steps=["A piece of wood at least 200 x 150 mm (8 x 6 in) on the bed, pushed as far left as "
"it goes with its top edge at the top of the cut area (the head's home corner); "