forgectrl ad0b441: OpenGlow branding, operational /status dashboard.
grblHAL-glowforge c472a13: position anchor at homing for the status
readers. Runbook updated.
Non-empty gf_serial / gf_password / gf_hostname in /data/forgefirm.conf
(set from the forgectrl GF Cloud tab) are applied with set_cfg before
Machine() is built, so they beat the OCOTP fuse identity - Machine sets
its fuse values with keep_value. The --timeout default comes from
GFHOME_TIMEOUT_S when the controller provides it, so one GUI setting
governs the whole session. Docs: control-panel runbook notes.
The runner's Machine subclass fetches lid/head images from the
forgectrl snapshot endpoint - forgectrl owns the imx-media pipeline
whenever a stream client (LightBurn) is connected, so direct V4L2
grabs fail busy. Head captures request lamp=0 (the cloud's focus
analysis needs torch-off images); the HCil measure-laser LED is still
driven directly. Direct capture remains the fallback when the daemon
is unreachable.
The sample config gains the factory-board settings: the estop motion
gate stays off (the board's estop sense reads low during any motion),
and the forgectrl URL is configurable.
New recipe installing /usr/sbin/gfhome.py and /etc/gfhome.conf.sample
(copied to /data/etc/gfhome.conf on first run). The runner signs the
machine in with its fused identity, opens the WSS control channel with
a held client reference (a clean disconnect is impossible through
ws_connect), and drives the GFUIService dispatch table itself - minus
print - so the service's camera homing sequence (settings -> hunt ->
lid image -> corner move -> lid image) runs against the real hardware
Machine. The service ends the sequence silently, so completion is a
hunt plus at least one motion followed by a configurable quiet window;
the lens is then re-referenced against the hall sensor for a
deterministic Z. Lid/e-stop are checked before the session, print
actions are refused, and exit codes distinguish configuration and
connection failures from an incomplete homing.
Installed in both images; invoked by the grblHAL controller for $H
when homing_mode = gfcloud in /data/forgefirm.conf.
gitsm-pinned build of the controller with a sysvinit script (defaults 92, after forgectrl), installed in both images. Reboot-verified on the bench: controller and forgectrl come up unattended and Grbl answers on TCP:23.
The head lis2hh12 (i2c-3 0x1e, direct-I2C at ~530 Hz - st_accel sysfs one-shots are ~6 Hz and the kernel has no IIO triggers) sees rail contact as a 20-40x jolt over the creep baseline within ~4 ms. bump_seek.py: 3/3 detected hits, zero false positives over ~180 mm of creep, jog-cancel stop and back-off. BRINGUP carries the full record and the driver integration design.
Sources live at github.com/ScottW514/forgectrl (history extracted, layout src/ + init/); the recipe pins SRCREV and installs the init script from the repo. Adds the build-forgectrl.sh bench cross-compile script.
NEON kernel for the YUV420 superpixel convert (vld2q deinterleave, rounding-halving greens, mlal/rshrn luma, pairwise-add chroma block sums), bit-identical to the scalar path and proven so on a live frame via the FORGECTRL_NEON_CHECK one-shot memcmp. Convert 75 to 18 ms; the stream now runs at the OV5648 frame rate. Motion coexistence re-proven at 15 fps (clamped 0).
Demosaic the superpixels straight to planar YUV420 and encode on the CODA960 (mainline coda V4L2 mem2mem, node found by personality); libjpeg stays as the automatic fallback and the snapshot path. All camera paths now demosaic from a cached bounce copy of the frame: the V4L2 MMAP capture buffers are uncached, and reading them in-place costs ~340 ms/frame vs 43 ms memcpy + 75 ms cached convert. Per-frame stats logged every 100 frames; /cam/status reports the encoder.
A stream request for the other camera kicks current stream clients via a generation counter (their streams end cleanly; viewers freeze on the last frame) and switches. The index page retry consults /cam/status first so a preempted view does not steal the camera back.
Snapshots of the non-streaming camera no longer fail 409: the worker borrows the mux for one frame (stream viewers see a short freeze). Stream switches wait up to 3 s for the previous pin to drain. Arbitration compares the engine home camera, not the transiently borrowed pipeline camera. Index page: single toggled stream with retry plus a head-peek button.
Adds the design matrix and its supporting tools, and records in BRINGUP
what the 60-run matrix overturned: sub-40-percent duty mimics flow
(three of five dead-pump trials looked healthier than a working pump),
the operating point and threshold now rest on 25 pooled observations,
periodic re-checks are thermally free with the fans running, and the
settle gate closes a bench-proven miss. Also records what is NOT yet
validated - warm-loop baselines and behaviour under laser heating - as
first-light commissioning items.
Records why the temperature formula changed (and that everything
derived from the old one had to be re-derived), the flow
characterization data at both heater duties, the false negative that
killed the dT-threshold design, and the downstream-rise check that
replaced it. Adds flow_characterize.py to the bench kit.
The UAPI raw->Celsius formula is an explicitly unverified best guess and
every absolute coolant threshold inherits it. temp_calibrate.py records
reference points (measured temperature paired with averaged raw ADC
readings) and least-squares fits the real line per sensor, printing it
against the guess with a difference table. Modes: watch / point / fit.
BRINGUP records the gate as complete with tach-verified results;
LIGHTBURN.md documents the per-layer Air Assist toggle driving the
cut-profile ventilation; fan_test.py joins the bench kit.
No factory X/Y home switches (unpopulated IO header only). Current
sensing is a dead end (PIC attrs are setpoints; stalled steppers do
not draw more current). Primary: head lis2hh12 accelerometer contact
detection (needs IIO bring-up). Fallback: low-current soft-bump
against the hard stops. Camera homing stays a future option.
Scope on GPIO2_IO30 (the SoC FIRE drive): two 2.000 s FIRE windows,
the second terminated only by the SDMA end-of-data backstop. Measured
2.0000 s exactly with clean edges on BOTH termination paths - normal
completion and true underrun (streaming=1, kernel underrun state
reached and acked; fire_test.py gains the U mode). The backstop drops
FIRE within one tick regardless of how the stream dies.
With the waveform, stream-path, latch, topology, and interlock-
semantics results from earlier today, every standing hardware
verification gate for live fire is now passed; what remains is the
laser-milestone software and a chain-armed first-light procedure.
fire_test.py phases A/B with the scope on the PSU LASER_ON pin, power
byte 0 throughout, HV unpowered, operator-executed unlock: latch LOCKED
severs FIRE entirely (pin flat + kernel laser_enable 0 under 40k
streamed fire bits); latch unlocked with the chain unarmed shows FIRE
live kernel-side while the PSU pin stays flat - the factory board
gates LASER_ON behind OK_2_FIRE exactly like the OpenGlow AND design.
interlock_circuit semantics pinned by the 13-to-7 transition: b0
LASER_ON monitor active low, b1 FIRE active high, b3 latch 1=locked.
The 1-tick FIRE drop measurement is deferred (SoC net inaccessible;
PSU pin needs an armed chain) - documented with both closure paths.
Power-bytes-only program played by the pulse engine with the scope on
LASER_PWM: full duty staircase observed, run-start 100 percent duty
reset confirmed on the pin, consecutive-power-byte drop confirmed
(second byte of a back-to-back pair discarded), and duty persists
after end-of-data - the laser-off guarantee rests entirely on FIRE.
Zero motion (motor_lock=15 + no step bytes; position counters pinned)
and the laser chain silent throughout. Adds pwm_stream_test.py to the
bench kit and records the FIRE/OK_2_FIRE/LASER_ON/HV_EN naming from
the OpenGlow LASER SAFING sheet.