mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-28 09:11:11 -07:00
gfhome: identity overrides from the shared config; budget from env
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.
This commit is contained in:
+23
-10
@@ -1,13 +1,19 @@
|
|||||||
# ForgeFIRM bring-up status & cold-start runbook
|
# ForgeFIRM bring-up status & cold-start runbook
|
||||||
|
|
||||||
Last updated: **2026-08-07** — homing is runtime-selectable
|
Last updated: **2026-08-07 (late)** — forgectrl is now the machine
|
||||||
(forgectrl web UI) and **Glowforge web-service (gfcloud) homing is
|
CONTROL PANEL: tabbed web UI (Status / Machine / GF Cloud / GRBL) with
|
||||||
LIVE-VERIFIED end-to-end** ($H → cloud homing sequence → homed at the
|
a status-first landing page (scaled lid snapshot + on-demand live
|
||||||
factory corner in 65 s). The first live run surfaced and fixed four
|
stream), homing-position calibration (`gfcloud_home_x/y/z`),
|
||||||
platform bugs — see Next work #3, incl. the new hardware fact that
|
cloud-identity overrides (`gf_serial`/`gf_password`/`gf_hostname`,
|
||||||
the estop sense reads low during any motion. Same day: fd-blocking
|
fuses = fallback, applied by the runner via set_cfg before Machine()),
|
||||||
protocol pacing (idle CPU ~2%) and the fortify step_us_min fix.
|
and a validated multi-key `/settings` API (empty value = clear; clears
|
||||||
Previous milestone: camera service (forgectrl MJPEG on :8080).
|
must ride the query string — empty form-body values are dropped by the
|
||||||
|
HTTP stack). Bench + browser-verified (save/clear roundtrips from the
|
||||||
|
real UI, live toggle, no JS errors). Earlier same day: **gfcloud
|
||||||
|
homing LIVE-VERIFIED end-to-end** ($H → homed at the factory corner in
|
||||||
|
65 s; four platform bugs fixed — see Next work #3, incl. the
|
||||||
|
estop-sense-reads-low-during-motion hardware fact); fd-blocking
|
||||||
|
protocol pacing; the fortify step_us_min fix.
|
||||||
Read together with `AUDIT_ACTION_PLAN.md` in the project root (sibling of
|
Read together with `AUDIT_ACTION_PLAN.md` in the project root (sibling of
|
||||||
this repo; per-finding status of the 2026-07-03 audit) and
|
this repo; per-finding status of the 2026-07-03 audit) and
|
||||||
`kernel-module-glowforge/UAPI.md` (the pulse-stream feeder contract).
|
`kernel-module-glowforge/UAPI.md` (the pulse-stream feeder contract).
|
||||||
@@ -183,8 +189,15 @@ repo's `init/`; bench builds cross-compile with
|
|||||||
pattern as build-glowforge.sh). One ulfius daemon exposes both OV5648
|
pattern as build-glowforge.sh). One ulfius daemon exposes both OV5648
|
||||||
cameras as MJPEG over the mainline imx-media pipeline:
|
cameras as MJPEG over the mainline imx-media pipeline:
|
||||||
|
|
||||||
- `GET /` — index page with a live view; `/?action=stream|snapshot` are
|
- `GET /` — the tabbed machine control panel (Status / Machine /
|
||||||
the mjpg-streamer-compatible aliases (lid camera).
|
GF Cloud / GRBL; ui.c): status page with a scaled lid snapshot +
|
||||||
|
on-demand live stream, and the settings forms for homing method,
|
||||||
|
home-position calibration, identity overrides, and the session
|
||||||
|
timeout. `/?action=stream|snapshot` remain the mjpg-streamer-
|
||||||
|
compatible aliases (lid camera; LightBurn uses the stream one).
|
||||||
|
- `GET/POST /settings` — the shared machine settings store
|
||||||
|
(/data/forgefirm.conf, validated keys, empty-value-clears via query
|
||||||
|
params; gf_password write-only).
|
||||||
- `GET /cam/stream?cam=lid|head` — multipart MJPEG at 1296×972 (2×2
|
- `GET /cam/stream?cam=lid|head` — multipart MJPEG at 1296×972 (2×2
|
||||||
Bayer-superpixel demosaic, JPEG q75; `FORGECTRL_STREAM_Q` overrides;
|
Bayer-superpixel demosaic, JPEG q75; `FORGECTRL_STREAM_Q` overrides;
|
||||||
`FORGECTRL_STREAM_FPS` caps the frame rate, unset/0 = sensor max).
|
`FORGECTRL_STREAM_FPS` caps the frame rate, unset/0 = sensor max).
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# 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
|
||||||
|
# forgectrl UI, GF Cloud tab) override it.
|
||||||
|
|
||||||
[SERVICE]
|
[SERVICE]
|
||||||
server_url: https://app.glowforge.com
|
server_url: https://app.glowforge.com
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ back-left home corner, Z at the top-of-travel hall trigger.
|
|||||||
The grblHAL-glowforge controller invokes this for $H when
|
The grblHAL-glowforge controller invokes this for $H when
|
||||||
homing_mode = gfcloud is set in /data/forgefirm.conf, releasing
|
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).
|
(with the controller stopped or its homing session active). The same
|
||||||
|
shared config supplies optional identity overrides (gf_serial /
|
||||||
|
gf_password / gf_hostname; the fuse identity is the fallback), managed
|
||||||
|
from the forgectrl UI.
|
||||||
|
|
||||||
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
|
||||||
@@ -29,6 +32,7 @@ SPDX-License-Identifier: MIT
|
|||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import queue
|
import queue
|
||||||
import shutil
|
import shutil
|
||||||
import signal
|
import signal
|
||||||
@@ -37,12 +41,13 @@ import time
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
|
|
||||||
from gfutilities.configuration import parse, get_cfg, log_level
|
from gfutilities.configuration import parse, get_cfg, log_level, set_cfg
|
||||||
from gfutilities.service.authentication import authenticate_machine
|
from gfutilities.service.authentication import authenticate_machine
|
||||||
from gfutilities.service.websocket import get_session, WsClient
|
from gfutilities.service.websocket import get_session, WsClient
|
||||||
|
|
||||||
CONF = '/data/etc/gfhome.conf'
|
CONF = '/data/etc/gfhome.conf'
|
||||||
CONF_SAMPLE = '/etc/gfhome.conf.sample'
|
CONF_SAMPLE = '/etc/gfhome.conf.sample'
|
||||||
|
MACHINE_CONF = os.environ.get('GFHOME_CONF', '/data/forgefirm.conf')
|
||||||
|
|
||||||
logging.basicConfig(format='(%(levelname)s) %(module)s:%(funcName)s %(message)s')
|
logging.basicConfig(format='(%(levelname)s) %(module)s:%(funcName)s %(message)s')
|
||||||
logger = logging.getLogger('openglow')
|
logger = logging.getLogger('openglow')
|
||||||
@@ -70,6 +75,30 @@ def load_config(path: str) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def apply_identity_overrides():
|
||||||
|
"""Identity overrides from the shared machine config (set in the
|
||||||
|
forgectrl UI): non-empty gf_serial / gf_password / gf_hostname beat
|
||||||
|
the OCOTP fuse identity - Machine.__init__ sets its fuse values
|
||||||
|
with keep_value, so whatever is in the config store first wins."""
|
||||||
|
keys = {}
|
||||||
|
try:
|
||||||
|
with open(MACHINE_CONF) as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith('#') or '=' not in line:
|
||||||
|
continue
|
||||||
|
k, v = line.split('=', 1)
|
||||||
|
keys[k.strip()] = v.strip()
|
||||||
|
except OSError:
|
||||||
|
return
|
||||||
|
for key, cfg in (('gf_serial', 'MACHINE.SERIAL'),
|
||||||
|
('gf_password', 'MACHINE.PASSWORD'),
|
||||||
|
('gf_hostname', 'MACHINE.HOSTNAME')):
|
||||||
|
if keys.get(key):
|
||||||
|
set_cfg(cfg, keys[key])
|
||||||
|
logger.info('identity override: %s from %s', cfg, MACHINE_CONF)
|
||||||
|
|
||||||
|
|
||||||
def make_machine():
|
def make_machine():
|
||||||
"""Build the hardware Machine with captures routed through forgectrl.
|
"""Build the hardware Machine with captures routed through forgectrl.
|
||||||
|
|
||||||
@@ -252,10 +281,17 @@ def home(machine, args) -> int:
|
|||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
|
try:
|
||||||
|
# The controller exports its own $H budget minus a margin, so
|
||||||
|
# the runner always gives up before the controller kills it.
|
||||||
|
timeout_default = max(30, int(os.environ.get('GFHOME_TIMEOUT_S', 240)))
|
||||||
|
except ValueError:
|
||||||
|
timeout_default = 240
|
||||||
|
|
||||||
ap = argparse.ArgumentParser(description='ForgeFIRM one-shot Glowforge cloud homing')
|
ap = argparse.ArgumentParser(description='ForgeFIRM one-shot Glowforge cloud homing')
|
||||||
ap.add_argument('-c', '--config', default=CONF, help='config file (default %s)' % CONF)
|
ap.add_argument('-c', '--config', default=CONF, help='config file (default %s)' % CONF)
|
||||||
ap.add_argument('--timeout', type=int, default=240,
|
ap.add_argument('--timeout', type=int, default=timeout_default,
|
||||||
help='overall time budget in seconds (default 240)')
|
help='overall time budget in seconds (default %d)' % timeout_default)
|
||||||
ap.add_argument('--start-timeout', type=int, default=120,
|
ap.add_argument('--start-timeout', type=int, default=120,
|
||||||
help='max seconds to wait for the service to begin homing (default 120)')
|
help='max seconds to wait for the service to begin homing (default 120)')
|
||||||
ap.add_argument('--quiet', type=int, default=10,
|
ap.add_argument('--quiet', type=int, default=10,
|
||||||
@@ -267,6 +303,8 @@ def main() -> int:
|
|||||||
if not load_config(args.config):
|
if not load_config(args.config):
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
apply_identity_overrides()
|
||||||
|
|
||||||
# Machine() reads the OCOTP identity and head info; it fails cleanly
|
# Machine() reads the OCOTP identity and head info; it fails cleanly
|
||||||
# when the controller still owns /dev/glowforge.
|
# when the controller still owns /dev/glowforge.
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user