diff --git a/csaxs_bec/bec_ipython_client/plugins/OMNY_shared/AI_docs/TODO_rt_namespace_harmonization.md b/csaxs_bec/bec_ipython_client/plugins/OMNY_shared/AI_docs/TODO_rt_namespace_harmonization.md new file mode 100644 index 00000000..bc7398e1 --- /dev/null +++ b/csaxs_bec/bec_ipython_client/plugins/OMNY_shared/AI_docs/TODO_rt_namespace_harmonization.md @@ -0,0 +1,37 @@ +# TODO: port the flOMNI `rt_*` naming to LamNI and OMNY + +**Status: done on flOMNI (2026-10-26)**, not yet on LamNI and OMNY. Goal: `.rt_` lists the whole +real-time system, and the names match across all three endstations. + +## flOMNI scheme (reference) + +| Function | flOMNI name | former name | +| --- | --- | --- | +| BEC device enable rtx/rty/rtz | `rt_on()` / `rt_off()` | unchanged | +| feedback | `rt_feedback_enable_with_reset()`, `rt_feedback_enable_without_reset()`, `rt_feedback_disable()`, `rt_feedback_status()` | `feedback_*` (kept as aliases) | +| laser tracker | `rt_laser_tracker_on()`, `rt_laser_tracker_off()`, `rt_laser_tracker_show_all()`, `rt_laser_tracker_tweak()` | `laser_tracker_*`, `laser_parameters_tweak_tracking` | +| signal strength | `rt_signal_strength()`, alias `show_signal_strength_interferometer()` | `show_signal_strength_interferometer` | +| loop settings (staff) | `rt_settings` incl. `tracking_show/set_target_y/set_target_z/set_interferometer_threshold/set_psd_threshold_low` | `realtime_system`, `laser_parameters_*` | + +## LamNI (`plugins/LamNI/lamni.py`) + +- `rt_on` / `rt_off` already exist (line ~774). +- `feedback_enable_with_reset`, `feedback_enable_without_reset`, `feedback_disable`, `feedback_status` + -> `rt_feedback_*` (keep the old names as aliases, as on flOMNI); also `feedback_disable_and_reset_angle` -> `rt_feedback_disable_and_reset_angle`. +- `show_signal_strength` -> `rt_signal_strength` (keep the old name as an alias). +- LamNI has no laser tracker and no `rt_settings` (no web HMI loop settings). +- Callers: `plugins/LamNI/x_ray_eye_align.py` and `plugins/omny/x_ray_eye_align.py` (the LamNI one calls + `rtx.controller` directly), LamNI tomo/optics mixins, `docs/user/ptychography/lamni.md`, tests with + `feedback_*` mocks (`test_lamni_optics_skip_if_in_position.py`). + +## OMNY (`plugins/omny/omny.py`, `plugins/omny/omny_rt.py`) + +- `rt_on` / `rt_off` already exist (`omny.py` ~2166). +- `omny_rt.py`: `feedback_*` -> `rt_feedback_*` (old names as aliases), `laser_tracker_on/off/show_all` -> `rt_laser_tracker_*`, + `laser_tracker_check_and_wait_for_signalstrength` -> `rt_laser_tracker_check_and_wait_for_signalstrength`, + `show_signal_strength_interferometer` -> `rt_signal_strength` + alias. +- Callers: OMNY tomo/alignment code, `docs/user/ptychography/omny.md`, `test_omny_tomo_scan.py` + (`omny.laser_tracker_on` mock), `test_omny_optics_skip_if_in_position.py`. + +Do not rename the controller methods (`dev.rtx.controller.feedback_*`, `laser_tracker_*`): only the +client-level user commands change. diff --git a/csaxs_bec/bec_ipython_client/plugins/flomni/docs/local_contact_only-howto_calibrate_the_fzp_scanner.md b/csaxs_bec/bec_ipython_client/plugins/flomni/docs/local_contact_only-howto_calibrate_the_fzp_scanner.md index aafb81e9..76dd0a35 100644 --- a/csaxs_bec/bec_ipython_client/plugins/flomni/docs/local_contact_only-howto_calibrate_the_fzp_scanner.md +++ b/csaxs_bec/bec_ipython_client/plugins/flomni/docs/local_contact_only-howto_calibrate_the_fzp_scanner.md @@ -103,7 +103,7 @@ correction (−4 before 2026-10-19, now 0: replaced by the feedforward). about ±2 V only). For the field of view above (2.8 × 1.8 µm) widen them, e.g. to ±10 V (FZP ≈ 0.13 µm/V in x, ≈ 0.05 µm/V in y). The feedforward stays off (`enable` 0,0) during this scan. - The detector trigger (`OnTargetTrigger`) checks the relative error sample vs FZP (parameters - `ontarget_tolerance_um` / `ontarget_hold_ms`, BEC `flomni.realtime_system.trigger_*`, default 10 nm / 3 ms; until + `ontarget_tolerance_um` / `ontarget_hold_ms`, BEC `flomni.rt_settings.trigger_*`, default 10 nm / 3 ms; until 2026-10-23 ConstEmitter lines) and waits `MinDelay_ms` (15 ms). **Step 3, default operating values (replace the values in the text above):** @@ -136,18 +136,18 @@ exposure grows with the step (1 µm: ≈ 6 nm max instead of 2.5 nm), so the set ## With the loop settings (since 2026-10-23) -The steps above are profiles of the loop settings (web HMI tab *Loop settings*, BEC `flomni.realtime_system`; +The steps above are profiles of the loop settings (web HMI tab *Loop settings*, BEC `flomni.rt_settings`; flOMNI `FeedbackLoop/loopsettings/`). The rules keep the modules consistent, so the individual parameters above do not have to be set by hand: | Step | Profile | BEC | |---|---|---| -| 1, FZP disabled (alignment, affine matrix) | `fzp_off` | `flomni.realtime_system.apply_profile("fzp_off")` | +| 1, FZP disabled (alignment, affine matrix) | `fzp_off` | `flomni.rt_settings.apply_profile("fzp_off")` | | 2, FZP-only calibration scan (clear `MatrixCorr` first: not part of the settings) | `fzp_calibration` | `apply_profile("fzp_calibration")` | | 3, back to operation | `production` | `apply_profile("production")` | | afterwards, fast scans with the FZP at ±9 V (scan modes, flOMNI `FeedbackLoop/scan_modes.md`) | `high_res`, `normal`, `low_res` | `apply_profile("high_res")` | -Single values: `flomni.realtime_system.set(fzp_range_V_x=7, fzp_range_V_y=7)`, +Single values: `flomni.rt_settings.set(fzp_range_V_x=7, fzp_range_V_y=7)`, `set(move_beyond_um_x=0.3)`, `set(exposure_during_motion=1)`; refused with the reason if a rule objects (`adjust=True` changes the dependent settings too). FZP settings only between scans. Live only until `save_as_default()`. diff --git a/csaxs_bec/bec_ipython_client/plugins/flomni/docs/local_contact_only-howto_limit_the_fzp_scanner_range.md b/csaxs_bec/bec_ipython_client/plugins/flomni/docs/local_contact_only-howto_limit_the_fzp_scanner_range.md index 488462ee..2ff5e696 100644 --- a/csaxs_bec/bec_ipython_client/plugins/flomni/docs/local_contact_only-howto_limit_the_fzp_scanner_range.md +++ b/csaxs_bec/bec_ipython_client/plugins/flomni/docs/local_contact_only-howto_limit_the_fzp_scanner_range.md @@ -40,7 +40,7 @@ Changing the FZP range now involves, in addition to the text above: - After a change: web HMI tab **Loop check** level 1 (expected settings) and, with the beamline ready, the scan test (level 4: positioning time, stability during the exposure per step size). -**With the loop settings (since 2026-10-23):** `flomni.realtime_system.set(fzp_range_V_x=..., fzp_range_V_y=...)` +**With the loop settings (since 2026-10-23):** `flomni.rt_settings.set(fzp_range_V_x=..., fzp_range_V_y=...)` (web HMI tab *Loop settings*) sets `PID_*_fzp` `Ub` / `Lb` and the `Feedforward` limits together; a move-beyond that no longer fits is refused, or clamped with `adjust=True` (rule `move_beyond_*_fits`). `FreqGen` limits stay manual (measurements only). diff --git a/csaxs_bec/bec_ipython_client/plugins/flomni/docs/local_contact_only-realtime_system.md b/csaxs_bec/bec_ipython_client/plugins/flomni/docs/local_contact_only-realtime_system.md index bb5551ec..cb36401d 100644 --- a/csaxs_bec/bec_ipython_client/plugins/flomni/docs/local_contact_only-realtime_system.md +++ b/csaxs_bec/bec_ipython_client/plugins/flomni/docs/local_contact_only-realtime_system.md @@ -1,6 +1,6 @@ # flOMNI real-time loop settings (beamline scientists / local contact only) -**Not for users.** `flomni.realtime_system` changes how the FZP scanner, the detector trigger and the laser tracker +**Not for users.** `flomni.rt_settings` changes how the FZP scanner, the detector trigger and the laser tracker work, and `save_as_default()` makes a change the default of every following loop start. A wrong combination can spoil the data of a beamtime. The user guide only says that these settings exist, are set by the local contact and are stored with every scan; the details are here. @@ -23,22 +23,22 @@ Background and reference (flOMNI control repository, `OMNY/flOMNI` on gitea): | Laser tracker and thresholds | `tracking_target_y` / `_z`, `psd_threshold_low`, `interferometer_threshold`, `interferometer_threshold_rot` | | FZP calibration (expert, only between scans) | `sample_hold_fzp_V`, `sample_hold_err_um`, `sample_hold_zero` | -`flomni.realtime_system.help("all")` explains every setting, `help("")` one setting (what it does, range, +`flomni.rt_settings.help("all")` explains every setting, `help("")` one setting (what it does, range, live and saved value, the loop parameters it sets, its rules). ## Commands -- `flomni.realtime_system.show()` — all settings with live and saved value, violated rules, scan state +- `flomni.rt_settings.show()` — all settings with live and saved value, violated rules, scan state (`show(expert=True)` also the FZP calibration group) -- `flomni.realtime_system.set(name=value, ...)` — change; refused with the reason if a rule objects (e.g. the +- `flomni.rt_settings.set(name=value, ...)` — change; refused with the reason if a rule objects (e.g. the feedforward must be off while the FZP loop is off); `set(..., adjust=True)` changes the dependent settings the rule names (never the ones given) -- `flomni.realtime_system.check(name=value)` — dry run -- `flomni.realtime_system.profiles()`, `apply_profile("production" | "fzp_off" | "fzp_calibration" | "high_res" | "high_res_1v5" | "normal" | "low_res")` (scan modes 2026-10-26: flOMNI `FeedbackLoop/scan_modes.md`) -- `flomni.realtime_system.unsaved()` — what differs from the loop's parameter files -- `flomni.realtime_system.save_as_default()` — writes the live values into the loop's parameter files: the next +- `flomni.rt_settings.check(name=value)` — dry run +- `flomni.rt_settings.profiles()`, `apply_profile("production" | "fzp_off" | "fzp_calibration" | "high_res" | "high_res_1v5" | "normal" | "low_res")` (scan modes 2026-10-26: flOMNI `FeedbackLoop/scan_modes.md`) +- `flomni.rt_settings.unsaved()` — what differs from the loop's parameter files +- `flomni.rt_settings.save_as_default()` — writes the live values into the loop's parameter files: the next loop start uses them. Only for a change that should stay (a backup of each file is kept on the loop machine). -- `flomni.realtime_system.help()`, `help("all")`, `help("")` +- `flomni.rt_settings.help()`, `help("all")`, `help("")` Changes are live only until the next start of the control loop, unless saved. FZP settings can only be changed between scans (refused while a scan runs); trigger and tracker settings any time. @@ -48,11 +48,11 @@ between scans (refused while a scan runs); trigger and tracker settings any time In step scans the detector is triggered when the relative position sample vs FZP (interferometer minus FZP cap sensor) stays within the tolerance in x and in y (each averaged over 0.5 ms) for the hold time. -- `flomni.realtime_system.trigger_show()` — tolerance, hold time, minimum delay, trigger after a move -- `flomni.realtime_system.trigger_set_tolerance_nm(10)` — about 30 for a fast low-resolution scan, 10 standard, +- `flomni.rt_settings.trigger_show()` — tolerance, hold time, minimum delay, trigger after a move +- `flomni.rt_settings.trigger_set_tolerance_nm(10)` — about 30 for a fast low-resolution scan, 10 standard, 2 for the highest resolution (a few ms more per point); 0 = the former criterion (on-target flags of the FZP loops) -- `flomni.realtime_system.trigger_set_hold_ms(3)` — hold time in ms -- `flomni.realtime_system.trigger_set_after_move(True)` — also one trigger after every position change of the +- `flomni.rt_settings.trigger_set_hold_ms(3)` — hold time in ms +- `flomni.rt_settings.trigger_set_after_move(True)` — also one trigger after every position change of the sample stage (e.g. `umv(dev.rtx, ...)`, `dev.rtx.controller.move_xyz(x, y, z)`) once on target; `False` (default): scan points only @@ -60,10 +60,10 @@ The former `flomni.interferometer_trigger_*` were removed 2026-10-23. ### Laser tracker -`flomni.laser_parameters_show_all()`, `laser_parameters_set_targety(v)` / `_targetz(v)`, -`laser_parameters_set_intensity_threshold_laser(v)`, `laser_parameters_set_psd_intensity_threshold_tracking_low(v)` -and the arrow-key tweak `laser_parameters_tweak_tracking()` use the same settings (`tracking_target_y` / `_z`, -`interferometer_threshold`, `psd_threshold_low`). +`flomni.rt_settings.tracking_show()`, `tracking_set_target_y(v)` / `tracking_set_target_z(v)`, +`tracking_set_interferometer_threshold(v)`, `tracking_set_psd_threshold_low(v)` and the arrow-key tweak +`flomni.rt_laser_tracker_tweak()` use the same settings (`tracking_target_y` / `_z`, `interferometer_threshold`, +`psd_threshold_low`). The former `flomni.laser_parameters_*` were moved here 2026-10-26. ## Stored with every scan @@ -74,7 +74,7 @@ as default or inconsistent, violated error rules, the definition with its sha1, ## Connection -`flomni.realtime_system` sends its requests through the device `flomni_loop_settings` (device server). The device +`flomni.rt_settings` sends its requests through the device `flomni_loop_settings` (device server). The device talks to the web HMI on the loop machine (`http://mpc2844:8080`, REST `/api/settings`; port 80 as fallback) with the API token in its device config (`ptycho_flomni.yaml`, `token:`; the line of `~/loop-data/webhmi/api_token` on mpc2844). Without that device (e.g. a simulated config) the client talks to the web HMI directly with diff --git a/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py b/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py index de017755..59df523e 100644 --- a/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py +++ b/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py @@ -495,38 +495,24 @@ class FlomniSampleTransferMixin: self.move_fheater_down() self.xrayeye_update_frame() - def laser_tracker_show_all(self): + def rt_laser_tracker_show_all(self): dev.rtx.controller.laser_tracker_show_all() - def laser_tracker_on(self): + def rt_laser_tracker_on(self): dev.rtx.controller.laser_tracker_on() time.sleep(0.2) dev.rtx.controller.laser_tracker_check_signalstrength() - def laser_tracker_off(self): + def rt_laser_tracker_off(self): dev.rtx.controller.laser_tracker_off() - # laser tracker targets and thresholds: settings of the real-time loop (flomni.realtime_system, group - # "tracking"; until 2026-10-23 ConstEmitter lines set through the CommunicationServer e-commands) - def laser_parameters_show_all(self): - self.realtime_system.show(group="tracking") + # laser tracker targets and thresholds: settings of the real-time loop, flomni.rt_settings.tracking_* + # (the former flomni.laser_parameters_* moved there 2026-10-26) - def laser_parameters_set_targety(self, val: float): - self.realtime_system.set(tracking_target_y=val) - - def laser_parameters_set_targetz(self, val: float): - self.realtime_system.set(tracking_target_z=val) - - def laser_parameters_set_intensity_threshold_laser(self, val: float): - self.realtime_system.set(interferometer_threshold=val) - - def laser_parameters_set_psd_intensity_threshold_tracking_low(self, val: float): - self.realtime_system.set(psd_threshold_low=val) - - def laser_parameters_tweak_tracking(self): + def rt_laser_tracker_tweak(self): """Interactive tweak of the two laser tracking target positions. - Arrow keys nudge the tracking targets (realtime_system tracking_target_y / _z) by a fixed step of + Arrow keys nudge the tracking targets (rt_settings tracking_target_y / _z) by a fixed step of 0.02 (absolute set of current +/- step): left / right : target y -/+ up / down : target z +/- @@ -547,11 +533,11 @@ class FlomniSampleTransferMixin: import tty controller = dev.rtx.controller - rts = self.realtime_system + rts = self.rt_settings if not controller.laser_tracker_check_enabled(): print( - "The laser tracker is disabled. Enable it with laser_tracker_on() " + "The laser tracker is disabled. Enable it with rt_laser_tracker_on() " "before tweaking the tracking targets - otherwise nothing moves on " "the hardware side." ) @@ -563,7 +549,7 @@ class FlomniSampleTransferMixin: return rts.tracking_targets() def signal_fzpx(): - # second channel (index 1) of show_signal_strength_interferometer, + # second channel (index 1) of rt_signal_strength, # i.e. the fzp x channel / tracker signal return controller.read_ssi_interferometer(1) @@ -630,14 +616,14 @@ class FlomniSampleTransferMixin: def umvr_fsamy_tracked(self, shift: float, step: float = 0.01, _internal: bool = False): """ Relative move of fsamy by `shift` mm, broken into steps of at most - `step` mm (default 0.01 mm = 10 um), calling laser_tracker_on() + `step` mm (default 0.01 mm = 10 um), calling rt_laser_tracker_on() after every step so the tracker stays locked during the move (fsamy travels a lot during sample change and the tracker can't keep up in one big jump). fsamy itself moves in mm, so shift/step are taken directly in mm -- no unit conversion needed. Uses dev.rtx.controller.laser_tracker_on() directly rather than - self.laser_tracker_on() (which adds a 0.2s sleep plus a + self.rt_laser_tracker_on() (which adds a 0.2s sleep plus a signal-strength check) since this runs once per small step and that overhead would add up across what can be 100+ steps for a typical sample-change-sized move; the goal here is just to keep the tracker @@ -663,7 +649,7 @@ class FlomniSampleTransferMixin: ) if not self.OMNYTools.yesno("Disable feedback and continue?", "y"): return - self.feedback_disable() + self.rt_feedback_disable() direction = 1 if shift >= 0 else -1 remaining = abs(shift) @@ -691,28 +677,37 @@ class FlomniSampleTransferMixin: shift_mm = target - current self.umvr_fsamy_tracked(shift_mm, step, _internal=_internal) - def show_signal_strength_interferometer(self): + def rt_signal_strength(self): dev.rtx.controller.show_signal_strength_interferometer() - def feedback_disable(self): + # the familiar name, kept as an alias + show_signal_strength_interferometer = rt_signal_strength + + def rt_feedback_disable(self): self.device_manager.devices.rtx.controller.feedback_disable() - def feedback_enable_with_reset(self): + def rt_feedback_enable_with_reset(self): self.device_manager.devices.rtx.controller.feedback_enable_with_reset() - self.feedback_status() + self.rt_feedback_status() - def feedback_enable_without_reset(self): + def rt_feedback_enable_without_reset(self): self.device_manager.devices.rtx.controller.feedback_enable_without_reset() - self.feedback_status() + self.rt_feedback_status() - def feedback_status(self): + def rt_feedback_status(self): feedback_status = self.device_manager.devices.rtx.controller.feedback_is_running() if feedback_status == True: print("The rt feedback is \x1b[92mrunning\x1b[0m.") else: print("The rt feedback is \x1b[91mNOT\x1b[0m running.") - # detector trigger: flomni.realtime_system.trigger_* (the former flomni.interferometer_trigger_* removed + # the familiar names, kept as aliases + feedback_disable = rt_feedback_disable + feedback_enable_with_reset = rt_feedback_enable_with_reset + feedback_enable_without_reset = rt_feedback_enable_without_reset + feedback_status = rt_feedback_status + + # detector trigger: flomni.rt_settings.trigger_* (the former flomni.interferometer_trigger_* removed # 2026-10-23, Mirko: "interferometer" was confusing next to flomni.laser_*) def lights_off(self): @@ -735,7 +730,7 @@ class FlomniSampleTransferMixin: self.feye_in() self.foptics_out() - self.feedback_disable() + self.rt_feedback_disable() self.ensure_fheater_up() @@ -751,7 +746,7 @@ class FlomniSampleTransferMixin: self.umv_fsamy_tracked(fsamy_in, _internal=True) time.sleep(0.05) - self.laser_tracker_off() + self.rt_laser_tracker_off() time.sleep(0.05) @@ -1762,8 +1757,8 @@ class Flomni( self.OMNYTools = OMNYTools(self.client) # expert functions of the real-time loop (settings with rules, via the web HMI on the loop machine; - # flomni_realtime_system.py): flomni.realtime_system.show() / .set(...) / .help() - self.realtime_system = FlomniRealtimeSystem(device_manager=self.device_manager) + # flomni_realtime_system.py): flomni.rt_settings.show() / .set(...) / .help() + self.rt_settings = FlomniRealtimeSystem(device_manager=self.device_manager) self.reconstructor = PtychoReconstructor(self.ptycho_reconstruct_foldername) self.tomo_id_manager = TomoIDManager() self.align = XrayEyeAlign(self.client, self) @@ -1868,7 +1863,7 @@ class Flomni( fsamx_in = self._get_user_param_safe("fsamx", "in") # name: dev[...] lookup (a device raised) if np.isclose(fsamx_in, dev.fsamx.readback.get(), rtol=0, atol=0.5): print("Stopping alignment. Returning to fsamx in position.") - self.feedback_disable() + self.rt_feedback_disable() umv(dev.fsamx, fsamx_in) raise exc 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 0c08651f..b539dbd9 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,4 @@ -"""flomni.realtime_system: the operating settings of the flOMNI real-time loop. +"""flomni.rt_settings: 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 @@ -9,12 +9,12 @@ The settings, the parameters they map onto and the rules between them live in th webhmi/loopsettings.py). This client only talks to its REST API (/api/settings); it holds no logic of its own, so the web page (tab "Loop settings") and BEC always behave the same way. - flomni.realtime_system.show() all settings, rules, scan state - flomni.realtime_system.set(trigger_tolerance_nm=5) change (refused if a rule objects) - flomni.realtime_system.set(fzp_enable=0, adjust=True) ... and adjust the dependent settings - flomni.realtime_system.check(fzp_range_V_x=1.0) dry run - flomni.realtime_system.apply_profile("fzp_off") a named set of settings - flomni.realtime_system.save_as_default() write the live values into the .xmp files + flomni.rt_settings.show() all settings, rules, scan state + flomni.rt_settings.set(trigger_tolerance_nm=5) change (refused if a rule objects) + flomni.rt_settings.set(fzp_enable=0, adjust=True) ... and adjust the dependent settings + flomni.rt_settings.check(fzp_range_V_x=1.0) dry run + flomni.rt_settings.apply_profile("fzp_off") a named set of settings + flomni.rt_settings.save_as_default() write the live values into the .xmp files Changes are live only: the next start of the control loop uses the .xmp files again (save_as_default keeps them). @@ -27,7 +27,7 @@ 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 " +STAFF_ONLY = ("flomni.rt_settings: beamline scientists / local contact only - not for users " "(plugins/flomni/docs/local_contact_only-realtime_system.md).") @@ -225,6 +225,27 @@ class FlomniRealtimeSystem(WebHmiClient): v = self.get() return v["tracking_target_y"], v["tracking_target_z"] + # the former flomni.laser_parameters_* (moved here 2026-10-26) + def tracking_show(self): + """Laser tracker: targets on the PSD and intensity thresholds.""" + self.show(group="tracking") + + def tracking_set_target_y(self, val: float): + """Laser tracker target y on the PSD (arrow-key tweak: flomni.rt_laser_tracker_tweak()).""" + return self.set(tracking_target_y=val) + + def tracking_set_target_z(self, val: float): + """Laser tracker target z on the PSD (arrow-key tweak: flomni.rt_laser_tracker_tweak()).""" + return self.set(tracking_target_z=val) + + def tracking_set_interferometer_threshold(self, val: float): + """Intensity threshold of the interferometer signal.""" + return self.set(interferometer_threshold=val) + + def tracking_set_psd_threshold_low(self, val: float): + """Lower PSD intensity threshold of the tracking.""" + return self.set(psd_threshold_low=val) + 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 @@ -232,7 +253,9 @@ class FlomniRealtimeSystem(WebHmiClient): 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)") + " trigger_set_after_move(True/False), tracking_targets(), tracking_show(), tracking_set_target_y(v)," + " tracking_set_target_z(v), tracking_set_interferometer_threshold(v), tracking_set_psd_threshold_low(v);" + " profiles(), unsaved(), get(name)") print('Explanations: help("all") or help(""), e.g. help("move_beyond_um_x")') return snap = self.read() diff --git a/csaxs_bec/bec_ipython_client/plugins/flomni/x_ray_eye_align.py b/csaxs_bec/bec_ipython_client/plugins/flomni/x_ray_eye_align.py index d8263501..a3a94091 100644 --- a/csaxs_bec/bec_ipython_client/plugins/flomni/x_ray_eye_align.py +++ b/csaxs_bec/bec_ipython_client/plugins/flomni/x_ray_eye_align.py @@ -270,9 +270,9 @@ class XrayEyeAlign: self.flomni.feye_in() - self.flomni.laser_tracker_on() + self.flomni.rt_laser_tracker_on() - self.flomni.feedback_enable_with_reset() + self.flomni.rt_feedback_enable_with_reset() # disable movement buttons self.movement_buttons_enabled(False, False) @@ -281,12 +281,12 @@ class XrayEyeAlign: self.gui.sample_name = sample_name # this makes sure we are in a defined state - self.flomni.feedback_disable() + self.flomni.rt_feedback_disable() if not self.test_wo_movements: self.flomni.fosa_out() self.flomni.ffzp_in() - self.flomni.feedback_disable() + self.flomni.rt_feedback_disable() fsamx_in = self.flomni._get_user_param_safe("fsamx", "in") umv(dev.fsamx, fsamx_in - 0.25) @@ -362,7 +362,7 @@ class XrayEyeAlign: self.movement_buttons_enabled(False, False) self.gui.enable_submit_button(False) - self.flomni.feedback_disable() + self.flomni.rt_feedback_disable() if not self.test_wo_movements: fsamx_in = self.flomni._get_user_param_safe("fsamx", "in") umv(dev.fsamx, fsamx_in) @@ -371,7 +371,7 @@ class XrayEyeAlign: time.sleep(0.5) - self.flomni.feedback_enable_with_reset() + self.flomni.rt_feedback_enable_with_reset() self.update_frame(keep_shutter_open) @@ -410,11 +410,11 @@ class XrayEyeAlign: f"delta_fsamy={delta_y_mm:.5f} mm" ) - self.flomni.feedback_disable() + self.flomni.rt_feedback_disable() if not self.test_wo_movements and delta_y_mm != 0: self.flomni.umvr_fsamy_tracked(delta_y_mm, _internal=True) time.sleep(2) - self.flomni.feedback_enable_with_reset() + self.flomni.rt_feedback_enable_with_reset() self._height_centered = True # From here through step 5, the sample's vertical position is @@ -471,12 +471,12 @@ class XrayEyeAlign: # allow movements, store movements to calculate center _xrayeyalignmvy = dev.omny_xray_gui.mvy.get() if _xrayeyalignmvy != 0: - self.flomni.feedback_disable() + self.flomni.rt_feedback_disable() if not self.test_wo_movements: self.flomni.umvr_fsamy_tracked(_xrayeyalignmvy / 1000, _internal=True) time.sleep(2) dev.omny_xray_gui.mvy.set(0) - self.flomni.feedback_enable_with_reset() + self.flomni.rt_feedback_enable_with_reset() self.update_frame(keep_shutter_open) time.sleep(0.1) diff --git a/csaxs_bec/device_configs/ptycho_flomni.yaml b/csaxs_bec/device_configs/ptycho_flomni.yaml index 450b4222..31bc56d7 100644 --- a/csaxs_bec/device_configs/ptycho_flomni.yaml +++ b/csaxs_bec/device_configs/ptycho_flomni.yaml @@ -620,7 +620,7 @@ flomni_temphum: # ############################################################ # The loop settings (FZP, trigger, tracker; flOMNI FeedbackLoop/loopsettings) stored with every scan: read from the # web HMI on the loop machine at the start of each scan (baseline). Web HMI down: available = 0, the scan continues. -# The user commands flomni.realtime_system go through this device (RPC), so the token is needed only here. +# The user commands flomni.rt_settings go through this device (RPC), so the token is needed only here. # token: the web HMI API token (~/loop-data/webhmi/api_token on mpc2844; behind the beamline firewall, Mirko # 2026-10-23). When empty, token_file (on the machine of the device server) is used. flomni_loop_settings: diff --git a/csaxs_bec/devices/omny/flomni_loop_settings.py b/csaxs_bec/devices/omny/flomni_loop_settings.py index 552d6667..d92338f1 100644 --- a/csaxs_bec/devices/omny/flomni_loop_settings.py +++ b/csaxs_bec/devices/omny/flomni_loop_settings.py @@ -20,7 +20,7 @@ deviceConfig: token (the API token itself), else token_file (path to a file with ~/.flomni_webhmi_token), optional url / host / timeout (default 2 s for the scan snapshot). Connection details: devices/omny/webhmi_client.py. -flomni.realtime_system (the user commands) calls api_get / api_post of this device by RPC, so the token is needed +flomni.rt_settings (the user commands) calls api_get / api_post of this device by RPC, so the token is needed only here. """ @@ -67,7 +67,7 @@ class FlomniLoopSettings(Device): super().__init__(prefix, name=name, **kwargs) self._client = WebHmiClient(url=url, token=token, token_file=token_file, host=host, device_manager=device_manager, timeout=timeout, client_name="BEC baseline") - # requests of flomni.realtime_system (RPC from the client, below): longer timeout, no back-off + # requests of flomni.rt_settings (RPC from the client, below): longer timeout, no back-off self._api = WebHmiClient(url=url, token=token, token_file=token_file, host=host, device_manager=device_manager, timeout=max(timeout, 10.0), client_name="BEC") self._failed_at = None @@ -127,7 +127,7 @@ class FlomniLoopSettings(Device): self.update() return super().read() - # ── RPC for flomni.realtime_system: the user commands go through this device, so the API token is needed only + # ── RPC for flomni.rt_settings: the user commands go through this device, so the API token is needed only # in its device config (device server), not on the client. Not called set / put: the device server treats # those names as ophyd writes (refused on a readOnly device). diff --git a/csaxs_bec/devices/omny/rt/rt_flomni_ophyd.py b/csaxs_bec/devices/omny/rt/rt_flomni_ophyd.py index b2616c80..a59612f2 100644 --- a/csaxs_bec/devices/omny/rt/rt_flomni_ophyd.py +++ b/csaxs_bec/devices/omny/rt/rt_flomni_ophyd.py @@ -396,7 +396,7 @@ class RtFlomniController(Controller): # emitter_* (ConstEmitter get / set through the CommunicationServer e-commands) removed 2026-10-23: the # ConstEmitter is no longer in the loop; its values are loop settings, set through the web HMI - # (flomni.realtime_system, csaxs_bec/bec_ipython_client/plugins/flomni/flomni_realtime_system.py). + # (flomni.rt_settings, csaxs_bec/bec_ipython_client/plugins/flomni/flomni_realtime_system.py). def move_xyz(self, x: float, y: float, z: float) -> None: """Move rtx, rty and rtz with one command (CommunicationServer p,,). diff --git a/csaxs_bec/devices/omny/webhmi_client.py b/csaxs_bec/devices/omny/webhmi_client.py index 92554527..8a22fa16 100644 --- a/csaxs_bec/devices/omny/webhmi_client.py +++ b/csaxs_bec/devices/omny/webhmi_client.py @@ -1,6 +1,6 @@ """Client of the web HMI on an OMNY-family loop machine (RT-system webhmi): REST requests with the API token. -Shared by the BEC client plugin (flomni.realtime_system, bec_ipython_client/plugins/flomni/flomni_realtime_system.py) +Shared by the BEC client plugin (flomni.rt_settings, bec_ipython_client/plugins/flomni/flomni_realtime_system.py) and the baseline device FlomniLoopSettings (devices/omny/flomni_loop_settings.py), so both talk to the web HMI the same way. Standard library only (runs in the IPython client and in the device server). diff --git a/docs/user/ptychography/flomni.md b/docs/user/ptychography/flomni.md index 9b7f1270..ad39c6fd 100644 --- a/docs/user/ptychography/flomni.md +++ b/docs/user/ptychography/flomni.md @@ -187,8 +187,10 @@ Following functions exist to move the optics in and out, with self-explaining na ### Interferometer +All commands of the real-time system start with `rt_`: `flomni.rt_` lists them. + The position feedback in flOMNI is controlled in closed loop to an interferometric position measurement. To show the signal of the interferometers: -`flomni.show_signal_strength_interferometer()` +`flomni.rt_signal_strength()` (or the former name `flomni.show_signal_strength_interferometer()`) Typical values with proper alignment, sample stage at the measurement position and laser tracker running are in the range of | Axis | Value | @@ -203,20 +205,24 @@ Typical values with proper alignment, sample stage at the measurement position a The horizontal interferometer is built according to the [tracking interferometer](https://www.dora.lib4ri.ch/psi/islandora/object/psi:7524). The tracker can be controlled by following commands. During commissioning of the setup it is worthy to check the status, but during general operation these commands should not be required. -- `flomni.laser_tracker_show_all()` -- `flomni.laser_tracker_on()` -- `flomni.laser_tracker_off()` +- `flomni.rt_laser_tracker_show_all()` +- `flomni.rt_laser_tracker_on()` +- `flomni.rt_laser_tracker_off()` +- `flomni.rt_laser_tracker_tweak()` (arrow keys move the tracking targets) #### Interferometer feedback commands The closed loop control of the Piezo stages can be controlled by -- `flomni.feedback_feedback_enable_with_reset()`. +- `flomni.rt_feedback_enable_with_reset()`. _There is also an enable without reset, which is used during tomography scans, when using coarse stages to increase the scan range. It should not be required to use manually._ -- `flomni.feedback_disable()` -- `flomni.feedback_status()` +- `flomni.rt_feedback_disable()` +- `flomni.rt_feedback_status()` + +The former names `flomni.feedback_enable_with_reset()`, `feedback_enable_without_reset()`, `feedback_disable()` +and `feedback_status()` still work. #### Detector trigger: position stability @@ -773,7 +779,8 @@ removed later. ### Real-time loop settings The operating settings of the real-time control loop (FZP scanner, detector trigger, laser tracker) are set by the -local contact; please do not change them. Every scan stores the settings it ran with (baseline device +local contact (`flomni.rt_settings`, including the laser tracker targets and thresholds +`flomni.rt_settings.tracking_*`); please do not change them. Every scan stores the settings it ran with (baseline device `flomni_loop_settings`). Details for beamline scientists: `csaxs_bec/bec_ipython_client/plugins/flomni/docs/local_contact_only-realtime_system.md`. diff --git a/omny_e2e_tests/_bootstrap.py b/omny_e2e_tests/_bootstrap.py index 133d9b4e..08517bf1 100644 --- a/omny_e2e_tests/_bootstrap.py +++ b/omny_e2e_tests/_bootstrap.py @@ -103,7 +103,7 @@ def build_flomni(services_config_path): if abs(fsamx.readback.get() - fsamx_in) > 0.3: fsamx.read_only = False flomni_module.umv(fsamx, fsamx_in) - flomni.feedback_enable_with_reset() + flomni.rt_feedback_enable_with_reset() return bec, flomni 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 ec664b87..d44bbf96 100644 --- a/tests/tests_bec_ipython_client/test_flomni_realtime_system.py +++ b/tests/tests_bec_ipython_client/test_flomni_realtime_system.py @@ -1,4 +1,4 @@ -"""Tests for flomni.realtime_system (flomni_realtime_system.py): the client of the web HMI loop-settings API on the +"""Tests for flomni.rt_settings (flomni_realtime_system.py): the client of the web HMI loop-settings API on the flOMNI loop machine. A small local HTTP server stands in for the web HMI (RT-system webhmi, /api/settings*); it checks the token header and answers like the real server. No BEC service, no loop.""" diff --git a/tests/tests_bec_ipython_client/test_flomni_review_fixes.py b/tests/tests_bec_ipython_client/test_flomni_review_fixes.py index 8e745039..bd521c27 100644 --- a/tests/tests_bec_ipython_client/test_flomni_review_fixes.py +++ b/tests/tests_bec_ipython_client/test_flomni_review_fixes.py @@ -163,7 +163,7 @@ def test_xray_eye_ctrl_c_returns_fsamx_in(monkeypatch): moved = [] monkeypatch.setattr(flomni_module, "umv", lambda *a: moved.append(a), raising=False) flomni.OMNYTools = types.SimpleNamespace(yesno=lambda *a, **k: True) - flomni.feedback_disable = lambda: None + flomni.rt_feedback_disable = lambda: None class _Align: def __init__(self, *a): diff --git a/tests/tests_devices/test_flomni_loop_settings.py b/tests/tests_devices/test_flomni_loop_settings.py index 08066006..a5350e06 100644 --- a/tests/tests_devices/test_flomni_loop_settings.py +++ b/tests/tests_devices/test_flomni_loop_settings.py @@ -98,7 +98,7 @@ def test_token_file_and_snapshot(url, tmp_path): def test_api_methods_for_realtime_system(url): - """api_get / api_post: the RPC used by flomni.realtime_system (token only in the device config).""" + """api_get / api_post: the RPC used by flomni.rt_settings (token only in the device config).""" dev = FlomniLoopSettings(name="ls", url=url, token=TOKEN) assert dev.api_get("/api/settings")["instrument"] == "flOMNI" with pytest.raises(Exception, match="not allowed"):