diff --git a/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py b/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py index 5be8b558..36c5ec29 100644 --- a/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py +++ b/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py @@ -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.", ] diff --git a/csaxs_bec/devices/omny/rt/rt_flomni_ophyd.py b/csaxs_bec/devices/omny/rt/rt_flomni_ophyd.py index 7309dd5b..afa49424 100644 --- a/csaxs_bec/devices/omny/rt/rt_flomni_ophyd.py +++ b/csaxs_bec/devices/omny/rt/rt_flomni_ophyd.py @@ -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,,). + + 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) diff --git a/docs/user/ptychography/flomni.md b/docs/user/ptychography/flomni.md index 238ec211..8031e5d2 100644 --- a/docs/user/ptychography/flomni.md +++ b/docs/user/ptychography/flomni.md @@ -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