The runbook operational sections now describe the machine as it runs:
forgectrl as the machine-services daemon (supervisor, pulse-device
broker, motion-liveness gate, cooling engine, cameras), controllers
spawned and supervised rather than init-started, standalone driver
runs as the bench/debug exception, diagnostics suspending the active
controller through the supervisor, the cooling engine as the thermal
owner with the drivers as thin clients, and the new /mode and /cool
endpoints. A dated correction closes the no-motion record with the
DRV8825 wedge diagnosis and the liveness/homing hardening; the
cold-start section gains the first-light and shared-services
milestones.
Hardware facts from the 2026-08-11 bench session: the DRV8825 drivers
can come out of a 40 V rail power-up unserviceable (playback and
counters run, motors dead; recovery is a longer true power-off, at
worst a machine power cycle) - so counters/anchors are never proof of
motion and the rail stays up. The head accelerometer is the motion
truth (device mapping, bench-characterized thresholds, read-rate
limits), and every probe move goes +X first - a cable lives at the
end of left travel.
The controller lifecycle belongs to the forgectrl supervisor (it
spawns the selected controller as a direct child); the init script no
longer starts grblHAL and remains only as a manual emergency stop.
BRINGUP item 2: grbl-mode laser software implemented + bench-verified
without fire (record in the gate list); first light pending.
LIGHTBURN.md: arming/button-press operation, S-max 1000, fire gates.
scripts/bench/laser_stream_test.py: host-side stream-dump contract
verification against the null-sink build.
The cloud-action-surface branches are merged: forgectrl builds from main
(same revision), and the forgefirm-app include pins the python3-gfhardware
master head (adds docs/CLOUD.md and the clean-shutdown gfhome).
gfhome.py, ffmachine.py, and gfcloud.py (with the gfcloud init script
and gfhome conf sample) live in the python3-gfhardware repository's
forgefirm-app/ directory. The three recipes fetch that repo through a
shared include (forgefirm-app.inc) carrying a single pinned SRCREV,
replacing the local file:// copies. Package names, dependencies, and
installed paths are unchanged.
ws_connect() returns the running WsClient, so gfhome no longer needs to
build the client by hand to get a clean disconnect; the exit path uses
WsClient.shutdown() to stop and join the socket thread.
Points the forgectrl recipe at the pushed cloud-action-surface commit
(controller_mode=cloud + the compatibility banner) so a test image
carries the cloud-mode UI. Repoint to main once the branch is merged.
Enable the read-only firmware-version probe and point it at
/data/forgefirm/gf-latest.json, where the web-service client records the
latest firmware Glowforge advertises and the version this release was
tested against. forgectrl reads that file for the cloud-mode
compatibility banner.
gfcloud runs the machine under the Glowforge web service (the factory
cloud experience: the app drives homing, framing, printing). Its init
service starts it only when controller_mode = cloud - mutually exclusive
with grblHAL, which already stands down for that mode - so it owns
/dev/glowforge exclusively. Persistent GFUIService loop with a clean
SIGTERM shutdown that safes the hardware; the init script mirrors
grblhal.init. The image installs gfcloud and python3-ffmachine.
Extract the shared-config identity overrides and the forgectrl-routed
ForgectrlMachine from gfhome into an installed module (python3-ffmachine);
gfhome imports it instead of carrying its own copy, so the coming gfcloud
daemon builds the same machine the same way.
Record the Glowforge service/firmware version each release validated
cloud mode against, kept distinct from the version cloud mode advertises;
forgectrl reads it to warn when the live service has moved past it.
Replace the local dispatch table with gfutilities' dispatch_action
(allow_print=False), so the homing runner and GFUIService share one
action surface and cannot drift.
b5c6d9c declares the shared config readers in glowforge_homing.c;
without the declaration the hard-float build read the homing-session
timeout from the wrong register and SIGTERMed every gfcloud homing
runner ~55 ms after spawn.
Bump to the forgectrl revision whose startup pass reloads
regulatory.db (required while the flashed kernel still has built-in
cfg80211 - its boot-time load fails pre-mount and stays failed
without a reload), hints a region only when one is set (unset =
automatic: the AP's 802.11d country, else world; a 00 hint over the
kernel's own world default reports the confusing intersection alias
"country 98"), and pins wlan0 power save off.
BRINGUP.md: bench record for the wireless-regulatory diagnosis and
the live-verified wifi_country / power-save flow; the kernel-batch
note now carries CFG80211/MAC80211=m, DEFAULT_PS off, and the lm75
vs-supply.
Bump to the forgectrl revision that adds the wifi_country setting
(System-tab region picker); the daemon applies it with iw reg set at
startup and on change, so iw joins RDEPENDS. regulatory.db itself
ships via the glowforge-image base (wireless-regdb-static).
Before writing the target slot, the installer now shows what it holds
(factory firmware v<ver>, ForgeFIRM, an unrecognized filesystem, or
unknown/unreadable content). Factory images are archived as before;
anything else requires the operator to type ERASE, since it is
overwritten without a backup. The archive manifest now records the
semantic FIRMWARE_VERSION (ver=), which the update manager displays in
the restore list. Bump forgectrl to the matching GUI change.
A factory slot's displayed version is now the semantic FIRMWARE_VERSION
from /etc/build (e.g. v2.6.0-2228) rather than the build datetime in
/etc/version - what the factory itself calls the release, and what the
update-manager GUI shows for each slot. The datetime still orders
releases for the -e newest-factory selection (a monotonic counter;
mashing the semantic version's digits would misorder a major-version
rollover). Bench: slot shows v2.6.0-2228, -e picks it correctly.
/etc/forgefirm/keys ships the ForgeFIRM release-signing public key
and the Glowforge factory keyring (public keys only) - the update
manager verifies release downloads/uploads against the former and
factory archives against the latter. forgectrl SRCREV bumped to the
update-manager commit; runtime deps on ffboot, fwup, the keyring, and
curl made explicit.
The embedded pubkey is the production key from the signing ceremony;
release.sh's key-match gate now refuses any other signer. Verified:
production-signed archives pass fwup 1.16 and the factory's 0.14.2;
dev-signed archives are rejected.
The runbook no longer records the bench machine's fuse identity (a
fuse identity cannot be rotated, so a public document must never
carry it); the forgectrl pin follows that repo's history rewrite.
Proves a fresh clone builds the release image on a hosted runner
(sibling checkouts for meta-openglow and the kernel-module externalsrc,
rm_work to fit the disk budget) and publishes artifact checksums for
comparison against locally built releases. Never produces release
artifacts - releases are built and signed on the maintainer's host.
Gates (clean tree, version single-source across FORGEFIRM_RELEASE /
rootfs stamp / .fw meta-version / tag, rootfs-vs-slot size with early
warning, installer-embedded pubkey must match the signing key,
factory-era fwup verification of the packed archive), then build,
pack, sign, checksum, and stage forgefirm.fw + sha256sums.txt +
forgefirm-image-glowforge.rootfs.wic.gz with the gh publish command
(--publish runs it where gh is authenticated). release.sh --dev packs
a dev-key-signed forgefirm-dev.fw from the release rootfs for the GUI
upload path. Signing keys are always passed explicitly - no defaults.
kas/README release order and the plan doc updated to match.
The image's fstab keeps the factory slots mounted under /factory, and
busybox mount's auto-type iteration against an already-mounted ext4
device provokes a cosmetic kernel 'Can't open blockdev' for each
foreign-type claim (reproduced and pinned on the bench: ext3-typed
mount of an ext4-held device prints it; ext4-typed does not). Probes
now reuse an existing mountpoint from /proc/mounts and mount fresh
targets with an explicit -t ext4.
Documents the new installer: A/B slot install with no repartitioning,
factory archives to /data, signature-verified forgefirm.fw, ffboot
switching, offline install, and the automatic legacy-layout
migration.
The image's busybox has no blockdev applet, so the grow path silently
skipped. Sector counts now come from /sys/class/block (no external
tool); log lines also go to /dev/kmsg so migration results are visible
in dmesg after boot.
Newer factory firmware's generic /etc/fw_env.config points at the
wrong device; its per-device /etc/fw_env_mmcblk2.config is the correct
one for the eMMC environment. The read-back verify caught the failed
write and aborted before the flip, as designed.
dd|gzip runs backgrounded while the installer prints compressed MB
every few seconds (old busybox dd has no status=progress); dd's exit
status is captured through a file so a device read failure is not
masked by gzip succeeding on truncated input.
The gf_hostname override is gone (the forgectrl UI no longer offers
it): the hostname is a pure derivation of the serial - base 23 over
the factory consonant alphabet - so a gf_serial override re-derives
MACHINE.HOSTNAME and the fuse derivation stands otherwise. BRINGUP
records the panel rework (units, fuse identity, always-on position)
as offline-verified; board deploy and pin bumps held during the
firmware-upgrade bench testing.
Newer factory firmware (2024) has no /factory/imgN mounts and a
read-only rootfs, so slot probing and post-write verification mount
under /tmp, with the active slot read from the running root. The
target-slot unmount sweeps /proc/mounts (older firmware DOES mount the
slots). A failed ffboot download keeps an existing /data/ffboot
instead of aborting, so a local-.fw install works fully offline.