gfhome: derive the service hostname from the effective serial

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.
This commit is contained in:
ScottW514
2026-08-08 12:44:56 -04:00
parent 882a1b1b2d
commit 6c9cce3184
3 changed files with 67 additions and 12 deletions
+35 -3
View File
@@ -215,13 +215,30 @@ cameras as MJPEG over the mainline imx-media pipeline:
GF Cloud / GRBL / Diagnostics; ui.c): status page with the GF Cloud / GRBL / Diagnostics; ui.c): status page with the
controller-mode selector (GRBL active; factory cloud disabled until controller-mode selector (GRBL active; factory cloud disabled until
implemented), the operational dashboard, a scaled lid snapshot + implemented), the operational dashboard, a scaled lid snapshot +
on-demand live stream, and the settings forms for homing method, on-demand live stream, and the settings forms for display units,
home-position calibration, the nine cooling tunables, identity homing method, home-position calibration, the nine cooling
overrides, and the session timeout. All tunables, identity overrides, and the session timeout. All
settings controls disable (with a banner) while the machine is not settings controls disable (with a banner) while the machine is not
idle **or a diagnostic is running**. `/?action=stream|snapshot` idle **or a diagnostic is running**. `/?action=stream|snapshot`
remain the mjpg-streamer- remain the mjpg-streamer-
compatible aliases (lid camera; LightBurn uses the stream one). compatible aliases (lid camera; LightBurn uses the stream one).
**Panel conventions (2026-08-08, operator-directed):** the header
identifies the machine by its **fuse identity** — the factory
hostname derived from the OCOTP serial (HW_OCOTP_MAC0 base-23 over
`BCDFGHJKMQRTVWXY2346789`, XXX-YYY; the C implementation matches
gfhardware id.py over 200k random serials) — regardless of any
cloud identity override; the `gf_hostname` override is REMOVED
(the service hostname always derives from whichever serial is in
effect — gfhome.py re-derives it from an overridden gf_serial);
**units** are a display-only preference (`ui_units` metric |
imperial): the backend stores metric, lengths convert mm↔in,
absolute temps °C↔°F, temperature DELTAS (the flow-rise family)
scale by 1.8 with no offset, and saves post **only fields whose
display string changed** (dirty tracking — unit round-trips never
masquerade as edits); **position always shows**, counters-only and
painted red while unreferenced (the machine moves fine unhomed —
relative to wherever it started), normal once anchored; sender
hints are unopinionated (no named Grbl clients).
- `GET/POST /settings` — the shared machine settings store - `GET/POST /settings` — the shared machine settings store
(/data/forgefirm.conf, validated keys incl. controller_mode and the (/data/forgefirm.conf, validated keys incl. controller_mode and the
cool_* cooling tunables, cool_* cooling tunables,
@@ -413,6 +430,21 @@ browser-verified: the click wrote `cool_flow_rise = 14.8` to the
conf (cleared after; the compiled default stands until the operator conf (cleared after; the compiled default stands until the operator
chooses otherwise). chooses otherwise).
**Units/identity/position panel rework (2026-08-08, later):
OFFLINE-VERIFIED ONLY — board deploy + bump HELD during the
operator's firmware-upgrade bench testing.** Verified against the
`tools/mock.py` harness in forgectrl (serves the ui.c panel with
mock endpoints; POSTs logged): fuse-identity header (XXX-XXX), red
unreferenced position (needed the `.kv>span:first-child` selector
fix — the old descendant selector out-specified `.b-bad` on nested
value spans), imperial placeholders 14.4→25.9 (delta) / 33→91.4
(absolute), position 12.34 mm→0.486 in, dirty-save posting exactly
one changed key converted back (27 °F→15 °C), diag bands ×1.8 with
Apply still posting metric, and a units round-trip leaving nothing
dirty. The C serial→hostname derivation matches gfhardware id.py on
200k random 32-bit serials (host-side cross-check). Pending on
go-ahead: deploy both binaries, live re-check, then SRCREV bumps.
## Hardware facts bank (measured) ## Hardware facts bank (measured)
- SDMA pulse engine: ring size = the `ring_mb` module parameter - SDMA pulse engine: ring size = the `ring_mb` module parameter
@@ -1,8 +1,9 @@
# gfhome - Glowforge web-service homing configuration. # gfhome - Glowforge web-service homing configuration.
# Copied to /data/etc/gfhome.conf on first run; edit the copy. # Copied to /data/etc/gfhome.conf on first run; edit the copy.
# Machine identity (serial/password) is read from the OCOTP fuses; # Machine identity (serial/password) is read from the OCOTP fuses;
# gf_serial / gf_password / gf_hostname in /data/forgefirm.conf (the # gf_serial / gf_password in /data/forgefirm.conf (the forgectrl UI,
# forgectrl UI, GF Cloud tab) override it. # GF Cloud tab) override it. The service hostname always derives from
# whichever serial is in effect.
[SERVICE] [SERVICE]
server_url: https://app.glowforge.com server_url: https://app.glowforge.com
@@ -14,8 +14,9 @@ homing_mode = gfcloud is set in /data/forgefirm.conf, releasing
/dev/glowforge for the duration of the run. It can also be run by hand /dev/glowforge for the duration of the run. It can also be run by hand
(with the controller stopped or its homing session active). The same (with the controller stopped or its homing session active). The same
shared config supplies optional identity overrides (gf_serial / shared config supplies optional identity overrides (gf_serial /
gf_password / gf_hostname; the fuse identity is the fallback), managed gf_password; the fuse identity is the fallback), managed from the
from the forgectrl UI. forgectrl UI. The service hostname is always derived from whichever
serial is in effect - it is never set independently.
The service ends the sequence silently - there is no completion The service ends the sequence silently - there is no completion
message - so the run is considered homed once a hunt and at least one message - so the run is considered homed once a hunt and at least one
@@ -75,11 +76,25 @@ def load_config(path: str) -> bool:
return True return True
def _hostname_for(serial):
"""The factory serial -> hostname encoding (base 23 over the
consonant alphabet, up to six characters, split XXX-YYY) - the
same derivation gfhardware applies to the fuse serial."""
enc = ''
serial = int(serial)
while serial > 0 and len(enc) < 6:
enc = 'BCDFGHJKMQRTVWXY2346789'[serial % 23] + enc
serial //= 23
return '{}-{}'.format(enc[:3], enc[3:])
def apply_identity_overrides(): def apply_identity_overrides():
"""Identity overrides from the shared machine config (set in the """Identity overrides from the shared machine config (set in the
forgectrl UI): non-empty gf_serial / gf_password / gf_hostname beat forgectrl UI): non-empty gf_serial / gf_password beat the OCOTP
the OCOTP fuse identity - Machine.__init__ sets its fuse values fuse identity - Machine.__init__ sets its fuse values with
with keep_value, so whatever is in the config store first wins.""" keep_value, so whatever is in the config store first wins. The
hostname is never overridden independently: it derives from the
serial, so a serial override re-derives it."""
keys = {} keys = {}
try: try:
with open(MACHINE_CONF) as f: with open(MACHINE_CONF) as f:
@@ -92,11 +107,18 @@ def apply_identity_overrides():
except OSError: except OSError:
return return
for key, cfg in (('gf_serial', 'MACHINE.SERIAL'), for key, cfg in (('gf_serial', 'MACHINE.SERIAL'),
('gf_password', 'MACHINE.PASSWORD'), ('gf_password', 'MACHINE.PASSWORD')):
('gf_hostname', 'MACHINE.HOSTNAME')):
if keys.get(key): if keys.get(key):
set_cfg(cfg, keys[key]) set_cfg(cfg, keys[key])
logger.info('identity override: %s from %s', cfg, MACHINE_CONF) logger.info('identity override: %s from %s', cfg, MACHINE_CONF)
if keys.get('gf_serial'):
try:
set_cfg('MACHINE.HOSTNAME', _hostname_for(keys['gf_serial']))
logger.info('identity override: MACHINE.HOSTNAME derived '
'from gf_serial')
except ValueError:
logger.warning('gf_serial is not numeric; hostname left '
'at the fuse derivation')
def make_machine(): def make_machine():