quadEM electrometers (TetrAMM, FX4, …) in continuous mode#
Objective
Show the recommended pattern for using an EPICS quadEM electrometer (such as the TetrAMM or the FX4) with bluesky when the device is operated as a continuous beam-position monitor (BPM).
Why no apstools device?#
apstools does not provide its own quadEM device class. Support already
exists upstream in ophyd.quadem (QuadEM, TetrAMM, NSLS_EM, APS_EM),
so the recommended pattern is to subclass the appropriate ophyd class
directly for beamline-specific behavior rather than to depend on an
apstools wrapper.
This is the same conclusion reached for the TetrAMM
(issue #878) and the FX4
(issue #1185): if the
device can be represented cleanly by ophyd.quadem.QuadEM, keep the
customization in beamline code.
The triggering problem#
ophyd.quadem.QuadEM.__init__() sets these staging signals:
self.stage_sigs.update(
[("acquire", 0), ("acquire_mode", 2)] # stop acquiring, single mode
)
For a detector that is triggered once per data point this is correct. But when the electrometer is used as a continuous BPM, these defaults cause a scan such as
RE(bp.rel_scan([tetramm], motor, -1, 1, 11))
to stall: the trigger presses Acquire in single mode and the trigger
status object never reports done, so the scan hangs between points
(issue #1023).
Recommended beamline pattern#
For continuous / BPM operation, subclass the ophyd device, clear the
staging signals so the acquisition mode is left untouched, and override
trigger() to press Acquire and immediately mark the status as
finished so a subsequent .read() returns the current values:
from ophyd import TetrAMM
from ophyd.status import Status
class ContinuousTetrAMM(TetrAMM):
"""TetrAMM as a continuous beam-position monitor.
* Do not change the acquire mode when staging.
* ``trigger()`` ensures acquisition is running, then reports done
immediately so ``read()`` returns the current values.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Leave the device in whatever (continuous) mode it is already in.
self.stage_sigs = {}
def trigger(self):
# Make sure the detector is acquiring, then report done.
self.acquire.put(1)
status = Status(self)
status.set_finished()
return status
tetramm = ContinuousTetrAMM("ioc_prefix:", name="tetramm")
tetramm.wait_for_connection()
The same pattern applies to any other quadEM-family device by subclassing the
appropriate ophyd class (for example a local QuadEM subclass with
port_name="FX4" for the FX4; see
issue #1185).
bluesky#
Use the device in a plan like any other detector:
RE(bp.rel_scan([tetramm], motor, -1, 1, 11))