feat(flomni): realtime_system.help(name) / help("all"); marked beamline scientists / local contact only
- help("<setting>"): what it does (help text of the flOMNI definition, as the web HMI tooltips), range, live and
saved value, the loop parameters it sets, its rules; help("all"): every setting by group
- realtime_system is for beamline scientists / local contact only (Mirko 2026-10-23): module docstring, a line at
show() / help(), user guide section heading and note
- tests: help output
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019aWcwG89wi7oVyrefukXX6
This commit is contained in:
3 files changed
+78
-8
No files matched your search
@@ -1,4 +1,8 @@
|
||||
"""flomni.realtime_system: the operating settings of the flOMNI real-time loop (expert functions).
|
||||
"""flomni.realtime_system: the operating settings of the flOMNI real-time loop.
|
||||
|
||||
BEAMLINE SCIENTISTS / LOCAL CONTACT ONLY - not for users. These settings change how the FZP scanner, the detector
|
||||
trigger and the laser tracker work; a wrong combination can spoil a beamtime's data. The rules refuse combinations
|
||||
that do not work, but not ones that are only unsuitable for an experiment.
|
||||
|
||||
The settings, the parameters they map onto and the rules between them live in the flOMNI instrument repository
|
||||
(FeedbackLoop/loopsettings/flomni.json) and are applied by the web HMI on the loop machine (RT-system
|
||||
@@ -23,6 +27,9 @@ from csaxs_bec.devices.omny.webhmi_client import WebHmiClient
|
||||
from csaxs_bec.devices.omny.webhmi_client import WebHmiError as RealtimeSystemError # name kept for BEC users
|
||||
|
||||
|
||||
STAFF_ONLY = "flomni.realtime_system: beamline scientists / local contact only - not for users."
|
||||
|
||||
|
||||
def _fmt(v):
|
||||
if v is None:
|
||||
return "-"
|
||||
@@ -82,6 +89,7 @@ class FlomniRealtimeSystem(WebHmiClient):
|
||||
snap = self.read()
|
||||
groups = {g["name"]: g for g in snap["groups"]}
|
||||
sc = snap.get("scan") or {}
|
||||
print(STAFF_ONLY)
|
||||
print(f"Loop settings {snap.get('instrument', '')} ({self.route}) - "
|
||||
+ (f"scan running (mode {_fmt(sc.get('mode'))})" if sc.get("active") else "no scan"))
|
||||
for gname, g in groups.items():
|
||||
@@ -216,8 +224,49 @@ class FlomniRealtimeSystem(WebHmiClient):
|
||||
v = self.get()
|
||||
return v["tracking_target_y"], v["tracking_target_z"]
|
||||
|
||||
def help(self):
|
||||
"""Print the functions with a short explanation."""
|
||||
print(__doc__.split("\n\nConnection:")[0])
|
||||
print("\nShortcuts: trigger_show(), trigger_set_tolerance_nm(v), trigger_set_hold_ms(v),"
|
||||
" trigger_set_after_move(True/False), tracking_targets(); profiles(), unsaved(), get(name)")
|
||||
def help(self, name: str = None):
|
||||
"""help(): the functions; help("all"): every setting with its explanation; help(name): one setting (what it
|
||||
does, unit, range, the loop parameters it sets, live and saved value), from the definition on the loop
|
||||
machine (the same text as the tooltips of the web HMI tab)."""
|
||||
if name is None:
|
||||
print(__doc__.split("\n\nConnection:")[0])
|
||||
print("\nShortcuts: trigger_show(), trigger_set_tolerance_nm(v), trigger_set_hold_ms(v),"
|
||||
" trigger_set_after_move(True/False), tracking_targets(); profiles(), unsaved(), get(name)")
|
||||
print('Explanations: help("all") or help("<setting>"), e.g. help("move_beyond_um_x")')
|
||||
return
|
||||
snap = self.read()
|
||||
settings = {s["name"]: s for s in snap["settings"]}
|
||||
groups = {g["name"]: g for g in snap["groups"]}
|
||||
if name == "all":
|
||||
print(STAFF_ONLY)
|
||||
for gname, g in groups.items():
|
||||
rows = [s for s in snap["settings"] if s["group"] == gname]
|
||||
if not rows:
|
||||
continue
|
||||
print(f"\n{g.get('title', gname)}" + (" (only between scans)" if g.get("interlock") else ""))
|
||||
for s in rows:
|
||||
unit = f" [{s['unit']}]" if s.get("unit") else ""
|
||||
print(f" {s['name']}{unit}: {s.get('title', '')}")
|
||||
if s.get("help"):
|
||||
print(f" {s['help']}")
|
||||
return
|
||||
s = settings.get(name)
|
||||
if s is None:
|
||||
raise RealtimeSystemError(f"unknown setting '{name}'; help('all') lists them")
|
||||
g = groups.get(s["group"], {})
|
||||
print(STAFF_ONLY)
|
||||
print(f"{s['name']} - {s.get('title', '')} (group {g.get('title', s['group'])}"
|
||||
+ (", only between scans)" if g.get("interlock") else ")"))
|
||||
if s.get("help"):
|
||||
print(f" {s['help']}")
|
||||
rng = "0 / 1" if s.get("type") == "bool" else f"{_fmt(s.get('min'))} ... {_fmt(s.get('max'))} {s.get('unit', '')}"
|
||||
print(f" range: {rng}")
|
||||
print(f" live: {_fmt(s.get('value'))} saved (.xmp): {_fmt(s.get('saved'))}"
|
||||
+ (" NOT SAVED" if s.get("unsaved") else "") + ("" if s.get("consistent", True) else " INCONSISTENT"))
|
||||
if s.get("info"):
|
||||
print(f" {s['info'].get('label', '')} {s['info']['value']:.4g} {s['info'].get('unit', '')}")
|
||||
print(" sets: " + ", ".join(f"{t['module']}.{t['param']}[{t['index']}] = {t.get('write', 'v')}"
|
||||
for t in s.get("targets", [])))
|
||||
rules = [r for r in snap.get("rules", []) if name in r.get("settings", [])]
|
||||
if rules:
|
||||
print(" rules: " + ", ".join(r["name"] + ("" if r.get("ok") else " (VIOLATED)") for r in rules))
|
||||
@@ -224,6 +224,8 @@ In step scans the detector is triggered when the sample is stable at the scan po
|
||||
sample vs FZP (interferometer minus FZP cap sensor) must stay within a tolerance in x and in y (each averaged
|
||||
over 0.5 ms) for a hold time. Both values are live and can be changed between scans:
|
||||
|
||||
(Beamline scientists / local contact only, see below.)
|
||||
|
||||
- `flomni.realtime_system.trigger_show()` — current tolerance, hold time, minimum delay, trigger after a move
|
||||
- `flomni.realtime_system.trigger_set_tolerance_nm(10)` — tolerance per axis in nm: about 30 for a fast
|
||||
low-resolution scan, 10 standard, 2 for the highest resolution (a few ms more per point);
|
||||
@@ -782,7 +784,10 @@ removed later.
|
||||
|
||||
[See short version](flomni.md#sample-storage-and-transfer)
|
||||
|
||||
### Real-time loop settings (expert): `flomni.realtime_system`
|
||||
### Real-time loop settings: `flomni.realtime_system` (beamline scientists / local contact only)
|
||||
|
||||
**Not for users.** These settings change how the FZP scanner, the detector trigger and the laser tracker work; a
|
||||
wrong combination can spoil the data of a beamtime. Users ask the local contact.
|
||||
|
||||
The operating settings of the real-time loop — FZP scanner on/off, FZP range, feedforward, move-beyond, exposure
|
||||
during the motion, Abbe correction, detector trigger, laser tracker targets and thresholds — with the rules between
|
||||
@@ -798,7 +803,8 @@ them. The rules and the mapping onto the loop parameters are kept on the loop ma
|
||||
- `flomni.realtime_system.profiles()`, `apply_profile("production" | "fzp_off" | "fzp_calibration" | "fast_scan")`
|
||||
- `flomni.realtime_system.unsaved()`, `save_as_default()` — the changes are live only until the next start of the
|
||||
control loop; `save_as_default()` writes them into the loop's parameter files
|
||||
- `flomni.realtime_system.help()`
|
||||
- `flomni.realtime_system.help()`; `help("all")` explains every setting, `help("move_beyond_um_x")` one setting
|
||||
(what it does, range, the loop parameters it sets, live and saved value, its rules)
|
||||
|
||||
Every scan stores the loop settings it ran with: the baseline device `flomni_loop_settings` (one value per
|
||||
setting, `available`, unsaved / inconsistent counts, the definition's sha1, a JSON of all settings);
|
||||
|
||||
@@ -197,3 +197,18 @@ def test_routes_through_the_device():
|
||||
dm.devices.flomni_loop_settings.enabled = False # disabled: direct HTTP
|
||||
with pytest.raises(RealtimeSystemError, match="No API token"):
|
||||
rts.get()
|
||||
|
||||
|
||||
def test_help(server, capsys):
|
||||
"""help(name) / help("all"): explanations from the definition, marked as beamline-scientist only."""
|
||||
url, tok, _ = server
|
||||
rts = FlomniRealtimeSystem(url=url, token_file=tok)
|
||||
rts.help("trigger_tolerance_nm")
|
||||
out = capsys.readouterr().out
|
||||
assert "local contact only" in out and "trigger_tolerance_nm" in out and "live: 10" in out
|
||||
rts.help("all")
|
||||
assert "Detector trigger" in capsys.readouterr().out
|
||||
rts.help()
|
||||
assert "BEAMLINE SCIENTISTS" in capsys.readouterr().out
|
||||
with pytest.raises(RealtimeSystemError, match="unknown setting"):
|
||||
rts.help("nosuch")
|
||||
Reference in new issue
Block a user