Retire next-work item 1: the gap question is answered from the chain

The button latch is SET by lid-open or the SoC lock and RESET by the
button only; the charge-pump watchdog feeds HV_ENABLE, not the latch.
A kernel-run gap inside an armed job drops HV_ENABLE and leaves the
latch alone, and the next run has HV_ENABLE back before its first step.
No keepalive.

laser.emission-witness carries a G4 P2 between the second and third
sides of its square and checks cnc/button_latch clear in every armed
sample, HV_ENABLE dropped across the dwell and back with emission after
it; the operator confirms all four sides.

BRINGUP: item 1 removed, items 2 to 21 are now 1 to 20, the five
cross-references follow, the flow-band sentence is in the facts bank.
CAMPAIGN-LOG: the answer, the retired item, and the unattended set run
green on the hot-deployed board (campaign c-20260831151846).
This commit is contained in:
ScottW514
2026-08-31 11:37:53 -04:00
parent eb8935c9b1
commit 85d266e572
3 changed files with 128 additions and 42 deletions
+29 -32
View File
@@ -355,7 +355,7 @@ factory 2.6.0-2228 session; measured numbers in the facts bank).
alike: the retrace is sized to `cnc/max_backtrack` and the lead follows it, alike: the retrace is sized to `cnc/max_backtrack` and the lead follows it,
so a pause with little history behind it shortens both rather than failing. so a pause with little history behind it shortens both rather than failing.
GRBL mode uses feed hold / cycle start, so a resumed GRBL cut picks up where GRBL mode uses feed hold / cycle start, so a resumed GRBL cut picks up where
the deceleration ended (item 16). A pause is not a cancel: the latch the deceleration ended (item 15). A pause is not a cancel: the latch
stays unlocked and the window open across it. There is no resume dwell: the stays unlocked and the window open across it. There is no resume dwell: the
safing chain re-arms ~216 ms before the first step (facts bank). safing chain re-arms ~216 ms before the first step (facts bank).
- **`lid_policy = hold`** selects stock grblHAL door behavior instead (park in - **`lid_policy = hold`** selects stock grblHAL door behavior instead (park in
@@ -958,7 +958,10 @@ is committed.
coolant thermistor conversion is the factory B-equation recovered from the coolant thermistor conversion is the factory B-equation recovered from the
v2.6.0 binary — derivation in `kernel-module-glowforge/UAPI.md`; the old v2.6.0 binary — derivation in `kernel-module-glowforge/UAPI.md`; the old
UAPI "best guess" linear formula was 3–5 °C high and everything derived from UAPI "best guess" linear formula was 3–5 °C high and everything derived from
it had to be re-derived. it had to be re-derived. The flow check's bands hold from 19 to 27 C, the
loop heater's ceiling in a 20 C room, with the margin widening warm; above
that only a running tube warms the loop, and the check takes the tube's
share off.
- **The four `pic/lid_ir_*` channels are first of all a photometer for the lid - **The four `pic/lid_ir_*` channels are first of all a photometer for the lid
lamp.** Measured against `lid_led` (sysfs brightness, 0 to 1023): all four lamp.** Measured against `lid_led` (sysfs brightness, 0 to 1023): all four
channels follow it as a straight line, 2 counts dark, 32 to 35 at 128, 54 to channels follow it as a straight line, 2 counts dark, 32 to 35 at 128, 54 to
@@ -1096,13 +1099,7 @@ is committed.
Open items only. Anything closed is in `CAMPAIGN-LOG.md`. Open items only. Anything closed is in `CAMPAIGN-LOG.md`.
1. **Laser commissioning leftovers.** Verify the hardware button latch persists 1. **Low-temperature gates and warm-up (planned).** Two keys in the Cooling
across kernel-run gaps mid-job (if OK_2_FIRE drops between motion bursts, the
fix is a stream keepalive across armed gaps). The flow check's bands
hold from 19 to 27 C, the loop heater's ceiling in a 20 C room, with the
margin widening warm; above that only a running tube warms the loop,
and the check takes the tube's share off.
2. **Low-temperature gates and warm-up (planned).** Two keys in the Cooling
card: `cool_temp_min` (hard floor, default ~5 °C, a fire gate) and card: `cool_temp_min` (hard floor, default ~5 °C, a fire gate) and
`cool_temp_start` (warm-up gate, default ~16 °C) — a job starting below the `cool_temp_start` (warm-up gate, default ~16 °C) — a job starting below the
gate holds in a factory-style warm-up phase with the loop heater on and gate holds in a factory-style warm-up phase with the loop heater on and
@@ -1111,7 +1108,7 @@ Open items only. Anything closed is in `CAMPAIGN-LOG.md`.
coolant. Sequencing: warm-up first, flow check after. Measured physics on coolant. Sequencing: warm-up first, flow check after. Measured physics on
this bench: 50 % duty warms the bulk ~0.5–0.8 °C/min and plateaus ~8–9 °C this bench: 50 % duty warms the bulk ~0.5–0.8 °C/min and plateaus ~8–9 °C
above ambient — the same unaided limit the factory has. above ambient — the same unaided limit the factory has.
3. **TEC handling (planned).** `thermal/tec_on` is a bare on/off output with no 2. **TEC handling (planned).** `thermal/tec_on` is a bare on/off output with no
readback, so presence cannot be detected: it becomes a `tec_present` user readback, so presence cannot be detected: it becomes a `tec_present` user
setting (Machine tab, default off; ForgeFIRM never drives `tec_on` unless setting (Machine tab, default off; ForgeFIRM never drives `tec_on` unless
set), which also covers retrofits. Operation when present: simple hysteresis set), which also covers retrofits. Operation when present: simple hysteresis
@@ -1123,7 +1120,7 @@ Open items only. Anything closed is in `CAMPAIGN-LOG.md`.
all is a spec-level claim (Glowforge ships it on the Pro; Basic/Plus use the all is a spec-level claim (Glowforge ships it on the Pro; Basic/Plus use the
same passive closed-loop cooling), not teardown-verified per unit — another same passive closed-loop cooling), not teardown-verified per unit — another
reason it is a setting. reason it is a setting.
4. **Fire watch (lid IR) redesign.** The gate stays disabled 3. **Fire watch (lid IR) redesign.** The gate stays disabled
(`cool_fire_ir_delta = 0`) until it is lamp-aware: the engine must own or (`cool_fire_ir_delta = 0`) until it is lamp-aware: the engine must own or
observe the lamp level (suspend the watch and re-baseline for a few ticks observe the lamp level (suspend the watch and re-baseline for a few ticks
after any `lid_led` change) and the threshold must be relative to the after any `lid_led` change) and the threshold must be relative to the
@@ -1146,11 +1143,11 @@ Open items only. Anything closed is in `CAMPAIGN-LOG.md`.
cloud job carries. By decision those header thresholds (`IR??`) are the cloud job carries. By decision those header thresholds (`IR??`) are the
prior for this redesign and nothing else: the cloud client declares them prior for this redesign and nothing else: the cloud client declares them
ignored, and the watch stays disabled until it is lamp-aware. ignored, and the watch stays disabled until it is lamp-aware.
5. **Limit-switch homing.** The planned second homing method (`$22` stays 0 4. **Limit-switch homing.** The planned second homing method (`$22` stays 0
until it lands); printable brackets are in `3d-models/`. Also: calibrate until it lands); printable brackets are in `3d-models/`. Also: calibrate
`gfcloud_home_x/y` against a jog to a known reference if the factory corner `gfcloud_home_x/y` against a jog to a known reference if the factory corner
offset matters. offset matters.
6. **Cameras.** **First light on an 8 MP (OV8856) machine**: the 5. **Cameras.** **First light on an 8 MP (OV8856) machine**: the
whole path is written but nothing has run on one, and only that hardware can whole path is written but nothing has run on one, and only that hardware can
answer whether the 2-lane RAW8 full-resolution mode locks the D-PHY at answer whether the 2-lane RAW8 full-resolution mode locks the D-PHY at
720 Mbps/lane and what exposure/gain the sensor wants; the details, the 720 Mbps/lane and what exposure/gain the sensor wants; the details, the
@@ -1165,7 +1162,7 @@ Open items only. Anything closed is in `CAMPAIGN-LOG.md`.
no register file. No shipped machine applies a per-unit shading table, no register file. No shipped machine applies a per-unit shading table,
so ForgeFIRM owes none. Finally the deferred emulator so ForgeFIRM owes none. Finally the deferred emulator
homing-image smoke, now that the emulator can be pointed at live snapshots. homing-image smoke, now that the emulator can be pointed at live snapshots.
7. **Cloud mode.** A print is no longer capped by the ring: the client holds 6. **Cloud mode.** A print is no longer capped by the ring: the client holds
the compressed body, fills the ring before the button, and tops it up as it the compressed body, fills the ring before the button, and tops it up as it
plays, with the body bounded by `pulse_reject_threshold_bytes` because plays, with the body bounded by `pulse_reject_threshold_bytes` because
memory is what that costs. A feed that wedges is caught by progress rather memory is what that costs. A feed that wedges is caught by progress rather
@@ -1207,7 +1204,7 @@ Open items only. Anything closed is in `CAMPAIGN-LOG.md`.
the cancel-with-a-rejected-`settings`-action case, a malformed frame the cancel-with-a-rejected-`settings`-action case, a malformed frame
(needs a MITM), a body past the memory guard (the service has no such job (needs a MITM), a body past the memory guard (the service has no such job
to send), and a wedged feed (a healthy machine will not stall on request). to send), and a wedged feed (a healthy machine will not stall on request).
8. **Shared machine services — remaining polish.** None of it blocking: 7. **Shared machine services — remaining polish.** None of it blocking:
- **Diagnostics as engine modes.** The flow tools still drive the thermal - **Diagnostics as engine modes.** The flow tools still drive the thermal
hardware themselves while the engine suspends its writes; the check hardware themselves while the engine suspends its writes; the check
parameters are already shared (`cool.h`), so what remains is folding the parameters are already shared (`cool.h`), so what remains is folding the
@@ -1223,26 +1220,26 @@ Open items only. Anything closed is in `CAMPAIGN-LOG.md`.
`ensure_engine` `popen()`s should move out of the HTTP callback so a slow `ensure_engine` `popen()`s should move out of the HTTP callback so a slow
media-ctl cannot stall the request thread. Changing the MHD start flags media-ctl cannot stall the request thread. Changing the MHD start flags
touches the streaming model, so this wants a bench slot of its own. touches the streaming model, so this wants a bench slot of its own.
9. **Physical-evidence negatives still open.** A present head answering I²C 8. **Physical-evidence negatives still open.** A present head answering I²C
badly (the K-11 runtime case) and a failed head capture leaving the measure badly (the K-11 runtime case) and a failed head capture leaving the measure
laser off — both need the head connected and a fault injected. Opportunistic: laser off — both need the head connected and a fault injected. Opportunistic:
`STATE_FAULT` recovery via `enable` the next time a DRV8825 fault line `STATE_FAULT` recovery via `enable` the next time a DRV8825 fault line
actually trips. actually trips.
10. **Debug-kernel checks.** Module load/unload under `CONFIG_DEBUG_MUTEXES` 9. **Debug-kernel checks.** Module load/unload under `CONFIG_DEBUG_MUTEXES`
and a forced `-EPROBE_DEFER` unwind still need a debug kernel build. Both and a forced `-EPROBE_DEFER` unwind still need a debug kernel build. Both
drills cycle what the rail policy avoids: a module unload powers the 40 V drills cycle what the rail policy avoids: a module unload powers the 40 V
rail off (a stepper driver can come out of the power-up unserviceable), rail off (a stepper driver can come out of the power-up unserviceable),
and a forced defer needs the 40 V regulator or the SDMA device unbound and a forced defer needs the 40 V regulator or the SDMA device unbound
under the module's probe. This is a bench slot with the rail-cycle gamble under the module's probe. This is a bench slot with the rail-cycle gamble
accepted, not a quick check. accepted, not a quick check.
11. **Wi-Fi SDIO CRC watch.** The uSDHC pads now carry the factory-exact values 10. **Wi-Fi SDIO CRC watch.** The uSDHC pads now carry the factory-exact values
and ship in every image. Watch `dmesg | grep -c "sdio .* failed"` across and ship in every image. Watch `dmesg | grep -c "sdio .* failed"` across
sessions (baseline: 1 event in 49 min of uptime). Effect if one lands sessions (baseline: 1 event in 49 min of uptime). Effect if one lands
mid-job: a 1–2 s sender stall — a cut-quality nuisance, never a safety mid-job: a 1–2 s sender stall — a cut-quality nuisance, never a safety
matter. Only if it still recurs, cap the bus with matter. Only if it still recurs, cap the bus with
`max-frequency = <25000000>` on `&usdhc1` (halves Wi-Fi throughput — last `max-frequency = <25000000>` on `&usdhc1` (halves Wi-Fi throughput — last
resort; the factory ran 50 MHz on these pads). resort; the factory ran 50 MHz on these pads).
12. **Release acceptance follow-through.** The campaign is the release gate 11. **Release acceptance follow-through.** The campaign is the release gate
and runs as designed: dev image `20260824230512`, 45 of 45 from nothing, and runs as designed: dev image `20260824230512`, 45 of 45 from nothing,
36 of them unattended with the bench actuator in the loop, release 36 of them unattended with the bench actuator in the loop, release
authorized (the export is on the board at `/data/forgetest/export/`). authorized (the export is on the board at `/data/forgetest/export/`).
@@ -1256,16 +1253,16 @@ Open items only. Anything closed is in `CAMPAIGN-LOG.md`.
changes off the offline tests (a gfutilities refactor, not a map). changes off the offline tests (a gfutilities refactor, not a map).
Tools that genuinely need a second host (LAN flood, remote auth probes) Tools that genuinely need a second host (LAN flood, remote auth probes)
stay host-side by design, and the registry marks them so. The first stay host-side by design, and the registry marks them so. The first
release is item 13. release is item 12.
13. **Publish.** The first release: `releases/v<version>/acceptance.json` 12. **Publish.** The first release: `releases/v<version>/acceptance.json`
from the authorized export, `scripts/release.sh`, the kas flip and the from the authorized export, `scripts/release.sh`, the kas flip and the
first GitHub release, per the site (Developers, "Release flow"), once first GitHub release, per the site (Developers, "Release flow"), once
ready to publish. Repoint the core submodule to ready to publish. Repoint the core submodule to
upstream if the `step_us_min` sizing fix merges. upstream if the `step_us_min` sizing fix merges.
14. **Update system Phase 5 — recovery refresh.** The remaining phase of 13. **Update system Phase 5 — recovery refresh.** The remaining phase of
`docs/UPDATE-SYSTEM.md` (a refreshed recovery image in boot0); Phases 0–4 `docs/UPDATE-SYSTEM.md` (a refreshed recovery image in boot0); Phases 0–4
are done. are done.
15. **Head-IRQ source validation — beam-emission hypothesis (exploratory, not 14. **Head-IRQ source validation — beam-emission hypothesis (exploratory, not
gating).** The EV_SW `head` bit (GPIO3_22, factory pad HEAD_IRQ) is the head gating).** The EV_SW `head` bit (GPIO3_22, factory pad HEAD_IRQ) is the head
MCU's attention line — idle LOW with a healthy head, pulsing on head reboot, MCU's attention line — idle LOW with a healthy head, pulsing on head reboot,
floating to the SoC pull-up with no head — so the raw level is not a floating to the SoC pull-up with no head — so the raw level is not a
@@ -1281,7 +1278,7 @@ Open items only. Anything closed is in `CAMPAIGN-LOG.md`.
log EV_SW head-bit edges plus `head/beam_detect_digital|_analog` while log EV_SW head-bit edges plus `head/beam_detect_digital|_analog` while
firing. firing.
16. **Gapless pause and resume in GRBL mode (planned).** A pause leaves a mark 15. **Gapless pause and resume in GRBL mode (planned).** A pause leaves a mark
in the cut. With laser mode on, the core stops the beam at the start of the in the cut. With laser mode on, the core stops the beam at the start of the
hold (`disable_laser_during_hold`, on by default), so the head travels the hold (`disable_laser_during_hold`, on by default), so the head travels the
whole deceleration dark, and the resume re-accelerates from a standstill at whole deceleration dark, and the resume re-accelerates from a standstill at
@@ -1315,7 +1312,7 @@ Open items only. Anything closed is in `CAMPAIGN-LOG.md`.
line does to it, and how it composes with the armed window's disarm grace line does to it, and how it composes with the armed window's disarm grace
across a long hold. across a long hold.
17. **Head crash and rail-contact detector (planned).** The head 16. **Head crash and rail-contact detector (planned).** The head
accelerometer is the motion-liveness probe and nothing more; the accelerometer is the motion-liveness probe and nothing more; the
factory runs two tiers off the same sensor (a per-axis alert that factory runs two tiers off the same sensor (a per-axis alert that
pauses, a per-axis abort), and its thresholds arrive in every pulse pauses, a per-axis abort), and its thresholds arrive in every pulse
@@ -1327,7 +1324,7 @@ Open items only. Anything closed is in `CAMPAIGN-LOG.md`.
are established. A pause on contact, on the factory's shape, would be are established. A pause on contact, on the factory's shape, would be
the first use. the first use.
18. **A sender change while a job runs: discussion.** Today a sender that 17. **A sender change while a job runs: discussion.** Today a sender that
disconnects mid-job leaves the motion running to the end of what the disconnects mid-job leaves the motion running to the end of what the
controller holds, with the window closed and fire suppressed (the controller holds, with the window closed and fire suppressed (the
consent belonged to the displaced session), so the job finishes dark consent belonged to the displaced session), so the job finishes dark
@@ -1344,9 +1341,9 @@ Open items only. Anything closed is in `CAMPAIGN-LOG.md`.
a hold parks the head over hot material with the assist air on the run a hold parks the head over hot material with the assist air on the run
profile, and the grace then closes the window in Hold as it does today; profile, and the grace then closes the window in Hold as it does today;
running on leaves a clean stop position but wastes the piece. Decide running on leaves a clean stop position but wastes the piece. Decide
with the gapless pause and resume item (16), which owns the resume with the gapless pause and resume item (15), which owns the resume
mechanics. mechanics.
19. **The flow check while the tube is lit.** The arm-time heater check 18. **The flow check while the tube is lit.** The arm-time heater check
starts at the session open, so with a prompt press the tube is lit starts at the session open, so with a prompt press the tube is lit
for most of its window, and a lit CW window adds about 1.5 C to the for most of its window, and a lit CW window adds about 1.5 C to the
rise (0.5 C at 45 % density) against a 1.6 C margin; on top of that the rise (0.5 C at 45 % density) against a 1.6 C margin; on top of that the
@@ -1379,7 +1376,7 @@ Open items only. Anything closed is in `CAMPAIGN-LOG.md`.
remains; a scope on the two sensor lines during a cut is the next remains; a scope on the two sensor lines during a cut is the next
instrument. It sits inside the ceiling's 2 C hysteresis and the flow instrument. It sits inside the ceiling's 2 C hysteresis and the flow
check reads means, so it is a measurement item, not a gate item. check reads means, so it is a measurement item, not a gate item.
20. **Laser power-good: what the line means.** `cnc/laser_pgood` and its 19. **Laser power-good: what the line means.** `cnc/laser_pgood` and its
sampled count are defined in the UAPI (active low, one sample every sampled count are defined in the UAPI (active low, one sample every
~3.9 ms), the facts bank records that the sampled count reads 0 through ~3.9 ms), the facts bank records that the sampled count reads 0 through
real cutting, and the cooling engine warns real cutting, and the cooling engine warns
@@ -1391,7 +1388,7 @@ Open items only. Anything closed is in `CAMPAIGN-LOG.md`.
scope against `hv_current` through an armed cut, its meaning written scope against `hv_current` through an armed cut, its meaning written
into the facts bank and the UAPI, and then either a warning that means into the facts bank and the UAPI, and then either a warning that means
something or no warning. something or no warning.
21. **Initial commissioning: measure and set the machine's own numbers 20. **Initial commissioning: measure and set the machine's own numbers
methodically.** Every tunable that was measured on the bench machine methodically.** Every tunable that was measured on the bench machine
and shipped as a default varies from machine to machine: the flow and shipped as a default varies from machine to machine: the flow
check's bands and `cool_flow_rise`, the tube's heat coefficients check's bands and `cool_flow_rise`, the tube's heat coefficients
@@ -1427,7 +1424,7 @@ covers the warm-up hold), the supply temperature window (the service sends
the whole ADC range and the factory binds it to nothing; the supply is the whole ADC range and the factory binds it to nothing; the supply is
watched per job instead), the head, lid, interconnect and fused temperature watched per job instead), the head, lid, interconnect and fused temperature
ceilings (no sensor at those locations; the chassis is watched per job), the ceilings (no sensor at those locations; the chassis is watched per job), the
head accelerometer thresholds (item 17), the lid IR thresholds (item 4), the head accelerometer thresholds (item 16), the lid IR thresholds (item 3), the
HV current caps (the sampled emission witness covers the idle case, and HV HV current caps (the sampled emission witness covers the idle case, and HV
current is ranged per job), the thermal report upload conditions and the current is ranged per job), the thermal report upload conditions and the
pump flag. Beam detect stays with item 15. pump flag. Beam detect stays with item 14.
+62
View File
@@ -4726,6 +4726,55 @@ does not carry, and the 5 MP driver has no way to take one. BRINGUP
item 6 now says so, and the factory-slot session it asked for is not item 6 now says so, and the factory-slot session it asked for is not
needed. needed.
## 2026-08-31: the mid-job gap question, answered from the chain
Item 1 asked whether the hardware button latch persists across a
kernel-run gap inside an armed job, with a stream keepalive as the fix if
it did not. The safing chain answers it (`docs/SAFETY.md` section 2): the
button latch is U23 latch 1, SET by lid-open OR the SoC lock (U32) and
RESET by the physical button only. The charge-pump watchdog feeds
HV_ENABLE, not the latch. A gap (a `G4` dwell, a hold, the end of a cycle
before the next) drops HV_ENABLE 454 ms after the last pump pulse and
leaves the latch as it was, while the driver keeps the SoC lock released
through the armed window; on the next run HV_ENABLE is back within ~3 ms,
~216 ms before the first step (the pads measurement of 2026-08-15). A
planner starve is not a gap: the stream pads the ring dark and the run
keeps playing. A keepalive would hold HV_ENABLE up while nothing is cut,
the one state the watchdog exists to prevent, so none is built.
The one thing never watched on the bench, the latch readback through a
lit-dwell-lit sequence, rides `laser.emission-witness` from now on: the
square carries a `G4 P2` between its second and third sides, the sampler
reads `cnc/button_latch` and `switches.hv_enable`, and the test checks
the latch clear in every armed sample, HV_ENABLE dropped across the
dwell and back with emission after it, and the operator confirms all
four sides. It runs with the attended set. Item 1 is retired; its
flow-band sentence is a fact and moved to the facts bank ("Cooling
operating point").
## 2026-08-31: the unattended set on the hot-deployed board
The first unattended run of the day (campaign c-20260831143855) stopped
on `cooling.aa-offset-calibrate`, the test's first campaign run ever,
queued right after `cooling.flow-verify`: the loop was still mixing
after the no-flow trial (19.6 C rise, the sensors 13.5 C apart), the
tool's fixed 6 s settle let the trend into its first edge (+44.7 and
+20.6 counts against 13 to 18 on the settled edges), and its spread
check refused the result (35.5 counts against a limit of 8). Fix: the
tool waits at the flow tools' stationary gate before its first edge
(forgectrl 7dbb5e1) and the test gives it 540 s (forgefirm eb8935c).
By operator decision the fix went onto the board without an image:
the pinned forgectrl built by bitbake (md5 7e1f28cc) and the suite
file installed over ssh on dev 20260831141210, forgectrl and forgetest
restarted. The test then passed alone (offset 15.0 counts, spread 6.0,
edges 12.2 to 18.2), and the unattended queue of 20 ran green behind it
in campaign c-20260831151846 (21 PASS, the 17 other unattended tests
inherited from the morning run, one ABORTED record from a page start
before the fix was in). The attended set is deferred by decision. The
campaign that authorizes a release runs on the image that carries every
fix, burned once.
## Superseded status notes ## Superseded status notes
### Shared machine services — remaining polish, as listed 2026-08-13 ### Shared machine services — remaining polish, as listed 2026-08-13
@@ -6247,6 +6296,19 @@ stay as decided there.
`MAGIC_SYSRQ`: no runtime cost unused, root-only exposure, and root can `MAGIC_SYSRQ`: no runtime cost unused, root-only exposure, and root can
load modules anyway). load modules anyway).
### Laser commissioning leftovers (item 1), retired 2026-08-31
Closed: the gap question is answered from the safing chain (the entry
above); the confirmation rides `laser.emission-witness`; the flow-band
sentence is in the facts bank. Items 2 to 21 are now 1 to 20.
1. **Laser commissioning leftovers.** Verify the hardware button latch persists
across kernel-run gaps mid-job (if OK_2_FIRE drops between motion bursts, the
fix is a stream keepalive across armed gaps). The flow check's bands
hold from 19 to 27 C, the loop heater's ceiling in a 20 C room, with the
margin widening warm; above that only a running tube warms the loop,
and the check takes the tube's share off.
## Reference notes ## Reference notes
### Head-IRQ source validation — the beam-emission hypothesis ### Head-IRQ source validation — the beam-emission hypothesis
+37 -10
View File
@@ -95,6 +95,8 @@ def sample(ctx):
"beam": hw.sysfs_int("head/beam_detect_analog"), "beam": hw.sysfs_int("head/beam_detect_analog"),
"beam_d": hw.sysfs_int("head/beam_detect_digital"), "beam_d": hw.sysfs_int("head/beam_detect_digital"),
"button_lit": hw.button_lit(), "button_lit": hw.button_lit(),
"hv_enable": (st.get("switches") or {}).get("hv_enable"),
"button_latch": hw.sysfs_int("cnc/button_latch"),
} }
@@ -347,14 +349,19 @@ def power_floor(ctx):
actions=["button"], actions=["button"],
steps=["Scrap under the head with 20 mm of free +X and +Y travel; lid closed; exhaust on.", steps=["Scrap under the head with 20 mm of free +X and +Y travel; lid closed; exhaust on.",
"Press the physical button when it lights white (the arm).", "Press the physical button when it lights white (the arm).",
"At the end, confirm the square the laser marked: the one judgment by eye in the " "At the end, confirm the square the laser marked, all four sides: the one judgment "
"campaign, the calibration of the sensor witnesses."], "by eye in the campaign, the calibration of the sensor witnesses."],
description="A 20 mm square outline at S400/F600 in dynamic laser mode: emission_samples " description="A 20 mm square outline at S400/F600 in dynamic laser mode with a 2 s dwell "
"(the kernel's LASER_ON sample count) goes nonzero during the fire window and " "(G4) between its second and third sides: emission_samples (the kernel's "
"returns to 0 at Idle, HV current rises during the burn, the head's beam " "LASER_ON sample count) goes nonzero during the fire window and returns to 0 "
"detector sees the beam, the armed window is observed, the M2 program end " "at Idle, HV current rises during the burn, the head's beam detector sees the "
"disarms promptly at Idle (job-based, not the 60 s idle grace) and the button " "beam, the armed window is observed, the M2 program end disarms promptly at "
"goes dark. The operator confirms the mark.") "Idle (job-based, not the 60 s idle grace) and the button goes dark. Across "
"the dwell the kernel run ends and restarts: HV_ENABLE drops with the "
"charge-pump watchdog and is back for the third side, while the hardware "
"button latch stays clear (cnc/button_latch 0 in every armed sample), so the "
"second half of the square marks without a second press. The operator "
"confirms all four sides.")
def emission_witness(ctx): def emission_witness(ctx):
ev = ctx.evidence ev = ctx.evidence
with ctx.grbl() as g, LiveJob(ctx, g): with ctx.grbl() as g, LiveJob(ctx, g):
@@ -367,7 +374,7 @@ def emission_witness(ctx):
ctx.check(not base["emission"], "emission_samples nonzero before the job (%s)", base["emission"]) ctx.check(not base["emission"], "emission_samples nonzero before the job (%s)", base["emission"])
ctx.ready(ARM_CUE % "20 mm +X and +Y") ctx.ready(ARM_CUE % "20 mm +X and +Y")
job = ["G91", "G21", "M4", "S400", job = ["G91", "G21", "M4", "S400",
"G1 X20 F600", "G1 Y20 F600", "G1 X-20 F600", "G1 Y-20 F600", "G1 X20 F600", "G1 Y20 F600", "G4 P2", "G1 X-20 F600", "G1 Y-20 F600",
"M5", "G90", "M2"] "M5", "G90", "M2"]
ctx.arm_press() ctx.arm_press()
try: try:
@@ -391,6 +398,21 @@ def emission_witness(ctx):
"pgood_peak": max((s["pgood"] for s in samples if s["pgood"] is not None), default=None)}) "pgood_peak": max((s["pgood"] for s in samples if s["pgood"] is not None), default=None)})
ctx.log("emission_samples peak=%s end=%s; hv %s..%s; lid_ir peak %s; armed seen %s", ctx.log("emission_samples peak=%s end=%s; hv %s..%s; lid_ir peak %s; armed seen %s",
peak, end, ev["hv_min"], ev["hv_max"], ir_peak, armed_seen) peak, end, ev["hv_min"], ev["hv_max"], ir_peak, armed_seen)
# The dwell: a kernel-run gap inside the armed window. The button
# latch has no input from the charge-pump watchdog (its SET inputs
# are the lid and the SoC lock), so it must read clear through the
# gap, while HV_ENABLE drops with the watchdog and returns for the
# third side.
latch_armed = [s["button_latch"] for s in samples if s["armed"] and s["button_latch"] is not None]
first_fire = next((i for i, s in enumerate(samples) if s["emission"]), None)
hv_en = [s["hv_enable"] for s in samples[first_fire:]] if first_fire is not None else []
dip = next((i for i, v in enumerate(hv_en) if v is False), None)
back_lit = dip is not None and any(
s["hv_enable"] and s["emission"] for s in samples[first_fire + dip:])
ev.update({"button_latch_armed_max": max(latch_armed) if latch_armed else None,
"hv_enable_dipped": dip is not None, "hv_enable_back_lit": back_lit})
ctx.log("dwell gap: button latch through the armed window max=%s; HV_ENABLE dipped=%s, "
"back with emission after it=%s", ev["button_latch_armed_max"], dip is not None, back_lit)
# X-3: job-based disarm at Idle after M2 # X-3: job-based disarm at Idle after M2
dt = wait_disarm(ctx, 75) dt = wait_disarm(ctx, 75)
ev["disarm_after_idle_s"] = round(dt, 1) if dt is not None else None ev["disarm_after_idle_s"] = round(dt, 1) if dt is not None else None
@@ -403,9 +425,14 @@ def emission_witness(ctx):
ctx.check(dt is not None and dt < 10.0, ctx.check(dt is not None and dt < 10.0,
"the M2 job did not disarm promptly at Idle (%s s; the idle grace is ~60 s)", "the M2 job did not disarm promptly at Idle (%s s; the idle grace is ~60 s)",
ev["disarm_after_idle_s"]) ev["disarm_after_idle_s"])
ctx.check(latch_armed and max(latch_armed) == 0,
"the hardware button latch read SET inside the armed window (max %s over %d samples)",
ev["button_latch_armed_max"], len(latch_armed))
ctx.check(ev["hv_enable_dipped"], "HV_ENABLE never dropped across the 2 s dwell (the kernel run did not end)")
ctx.check(back_lit, "HV_ENABLE did not return with emission after the dwell (the third side ran dark)")
judge_beam(ctx, beam) judge_beam(ctx, beam)
check_button_dark(ctx, ev) check_button_dark(ctx, ev)
ctx.confirm("Did the laser mark a 20 mm square outline on the scrap?") ctx.confirm("Did the laser mark a 20 mm square outline on the scrap, all four sides?")
ctx.log("PASS: emission peak %s -> 0, HV %s..%s, beam +%s, disarmed %.1f s after Idle, button " ctx.log("PASS: emission peak %s -> 0, HV %s..%s, beam +%s, disarmed %.1f s after Idle, button "
"dark, mark confirmed", peak, ev["hv_min"], ev["hv_max"], beam["delta"], dt) "dark, mark confirmed", peak, ev["hv_min"], ev["hv_max"], beam["delta"], dt)