diff --git a/pxiii_bec/device_configs/x06da_device_config.yaml b/pxiii_bec/device_configs/x06da_device_config.yaml index c49d4af..e312153 100644 --- a/pxiii_bec/device_configs/x06da_device_config.yaml +++ b/pxiii_bec/device_configs/x06da_device_config.yaml @@ -15,6 +15,7 @@ slh_trxw: readOnly: false softwareTrigger: false fi1_try: + description: Beam attenuator in OP deviceClass: ophyd.EpicsMotor deviceConfig: {prefix: 'X06DA-OP-FI1:TRY'} onFailure: buffer @@ -193,6 +194,7 @@ vfm_trydw: readOnly: false softwareTrigger: false vfm_pitch: + description: KB mirror vertical steering deviceClass: ophyd.EpicsMotor deviceConfig: {prefix: 'X06DA-ES1-VFM:PITCH'} onFailure: buffer @@ -273,6 +275,7 @@ hfm_trydr: readOnly: false softwareTrigger: false hfm_pitch: + description: KB mirror horizontal steering deviceClass: ophyd.EpicsMotor deviceConfig: {prefix: 'X06DA-ES1-HFM:PITCH'} enabled: false @@ -313,6 +316,7 @@ hfm_try: readOnly: false softwareTrigger: false xbox_diode: + description: Exposure box diode deviceClass: ophyd.EpicsSignalRO deviceConfig: {read_pv: 'X06DA-ES-DI1:READOUT'} onFailure: buffer @@ -321,6 +325,7 @@ xbox_diode: readOnly: true softwareTrigger: false bstop_diode: + description: Beamstop diode deviceClass: ophyd.EpicsSignalRO deviceConfig: {read_pv: 'X06DA-ES-BS:READOUT'} onFailure: buffer @@ -329,14 +334,16 @@ bstop_diode: readOnly: true softwareTrigger: false omega: + description: ABR rotation stage deviceClass: pxiii_bec.devices.A3200Axis - deviceConfig: {prefix: 'X06DA-ES-DF1:OMEGA'} + deviceConfig: {prefix: 'X06DA-ES-DF1:OMEGA', base_pv: 'X06DA-ES'} onFailure: buffer enabled: true readoutPriority: monitored readOnly: false softwareTrigger: false abr: + description: Aerotech ABR motion system deviceClass: pxiii_bec.devices.AerotechAbrStage deviceConfig: {prefix: X06DA-ES} onFailure: buffer diff --git a/pxiii_bec/devices/A3200.py b/pxiii_bec/devices/A3200.py index 3ead2c6..467d29c 100644 --- a/pxiii_bec/devices/A3200.py +++ b/pxiii_bec/devices/A3200.py @@ -85,7 +85,7 @@ from ophyd import Component, Device, EpicsSignal, EpicsSignalRO, Kind from ophyd.status import MoveStatus, SubscriptionStatus, DeviceStatus -from .aeroA3200 import A3200Axis, A3200RasterScanner, A3200Oscillator +from .A3200utils import A3200Axis, A3200RasterScanner, A3200Oscillator ABR_DONE = 0 @@ -145,6 +145,8 @@ class AerotechAbrStage(Device): an AeroBasic program on the controller and we communicate to it via 10+1 global variables. """ + USER_ACCESS = ['reset', 'kickoff', 'complete'] + taskStop = Component(EpicsSignal, "-AERO:TSK-STOP", put_complete=True, kind=Kind.omitted) status = Component(EpicsSignal, "-AERO:STAT", put_complete=True, kind=Kind.omitted) @@ -196,12 +198,17 @@ class AerotechAbrStage(Device): def configure(self, d: dict) -> tuple: """" Configure the exposure scripts - Performs the configuration of the exposure scrips, i.e. sets the - required global variables. + Script execution at the PX beamlines is based on scripts that are always + running on the controller that execute commands when commanded by + setting a pre-defined set of global variables. This method performs the + configuration of the exposure scrips by setting the required global + variables. - Parameters - ---------- - scan_command: + Parameters in d: dict + --------------------- + scan_command: int + The index of the desired AeroBasic program to be executed. + Usually supported values are taken from an Enum. var_1: var_2: var_3: @@ -218,8 +225,7 @@ class AerotechAbrStage(Device): # ToDo: Check if idle before reconfiguring # Set the corresponding global variables - if "scan_command" in d: - self.scan_command.set(d['scan_command']).wait() + self.scan_command.set(d['scan_command']).wait() if "var_1" in d and d['var_1'] is not None: self._var_1.set(d['var_1']).wait() if "var_2" in d and d['var_2'] is not None: @@ -271,11 +277,12 @@ class AerotechAbrStage(Device): Executes the homing procedure and waits (default) until it is completed. + TODO: Return a status object to do this wwith futures and monitoring. + PARAMETERS `wait` true / false if the routine is to wait for the homing to finish. """ self.omega.home.set(1, settle_time=1).wait() - poll(1.0) if not wait: return while not self.omega.is_homed(): diff --git a/pxiii_bec/devices/aeroA3200.py b/pxiii_bec/devices/A3200utils.py similarity index 84% rename from pxiii_bec/devices/aeroA3200.py rename to pxiii_bec/devices/A3200utils.py index 34ef135..57b11f9 100644 --- a/pxiii_bec/devices/aeroA3200.py +++ b/pxiii_bec/devices/A3200utils.py @@ -4,11 +4,15 @@ Created on Tue Jun 11 11:28:38 2024 @author: mohacsi_i """ - -from ophyd import Component, PVPositioner, Device, Signal, EpicsSignal, EpicsSignalRO, Kind +import types +from ophyd import Component, PVPositioner, Device, Signal, EpicsSignal, EpicsSignalRO, Kind, PositionerBase from ophyd.status import Status, MoveStatus, SubscriptionStatus, DeviceStatus +from bec_lib import bec_logger +logger = bec_logger.logger + + ABR_DONE = 0 ABR_READY = 1 ABR_BUSY = 2 @@ -24,7 +28,13 @@ class A3200Axis(PVPositioner): """Positioner wrapper for motors on the Aerotech A3200 controller. As the IOC does not provide a motor record, this class simply wraps axes into a standard Ophyd positioner for the BEC. It also has some additional - functionality for diagnostics.""" + functionality for diagnostics. + """ + USER_ACCESS = ['omove'] + + abr_mode_direct = Component(EpicsSignal, "-DF1:MODE-DIRECT", put_complete=True, kind=Kind.omitted) + abr_mode = Component(EpicsSignal, "-DF1:AXES-MODE", auto_monitor=True, put_complete=True, kind=Kind.omitted) + # Basic PV positioner interface done = Component(EpicsSignalRO, "-DONE", auto_monitor=True, kind=Kind.config) readback = Component(EpicsSignalRO, "-RBV", auto_monitor=True, kind=Kind.hinted) @@ -58,52 +68,51 @@ class A3200Axis(PVPositioner): vmax = Component(Signal, kind=Kind.config) offset = Component(EpicsSignal, "-OFF", put_complete=True, kind=Kind.config) - def __init__(self, prefix="", *, name, kind=None, read_attrs=None, configuration_attrs=None, parent=None, llm=0, hlm=0, vmin=0, vmax=0, **kwargs): + def __init__(self, prefix="", *, name, base_pv="", kind=None, read_attrs=None, configuration_attrs=None, parent=None, llm=0, hlm=0, vmin=0, vmax=0, **kwargs): """ __init__ MUST have a full argument list""" + + """ Patching the parent's PVs into the axis class to check for direct/locked mode""" + if parent is None: + def maybe_add_prefix(self, instance, kw, suffix): + """ Patched not to enforce parent prefix""" + if kw in self.add_prefix: + return suffix + return suffix + self.__class__.__dict__["abr_mode"].maybe_add_prefix = types.MethodType(maybe_add_prefix, self.__class__.__dict__["abr_mode"]) + self.__class__.__dict__["abr_mode_direct"].maybe_add_prefix = types.MethodType(maybe_add_prefix, self.__class__.__dict__["abr_mode_direct"]) + logger.info(self.__class__.__dict__["abr_mode"].kwargs) + self.__class__.__dict__["abr_mode"].suffix = base_pv + "-DF1:AXES-MODE" + self.__class__.__dict__["abr_mode_direct"].suffix = base_pv + "-DF1:MODE-DIRECT" + super().__init__(prefix=prefix, name=name, kind=kind, read_attrs=read_attrs, configuration_attrs=configuration_attrs, parent=parent, **kwargs) self.llm.set(llm).wait() self.hlm.set(hlm).wait() self.vmin.set(vmin).wait() self.vmax.set(vmax).wait() - - def move(self, position, velocity=None, wait=True, relative=False, direct=False, **kwargs) -> MoveStatus: - """ Native absolute movement on the A3200 """ - - # ToDo: Ensure we can stop (if a stop_signal is configured). + def move(self, position, velocity=None, wait=True, relative=False, direct=False, timeout=None, **kwargs) -> MoveStatus: + """ Native absolute/relative movement on the A3200 """ # Check if we're in direct movement mode - if self.parent is not None: - if self.parent.mode != DIRECT_MODE: - if direct: - self.parent.set_direct_mode() - else: - raise RuntimeError("ABR is not in direct mode!") - - # Set velocity if provided - if velocity is not None: - self.velo.set(velocity).wait() - - # Set up relative motion - if relative: - raise NotImplementedError("Relative movement is not yet supported by the ophyd device") - - return super().move(position, wait=wait, **kwargs) - - - - def rmove(self, distance, wait=True, velo=None, timeout=None, moved_cb=None) -> Status: - """ Native relative movement on the A3200 """ + if self.abr_mode.value not in (DIRECT_MODE, "DIRECT"): + if direct: + self.abr_mode_direct.set(1).wait() + else: + raise RuntimeError(f"ABR axis not in direct mode: {self.abr_mode.value}") # Before moving, ensure we can stop (if a stop_signal is configured). if self.stop_signal is not None: self.stop_signal.wait_for_connection() - if velo is not None: - self.velo.set(velo).wait() + # Set velocity if provided + if velocity is not None: + self.velocity.set(velocity).wait() - # Issue move command - status = super().move(position=distance, timeout=timeout, moved_cb=moved_cb) + # This is adapted from pv_positioner.py + self.check_value(position) + + # Get MoveStatus from parent of parent + status = PositionerBase.move(self, position=position, timeout=timeout) has_done = self.done is not None if not has_done: @@ -112,16 +121,24 @@ class A3200Axis(PVPositioner): self._move_changed(value=moving_val) try: - # Relative movement instead of setpoint - self.relmove.put(distance) + if relative: + # Relative movement instead of setpoint + self.relmove.put(position, wait=True) + else: + # Standard absolute movement + self.setpoint.put(position, wait=True) if wait: status.wait() except KeyboardInterrupt: self.stop() - raise - + raise return status + + def omove(self, position, velocity=None, wait=True, relative=False, direct=False, **kwargs) -> MoveStatus: + """ Exposes the ophyd move command through BEC abstraction""" + return self.move(position, velocity, wait, relative, direct, **kwargs) + def rock(self, distance, counts: int, velocity=None, wait=True) -> Status: """ Repeated single axis zigzag scan I guess PSO should be configured for this""" diff --git a/pxiii_bec/devices/__init__.py b/pxiii_bec/devices/__init__.py index 4031fa7..76f5548 100644 --- a/pxiii_bec/devices/__init__.py +++ b/pxiii_bec/devices/__init__.py @@ -1,2 +1,2 @@ -from .aeroA3200 import A3200Axis -from .A3200 import AerotechAbrStage \ No newline at end of file +from .A3200 import AerotechAbrStage +from .A3200utils import A3200Axis diff --git a/pxiii_bec/scans/mx_measurements.py b/pxiii_bec/scans/mx_measurements.py index 41573c4..a4dcad3 100644 --- a/pxiii_bec/scans/mx_measurements.py +++ b/pxiii_bec/scans/mx_measurements.py @@ -23,18 +23,18 @@ class AbrCmd: VERTICAL_LINE_SCAN = 3 SCREENING = 4 SUPER_FAST_OMEGA = 5 - STILL_WEDGE = 6 - STILLS = 7 - REPEAT_SINGLE_OSCILLATION = 8 + STILL_WEDGE = 6 # NOTE: Unused + STILLS = 7 # NOTE: Unused + REPEAT_SINGLE_OSCILLATION = 8 # NOTE: Unused SINGLE_OSCILLATION = 9 - OLD_FASHIONED = 10 + OLD_FASHIONED = 10 # NOTE: Unused RASTER_SCAN = 11 - JET_ROTATION = 12 - X_HELICAL = 13 - X_RUNSEQ = 14 + JET_ROTATION = 12 # NOTE: Unused + X_HELICAL = 13 # NOTE: Unused + X_RUNSEQ = 14 # NOTE: Unused JUNGFRAU = 15 - MSOX = 16 - SLIT_SCAN = 17 + MSOX = 16 # NOTE: Unused + SLIT_SCAN = 17 # NOTE: Unused RASTER_SCAN_STILL = 18 SCAN_SASTT = 19 SCAN_SASTT_V2 = 20 @@ -87,6 +87,12 @@ class AerotechFlyscanBase(AsyncFlyScanBase): def pre_scan(self): # ToDo: Move roughly to start position + stat = yield from self.stubs.send_rpc_and_wait("abr","status.get") + if stat not in (0, "OK"): + raise RuntimeError("Aerotech ABR seems to be in error state {stat}, please reset") + + if self.abr_reset: + yield from self.stubs.send_rpc_and_wait("abr", "reset") # Reset the scan engine if self.abr_reset: