Refactor/flomni rt namespace #341

Merged
holler merged 2 commits from refactor/flomni_rt_namespace into main 2026-10-26 15:19:50 +01:00
16 changed files with 162 additions and 100 deletions

No files matched your search

@@ -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: `<endstation>.rt_<TAB>` 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.
@@ -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()`.
@@ -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).
@@ -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("<setting>")` one setting (what it does, range,
`flomni.rt_settings.help("all")` explains every setting, `help("<setting>")` 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("<setting>")`
- `flomni.rt_settings.help()`, `help("all")`, `help("<setting>")`
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
@@ -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
@@ -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("<setting>"), e.g. help("move_beyond_um_x")')
return
snap = self.read()
@@ -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)
+1 -1
View File
@@ -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:
@@ -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).
+1 -1
View File
@@ -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<x>,<y>,<z>).
+1 -1
View File
@@ -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).
+15 -8
View File
@@ -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_<TAB>` 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`.
+1 -1
View File
@@ -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
@@ -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."""
@@ -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):
@@ -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"):