mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 08:41:13 -07:00
ForgeFIRM's own files live under /data/forgefirm; two configuration files did not. The machine settings sat at /data/forgefirm.conf, in the root of /data beside the factory's own files, and the cloud-mode configuration sat at /data/etc/gfhome.conf, inside a directory the factory owns. Both move: /data/forgefirm.conf -> /data/forgefirm/forgefirm.conf /data/etc/gfhome.conf -> /data/forgefirm/gfhome.conf There is no migration: only the bench has ever run this firmware. /data/etc now holds only the factory's wpa_supplicant.conf. The acceptance check of the file modes reads the settings file at its new path, and the two bench tools that read it directly follow. The pins move to the revisions that carry the change, forgectrl also bringing the fix that reads the module's disabled state as idle: forgectrl 468ee21 (0.1.12) grblhal-glowforge 9ee624b (0.1.10) forgefirm-app 56f134a (0.1.28+git) The lock moves meta-openglow to b7ad6d9, which pins python3-gfhardware on the same revision. The four upstream layers stay where they were: `kas lock --update` moves every floating repository, and a release is not the place to take poky, meta-openembedded and meta-freescale along for the ride.
175 lines
5.6 KiB
Python
175 lines
5.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Shared helpers for the bench tools that can run either ON the board or
|
|
from a LAN host.
|
|
|
|
from gfbench import HOST, LOCAL, board, degc, data_path
|
|
|
|
HOST / LOCAL the machine address and whether this process runs on it.
|
|
GF_HOST names a remote machine (host mode: sysfs through
|
|
ssh, Grbl and forgectrl over the LAN). Unset, the tool
|
|
runs on the board itself when /sys/glowforge exists (local
|
|
mode: sysfs directly, Grbl and forgectrl on 127.0.0.1) -
|
|
the acceptance tool's bench page runs them that way.
|
|
board(cmd) run a shell command on the machine and return its stdout
|
|
(local: sh -c; host: ssh, or the client named by GF_SSH,
|
|
e.g. GF_SSH='wsl -d <distro> -- ssh').
|
|
degc(raw) the factory B-equation coolant conversion
|
|
(docs.forgefirm.org, the sensors page).
|
|
data_path(f) where a tool keeps its data files: FORGETEST_BENCH_DATA
|
|
when set (the bench page passes <data>/bench/), else next
|
|
to the tool.
|
|
forgectrl_* the machine-services HTTP API (:80) with the panel token
|
|
from GF_TOKEN or, on the board, /data/forgefirm/panel.token.
|
|
"""
|
|
import json
|
|
import math
|
|
import os
|
|
import shlex
|
|
import subprocess
|
|
import urllib.error
|
|
import urllib.parse
|
|
import urllib.request
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
_LOCAL_HOSTS = ("127.0.0.1", "localhost", "::1")
|
|
|
|
|
|
def _resolve():
|
|
host = os.environ.get("GF_HOST")
|
|
if host:
|
|
return host, host in _LOCAL_HOSTS
|
|
if os.path.isdir("/sys/glowforge"):
|
|
return "127.0.0.1", True
|
|
raise SystemExit("set GF_HOST to the machine IP address (or run this on the board)")
|
|
|
|
|
|
HOST, LOCAL = _resolve()
|
|
SSH = shlex.split(os.environ.get("GF_SSH", "ssh"))
|
|
|
|
|
|
def board(cmd, timeout=30):
|
|
"""stdout of a shell command run on the machine (not stripped)."""
|
|
if LOCAL:
|
|
argv = ["sh", "-c", cmd]
|
|
else:
|
|
argv = SSH + ["-o", "PreferredAuthentications=none", "root@" + HOST, cmd]
|
|
r = subprocess.run(argv, capture_output=True, text=True, timeout=timeout)
|
|
return r.stdout
|
|
|
|
|
|
# Factory B-equation conversion: 10k B3380 NTC in a 10k divider behind a
|
|
# 1.3x gain stage, 10-bit ADC.
|
|
F = 1024.0 * 1.3
|
|
RD, BETA = 10000.0, 3380.0
|
|
RINF = 10000.0 * math.exp(-3380.0 / 298.15)
|
|
|
|
|
|
def degc(raw):
|
|
try:
|
|
raw = float(raw)
|
|
except (TypeError, ValueError):
|
|
return float("nan")
|
|
if raw <= 1.0 or raw >= F:
|
|
return float("nan")
|
|
r = RD / (F / raw - 1.0)
|
|
return BETA / math.log(r / RINF) - 273.15
|
|
|
|
|
|
def data_dir():
|
|
d = os.environ.get("FORGETEST_BENCH_DATA")
|
|
if d:
|
|
os.makedirs(d, exist_ok=True)
|
|
return d
|
|
return HERE
|
|
|
|
|
|
def data_path(name):
|
|
return os.path.join(data_dir(), name)
|
|
|
|
|
|
# ------------------------------------------------------------- forgectrl
|
|
|
|
def forgectrl_base():
|
|
return os.environ.get("FORGECTRL_URL") or "http://%s:%s" % (HOST, os.environ.get("FORGECTRL_PORT") or "80")
|
|
|
|
|
|
def token():
|
|
tok = os.environ.get("GF_TOKEN")
|
|
if tok:
|
|
return tok
|
|
if LOCAL:
|
|
try:
|
|
with open("/data/forgefirm/panel.token", "r", encoding="utf-8") as f:
|
|
return f.read().strip()
|
|
except OSError:
|
|
pass
|
|
return ""
|
|
|
|
|
|
def forgectrl_request(method, path, params=None, data=None, timeout=8.0):
|
|
"""(status, body): body is parsed JSON when the response is JSON, else
|
|
text. Raises OSError-derived errors when the daemon is unreachable."""
|
|
url = forgectrl_base() + path
|
|
if params:
|
|
url += ("&" if "?" in url else "?") + urllib.parse.urlencode(params)
|
|
body = None
|
|
hdrs = {"Host": urllib.parse.urlsplit(forgectrl_base()).netloc}
|
|
if data is not None:
|
|
body = urllib.parse.urlencode(data).encode()
|
|
hdrs["Content-Type"] = "application/x-www-form-urlencoded"
|
|
tok = token()
|
|
if tok:
|
|
hdrs["X-ForgeFIRM-Token"] = tok
|
|
req = urllib.request.Request(url, data=body, method=method, headers=hdrs)
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
status, content, ctype = resp.status, resp.read(), resp.headers.get("Content-Type", "")
|
|
except urllib.error.HTTPError as e:
|
|
status, content = e.code, e.read()
|
|
ctype = e.headers.get("Content-Type", "") if e.headers else ""
|
|
text = content.decode("utf-8", "replace")
|
|
if "json" in ctype:
|
|
try:
|
|
return status, json.loads(text)
|
|
except ValueError:
|
|
pass
|
|
return status, text
|
|
|
|
|
|
def forgectrl_get(path, **kw):
|
|
return forgectrl_request("GET", path, **kw)
|
|
|
|
|
|
def forgectrl_post(path, **kw):
|
|
return forgectrl_request("POST", path, **kw)
|
|
|
|
|
|
SETTINGS_FILE = "/data/forgefirm/forgefirm.conf"
|
|
|
|
|
|
def setting(key, default=None):
|
|
"""One shared machine setting (forgectrl's /settings; on the board with
|
|
forgectrl stopped - a takeover - the settings file itself), or default
|
|
when unset or unreadable."""
|
|
try:
|
|
st, body = forgectrl_get("/settings")
|
|
if st == 200 and isinstance(body, dict):
|
|
val = body.get(key)
|
|
return default if val in (None, "") else val
|
|
except OSError:
|
|
pass
|
|
if LOCAL:
|
|
try:
|
|
with open(SETTINGS_FILE, "r", encoding="utf-8") 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)
|
|
if k.strip() == key:
|
|
v = v.strip()
|
|
return default if v == "" else v
|
|
except OSError:
|
|
pass
|
|
return default
|