From 76aa2b9e56b52044cb1855a91642f6c9b199efa8 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 23 Oct 2026 18:56:12 +0000 Subject: [PATCH] feat(flomni): realtime_system.help(name) / help("all"); marked beamline scientists / local contact only - help(""): 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 Claude-Session: https://claude.ai/code/session_019aWcwG89wi7oVyrefukXX6 --- .../plugins/flomni/flomni_realtime_system.py | 61 +++++++++++++++++-- docs/user/ptychography/flomni.md | 10 ++- .../test_flomni_realtime_system.py | 15 +++++ 3 files changed, 78 insertions(+), 8 deletions(-) diff --git a/csaxs_bec/bec_ipython_client/plugins/flomni/flomni_realtime_system.py b/csaxs_bec/bec_ipython_client/plugins/flomni/flomni_realtime_system.py index 374c5366..be6d666f 100644 --- a/csaxs_bec/bec_ipython_client/plugins/flomni/flomni_realtime_system.py +++ b/csaxs_bec/bec_ipython_client/plugins/flomni/flomni_realtime_system.py @@ -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(""), 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)) diff --git a/docs/user/ptychography/flomni.md b/docs/user/ptychography/flomni.md index 61baa612..b8b99e4a 100644 --- a/docs/user/ptychography/flomni.md +++ b/docs/user/ptychography/flomni.md @@ -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); diff --git a/tests/tests_bec_ipython_client/test_flomni_realtime_system.py b/tests/tests_bec_ipython_client/test_flomni_realtime_system.py index 18274088..ec664b87 100644 --- a/tests/tests_bec_ipython_client/test_flomni_realtime_system.py +++ b/tests/tests_bec_ipython_client/test_flomni_realtime_system.py @@ -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")