laser: feed hold and resume in GRBL mode, the sender-change hold

Stream harness rule 21: a feed hold leaves no dark ground in either mode (lit into the hold, dark while held, lit from the first step out), with realtime and wait-state steps in the session runner. Lifecycle harness: the hold a sender change puts a running job into, the resume that re-arms a held job from the sender and from the button, a reset from a held job, and the resume after the grace closed the window in Hold. Live-fire drills: holdres (the pause as a corner in time, the re-arm after the grace); senderchg follows the hold. BRINGUP: the gapless-pause and sender-change items close, the facts bank gains the measured hold and resume behavior; CAMPAIGN-LOG records the proof and the bench runs. Acceptance: the pause-resume-lid-cancel text follows the behavior; the driver stays covered by src/**.
This commit is contained in:
ScottW514
2026-09-01 18:03:45 -04:00
parent 6a48cd3969
commit 8a9c6a9062
6 changed files with 530 additions and 103 deletions
+38 -54
View File
@@ -348,10 +348,19 @@ factory 2.6.0-2228 session; measured numbers in the facts bank).
`cloud_resume_lead_ticks` 1950), on a preloaded job and a live-fed one
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.
GRBL mode uses feed hold / cycle start, so a resumed GRBL cut picks up where
the deceleration ended (item 7). A pause is not a cancel: the latch
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).
GRBL mode uses feed hold / cycle start: the deceleration runs lit and
velocity-scaled, the dwell is dark, and the resume is lit from its first
step, so a pause is a sharp corner in time (facts bank "Feed hold and
resume in GRBL mode"). A pause is not a cancel: the latch 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). A pause that outlives
the disarm grace closes the window; the next cycle start (`~`, the button,
or the cooling client's auto-resume) then re-arms first: the button lights
and the press resumes the job.
- **A sender change while a job runs** holds the job and closes the window
(the consent belonged to the displaced session), so the next sender finds
the cut in Hold where it stopped, with a short dark deceleration behind
it, and resumes it through the same re-arm, or resets it.
- **`lid_policy = hold`** selects stock grblHAL door behavior instead (park in
Door, cycle start after the lid closes resumes with position intact).
@@ -909,6 +918,28 @@ is committed.
above it. Raw `hv_current` counts are a presence/absence witness only: the
per-rung means are non-monotonic at the top of the ladder and the signal
has no characterized transfer function.
- **Feed hold and resume in GRBL mode** (stream-measured on the null-sink
build at the 28160 Hz tick: a 100 mm/s cut at S500, `laser_dose_curve = off`,
floor 10 %, `!` mid-line, `~` after `Hold:0`): the planned deceleration runs
lit in both modes. The core's `disable_laser_during_hold` acts in
`state_suspend_manager`, which runs only once the hold has completed, so the
beam goes off at the end of the deceleration, never at its start. Under
`M4` the density follows velocity down to the floor (fire per step 2.9 at
cruise, 5.4 in the last 25 ms at 13 mm/s), the stream between the last step
and the first step is dark, and the resume lights 9 ticks after the first
step with the deceleration's profile in reverse: **a pause under `M4` is a
sharp corner in time**, and the corner rolloff governs its mark. Under `M3`
the fire rate stays constant through the deceleration (fire per step rises
2.9 to 22, the `M3` corner dose) and the resume is lit from its first step
as well: the segments the core prepares while held carry the spindle
update (the core fork sets it at hold completion; without that the `M3`
resume ran dark for 2453 ticks, 87 ms, one segment buffer). A hold whose
window closed under the grace resumes through the resume gate: the
sender's `~`, the button, or the cooling client's auto-resume lights the
button and waits for the press from the poll, never inside the core's held
state (a blocking arm wait there pumps the core's suspend loop, which spins
until the hold ends, so the press is never read), and the cycle start is
issued once the press has re-armed.
- **Factory power model** (three cloud cuts of one 1" square, same location,
material and speed, only the UI power setting changed, pulse files captured
from each): the **power byte is pinned at 127**
@@ -1229,54 +1260,7 @@ feature requests, enhancements) will eventually be tracked as GitHub issues.
cheap opportunistic check during live fire still stands: log GPIO3_22
edges plus `head/beam_detect_digital|_analog` while firing.
7. **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
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
the point the decel ended: an unburned length, then a restart that dwells
through the accel. At constant power (`M3`) that restart is a deeper spot
you can see; `M4` scales power with velocity and mostly hides it, but
neither closes the gap. GRBL mode should pause and resume with no
discontinuity in the cut, the way the factory does.
Cloud mode already does, on the kernel's waypoint resume
(`cloud_pause_backtrack_ticks` 2000, `cloud_resume_lead_ticks` 1950), and
the kernel offers the same mechanism to a live feed, bounded by the ring's
retained history (`cnc/max_backtrack`; the facts bank "SDMA pulse engine"
and [the pulse feeder contract](https://docs.forgefirm.org/technical/forgefirm/pulse-feeder-contract/)). What is not settled is the bookkeeping above it: a
backward run moves the head and the kernel's counters while grblHAL's
planner still holds a partly executed block, so borrowing the mechanism
means reconciling the two, and a GRBL cut runs a much shorter queue than a
cloud print does.
So the equivalent likely belongs above the ring, where grblHAL still holds
what the kernel does not: the planned path. Shape to evaluate: capture the
point where the beam went off at the hold; on the resume plan a laser-off
retrace back along the path and a laser-off accelerate-in, and unmask FIRE
only once the head is at feed and has passed the captured point. Open: how
far back is enough (2000/1950 ticks is a reference, not a transferable
number, since the tick rates differ), whether the retrace can reuse
planner blocks or needs a synthesized one, what a hold inside an arc or a
raster line does to it, and how it composes with the armed window's disarm
grace across a long hold.
8. **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
controller holds, with the window closed and fire suppressed (the
consent belonged to the displaced session), so the job finishes dark
and the material is left with an unfinished cut. This is the stock
Grbl and grblHAL expectation for the motion (the core switches streams
with no hold and no alarm, and senders treat a lost connection as a
failed job); ForgeFIRM adds only the disarm on top. The open question
is whether the disarm should also
feed-hold the job, so a reconnecting sender can press and resume where
the cut stopped instead of finding the head at the end of a dark pass:
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;
running on leaves a clean stop position but wastes the piece. Decide
with the gapless pause and resume item (7), which owns the resume
mechanics.
9. **The flow check on a second machine.** The lit-tube flow check is in
7. **The flow check on a second machine.** The lit-tube flow check is in
place: the engine reads means, takes its baseline under the run
profile, and takes the tube's share off (`cool_laser_heat_cw`,
`cool_laser_heat_density`); `cooling.flow-under-load` is the catalog's
@@ -1286,7 +1270,7 @@ feature requests, enhancements) will eventually be tracked as GitHub issues.
far): re-measure the two heat coefficients and the machine's
air-assist offset; and if a lit check still trips, the
void-on-emission design with the tube as its own flow tracer.
10. **Laser power-good: what the line means.** `cnc/laser_pgood` and its
8. **Laser power-good: what the line means.** `cnc/laser_pgood` and its
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
real cutting, and the cooling engine warns
@@ -1298,7 +1282,7 @@ feature requests, enhancements) will eventually be tracked as GitHub issues.
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
something or no warning.
11. **Initial commissioning: measure and set the machine's own numbers
9. **Initial commissioning: measure and set the machine's own numbers
methodically.** Every tunable that was measured on the bench machine
and shipped as a default varies from machine to machine: the flow
check's bands and `cool_flow_rise`, the tube's heat coefficients
+119
View File
@@ -6858,6 +6858,125 @@ drill's threshold 40 is 0.31 g, not 0.62 g).
Normal commanded motion reads under 0.2 g and a rail strike 1.8 g
and up, so the factory band sits where the bench says it should.
## 2026-09-01: feed hold and resume in GRBL mode, measured on the null-sink stream
Prompted by the "gapless pause" item, which said the head travels the
hold's deceleration dark. A scratch harness ran the native null-sink
controller (`GFSINK_DUMP`, the stream harness's launch pattern) built from
grblHAL 575ff97: `G1 X150 F6000` at S500 (`laser_dose_curve = off`, floor
10 %), `!` 0.7 s in, `~` after `Hold:0`, under `M4` and then `M3`; then the
same job with `laser_disarm_s = 2` and a switch file, held past the grace
and resumed by `~` and by the button. The dump was read in 25 ms windows
(704 ticks) on both sides of the stop.
- **The deceleration is lit, in both modes.** The item's claim was wrong:
the core's `disable_laser_during_hold` acts in `state_suspend_manager`,
which runs only once the handler is `state_await_resume`, so the beam
goes off when the hold completes, not when it starts. `M4`: fire per
step 2.89 at 100 mm/s, 2.86 at 82, 3.02 at 64, 3.27 at 47, 3.74 at 29,
5.41 at 13 (the 10 % floor). `M3`: 381 to 385 fire ticks in every
window, so fire per step rises from 2.9 to 22.4.
- **The dwell is dark.** Between the last step and the first step: 10 fire
ticks under `M4` (the tail of the last pulse, far inside the stepless
FIRE limit), 0 under `M3`.
- **`M4` resumes lit from its first step.** First fire 9 ticks after the
first step; fire per step 7.60 at 11 mm/s, 4.51 at 28, 3.67 at 46, 3.32
at 63, 3.08 at 81, 2.94 at 96, 2.84 at 100: the deceleration's profile
in reverse. A pause under `M4` is a sharp corner in time.
- **`M3` resumes dark for 87 ms.** First fire 2453 ticks after the first
step; the first three windows (113 steps, 2.1 mm) carry no fire, the
fourth 198 ticks, then 384. The cause is not isolated; the restore's
laser-on reaches the stream about one segment buffer late.
- **Resume after the grace closed the window.** `~`: `[MSG:Restoring
spindle]`, then the arm prompt, then nothing: presses of 0.15 s and
0.5 s were not honored, `?` kept answering `Hold:0` at the pause
position, `M5` got no ok. The controller sits in the arm wait until its
timeout (not waited out). Button: the press that resumes is still down
when the arm wait starts, so it is taken as the consent (`button
pressed - job resumed`, `Restoring spindle`, the prompt, `laser armed`,
all in one press) and the job continued lit with the plain `M4` profile.
Disposition: item 7 is re-scoped to the `M3` resume lead plus a harness
rule; the `~` wedge is recorded under item 8, whose hold option depends on
it; the measurements are in the facts bank. Nothing changed in code.
## 2026-09-01: the M3 resume lead and the held-job resume fixed, host-proven
Both findings of the entry above, fixed the same day and proven on the
null-sink harnesses. Items 7 and 8 close.
- **The `M3` resume lead, root cause.** A laser-push trace in the stream
engine showed the core issuing the `M3` relight 2451 producer ticks
after the first resume step: the segments a resume executes first are
prepared while the job is still held (`state_await_hold` clears the
step-control flags at hold completion and prepping proceeds from there),
and with `update_spindle_rpm` cleared an `M3` block prepares them
without a spindle update, so the level the restore sets reaches the
stream only with the first segment prepared after the buffer drains.
`M4` never showed it because a dynamic block updates every segment.
Fix, core fork (`state_machine.c`, hold completion): in laser mode reset
the stepper's rpm cache to 0 and set `update_spindle_rpm`, so the first
segment prepared while held re-asserts the programmed power. Proof: the
same drill, `M3` first fire 0 ticks after the first resume step (was
2453), the relight landing at the segment load about 90 ticks before
the step, exactly as at a job start; `M4` unchanged (9 ticks).
- **The `~` wedge, root cause.** The resume's spindle restore runs inside
the held state; the blocking arm wait it reaches pumps
`protocol_execute_realtime`, which enters the core's suspend loop
(`while(sys.suspend)`) and spins there until the hold ends, so the arm
loop's switch read never runs again. The button path escaped only
because the resuming press was still down at the wait's first read.
Fix, driver: a resume gate (`gflaser_resume_gate`) that every cycle
start passes on its way to the core: the sender's `~` in `serial.c`,
the button toggle in Hold, and the cooling client's auto-resume. A held
laser job whose window has closed re-arms first, with the press
collected from the poll (`rearm_poll`, never inside the held state) and
the cycle start issued once the window is open; the blocking wait is
refused inside a held state as a belt-and-braces (dark, reported). The
arm flow is split into `arm_gates` and `arm_complete`, shared by both.
Proof: the same drill, `~` after the grace: the prompt, the press,
`laser armed`, the job finished at X=150 with the plain `M4` profile.
- **A sender change now holds the job.** `gflaser_poll` feed-holds a
running job before it disarms on a sender change, so the next sender
finds the cut in Hold where it stopped (the deceleration behind it runs
dark, since the consent belonged to the displaced session) and resumes
it through the gate, or resets it.
- **Harness coverage.** Stream harness rule 21 (`hold-m4`, `hold-m3`):
lit into the hold, dark while held, lit from the first step out, with
the realtime `!`/`~` steps and a `wait_state` helper added to the
session runner. Lifecycle harness: `sender-change-mid-job` now asserts
the hold and the `~` re-arm; new `sender-change-rearm` (button build:
the prompt on `~`, the press, the finish), `sender-change-reset` (a
reset from the held job ends in Idle, no alarm), `resume-after-grace`
(the window closes in Hold, `~` prompts, the press re-arms, the cut
finishes). Unit tests: `serial_test` and `laser_arm_test` stub the new
calls.
**Bench, the same day, on dev image 20260831225403 with the controller
hot-deployed from the working tree** (cross-built with the recipe's own
toolchain and flags from the Yocto work directory; md5 c8494aac, the image
binary saved as `/tmp/grblHAL_glowforge.prev`). Two armed runs, driven
from the LAN by `live_fire_drills.py`, the operator on the button:
- **`holdres`** (new drill): a 30 mm `M4` line at F300 S400 held about 2 s
in and resumed, then the 90 degree corner; the same hold under `M3` on
the return line; then a hold that outlived the grace. All three legs
held and resumed; the window closed in Hold after 61 s, `~` lit the
button with `press the button to resume the laser job`, the press
re-armed, the line finished. The operator judged the marks good: the
`M4` pause against the corner, and no dark lead after the `M3` pause.
- **`senderchg`** (rewritten for the hold): a 20 mm `M3` line at F60, the
connection dropped 5.0 s in. The job was held 1.9 s after the drop with
the window closed (`hv_current` dark 0.03 s after the drop), the new
session's `~` prompted at +9.7 s, the press re-armed at +15.2 s
(`laser armed`, then the core's `Restoring spindle`), and the rest of
the line marked: 44 lit samples before the drop, 0 between the drop and
the press, 106 after. Record `senderchg_20260901-180135.json` on the
driving host.
Items 7 and 8 are bench-proven. The board runs the hot-deployed binary
until the next flash.
## Reference notes
### Head-IRQ source validation — the beam-emission hypothesis