feat(flomni): trigger after a position change (interferometer_trigger_set_after_move, emitter_set_trigger_on_move -> em), three-axis move (controller.move_xyz -> p<x>,<y>,<z>); eg with 3 values
CI for csaxs_bec / test (pull_request) Successful in 4m22s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 4m32s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TNtKEk4jAyjebMDQFwRHJQ
This commit was merged in pull request #335.
This commit is contained in:
Mirko HollerandClaude Opus 5.5 committed 2026-10-20 11:47:36 +02:00
1 parent d9b23b9dc2
commit 8796953913
3 files changed
+57 -4

No files matched your search

@@ -724,6 +724,9 @@ class FlomniSampleTransferMixin:
f"for {values['ontarget_hold_ms']:.1f} ms.")
else:
print("Detector trigger: former criterion (on-target flags of the FZP PID loops).")
if values.get("trigger_on_move") is not None:
mode = "on" if values["trigger_on_move"] else "off"
print(f"Trigger after every position change (not only at scan points): {mode}.")
def interferometer_trigger_set_tolerance_nm(self, tolerance_nm: float):
"""Set the detector-trigger tolerance in nm (per axis, x and y).
@@ -755,6 +758,16 @@ class FlomniSampleTransferMixin:
dev.rtx.controller.emitter_set_ontarget_hold_ms(hold_ms)
self.interferometer_trigger_show()
def interferometer_trigger_set_after_move(self, enable: bool):
"""Send one detector trigger after every position change, not only at scan points.
With True every direct move of the sample stage (e.g. umv of rtx / rty / rtz, or
dev.rtx.controller.move_xyz) gives exactly one trigger, as soon as the position is stable
(same tolerance and hold time as for scan points). False: triggers at scan points only.
"""
dev.rtx.controller.emitter_set_trigger_on_move(1 if enable else 0)
self.interferometer_trigger_show()
def interferometer_trigger_help(self):
"""Print the detector-trigger commands with a short explanation."""
lines = [
@@ -765,6 +778,9 @@ class FlomniSampleTransferMixin:
" 2 finest; 0 = former",
" criterion (FZP PID flags)",
" flomni.interferometer_trigger_set_hold_ms(val) e.g. 3",
" flomni.interferometer_trigger_set_after_move(True) also one trigger after every",
" position change (False: off)",
" dev.rtx.controller.move_xyz(x, y, z) move all three axes at once",
"The values are live; after a restart of the control loop the start values",
"(10 nm, 3 ms) from the loop parameter file apply again.",
]
+37 -4
View File
@@ -55,6 +55,8 @@ class RtFlomniController(Controller):
"emitter_set_ontarget_tolerance",
"emitter_set_ontarget_hold_ms",
"emitter_help",
"emitter_set_trigger_on_move",
"move_xyz",
]
def __init__(
@@ -448,16 +450,21 @@ class RtFlomniController(Controller):
without interruption.
Returns:
dict: ontarget_tolerance_um, ontarget_hold_ms.
dict: ontarget_tolerance_um, ontarget_hold_ms, trigger_on_move (None from a server
that does not report it yet).
"""
ret = self.socket_put_and_receive("eg")
ret = ret.split("\n")[0]
values = [float(val) for val in ret.split(",")]
if len(values) != 2:
if len(values) not in (2, 3):
raise RtCommunicationError(
f"Expected to receive 2 on-target values. Instead received {values}"
f"Expected to receive 2 or 3 on-target values. Instead received {values}"
)
return {"ontarget_tolerance_um": values[0], "ontarget_hold_ms": values[1]}
return {
"ontarget_tolerance_um": values[0],
"ontarget_hold_ms": values[1],
"trigger_on_move": values[2] if len(values) == 3 else None,
}
def emitter_set_ontarget_tolerance(self, val_um: float) -> None:
"""Set the detector-trigger tolerance in um, per axis.
@@ -471,6 +478,30 @@ class RtFlomniController(Controller):
"""Set the time in ms the position must stay within the tolerance before the trigger."""
self.socket_put(f"eh{val_ms:.3f}")
def emitter_set_trigger_on_move(self, val: int) -> None:
"""1: send one detector trigger after every direct position change (pa / p), once on target
(same criterion as a step-scan point); 0: triggers at step-scan points only."""
self.socket_put(f"em{1 if val else 0}")
def move_xyz(self, x: float, y: float, z: float) -> None:
"""Move rtx, rty and rtz with one command (CommunicationServer p<x>,<y>,<z>).
The three targets change in the same loop step, so with trigger_on_move there is exactly one
detector trigger for the move. Values as for the rtx / rty / rtz axes (axis sign applied).
Does not wait for the move.
"""
if self.laser_tracker_check_signalstrength() == "toolow":
raise RtError(
"The interferometer signal is too low for movements. Realignment required."
)
targets = []
for name, val in (("rtx", x), ("rty", y), ("rtz", z)):
axis = self.get_axis_by_name(name)
target_val = val * axis.sign
axis.user_setpoint.setpoint = target_val
targets.append(target_val)
self.socket_put(f"p{targets[0]:.4f},{targets[1]:.4f},{targets[2]:.4f}")
def emitter_help(self) -> None:
"""Print the ConstEmitter commands (loop runtime constants) with a short explanation."""
t = PrettyTable()
@@ -492,6 +523,8 @@ class RtFlomniController(Controller):
" <= 0: FZP PID flags"),
("emitter_set_ontarget_hold_ms(val_ms)", "eh",
"detector trigger: ... held for val ms without interruption"),
("emitter_set_trigger_on_move(val)", "em",
"1: one detector trigger after every direct move (pa / p) once on target; 0: off"),
]
for row in rows:
t.add_row(row)
+4
View File
@@ -229,6 +229,10 @@ over 0.5 ms) for a hold time. Both values are live and can be changed between sc
low-resolution scan, 10 standard, 2 for the highest resolution (a few ms more per point);
0 selects the former criterion (on-target flags of the FZP loops)
- `flomni.interferometer_trigger_set_hold_ms(3)` — hold time in ms
- `flomni.interferometer_trigger_set_after_move(True)` — additionally one trigger after every position change
of the sample stage (e.g. `umv(dev.rtx, ...)`), as soon as the position is stable with the same tolerance and
hold time; `False` (default): triggers at scan points only
- `dev.rtx.controller.move_xyz(x, y, z)` — moves rtx, rty and rtz with one command (one trigger for the move)
- `flomni.interferometer_trigger_help()` — this list
After a restart of the control loop the start values (10 nm, 3 ms) apply again. Measured with 3 µm steps