fix(flomni): beamline detection for the trigger source by ddg1 only (exposure box motors removed)
CI for csaxs_bec / test (pull_request) Successful in 3m49s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 3m34s

Mirko 2026-10-27: a real (not simulated) ddg1 is sufficient. Simulated or no ddg1 -> soft trigger;
a failing check still counts as beamline (hardware trigger). Docs + PDF.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017KicuVBRC7jB1TLG5hLZwx
This commit was merged in pull request #344.
This commit is contained in:
Mirko HollerandClaude Opus 5.5 committed 2026-10-27 08:52:18 +01:00
1 parent 5e3b4b50d5
commit 6e0ffa3b74
4 files changed
+11 -24

No files matched your search

@@ -70,11 +70,10 @@ default and is in no profile (flOMNI `FeedbackLoop/soft_trigger.md`).
**`flomni_fermat_scan` sets the trigger source at every scan start** (2026-10-26):
- **At the beamline → hardware.** At the beamline means: `ddg1` is configured with a real (not simulated) device
class, or one of the exposure box motors `ebtrx`, `ebtry`, `ebtrz`, `ebsupport` is in the session. If this cannot
be confirmed (web HMI not reachable, refused) the scan is aborted: with the soft trigger the detector would not
be triggered while flOMNI exposes internally. A failing check also counts as beamline.
- **Otherwise (simulated `ddg1`, no beamline devices) → soft** with `soft_exposure_ms` = `exp_time` ×
`frames_per_trigger` × 1000. If that fails the scan continues with a warning (alarm).
class (only `ddg1` decides, 2026-10-27). If this cannot be confirmed (web HMI not reachable, refused) the scan is
aborted: with the soft trigger the detector would not be triggered while flOMNI exposes internally. A failing check
also counts as beamline.
- **Otherwise (simulated or no `ddg1`) → soft** with `soft_exposure_ms` = `exp_time` × `frames_per_trigger` × 1000. If that fails the scan continues with a warning (alarm).
By hand: `flomni.rt_settings.trigger_set_soft(True, exposure_ms=100)`, `trigger_set_soft(False)`;
`trigger_show()` shows both groups.
+5 -15
View File
@@ -69,10 +69,6 @@ class FlomniFermatScan(ScanBase):
MAX_CORRIDOR_SIZE = 3 # Corridor size is capped at this value (in um) for stability
MIN_FERMAT_ASYMMETRY = 0.1 # Practical working range for fermat_asymmetry (soft-clamped)
MAX_FERMAT_ASYMMETRY = 10.0
# Devices that exist only in the beamline configuration (bl_endstation.yaml: exposure box motors); with one of
# them, or a ddg1 that is not simulated, the session is at the beamline -> hardware trigger (flOMNI soft trigger,
# FeedbackLoop/soft_trigger.md, 2026-10-26)
BEAMLINE_DEVICES = ("ebtrx", "ebtry", "ebtrz", "ebsupport")
def __init__(
# fmt: off
@@ -386,26 +382,20 @@ class FlomniFermatScan(ScanBase):
def at_beamline(self) -> tuple[bool, str]:
"""
Is this session at the beamline? True when ddg1 is configured with a real (not simulated) device class or
one of BEAMLINE_DEVICES is configured; False only when both say no. A failing check counts as beamline,
so the hardware trigger is the fallback.
Is this session at the beamline? True when ddg1 is configured with a real (not simulated) device class
(flOMNI soft trigger, FeedbackLoop/soft_trigger.md; ddg1 only, Mirko 2026-10-27). A failing check counts as
beamline, so the hardware trigger is the fallback.
Returns:
(at_beamline, reason)
"""
try:
devices = self.device_manager.devices
reasons = []
if "ddg1" in devices:
cls = str((getattr(devices["ddg1"], "_config", None) or {}).get("deviceClass", ""))
if ".sim." not in cls and "Simulated" not in cls:
reasons.append(f"ddg1 is {cls or 'not simulated'}")
found = [name for name in self.BEAMLINE_DEVICES if name in devices]
if found:
reasons.append("beamline devices " + ", ".join(found))
if reasons:
return True, "; ".join(reasons)
return False, "no beamline devices, ddg1 simulated or absent"
return True, f"ddg1 is {cls or 'not simulated'}"
return False, "ddg1 simulated or absent"
except Exception as exc: # pylint: disable=broad-except
return True, f"beamline check failed ({exc}), treated as beamline"
+2 -4
View File
@@ -162,7 +162,6 @@ def _trigger_stub(devices, apply_result=None, apply_exc=None, exp_time=0.1, fram
"""A stand-in for a FlomniFermatScan instance with a device list {name: deviceClass} and the loop-settings
device answering /api/settings/apply."""
scan = mock.MagicMock()
scan.BEAMLINE_DEVICES = FlomniFermatScan.BEAMLINE_DEVICES
scan.device_manager.devices = {
name: mock.MagicMock(_config={"deviceClass": cls}) for name, cls in devices.items()
}
@@ -187,9 +186,8 @@ def test_at_beamline_detection():
assert FlomniFermatScan.at_beamline(_trigger_stub({"ddg1": SIM_DDG1}))[0] is False
assert FlomniFermatScan.at_beamline(_trigger_stub({}))[0] is False
assert FlomniFermatScan.at_beamline(_trigger_stub({"ddg1": REAL_DDG1}))[0] is True
# a beamline-only device counts even with a simulated ddg1
at_bl, reason = FlomniFermatScan.at_beamline(_trigger_stub({"ddg1": SIM_DDG1, "ebtrx": MOTOR}))
assert at_bl is True and "ebtrx" in reason
# only ddg1 decides: other devices (e.g. exposure box motors) do not count
assert FlomniFermatScan.at_beamline(_trigger_stub({"ddg1": SIM_DDG1, "ebtrx": MOTOR}))[0] is False
# a failing check counts as beamline (hardware trigger is the fallback)
scan = _trigger_stub({})
scan.device_manager.devices = None