From eec897f71350a528940e6e135584f79ff95cefce Mon Sep 17 00:00:00 2001 From: Mohacsi Istvan Date: Thu, 13 Jun 2024 12:07:03 +0200 Subject: [PATCH 01/25] Device draft from office --- pxiii_bec/devices/aeroA3200.py | 63 ++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 pxiii_bec/devices/aeroA3200.py diff --git a/pxiii_bec/devices/aeroA3200.py b/pxiii_bec/devices/aeroA3200.py new file mode 100644 index 0000000..7b9b643 --- /dev/null +++ b/pxiii_bec/devices/aeroA3200.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +""" +Created on Tue Jun 11 11:28:38 2024 + +@author: mohacsi_i +""" + +from ophyd import Device, Component, EpicsMotor, EpicsSignal, EpicsSignalRO, Kind, DerivedSignal +from ophyd.status import Status, SubscriptionStatus, StatusBase, DeviceStatus +from ophyd.flyers import FlyerInterface +from time import sleep +import warnings +import numpy as np +import time + + + +class A3200Axis(Device): + """Ophyd proxy class for the Aerotech Automation 1's core controller functionality""" + error = Component(EpicsSignalRO, "-STAT", auto_monitor=True, kind=Kind.config) + done = Component(EpicsSignalRO, "-DONE", auto_monitor=True, kind=Kind.config) + readback = Component(EpicsSignalRO, "-RBV", auto_monitor=True, kind=Kind.hinted) + setpoint = Component(EpicsSignal, "-SETP", read_pv="-GETP", auto_monitor=True, kind=Kind.config) + rmove = Component(EpicsSignal, "-INCP", kind=Kind.config) + + + # PV that diasables the execution of move commands + disable = Component(EpicsSignal, "-DIS", kind=Kind.config) + + + velo = Component(EpicsSignal, "-SETV", kind=Kind.config) + # HW status word + dshw = Component(EpicsSignalRO, "-DSHW", auto_monitor=True, kind=Kind.hinted) + + + +class A3200AxisRock(Device): + """Ophyd proxy class for the Aerotech Automation 1's core controller functionality""" + error = Component(EpicsSignalRO, "-STAT", auto_monitor=True, kind=Kind.config) + done = Component(EpicsSignalRO, "-DONE", auto_monitor=True, kind=Kind.config) + readback = Component(EpicsSignalRO, "-RBV", auto_monitor=True, kind=Kind.hinted) + setpoint = Component(EpicsSignal, "-SETP", read_pv="-GETP", auto_monitor=True, kind=Kind.config) + rmove = Component(EpicsSignal, "-INCP", kind=Kind.config) + + + # PV that diasables the execution of move commands + disable = Component(EpicsSignal, "-DIS", kind=Kind.config) + + + velo = Component(EpicsSignal, "-SETV", kind=Kind.config) + # HW status word + dshw = Component(EpicsSignalRO, "-DSHW", auto_monitor=True, kind=Kind.hinted) + + # Rock movement + rock = Component(EpicsSignal, "-ROCK", kind=Kind.config) + rock_velo = Component(EpicsSignal, "-RSETV", kind=Kind.config) + rock_dist = Component(EpicsSignal, "-RINCP", kind=Kind.config) + rock_cont = Component(EpicsSignal, "-COUNT", kind=Kind.config) + rock_accl = Component(EpicsSignal, "-RRATE", kind=Kind.config) + + + + -- 2.49.1 From 20dff942c1695cd3a5ac893bf689efc60d7e074f Mon Sep 17 00:00:00 2001 From: panepucci Date: Thu, 13 Jun 2024 12:41:32 +0200 Subject: [PATCH 02/25] Basic PVPositioner works --- pxiii_bec/devices/aeroA3200.py | 46 ++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/pxiii_bec/devices/aeroA3200.py b/pxiii_bec/devices/aeroA3200.py index 7b9b643..8378204 100644 --- a/pxiii_bec/devices/aeroA3200.py +++ b/pxiii_bec/devices/aeroA3200.py @@ -5,7 +5,7 @@ Created on Tue Jun 11 11:28:38 2024 @author: mohacsi_i """ -from ophyd import Device, Component, EpicsMotor, EpicsSignal, EpicsSignalRO, Kind, DerivedSignal +from ophyd import Device, Component, PVPositioner, EpicsSignal, EpicsSignalRO, Kind, DerivedSignal from ophyd.status import Status, SubscriptionStatus, StatusBase, DeviceStatus from ophyd.flyers import FlyerInterface from time import sleep @@ -15,12 +15,14 @@ import time -class A3200Axis(Device): +class A3200Axis(PVPositioner): """Ophyd proxy class for the Aerotech Automation 1's core controller functionality""" - error = Component(EpicsSignalRO, "-STAT", auto_monitor=True, kind=Kind.config) + # Basic PV positioner interface done = Component(EpicsSignalRO, "-DONE", auto_monitor=True, kind=Kind.config) readback = Component(EpicsSignalRO, "-RBV", auto_monitor=True, kind=Kind.hinted) - setpoint = Component(EpicsSignal, "-SETP", read_pv="-GETP", auto_monitor=True, kind=Kind.config) + setpoint = Component(EpicsSignal, "-SETP", kind=Kind.config) + + error = Component(EpicsSignalRO, "-STAT", auto_monitor=True, kind=Kind.config) rmove = Component(EpicsSignal, "-INCP", kind=Kind.config) @@ -34,29 +36,29 @@ class A3200Axis(Device): -class A3200AxisRock(Device): - """Ophyd proxy class for the Aerotech Automation 1's core controller functionality""" - error = Component(EpicsSignalRO, "-STAT", auto_monitor=True, kind=Kind.config) - done = Component(EpicsSignalRO, "-DONE", auto_monitor=True, kind=Kind.config) - readback = Component(EpicsSignalRO, "-RBV", auto_monitor=True, kind=Kind.hinted) - setpoint = Component(EpicsSignal, "-SETP", read_pv="-GETP", auto_monitor=True, kind=Kind.config) - rmove = Component(EpicsSignal, "-INCP", kind=Kind.config) +# class A3200AxisRock(Device): +# """Ophyd proxy class for the Aerotech Automation 1's core controller functionality""" +# error = Component(EpicsSignalRO, "-STAT", auto_monitor=True, kind=Kind.config) +# done = Component(EpicsSignalRO, "-DONE", auto_monitor=True, kind=Kind.config) +# readback = Component(EpicsSignalRO, "-RBV", auto_monitor=True, kind=Kind.hinted) +# setpoint = Component(EpicsSignal, "-SETP", read_pv="-GETP", auto_monitor=True, kind=Kind.config) +# rmove = Component(EpicsSignal, "-INCP", kind=Kind.config) - # PV that diasables the execution of move commands - disable = Component(EpicsSignal, "-DIS", kind=Kind.config) +# # PV that diasables the execution of move commands +# disable = Component(EpicsSignal, "-DIS", kind=Kind.config) - velo = Component(EpicsSignal, "-SETV", kind=Kind.config) - # HW status word - dshw = Component(EpicsSignalRO, "-DSHW", auto_monitor=True, kind=Kind.hinted) +# velo = Component(EpicsSignal, "-SETV", kind=Kind.config) +# # HW status word +# dshw = Component(EpicsSignalRO, "-DSHW", auto_monitor=True, kind=Kind.hinted) - # Rock movement - rock = Component(EpicsSignal, "-ROCK", kind=Kind.config) - rock_velo = Component(EpicsSignal, "-RSETV", kind=Kind.config) - rock_dist = Component(EpicsSignal, "-RINCP", kind=Kind.config) - rock_cont = Component(EpicsSignal, "-COUNT", kind=Kind.config) - rock_accl = Component(EpicsSignal, "-RRATE", kind=Kind.config) +# # Rock movement +# rock = Component(EpicsSignal, "-ROCK", kind=Kind.config) +# rock_velo = Component(EpicsSignal, "-RSETV", kind=Kind.config) +# rock_dist = Component(EpicsSignal, "-RINCP", kind=Kind.config) +# rock_cont = Component(EpicsSignal, "-COUNT", kind=Kind.config) +# rock_accl = Component(EpicsSignal, "-RRATE", kind=Kind.config) -- 2.49.1 From 1a204693dc1b6e685ed54d0173cca785c27741f9 Mon Sep 17 00:00:00 2001 From: panepucci Date: Thu, 13 Jun 2024 13:34:02 +0200 Subject: [PATCH 03/25] Added rocking --- pxiii_bec/devices/aeroA3200.py | 80 ++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 32 deletions(-) diff --git a/pxiii_bec/devices/aeroA3200.py b/pxiii_bec/devices/aeroA3200.py index 8378204..ad907ac 100644 --- a/pxiii_bec/devices/aeroA3200.py +++ b/pxiii_bec/devices/aeroA3200.py @@ -14,7 +14,6 @@ import numpy as np import time - class A3200Axis(PVPositioner): """Ophyd proxy class for the Aerotech Automation 1's core controller functionality""" # Basic PV positioner interface @@ -22,44 +21,61 @@ class A3200Axis(PVPositioner): readback = Component(EpicsSignalRO, "-RBV", auto_monitor=True, kind=Kind.hinted) setpoint = Component(EpicsSignal, "-SETP", kind=Kind.config) - error = Component(EpicsSignalRO, "-STAT", auto_monitor=True, kind=Kind.config) - rmove = Component(EpicsSignal, "-INCP", kind=Kind.config) - - - # PV that diasables the execution of move commands - disable = Component(EpicsSignal, "-DIS", kind=Kind.config) - - velo = Component(EpicsSignal, "-SETV", kind=Kind.config) - # HW status word + error = Component(EpicsSignalRO, "-STAT", auto_monitor=True, kind=Kind.config) + # PV to issue native relative movements on the A3200 + relmove = Component(EpicsSignal, "-INCP", put_complete=True, kind=Kind.config) + # PV to home axis + home = Component(EpicsSignal, "-HOME", kind=Kind.config) + + # HW status words dshw = Component(EpicsSignalRO, "-DSHW", auto_monitor=True, kind=Kind.hinted) + ashw = Component(EpicsSignalRO, "-ASHW", auto_monitor=True, kind=Kind.hinted) + fslw = Component(EpicsSignalRO, "-FSLW", auto_monitor=True, kind=Kind.hinted) + # Rock movement + _rock = Component(EpicsSignal, "-ROCK", put_complete=True, kind=Kind.config) + _rock_dist = Component(EpicsSignal, "-RINCP", put_complete=True, kind=Kind.config) + _rock_velo = Component(EpicsSignal, "-RSETV", put_complete=True, kind=Kind.config) + _rock_count = Component(EpicsSignal, "-COUNT", put_complete=True, kind=Kind.config) + _rock_accel = Component(EpicsSignal, "-RRATE", put_complete=True, kind=Kind.config) + def rmove(self, distance, wait=True, timeout=None, moved_cb=None) -> Status: + """ Native relative movement on the A3200 """ + # 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() + status = super(PVPositioner, self).move(position=distance, timeout=timeout, moved_cb=moved_cb) -# class A3200AxisRock(Device): -# """Ophyd proxy class for the Aerotech Automation 1's core controller functionality""" -# error = Component(EpicsSignalRO, "-STAT", auto_monitor=True, kind=Kind.config) -# done = Component(EpicsSignalRO, "-DONE", auto_monitor=True, kind=Kind.config) -# readback = Component(EpicsSignalRO, "-RBV", auto_monitor=True, kind=Kind.hinted) -# setpoint = Component(EpicsSignal, "-SETP", read_pv="-GETP", auto_monitor=True, kind=Kind.config) -# rmove = Component(EpicsSignal, "-INCP", kind=Kind.config) + has_done = self.done is not None + if not has_done: + moving_val = 1 - self.done_value + self._move_changed(value=self.done_value) + self._move_changed(value=moving_val) - -# # PV that diasables the execution of move commands -# disable = Component(EpicsSignal, "-DIS", kind=Kind.config) - - -# velo = Component(EpicsSignal, "-SETV", kind=Kind.config) -# # HW status word -# dshw = Component(EpicsSignalRO, "-DSHW", auto_monitor=True, kind=Kind.hinted) - -# # Rock movement -# rock = Component(EpicsSignal, "-ROCK", kind=Kind.config) -# rock_velo = Component(EpicsSignal, "-RSETV", kind=Kind.config) -# rock_dist = Component(EpicsSignal, "-RINCP", kind=Kind.config) -# rock_cont = Component(EpicsSignal, "-COUNT", kind=Kind.config) -# rock_accl = Component(EpicsSignal, "-RRATE", kind=Kind.config) + try: + # Relative movement instead of setpoint + self.relmove.put(distance) + if wait: + status.wait() + except KeyboardInterrupt: + self.stop() + raise + return status + def rock(self, distance, counts: int, velocity=None, acceleration=None, wait=True): + """ Repeated single axis zigzag scan I guess PSO should be configured for this""" + self._rock_dist.put(distance) + self._rock_count.put(counts) + if velocity is not None: + self._rock_velo.put(velocity) + if acceleration is not None: + self._rock_accel.put(acceleration) + self._rock.put(1) + status = super(PVPositioner, self).move(position=distance) + if wait: + status.wait() + return status \ No newline at end of file -- 2.49.1 From 2ee2b25c21a810ffbcd5eaee0328e9aca1af8960 Mon Sep 17 00:00:00 2001 From: panepucci Date: Fri, 21 Jun 2024 12:13:57 +0200 Subject: [PATCH 04/25] Cleaned up imports and PVs --- pxiii_bec/devices/aeroA3200.py | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/pxiii_bec/devices/aeroA3200.py b/pxiii_bec/devices/aeroA3200.py index ad907ac..58e13aa 100644 --- a/pxiii_bec/devices/aeroA3200.py +++ b/pxiii_bec/devices/aeroA3200.py @@ -5,17 +5,15 @@ Created on Tue Jun 11 11:28:38 2024 @author: mohacsi_i """ -from ophyd import Device, Component, PVPositioner, EpicsSignal, EpicsSignalRO, Kind, DerivedSignal -from ophyd.status import Status, SubscriptionStatus, StatusBase, DeviceStatus -from ophyd.flyers import FlyerInterface -from time import sleep -import warnings -import numpy as np -import time +from ophyd import Component, PVPositioner, EpicsSignal, EpicsSignalRO, Kind +from ophyd.status import Status class A3200Axis(PVPositioner): - """Ophyd proxy class for the Aerotech Automation 1's core controller functionality""" + """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.""" # Basic PV positioner interface done = Component(EpicsSignalRO, "-DONE", auto_monitor=True, kind=Kind.config) readback = Component(EpicsSignalRO, "-RBV", auto_monitor=True, kind=Kind.hinted) @@ -38,14 +36,14 @@ class A3200Axis(PVPositioner): _rock_dist = Component(EpicsSignal, "-RINCP", put_complete=True, kind=Kind.config) _rock_velo = Component(EpicsSignal, "-RSETV", put_complete=True, kind=Kind.config) _rock_count = Component(EpicsSignal, "-COUNT", put_complete=True, kind=Kind.config) - _rock_accel = Component(EpicsSignal, "-RRATE", put_complete=True, kind=Kind.config) + #_rock_accel = Component(EpicsSignal, "-RRATE", put_complete=True, kind=Kind.config) def rmove(self, distance, wait=True, timeout=None, moved_cb=None) -> Status: """ Native relative movement on the A3200 """ # 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() - status = super(PVPositioner, self).move(position=distance, timeout=timeout, moved_cb=moved_cb) + status = super(self).move(position=distance, timeout=timeout, moved_cb=moved_cb) has_done = self.done is not None if not has_done: @@ -64,18 +62,25 @@ class A3200Axis(PVPositioner): return status - def rock(self, distance, counts: int, velocity=None, acceleration=None, wait=True): + def rock(self, distance, counts: int, velocity=None, acceleration=None, wait=True) -> Status: """ Repeated single axis zigzag scan I guess PSO should be configured for this""" self._rock_dist.put(distance) self._rock_count.put(counts) if velocity is not None: self._rock_velo.put(velocity) - if acceleration is not None: - self._rock_accel.put(acceleration) + #if acceleration is not None: + # self._rock_accel.put(acceleration) self._rock.put(1) status = super(PVPositioner, self).move(position=distance) if wait: status.wait() - return status \ No newline at end of file + return status + + +# Automatically start an axis if directly invoked +if __name__ == "__main__": + omega = A3200Axis(prefix="X06DA-ES-DF1:OMEGA", name="omega") + omega.wait_for_connection() + -- 2.49.1 From 65800812a5fb6b61fd9104f394b0a76a09ceafd9 Mon Sep 17 00:00:00 2001 From: panepucci Date: Wed, 26 Jun 2024 10:53:54 +0200 Subject: [PATCH 05/25] ECMC virtal energy motors --- pxiii_bec/device_configs/x06da_device_config.yaml | 4 ++-- pxiii_bec/devices/aeroA3200.py | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/pxiii_bec/device_configs/x06da_device_config.yaml b/pxiii_bec/device_configs/x06da_device_config.yaml index 16388a1..cc6cf30 100644 --- a/pxiii_bec/device_configs/x06da_device_config.yaml +++ b/pxiii_bec/device_configs/x06da_device_config.yaml @@ -32,7 +32,7 @@ dccm_pitch1: softwareTrigger: false dccm_energy1: deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-OP-DCCM:ENERGY1'} + deviceConfig: {prefix: 'X06DA-OP-DCCM:_ENERGY1'} onFailure: buffer enabled: true readoutPriority: monitored @@ -56,7 +56,7 @@ dccm_pitch2: softwareTrigger: false dccm_energy2: deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-OP-DCCM:ENERGY2'} + deviceConfig: {prefix: 'X06DA-OP-DCCM:_ENERGY2'} onFailure: buffer enabled: true readoutPriority: monitored diff --git a/pxiii_bec/devices/aeroA3200.py b/pxiii_bec/devices/aeroA3200.py index 58e13aa..c95b35c 100644 --- a/pxiii_bec/devices/aeroA3200.py +++ b/pxiii_bec/devices/aeroA3200.py @@ -43,7 +43,7 @@ class A3200Axis(PVPositioner): # 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() - status = super(self).move(position=distance, timeout=timeout, moved_cb=moved_cb) + status = super().move(position=distance, timeout=timeout, moved_cb=moved_cb) has_done = self.done is not None if not has_done: @@ -62,7 +62,7 @@ class A3200Axis(PVPositioner): return status - def rock(self, distance, counts: int, velocity=None, acceleration=None, wait=True) -> Status: + def rock(self, distance, counts: int, velocity=None, wait=True) -> Status: """ Repeated single axis zigzag scan I guess PSO should be configured for this""" self._rock_dist.put(distance) @@ -73,7 +73,7 @@ class A3200Axis(PVPositioner): # self._rock_accel.put(acceleration) self._rock.put(1) - status = super(PVPositioner, self).move(position=distance) + status = super().move(position=distance) if wait: status.wait() return status @@ -83,4 +83,3 @@ class A3200Axis(PVPositioner): if __name__ == "__main__": omega = A3200Axis(prefix="X06DA-ES-DF1:OMEGA", name="omega") omega.wait_for_connection() - -- 2.49.1 From e7fd8e453da3d096071fabe3cd1ae2c333a4c724 Mon Sep 17 00:00:00 2001 From: panepucci Date: Tue, 16 Jul 2024 14:32:09 +0200 Subject: [PATCH 06/25] Still a lot to do --- .../device_configs/x06da_device_config.yaml | 4 +- pxiii_bec/devices/A3200.py | 884 ++++++++++++++++++ pxiii_bec/devices/aeroA3200.py | 286 +++++- 3 files changed, 1169 insertions(+), 5 deletions(-) create mode 100644 pxiii_bec/devices/A3200.py diff --git a/pxiii_bec/device_configs/x06da_device_config.yaml b/pxiii_bec/device_configs/x06da_device_config.yaml index f20df44..1893b72 100644 --- a/pxiii_bec/device_configs/x06da_device_config.yaml +++ b/pxiii_bec/device_configs/x06da_device_config.yaml @@ -33,7 +33,7 @@ dccm_pitch1: softwareTrigger: false dccm_energy1: deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-OP-DCCM:_ENERGY1'} + deviceConfig: {prefix: 'X06DA-OP-DCCM:ENERGY1'} onFailure: buffer enabled: true readoutPriority: monitored @@ -57,7 +57,7 @@ dccm_pitch2: softwareTrigger: false dccm_energy2: deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-OP-DCCM:_ENERGY2'} + deviceConfig: {prefix: 'X06DA-OP-DCCM:ENERGY2'} onFailure: buffer enabled: true readoutPriority: monitored diff --git a/pxiii_bec/devices/A3200.py b/pxiii_bec/devices/A3200.py new file mode 100644 index 0000000..2788996 --- /dev/null +++ b/pxiii_bec/devices/A3200.py @@ -0,0 +1,884 @@ +""" +``Aerotech`` --- Aerotech control software +****************************************** + +This module provides an object to control the Aerotech Abr rotational stage. + +The following constants are defined. + +Measurement phases: + + Aerotech.ABR_DONE + Aerotech.ABR_READY + Aerotech.ABR_BUSY + +Axis mode + + Aerotech.DIRECT_MODE + Aerotech.MEASURING_MODE + +Shutter + + Aerotech.SHUTTER_OPEN + Aerotech.SHUTTER_CLOSE + +Methods in the Abr class +======================== + +Aerotech.is_homed() +Aerotech.do_homing(wait=True) +Aerotech.get_ready(ostart=None, orange=None, etime=None, wait=True) +Aerotech.is_done() +Aerotech.is_ready() +Aerotech.is_busy() +Aerotech.stop() +Aerotech.start_exposure() +Aerotech.wait_status(status) +Aerotech.set_mode(, value) +Aerotech.get_mode() +Aerotech.set_direct_mode() +Aerotech.set_measuring_mode() +Aerotech.move(angle, wait=False, speed=None) +Aerotech.set_shutter(state) + +Attribute Access in Abr class +============================= + +The Abr class overwrites the getattr and setattr methods to provide a pythonic +way of controlling the Abr. + +The following properties are implemented: + +velo + + Sets the velocity of rotation: ``-ES-DF1:ROTX-SETV`` + +omega + + Move the omega angle without any wait: ``-ES-DF1:ROTX-VAL`` + +exposure_time + + Sets the PV for the measurement's exposure time: ``-ES-OSC:ETIME`` + +start_angle + + Sets the PV for the measurement's starting angle: ``-ES-OSC:START-POS`` + +oscillation_angle + + Sets the PV for the measurement's oscillation angle: ``-ES-OSC:RANGE`` + +shutter + + Controls the shutter: ``-ES-PH1:SET`` and ``-ES-PH1:GET`` + +mode + + Controls the axis mode: ``-ES-DF1:AXES-MODE`` + +measurement_state + + Controls the PV for the measurement state: ``-ES-OSC:DONE`` + + +Examples +======== + + import Aerotech + abr = Aerotech.Abr() + + # move omega to 270.0 degrees + abr.omega = 270.0 + + # move omega to 180 degrees and wait for movement to finish + abr.move(180, wait=True) + + # move omega to 3000 degrees at 360 degrees/s and wait for movement to finish + abr.move(3000, speed=360, wait=True) + + # stop any movement + abr.stop() # this function only returns after the STATUS is back to OK + +""" +import time +import math +from beamline import BEAMLINE +from epics import PV, poll +from epics.ca import pend_io + + +from ophyd import Component, Device, EpicsSignal, EpicsSignalRO, Kind +from ophyd.status import Status + + +class A3200Axis(PVPositioner): + + +ABR_DONE = 0 +ABR_READY = 1 +ABR_BUSY = 2 + +GRID_SCAN_BUSY = 0 +GRID_SCAN_DONE = 1 + +DIRECT_MODE = 0 +MEASURING_MODE = 1 + +SHUTTER_CLOSE = 0 +SHUTTER_OPEN = 1 + +FULL_PERIOD = 0 +HALF_PERIOD = 1 + + +def pv_wait(pv, val, timeout=10.0): + timeout = timeout + time.time() + timeisup = False + while val != pv.get() and not timeisup: + poll(0.01) + timeisup = time.time() > timeout + if timeisup: + raise RuntimeWarning("timeout waiting for %s to reach %s" % (pv.pvname, str(val))) + + +CMD_NONE = 0 +CMD_RASTER_SCAN_SIMPLE = 1 +CMD_MEASURE_STANDARD = 2 +CMD_VERTICAL_LINE_SCAN = 3 +CMD_SCREENING = 4 +CMD_SUPER_FAST_OMEGA = 5 +CMD_STILL_WEDGE = 6 +CMD_STILLS = 7 +CMD_REPEAT_SINGLE_OSCILLATION = 8 +CMD_SINGLE_OSCILLATION = 9 +CMD_OLD_FASHIONED = 10 +CMD_RASTER_SCAN = 11 +CMD_JET_ROTATION = 12 +CMD_X_HELICAL = 13 +CMD_X_RUNSEQ = 14 +CMD_JUNGFRAU = 15 +CMD_MSOX = 16 +CMD_SLIT_SCAN = 17 +CMD_RASTER_SCAN_STILL = 18 +CMD_SCAN_SASTT = 19 +CMD_SCAN_SASTT_V2 = 20 +CMD_SCAN_SASTT_V3 = 21 + +AXIS_OMEGA = 1 +AXIS_GMX = 2 +AXIS_GMY = 3 +AXIS_GMZ = 4 +AXIS_STY = 5 +AXIS_STZ = 6 + + +class AerotechAbrStage(Device): + + taskStop = Component(EpicsSignal, "-ES-AERO:TSK-STOP", put_complete=True, kind=Kind.omitted) + status = Component(EpicsSignal, "-ES-AERO:STAT", put_complete=True, kind=Kind.omitted) + + # Enable/disable motor movement via the IOC (i.e. make it task-only) + axisModeLocked = Component(EpicsSignal, "-ES-DF1:LOCK", put_complete=True, kind=Kind.omitted) + axisModeDirect = Component(EpicsSignal, "-ES-DF1:MODE-DIRECT", put_complete=True, kind=Kind.omitted) + axisAxesMode = Component(EpicsSignal, "-ES-DF1:AXES-MODE", put_complete=True, kind=Kind.omitted) + + _shutter = Component( + EpicsSignal, "-ES-PH1:GET", write_pv="-ES-PH1:SET", put_complete=True, kind=Kind.config, + ) + + raster = Component(A3200RasterScanner, "", kind=Kind.config) + osc = Component(A3200Oscillator, "", kind=Kind.config) + + omega = Component(A3200Axis, "-ES-DF1:OMEGA", kind=Kind.hinted) + gmx = Component(A3200Axis, "-ES-DF1:GMX", kind=Kind.hinted) + gmy = Component(A3200Axis, "-ES-DF1:GMY", kind=Kind.hinted) + gmz = Component(A3200Axis, "-ES-DF1:GMZ", kind=Kind.hinted) + + + _zcmd = Component(EpicsSignal, "-ES-PSO:CMD", put_complete=True, kind=Kind.omitted) + _start_command = Component(EpicsSignal, "-ES-PSO:START-TEST.PROC", put_complete=True, kind=Kind.omitted) + _stop_command = Component(EpicsSignal, "-ES-PSO:STOP-TEST.PROC", put_complete=True, kind=Kind.omitted) + + _var_1 = Component(EpicsSignal, "-ES-PSO:VAR-1", put_complete=True, kind=Kind.omitted) + _var_2 = Component(EpicsSignal, "-ES-PSO:VAR-2", put_complete=True, kind=Kind.omitted) + _var_3 = Component(EpicsSignal, "-ES-PSO:VAR-3", put_complete=True, kind=Kind.omitted) + _var_4 = Component(EpicsSignal, "-ES-PSO:VAR-4", put_complete=True, kind=Kind.omitted) + _var_5 = Component(EpicsSignal, "-ES-PSO:VAR-5", put_complete=True, kind=Kind.omitted) + _var_6 = Component(EpicsSignal, "-ES-PSO:VAR-6", put_complete=True, kind=Kind.omitted) + _var_7 = Component(EpicsSignal, "-ES-PSO:VAR-7", put_complete=True, kind=Kind.omitted) + _var_8 = Component(EpicsSignal, "-ES-PSO:VAR-8", put_complete=True, kind=Kind.omitted) + _var_9 = Component(EpicsSignal, "-ES-PSO:VAR-9", put_complete=True, kind=Kind.omitted) + _var_10 = Component(EpicsSignal, "-ES-PSO:VAR-10", put_complete=True, kind=Kind.omitted) + + # A few PVs still needed from grid + raster_scan_done = Component(EpicsSignal, "-ES-GRD:SCAN-DONE", kind=Kind.config) + raster_num_rows = Component(EpicsSignal, "-ES-GRD:SCAN-DONE", kind=Kind.config) + + + def __init__(self): + + self.__pv_ostart = PV(f"{BEAMLINE}-ES-OSC:START-POS") + self.__pv_orange = PV(f"{BEAMLINE}-ES-OSC:RANGE") + self.__pv_etime = PV(f"{BEAMLINE}-ES-OSC:ETIME") + self.__pv_get_ready = PV(f"{BEAMLINE}-ES-OSC:READY.PROC") + self.__pv_start = PV(f"{BEAMLINE}-ES-OSC:START") + self.__pv_phase = PV(f"{BEAMLINE}-ES-OSC:DONE") + + pend_io(10) + + + def command(self, cmd): + self._zcmd.set(cmd).wait() + + def start_command(self): + self._start_command.set(1).wait() + + def measure_standard(self, start, wedge, etime, ready_rate=500.0): + """measure a standard wedge from `start` to `start` + `wedge` during `etime` seconds""" + self.command(CMD_MEASURE_STANDARD) + self._var_1.set(start).wait() + self._var_2.set(wedge).wait() + self._var_3.set(etime).wait() + self._var_4.set(ready_rate).wait() + self.start_command() + + def measure_scan_slits(self, delta_x, delta_y, velocity): + """measure a standard wedge from `start` to `start` + `wedge` during `etime` seconds""" + self.command(CMD_SLIT_SCAN) + self._var_1.set(delta_x).wait() + self._var_2.set(delta_y).wait() + self._var_3.set(velocity).wait() + self.start_command() + + def measure_raster_simple(self, etime, cell_width, cell_height, ncols, nrows): + """measures the specified grid assuming the goniometer is currently at the CENTER of TOP-LEFT CELL + + etime: a float in seconds + cell_width: a float in mm + cell_height: a float in mm + ncols: an int + nrows: an int + + """ + # self.reset() + self.command(CMD_RASTER_SCAN_SIMPLE) + self.raster.scan_done.set(0).wait() # make SCAN DONE go into busy state + self._var_1.set(etime).wait() + self._var_2.set(cell_width).wait() + self._var_3.set(cell_height).wait() + self._var_4.set(int(ncols)).wait() + self._var_5.set(int(nrows)).wait() + self._var_6.set(0).wait() + self._var_7.set(0).wait() + self._var_8.set(0).wait() + self.raster._raster_num_rows = nrows + self.start_command() + + def measure_sastt(self, etime, cell_width, cell_height, ncols, nrows, even_tweak=0.0, odd_tweak=0.0, version=1): + """measures the specified grid assuming the goniometer is currently at the CENTER of the required grid + + etime: a float in seconds + cell_width: a float in mm + cell_height: a float in mm + ncols: an int + nrows: an int + + odd_tweak, even_tweak: float, a distance to be added to the start of + the open shutter command. used on in the scan mode + version=3 + + - these values can be either positive/negative + - they should be small, i.e. just to tweak a trriggering event + by minor amounts + version: an int (1, 2, 3) + 1 = original snake scan, single PSO window + 2 = scan always from LEFT---RIGHT + 3 = snake scan alternating PSO window for even/odd rows + + """ + # self.reset() + if version == 1: + self.command(CMD_SCAN_SASTT) + elif version == 2: + self.command(CMD_SCAN_SASTT_V2) + elif version == 3: + self.command(CMD_SCAN_SASTT_V3) + else: + raise RuntimeError("non existing SAS-TT scan mode") + + self.raster.scan_done.set(0).wait() # make SCAN DONE go into busy state + self._var_1.set(etime).wait() + self._var_2.set(cell_width).wait() + self._var_3.set(cell_height).wait() + self._var_4.set(int(ncols)).wait() + self._var_5.set(int(nrows)).wait() + self._var_6.set(even_tweak).wait() + self._var_7.set(odd_tweak).wait() + self._var_8.set(0).wait() + self.raster._raster_num_rows = nrows + self.start_command() + + def measure_vertical_line(self, cellHeight, numCells, exposureTime): + """measure a vertical line using the GMY motor + + The line is nummCells * cellHeight long and the exposureTime is per cell. + + `cellHeight` in mm + `numCells` an integer + `exposureTime` in seconds / per cellHeight + """ + self.command(CMD_VERTICAL_LINE_SCAN) + self._var_1.set(cellHeight).wait() + self._var_2.set(numCells).wait() + self._var_3.set(exposureTime).wait() + self.start_command() + + def measure_screening(self, start, oscangle, etime, degrees, frames, delta=0.5): + self.command(CMD_SCREENING) + self._var_1.set(start).wait() + self._var_2.set(oscangle).wait() + self._var_3.set(etime).wait() + self._var_4.set(degrees).wait() + self._var_5.set(frames).wait() + self._var_6.set(delta).wait() + self.start_command() + + def measure_fast_omega(self, start, wedge, etime, rate=200.0): + self.command(CMD_SUPER_FAST_OMEGA) + self._var_1.set(start).wait() + self._var_2.set(wedge).wait() + self._var_3.set(etime).wait() + self._var_4.set(rate).wait() + self.start_command() + + def measure_still_wedge(self, start, wedge, etime, oscangle, sleep_after_shutter_close=0.010): + self.command(CMD_STILL_WEDGE) + self._var_1.set(start).wait() + self._var_2.set(wedge).wait() + self._var_3.set(etime).wait() + self._var_4.set(oscangle).wait() + self._var_5.set(sleep_after_shutter_close).wait() + self.start_command() + + def measure_stills(self, number_of_frames, exposure_time, idle=0.0): + """collect still images, i.e. no omega rotation + + number_of_frames => an integer + exposure_time => a float + idle => sleep in between each exposure + + IMPORTANT: use idle=0.0 to prevent shutter open/close + + """ + self.command(CMD_STILLS) + self._var_1.set(number_of_frames).wait() + self._var_2.set(exposure_time).wait() + self._var_3.set(idle).wait() + self.start_command() + + def measure_repeat_singles(self, frames, etime, oscangle, sleep=0.0, delta=0.5): + self.command(CMD_REPEAT_SINGLE_OSCILLATION) + self._var_1.set(frames).wait() + self._var_2.set(etime).wait() + self._var_3.set(oscangle).wait() + self._var_4.set(sleep).wait() + self._var_5.set(delta).wait() + self.start_command() + + def measure_single(self, start_angle, oscillation_angle, exposure_time, delta=0.0, settle=0.0): + self.command(CMD_SINGLE_OSCILLATION) + self._var_1.set(start_angle).wait() + self._var_2.set(oscillation_angle).wait() + self._var_3.set(exposure_time).wait() + self._var_4.set(delta).wait() + self._var_5.set(settle).wait() + self.start_command() + + def measure_raster(self, positions, columns, angle, etime): + """raster via generic experiment interface""" + self.reset() + if len(positions) == 1: + raise RuntimeWarning("Raster scan with one cell makes no sense") + self.command(CMD_RASTER_SCAN) + + rows = len(positions) / columns + x1, y1, z1 = tuple(positions[0]) # first cell on first row + x2, y2, z2 = tuple(positions[1]) # second cell on first row + x4, y4, z4 = tuple(positions[columns - 1]) # last cell on first row + + if rows > 1: + x3, y3, z3 = tuple(positions[columns]) # first cell on second row + ystep = y3 - y1 + zstep = z3 - z1 + gmy_step = math.sqrt(pow(y3 - y1, 2) + pow(z3 - z1, 2)) + else: + ystep = 0.010 + zstep = 0.010 + gmy_step = 0.010 + + half_cell = abs(x2 - x1) / 2.0 + start_x = x1 - half_cell + end_x = x4 + half_cell + + self._raster_starting_x = start_x + self._raster_starting_y = y1 + self._raster_starting_z = z1 + self._raster_step_y = ystep + self._raster_step_z = zstep + self._raster_current_row = 0 + self._raster_num_rows = rows + + self._var_1.set(self.position).wait() + self._var_2.set(start_x).wait() + self._var_3.set(end_x).wait() + self._var_4.set(columns).wait() + self._var_5.set(rows).wait() + self._var_6.set(angle).wait() + self._var_7.set(etime).wait() + self._var_8.set(HALF_PERIOD).wait() + self._var_9.set(self.gmx.offset.get()).wait() + self._var_10.set(gmy_step).wait() + + self.start_command() + + def measure_raster_still(self, positions, columns, etime, delay): + """raster via generic experiment interface + + positions: list of coordinates for each cell + columns: how many columns in grid + etime: exposure time / cell_width + delay: delay configured in delay generator + to trigger detector after aerotech; + must be greater than actual shutter delay + """ + self.reset() + if len(positions) == 1: + raise RuntimeWarning("Raster scan with one cell makes no sense") + self.command(CMD_RASTER_SCAN_STILL) + + rows = len(positions) / columns + x1, y1, z1 = tuple(positions[0]) # first cell on first row + x2, y2, z2 = tuple(positions[1]) # second cell on first row + x4, y4, z4 = tuple(positions[columns - 1]) # last cell on first row + + if rows > 1: + x3, y3, z3 = tuple(positions[columns]) # first cell on second row + ystep = y3 - y1 + zstep = z3 - z1 + gmy_step = math.sqrt(pow(y3 - y1, 2) + pow(z3 - z1, 2)) + else: + ystep = 0.010 + zstep = 0.010 + gmy_step = 0.010 + + half_cell = abs(x2 - x1) / 2.0 + start_x = x1 - half_cell + end_x = x4 + half_cell + + self._raster_starting_x = start_x + self._raster_starting_y = y1 + self._raster_starting_z = z1 + self._raster_step_y = ystep + self._raster_step_z = zstep + self._raster_current_row = 0 + self._raster_num_rows = rows + + self._var_1.set(self.position).wait() + self._var_2.set(start_x).wait() + self._var_3.set(end_x).wait() + self._var_4.set(columns).wait() + self._var_5.set(rows).wait() + self._var_6.set(delay).wait() + self._var_7.set(etime).wait() + self._var_8.set(HALF_PERIOD).wait() + self._var_9.set(self.gmx.offset.get()).wait() + self._var_10.set(gmy_step).wait() + + self.start_command() + + def measure_jungfrau(self, columns, rows, width, height, etime, sleep=0.100): + """for Jungfrau, grid from current position""" + self.reset() + self.command(CMD_JUNGFRAU) + self._var_1.set(columns).wait() + self._var_2.set(rows).wait() + self._var_3.set(height).wait() + self._var_4.set(width).wait() + self._var_5.set(etime).wait() + self._var_6.set(sleep).wait() + self._var_7.set(0.0).wait() + self._var_8.set(0.0).wait() + self._var_9.set(0.0).wait() + self._var_10.set(0.0).wait() + + self.start_command() + timeout = None if sleep <= 0 else 5.0 + (rows * sleep) + (columns * rows * etime) + + if timeout: + # Define wait until the busy flag goes down (excluding initial update) + timestamp_ = 0 + def isReady(*args, old_value, value, timestamp, **kwargs): + nonlocal timestamp_ + result = False if (timestamp_== 0) else bool(value==GRID_SCAN_DONE) + print(f"Old {old_value}\tNew: {value}\tResult: {result}") + timestamp_ = timestamp + return result + + # Subscribe and wait for update + status = SubscriptionStatus(self.raster.scan_done, isReady, timeout=timeout, settle_time=0.5) + status.wait() + + self.set_direct_mode() + + def measure_msox(self, start, wedge, etime, num_datasets=1, rest_time=0.0): + """for msox + start = start omega + wedge = total range to measure + etime = total time for wedge + num_datasets = how many times to repeat + rest_time = how many seconds to rest in between datasets + """ + self.reset() + self.command(CMD_MSOX) + self._var_1.set(start).wait() + self._var_2.set(wedge).wait() + self._var_3.set(etime).wait() + self._var_4.set(num_datasets).wait() + self._var_5.set(rest_time).wait() + self._var_6.set(0).wait() + self._var_7.set(0).wait() + self._var_8.set(0).wait() + self._var_9.set(0).wait() + self._var_10.set(0).wait() + + self.start_command() + + + + def configure_stills(self, frames, etime, sleep=0.0): + self._var_1.set(frames).wait() + self._var_2.set(etime).wait() + self._var_3.set(sleep).wait() + + def reset(self): + self.command(CMD_NONE) + self.set_direct_mode() + + def raster_2d(self): + pass + + def is_ioc_ok(self): + return 0 == self.status.get() + + def is_ready_and_willing(self): + return self.is_ioc_ok() and self.omega.is_homed() + + def is_omega_ok(self): + return 0 == self.self.omega.status.get() + + def is_homed(self): + """Are the motors homed?""" + return 1 == self.omega.is_homed.get() + + def do_homing(self, wait=True): + """Execute the homing procedure. + + Executes the homing procedure and waits (default) until it is completed. + + PARAMETERS + `wait` true / false if the routine is to wait for the homing to finish. + """ + self.omega.home.set(1).wait() + poll(1.0) + if not wait: + return + while not self.omega.is_homed(): + poll(0.2) + + @property + def velo(self): + return self.omega.velocity.get() + + @velo.setter + def velo(self, value): + self.omega.velocity.set(value).wait() + + @property + def etime(self): + return self.osc.etime.get() + + @etime.setter + def etime(self, value): + self.osc.etime.set(value).wait() + + @property + def start_angle(self): + return self.osc.ostart_pos.get() + + @start_angle.setter + def start_angle(self, value): + self.osc.ostart_pos(value).wait() + + @property + def measurement_state(self): + return self.osc.phase.get() + + @measurement_state.setter + def measurement_state(self, value): + self.osc.phase.set(value).wait() + + @property + def shutter(self): + return self._shutter.get() + + @shutter.setter + def shutter(self, value): + if self.axisAxesMode.get(): + print("ABR is not in direct mode; cannot manipulate shutter") + return False + state = str(state).lower() + if state not in ["1", "0", "closed", "open"]: + print("unknown shutter state requested") + return None + elif state in ["1", "open"]: + state = 1 + elif state == ["0", "closed"]: + state = 0 + self._shutter.set(state).wait() + return state == self._shutter.get() + + @property + def mode(self): + return self.axisAxesMode.get() + + @mode.setter + def mode(self, value): + self.axisAxesMode.set(value).wait() + + + + + + def get_ready(self, ostart=None, orange=None, etime=None, wait=True): + self.wait_for_movements() + if self.measurement_state == ABR_BUSY: + raise RuntimeError("ABR is not DONE!!!!") + + if self.measurement_state == ABR_READY: + self.measurement_state = ABR_DONE + + if ostart is not None: + self.osc.ostart.set(ostart).wait() + if orange is not None: + self.osc.orange.set(orange).wait() + if etime is not None: + self.osc.etime.set(etime).wait() + + self.osc.get_ready.set(1).wait() + + poll(0.1) + + if wait: + for n in range(10): + try: + self.wait_status(ABR_READY, timeout=5).wait() + except RuntimeWarning as e: + print(str(e), end=" ") + print(" --- trying ready again.") + self.osc.get_ready.set(1).wait() + + + def wait_for_movements(self, timeout=60.0): + timeout = timeout + time.time() + timeisup = False + + try: + moving = self.is_moving() + except: + moving = True + + while not timeisup and moving: + poll(0.1) + try: + moving = self.is_moving() + except: + print("Aerotech() Failed to retrieve moving state for axes omega, gmx, gmy, gmz") + moving = True + timeisup = timeout < time.time() + + if timeisup: + raise RuntimeWarning("timeout waiting for all axis to stop moving") + + def is_moving(self): + return ( + self.omega.moving + and self.gmx.moving and self.gmy.moving and self.gmz.moving ) + + def stop(self, wait_after_reload=1.0): + """Stops current motions; reloads aerobasic programs; goes DIRECT + + This will stop movements in both DIRECT and MEASURING modes. During the + stop the `status` temporarely goes to ERROR but reverts to OK after a + couple of seconds. + + """ + self.taskStop.put(1, wait=True) + poll(wait_after_reload) + self.reset() + poll(0.1) + + reload_programs = stop + + def start_exposure(self): + """Starts the previously configured exposure.""" + self.wait_for_movements() + self.osc.taskStart.set(1).wait() + for n in range(10): + try: + self.osc.wait_status(ABR_BUSY, timeout=1) + except RuntimeWarning as e: + print(str(e), end=" ") + print(" --- trying start again.") + self.osc.taskStart.set(1).wait() + + def velo(self, axis: int, speed: float = None) -> float: + """set's the speed on the given axis to speed""" + if AXIS_GMX == axis: + velo_pv = self.__pv_gmx_velo + min, max = 0.01, 100.0 + elif AXIS_GMY == axis: + velo_pv = self.__pv_gmy_velo + min, max = 0.01, 10.0 + elif AXIS_GMZ == axis: + velo_pv = self.__pv_gmz_velo + min, max = 0.01, 10.0 + elif AXIS_OMEGA == axis: + velo_pv = self.__pv_omega_velo + min, max = 0.01, 720.0 + else: + raise Exception("unknown axis index") + + if speed is not None: + if not (min <= speed <= max): + raise Exception(f"requested speed {speed} outside range {min} < speed < {max}") + velo_pv.put(speed) + poll(0.05) + return velo_pv.get() + + + def select_axis(self, axis): + """returns PVs and limits for the given axis + + axis: Aerotech.AXIS_{OMEGA=1, GMX=2, GMY=3, GMZ=4} + + return (val_pv, rbv_pv, velo_pv, done_pv, velomin, velomax) + """ + + if AXIS_GMX == axis: + val_pv = self.__pv_gmx_val + rbv_pv = self.__pv_gmx_rbv + velo_pv = self.__pv_gmx_velo + done_pv = self.__pv_gmx_done + min, max = 0.01, 100.0 + elif AXIS_GMY == axis: + val_pv = self.__pv_gmy_val + rbv_pv = self.__pv_gmy_rbv + velo_pv = self.__pv_gmy_velo + done_pv = self.__pv_gmy_done + min, max = 0.01, 10.0 + elif AXIS_GMZ == axis: + val_pv = self.__pv_gmz_val + rbv_pv = self.__pv_gmz_rbv + velo_pv = self.__pv_gmz_velo + done_pv = self.__pv_gmz_done + min, max = 0.01, 10.0 + elif AXIS_OMEGA == axis: + val_pv = self.__pv_omega_set + rbv_pv = self.__pv_omega_get + velo_pv = self.__pv_omega_velo + done_pv = self.__pv_omega_done + min, max = 0.01, 720.0 + else: + raise Exception("unknown axis index") + + return (val_pv, rbv_pv, velo_pv, done_pv, min, max) + + + + def rbv(self): + x = self.__pv_gmx_rbv.get() + y = self.__pv_gmy_get.get() + z = self.__pv_gmz_get.get() + omega = self.__pv_omega_get.get() + return {"gmx": x, "gmy": y, "gmz": z, "omega": omega} + + def move(self, position, wait=False, velo=None, direct=False, **kwargs) -> StatusBase: + """Move omega to `angle` at `speed` deg/s and `wait` (or not). + + If a `speed` is specified, it will be used for the movement but the speed + setting will be reverted to its old value afterwards. + + If `wait` is true, the routine waits until the omega position is close + to within 0.01 degrees to the target or that a timeout occured. The + timeout is the time required for the movement plus 5 seconds. + + In case of a timeout a RuntimeWarning is raised. + + PARAMETERS: + `position` [degrees] a float specifying where to move to. + No limits. + `velo` [degrees/s] a float specifying speed to move at. + Limits: 0 - 360 + `wait` [boolean] wait or not for movement to finish. + """ + return self.omega.move(position, wait=wait, velo=velo, direct=direct, **kwargs) + + def set_shutter(self, state): + if self.__pv_axes_mode.get(): + print("ABR is not in direct mode; cannot manipulate shutter") + return False + state = str(state).lower() + if state not in ["1", "0", "closed", "open"]: + print("unknown shutter state requested") + return None + elif state in ["1", "open"]: + state = 1 + elif state == ["0", "closed"]: + state = 0 + self.__pv_shutter_set.put(state, wait=True) + return state == self.__pv_shutter_get.get() + + +if __name__ == "__main__": + import argparse + from time import sleep + + parser = argparse.ArgumentParser(description="aerotech client") + parser.add_argument("--stop", help="stop/reset/restart aerotech", action="store_true") + parser.add_argument("--sanitize-speeds", help="reset speeds on axis to sane defaults", action="store_true") + parser.add_argument("--measure-standard", help="measure a standard dataset", action="store") + parser.add_argument("--omega", help="move omega to...", action="store") + parser.add_argument("--gmx", help="move gmx to...", action="store") + parser.add_argument("--gmy", help="move gmy to...", action="store") + parser.add_argument("--gmz", help="move gmz to...", action="store") + args = parser.parse_args() + + abr = Abr() + if args.stop: + abr.stop() + + + elif args.sanitize_speeds: + abr.velo(AXIS_GMX, 50.0) + abr.velo(AXIS_GMY, 10.0) + abr.velo(AXIS_GMZ, 10.0) + abr.velo(AXIS_OMEGA, 180.0) + + elif args.gmx: + abr.move_x(args.gmx) + + elif args.measure_standard: + start_angle, total_range, total_time = args.measure_standard.split(",") + abr.measure_standard(start_angle, total_range, total_time) + diff --git a/pxiii_bec/devices/aeroA3200.py b/pxiii_bec/devices/aeroA3200.py index c95b35c..18ff5b7 100644 --- a/pxiii_bec/devices/aeroA3200.py +++ b/pxiii_bec/devices/aeroA3200.py @@ -5,7 +5,7 @@ Created on Tue Jun 11 11:28:38 2024 @author: mohacsi_i """ -from ophyd import Component, PVPositioner, EpicsSignal, EpicsSignalRO, Kind +from ophyd import Component, PVPositioner, Device, EpicsSignal, EpicsSignalRO, Kind from ophyd.status import Status @@ -17,14 +17,17 @@ class A3200Axis(PVPositioner): # Basic PV positioner interface done = Component(EpicsSignalRO, "-DONE", auto_monitor=True, kind=Kind.config) readback = Component(EpicsSignalRO, "-RBV", auto_monitor=True, kind=Kind.hinted) + # Setpoint is one of the two... setpoint = Component(EpicsSignal, "-SETP", kind=Kind.config) + #setpoint = Component(EpicsSignal, "-VAL", kind=Kind.config) velo = Component(EpicsSignal, "-SETV", kind=Kind.config) - error = Component(EpicsSignalRO, "-STAT", auto_monitor=True, kind=Kind.config) + status = Component(EpicsSignalRO, "-STAT", auto_monitor=True, kind=Kind.config) # PV to issue native relative movements on the A3200 relmove = Component(EpicsSignal, "-INCP", put_complete=True, kind=Kind.config) # PV to home axis home = Component(EpicsSignal, "-HOME", kind=Kind.config) + ishomed = Component(EpicsSignal, "-AS00", kind=Kind.config) # HW status words dshw = Component(EpicsSignalRO, "-DSHW", auto_monitor=True, kind=Kind.hinted) @@ -38,11 +41,49 @@ class A3200Axis(PVPositioner): _rock_count = Component(EpicsSignal, "-COUNT", put_complete=True, kind=Kind.config) #_rock_accel = Component(EpicsSignal, "-RRATE", put_complete=True, kind=Kind.config) - def rmove(self, distance, wait=True, timeout=None, moved_cb=None) -> Status: + hlm = Component(Signal, kind=Kind.config) + llm = Component(Signal, kind=Kind.config) + vmin = Component(Signal, kind=Kind.config) + 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): + """ __init__ MUST have a full argument list""" + 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, wait=True, velo=None, direct=False, **kwargs): + """ Native absolute movement on the A3200 """ + + 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!") + + if velo is not None: + self.velo.set(velo).wait() + + 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 """ + # 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() + + # Issue move command status = super().move(position=distance, timeout=timeout, moved_cb=moved_cb) has_done = self.done is not None @@ -79,6 +120,245 @@ class A3200Axis(PVPositioner): return status + + + +class A3200RasterScanner(Device): + """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.""" + + x_start = Component(EpicsSignal, "-ES-GRD:GMX-START", kind=Kind.config) + x_start = Component(EpicsSignal, "-ES-GRD:GMX-END", kind=Kind.config) + omega = Component(EpicsSignal, "-ES-OSC:#M-START", kind=Kind.config) + osc = Component(EpicsSignal, "-ES-GRD:ANGLE", kind=Kind.config) + celltime = Component(EpicsSignal, "-ES-GRD:CELL-TIME", kind=Kind.config) + + y_start = Component(EpicsSignal, "-ES-OSC:#STY-START", kind=Kind.config) + y_end = Component(EpicsSignal, "-ES-OSC:#STY-END", kind=Kind.config) + z_start = Component(EpicsSignal, "-ES-OSC:#STZ-START", kind=Kind.config) + z_end = Component(EpicsSignal, "-ES-OSC:#STZ-END", kind=Kind.config) + + columns = Component(EpicsSignal, "-ES-GRD:COLUMNS", kind=Kind.config) + rows = Component(EpicsSignal, "-ES-GRD:ROWS", kind=Kind.config) + delay = Component(EpicsSignal, "-ES-GRD:RAST-DLY", kind=Kind.config) + osc_mode = Component(EpicsSignal, "-ES-GRD:SET-MODE", kind=Kind.config) + + velo = Component(EpicsSignal, "-ES-GRD:#X-VEL", kind=Kind.config) + get_ready = Component(EpicsSignal, "-ES-GRD:READY.PROC", kind=Kind.config) + grid_start = Component(EpicsSignal, "-ES-GRD:START.PROC", kind=Kind.config) + grid_next = Component(EpicsSignal, "-ES-GRD:NEXT-ROW", kind=Kind.config) + row_done = Component(EpicsSignal, "-ES-GRD:ROW-DONE", kind=Kind.config) + scan_done = Component(EpicsSignal, "-ES-GRD:SCAN-DONE", kind=Kind.config) + grid_done = Component(EpicsSignal, "-ES-GRD:DONE", kind=Kind.config) + + # Also needs the command interface + _zcmd = Component(EpicsSignal, "-ES-PSO:CMD", put_complete=True, kind=Kind.omitted) + _start_command = Component(EpicsSignal, "-ES-PSO:START-TEST.PROC", put_complete=True, kind=Kind.omitted) + _stop_command = Component(EpicsSignal, "-ES-PSO:STOP-TEST.PROC", put_complete=True, kind=Kind.omitted) + + + def raster_setup(self, positions, columns, angle, etime): + """configures parameters for a raster scan + + positions : a list of xyz indicating the positions of each cell + columns: an integer with how many columns + angle : the oscillation angle for each row + etime : the exposure time per cell + """ + self.parent.axisModeDirect.set(1).wait() + if columns == 1: + raise RuntimeWarning("Fast scans are not available with vertical line scans.") + + rows = len(positions) / columns + x1, y1, z1 = tuple(positions[0]) # first cell on first row + x2, y2, z2 = tuple(positions[1]) # second cell on first row + x4, y4, z4 = tuple(positions[columns - 1]) # last cell on first row + + if rows > 1: + x3, y3, z3 = tuple(positions[columns]) # first cell on second row + ystep = y3 - y1 + zstep = z3 - z1 + gmy_step = math.sqrt(pow(y3 - y1, 2) + pow(z3 - z1, 2)) + else: + ystep = 0.010 + zstep = 0.010 + gmy_step = 0.010 + + half_cell = abs(x2 - x1) / 2.0 + start_x = x1 - half_cell + end_x = x4 + half_cell + + self._raster_starting_x = start_x + self._raster_starting_y = y1 + self._raster_starting_z = z1 + self._raster_step_y = ystep + self._raster_step_z = zstep + self._raster_current_row = 0 + self._raster_num_rows = rows + + self.x_start.set(start_x).wait() + self.x_end.set(end_x).wait() + self.omega.set(self.position).wait() + self.osc.set(angle).wait() + self.celltime.set(etime).wait() + self.y_start.set(y1).wait() + self.z_start.set(z1).wait() + self.y_step.set(ystep).wait() + self.z_step.set(zstep).wait() + self.columns.set(columns).wait() + self.rows.set(rows).wait() + + def raster_next_row_yz(self): + r = self._raster_current_row + self._raster_current_row = r + 1 + + y, z = ( + r * self.step_y.value + self._raster_starting_y, + r * self.step_z.value + self._raster_starting_z, + ) + + if r < self._raster_num_rows: + return (y, z) + else: + return (None, None) + + def next_row(self): + """start rastering a new row""" + self.grid_next.set(1).wait() + + def raster_get_ready(self): + """ + WTF is this??? + """ + + self.get_ready.set(1).wait() + for n in range(10): + try: + # Define wait until the busy flag goes down (excluding initial update) + timestamp_ = 0 + def isReady(*args, old_value, value, timestamp, **kwargs): + nonlocal timestamp_ + result = False if (timestamp_== 0) else bool(value==ABR_READY) + print(f"Old {old_value}\tNew: {value}\tResult: {result}") + timestamp_ = timestamp + return result + + # Subscribe and wait for update + status = SubscriptionStatus(self.grid_done, isReady, timeout=2, settle_time=0.5) + status.wait() + except TimeoutError as e: + print(str(e), end=" ") + print(" --- trying again.") + self.get_ready.put(1, wait=True) + + def raster_scan_row(self): + """start rastering a new row, either first or following rows""" + if ABR_READY == self._grid_done.get(): + self.grid_start.set(1) + else: + self.grid_next.set(1) + + + def is_scan_done(self): + return 1 == scan_done.get() + + def wait_scan_done(self, timeout=60.0) -> SubscriptionStatus: + # Define wait until the busy flag goes down (excluding initial update) + timestamp_ = 0 + def isReady(*args, old_value, value, timestamp, **kwargs): + nonlocal timestamp_ + result = False if (timestamp_== 0) else bool(value==1) + print(f"Old {old_value}\tNew: {value}\tResult: {result}") + timestamp_ = timestamp + return result + + # Subscribe and wait for update + status = SubscriptionStatus(self.scan_done, isReady, timeout=timeout, settle_time=0.5) + status.wait() + + return status + + def raster_is_row_scanning(self): + return 1 == self.scan_done.get() + + def raster_wait_for_row(self, timeout=60) -> SubscriptionStatus: + # Define wait subscription + timestamp_ = 0 + def isReady(*args, old_value, value, timestamp, **kwargs): + nonlocal timestamp_ + result = False if (timestamp_== 0) else bool(value==1) + print(f"Old {old_value}\tNew: {value}\tResult: {result}") + timestamp_ = timestamp + return result + + # Subscribe and wait for update + status = SubscriptionStatus(self.row_done, isReady, timeout=timeout, settle_time=0.5) + status.wait() + + return status + + + + +class A3200Oscillator(Device): + """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.""" + + ostart_pos = Component(EpicsSignal, "-ES-OSC:START-POS", put_complete=True, kind=Kind.config) + orange = Component(EpicsSignal, "-ES-OSC:RANGE", put_complete=True, kind=Kind.config) + etime = Component(EpicsSignal, "-ES-OSC:ETIME", put_complete=True, kind=Kind.config) + get_ready = Component(EpicsSignal, "-ES-OSC:READY.PROC", put_complete=True, kind=Kind.config) + taskStart = Component(EpicsSignal, "-ES-OSC:START", put_complete=True, kind=Kind.config) + phase = Component(EpicsSignal, "-ES-OSC:DONE", auto_monitor=True, kind=Kind.config) + + @property + def is_done(self): + return ABR_DONE == self.phase.get() + + @property + def is_ready(self): + return ABR_READY == self.phase.get() + + @property + def is_busy(self): + return ABR_BUSY == self.phase.get() + + def wait_status(self, target, timeout=60.0) -> SubscriptionStatus: + """Wait for the Aertotech IOC to reach the desired `status`. + + PARAMETERS + `status` can be any the three ABR_DONE, ABR_READY or ABR_BUSY. + + RETURNS + SubscriptionStatus : A waitable status subscribed on 'phase' updates. + """ + # Define wait until the busy flag goes down (excluding initial update) + def inStatus(*args, old_value, value, timestamp, **kwargs): + result = bool(value==target) + print(f"Old {old_value}\tNew: {value}\tResult: {result}") + return result + + # Subscribe and wait for update + status = SubscriptionStatus(self.phase, inStatus, timeout=timeout, settle_time=0.5) + return status + + + + + + + + + + + + + + # Automatically start an axis if directly invoked if __name__ == "__main__": omega = A3200Axis(prefix="X06DA-ES-DF1:OMEGA", name="omega") -- 2.49.1 From 13c6d7b8fb91a54fef9c00aeb9275650b49639e2 Mon Sep 17 00:00:00 2001 From: Unknown MX Person Date: Tue, 27 Aug 2024 17:58:33 +0200 Subject: [PATCH 07/25] Starting to move things to scans --- pxiii_bec/devices/A3200.py | 222 +++----- pxiii_bec/devices/aeroA3200.py | 58 +- pxiii_bec/scans/mx_measurements.py | 877 +++++++++++++++++++++++++++++ 3 files changed, 1007 insertions(+), 150 deletions(-) create mode 100644 pxiii_bec/scans/mx_measurements.py diff --git a/pxiii_bec/devices/A3200.py b/pxiii_bec/devices/A3200.py index 2788996..9109243 100644 --- a/pxiii_bec/devices/A3200.py +++ b/pxiii_bec/devices/A3200.py @@ -103,16 +103,12 @@ Examples """ import time import math -from beamline import BEAMLINE -from epics import PV, poll -from epics.ca import pend_io - from ophyd import Component, Device, EpicsSignal, EpicsSignalRO, Kind -from ophyd.status import Status +from ophyd.status import MoveStatus, SubscriptionStatus, DeviceStatus -class A3200Axis(PVPositioner): +from aeroA3200 import A3200Axis, A3200RasterScanner, A3200Oscillator ABR_DONE = 0 @@ -131,17 +127,6 @@ SHUTTER_OPEN = 1 FULL_PERIOD = 0 HALF_PERIOD = 1 - -def pv_wait(pv, val, timeout=10.0): - timeout = timeout + time.time() - timeisup = False - while val != pv.get() and not timeisup: - poll(0.01) - timeisup = time.time() > timeout - if timeisup: - raise RuntimeWarning("timeout waiting for %s to reach %s" % (pv.pvname, str(val))) - - CMD_NONE = 0 CMD_RASTER_SCAN_SIMPLE = 1 CMD_MEASURE_STANDARD = 2 @@ -196,9 +181,9 @@ class AerotechAbrStage(Device): gmz = Component(A3200Axis, "-ES-DF1:GMZ", kind=Kind.hinted) - _zcmd = Component(EpicsSignal, "-ES-PSO:CMD", put_complete=True, kind=Kind.omitted) - _start_command = Component(EpicsSignal, "-ES-PSO:START-TEST.PROC", put_complete=True, kind=Kind.omitted) - _stop_command = Component(EpicsSignal, "-ES-PSO:STOP-TEST.PROC", put_complete=True, kind=Kind.omitted) + scan_command = Component(EpicsSignal, "-ES-PSO:CMD", put_complete=True, kind=Kind.omitted) + start_command = Component(EpicsSignal, "-ES-PSO:START-TEST.PROC", put_complete=True, kind=Kind.omitted) + stop_command = Component(EpicsSignal, "-ES-PSO:STOP-TEST.PROC", put_complete=True, kind=Kind.omitted) _var_1 = Component(EpicsSignal, "-ES-PSO:VAR-1", put_complete=True, kind=Kind.omitted) _var_2 = Component(EpicsSignal, "-ES-PSO:VAR-2", put_complete=True, kind=Kind.omitted) @@ -216,23 +201,17 @@ class AerotechAbrStage(Device): raster_num_rows = Component(EpicsSignal, "-ES-GRD:SCAN-DONE", kind=Kind.config) - def __init__(self): + # def __init__(self): + # self.__pv_ostart = PV(f"{BEAMLINE}-ES-OSC:START-POS") + # self.__pv_orange = PV(f"{BEAMLINE}-ES-OSC:RANGE") + # self.__pv_etime = PV(f"{BEAMLINE}-ES-OSC:ETIME") + # self.__pv_get_ready = PV(f"{BEAMLINE}-ES-OSC:READY.PROC") + # self.__pv_start = PV(f"{BEAMLINE}-ES-OSC:START") + # self.__pv_phase = PV(f"{BEAMLINE}-ES-OSC:DONE") - self.__pv_ostart = PV(f"{BEAMLINE}-ES-OSC:START-POS") - self.__pv_orange = PV(f"{BEAMLINE}-ES-OSC:RANGE") - self.__pv_etime = PV(f"{BEAMLINE}-ES-OSC:ETIME") - self.__pv_get_ready = PV(f"{BEAMLINE}-ES-OSC:READY.PROC") - self.__pv_start = PV(f"{BEAMLINE}-ES-OSC:START") - self.__pv_phase = PV(f"{BEAMLINE}-ES-OSC:DONE") - - pend_io(10) + # pend_io(10) - def command(self, cmd): - self._zcmd.set(cmd).wait() - - def start_command(self): - self._start_command.set(1).wait() def measure_standard(self, start, wedge, etime, ready_rate=500.0): """measure a standard wedge from `start` to `start` + `wedge` during `etime` seconds""" @@ -555,19 +534,48 @@ class AerotechAbrStage(Device): self.start_command() + def configure(self, d: dict) -> tuple: - def configure_stills(self, frames, etime, sleep=0.0): - self._var_1.set(frames).wait() - self._var_2.set(etime).wait() - self._var_3.set(sleep).wait() + old = self.read_configuration() + + # ToDo: Check if idle before reconfiguring + + # Set the corresponding global variables + if "scan_command" in d: + self.scan_command.set(d['scan_command']).wait() + if "var_1" in d: + self._var_1.set(d['var_1']).wait() + if "var_2" in d: + self._var_2.set(d['var_2']).wait() + if "var_3" in d: + self._var_3.set(d['var_3']).wait() + if "var_4" in d: + self._var_4.set(d['var_4']).wait() + if "var_5" in d: + self._var_5.set(d['var_5']).wait() + if "var_6" in d: + self._var_6.set(d['var_6']).wait() + if "var_7" in d: + self._var_7.set(d['var_7']).wait() + if "var_8" in d: + self._var_8.set(d['var_8']).wait() + if "var_9" in d: + self._var_9.set(d['var_9']).wait() + if "var_10" in d: + self._var_10.set(d['var_10']).wait() + + new = self.read_configuration() + return old, new + + def complete(self, timeout=None) -> DeviceStatus: + """ ToDo: WTF was this device doing here? Whatever... + """ + return self.raster.complete() def reset(self): self.command(CMD_NONE) self.set_direct_mode() - def raster_2d(self): - pass - def is_ioc_ok(self): return 0 == self.status.get() @@ -742,78 +750,7 @@ class AerotechAbrStage(Device): print(" --- trying start again.") self.osc.taskStart.set(1).wait() - def velo(self, axis: int, speed: float = None) -> float: - """set's the speed on the given axis to speed""" - if AXIS_GMX == axis: - velo_pv = self.__pv_gmx_velo - min, max = 0.01, 100.0 - elif AXIS_GMY == axis: - velo_pv = self.__pv_gmy_velo - min, max = 0.01, 10.0 - elif AXIS_GMZ == axis: - velo_pv = self.__pv_gmz_velo - min, max = 0.01, 10.0 - elif AXIS_OMEGA == axis: - velo_pv = self.__pv_omega_velo - min, max = 0.01, 720.0 - else: - raise Exception("unknown axis index") - - if speed is not None: - if not (min <= speed <= max): - raise Exception(f"requested speed {speed} outside range {min} < speed < {max}") - velo_pv.put(speed) - poll(0.05) - return velo_pv.get() - - - def select_axis(self, axis): - """returns PVs and limits for the given axis - - axis: Aerotech.AXIS_{OMEGA=1, GMX=2, GMY=3, GMZ=4} - - return (val_pv, rbv_pv, velo_pv, done_pv, velomin, velomax) - """ - - if AXIS_GMX == axis: - val_pv = self.__pv_gmx_val - rbv_pv = self.__pv_gmx_rbv - velo_pv = self.__pv_gmx_velo - done_pv = self.__pv_gmx_done - min, max = 0.01, 100.0 - elif AXIS_GMY == axis: - val_pv = self.__pv_gmy_val - rbv_pv = self.__pv_gmy_rbv - velo_pv = self.__pv_gmy_velo - done_pv = self.__pv_gmy_done - min, max = 0.01, 10.0 - elif AXIS_GMZ == axis: - val_pv = self.__pv_gmz_val - rbv_pv = self.__pv_gmz_rbv - velo_pv = self.__pv_gmz_velo - done_pv = self.__pv_gmz_done - min, max = 0.01, 10.0 - elif AXIS_OMEGA == axis: - val_pv = self.__pv_omega_set - rbv_pv = self.__pv_omega_get - velo_pv = self.__pv_omega_velo - done_pv = self.__pv_omega_done - min, max = 0.01, 720.0 - else: - raise Exception("unknown axis index") - - return (val_pv, rbv_pv, velo_pv, done_pv, min, max) - - - - def rbv(self): - x = self.__pv_gmx_rbv.get() - y = self.__pv_gmy_get.get() - z = self.__pv_gmz_get.get() - omega = self.__pv_omega_get.get() - return {"gmx": x, "gmy": y, "gmz": z, "omega": omega} - - def move(self, position, wait=False, velo=None, direct=False, **kwargs) -> StatusBase: + def move(self, position, wait=False, velocity=None, relative=None, direct=False, **kwargs) -> MoveStatus: """Move omega to `angle` at `speed` deg/s and `wait` (or not). If a `speed` is specified, it will be used for the movement but the speed @@ -832,10 +769,10 @@ class AerotechAbrStage(Device): Limits: 0 - 360 `wait` [boolean] wait or not for movement to finish. """ - return self.omega.move(position, wait=wait, velo=velo, direct=direct, **kwargs) + return self.omega.move(position, wait=wait, velocity=velocity, relative=relative, direct=direct, **kwargs) def set_shutter(self, state): - if self.__pv_axes_mode.get(): + if self.axisAxesMode.get(): print("ABR is not in direct mode; cannot manipulate shutter") return False state = str(state).lower() @@ -846,39 +783,42 @@ class AerotechAbrStage(Device): state = 1 elif state == ["0", "closed"]: state = 0 - self.__pv_shutter_set.put(state, wait=True) - return state == self.__pv_shutter_get.get() + self._shutter.put(state, wait=True) + return state == self._shutter.get() if __name__ == "__main__": - import argparse - from time import sleep + aero = AerotechAbrStage(prefix="X06DA-ES-DF1", name="abr") + aero.wait_for_connection() - parser = argparse.ArgumentParser(description="aerotech client") - parser.add_argument("--stop", help="stop/reset/restart aerotech", action="store_true") - parser.add_argument("--sanitize-speeds", help="reset speeds on axis to sane defaults", action="store_true") - parser.add_argument("--measure-standard", help="measure a standard dataset", action="store") - parser.add_argument("--omega", help="move omega to...", action="store") - parser.add_argument("--gmx", help="move gmx to...", action="store") - parser.add_argument("--gmy", help="move gmy to...", action="store") - parser.add_argument("--gmz", help="move gmz to...", action="store") - args = parser.parse_args() + # import argparse + # from time import sleep - abr = Abr() - if args.stop: - abr.stop() + # parser = argparse.ArgumentParser(description="aerotech client") + # parser.add_argument("--stop", help="stop/reset/restart aerotech", action="store_true") + # parser.add_argument("--sanitize-speeds", help="reset speeds on axis to sane defaults", action="store_true") + # parser.add_argument("--measure-standard", help="measure a standard dataset", action="store") + # parser.add_argument("--omega", help="move omega to...", action="store") + # parser.add_argument("--gmx", help="move gmx to...", action="store") + # parser.add_argument("--gmy", help="move gmy to...", action="store") + # parser.add_argument("--gmz", help="move gmz to...", action="store") + # args = parser.parse_args() + + # abr = Abr() + # if args.stop: + # abr.stop() - elif args.sanitize_speeds: - abr.velo(AXIS_GMX, 50.0) - abr.velo(AXIS_GMY, 10.0) - abr.velo(AXIS_GMZ, 10.0) - abr.velo(AXIS_OMEGA, 180.0) + # elif args.sanitize_speeds: + # abr.velo(AXIS_GMX, 50.0) + # abr.velo(AXIS_GMY, 10.0) + # abr.velo(AXIS_GMZ, 10.0) + # abr.velo(AXIS_OMEGA, 180.0) - elif args.gmx: - abr.move_x(args.gmx) + # elif args.gmx: + # abr.move_x(args.gmx) - elif args.measure_standard: - start_angle, total_range, total_time = args.measure_standard.split(",") - abr.measure_standard(start_angle, total_range, total_time) + # elif args.measure_standard: + # start_angle, total_range, total_time = args.measure_standard.split(",") + # abr.measure_standard(start_angle, total_range, total_time) diff --git a/pxiii_bec/devices/aeroA3200.py b/pxiii_bec/devices/aeroA3200.py index 18ff5b7..11a2bcd 100644 --- a/pxiii_bec/devices/aeroA3200.py +++ b/pxiii_bec/devices/aeroA3200.py @@ -5,8 +5,19 @@ Created on Tue Jun 11 11:28:38 2024 @author: mohacsi_i """ -from ophyd import Component, PVPositioner, Device, EpicsSignal, EpicsSignalRO, Kind -from ophyd.status import Status +from ophyd import Component, PVPositioner, Device, Signal, EpicsSignal, EpicsSignalRO, Kind +from ophyd.status import Status, MoveStatus, SubscriptionStatus, DeviceStatus + + +ABR_DONE = 0 +ABR_READY = 1 +ABR_BUSY = 2 + +GRID_SCAN_BUSY = 0 +GRID_SCAN_DONE = 1 + +DIRECT_MODE = 0 +MEASURING_MODE = 1 class A3200Axis(PVPositioner): @@ -21,7 +32,7 @@ class A3200Axis(PVPositioner): setpoint = Component(EpicsSignal, "-SETP", kind=Kind.config) #setpoint = Component(EpicsSignal, "-VAL", kind=Kind.config) - velo = Component(EpicsSignal, "-SETV", kind=Kind.config) + velocity = Component(EpicsSignal, "-SETV", kind=Kind.config) status = Component(EpicsSignalRO, "-STAT", auto_monitor=True, kind=Kind.config) # PV to issue native relative movements on the A3200 relmove = Component(EpicsSignal, "-INCP", put_complete=True, kind=Kind.config) @@ -30,9 +41,9 @@ class A3200Axis(PVPositioner): ishomed = Component(EpicsSignal, "-AS00", kind=Kind.config) # HW status words - dshw = Component(EpicsSignalRO, "-DSHW", auto_monitor=True, kind=Kind.hinted) - ashw = Component(EpicsSignalRO, "-ASHW", auto_monitor=True, kind=Kind.hinted) - fslw = Component(EpicsSignalRO, "-FSLW", auto_monitor=True, kind=Kind.hinted) + dshw = Component(EpicsSignalRO, "-DSHW", auto_monitor=True, kind=Kind.normal) + ashw = Component(EpicsSignalRO, "-ASHW", auto_monitor=True, kind=Kind.normal) + fslw = Component(EpicsSignalRO, "-FSLW", auto_monitor=True, kind=Kind.normal) # Rock movement _rock = Component(EpicsSignal, "-ROCK", put_complete=True, kind=Kind.config) @@ -56,9 +67,12 @@ class A3200Axis(PVPositioner): self.vmax.set(vmax).wait() - def move(self, position, wait=True, velo=None, direct=False, **kwargs): + 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). + + # Check if we're in direct movement mode if self.parent is not None: if self.parent.mode != DIRECT_MODE: if direct: @@ -66,8 +80,13 @@ class A3200Axis(PVPositioner): else: raise RuntimeError("ABR is not in direct mode!") - if velo is not None: - self.velo.set(velo).wait() + # 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) @@ -299,6 +318,27 @@ class A3200RasterScanner(Device): return status + def complete(self, timeout=None) -> DeviceStatus: + if timeout is not None: + # Define wait until the busy flag goes down (excluding initial update) + timestamp_ = 0 + def isReady(*args, old_value, value, timestamp, **kwargs): + nonlocal timestamp_ + result = False if (timestamp_== 0) else bool(value==GRID_SCAN_DONE) + print(f"Old {old_value}\tNew: {value}\tResult: {result}") + timestamp_ = timestamp + return result + + # Subscribe and wait for update + status = SubscriptionStatus(self.scan_done, isReady, timeout=timeout, settle_time=0.5) + return status + status = DeviceStatus(self, settle_time=0.5) + status.set_finished() + return status + + + + diff --git a/pxiii_bec/scans/mx_measurements.py b/pxiii_bec/scans/mx_measurements.py new file mode 100644 index 0000000..5208f8f --- /dev/null +++ b/pxiii_bec/scans/mx_measurements.py @@ -0,0 +1,877 @@ + ++ 119 +− 0 +import time + +import numpy as np + +from bec_lib import bec_logger +from bec_server.scan_server.scans import AsyncFlyScanBase, ScanArgType, ScanBase + +logger = bec_logger.logger + + +CMD_NONE = 0 +CMD_RASTER_SCAN_SIMPLE = 1 +CMD_MEASURE_STANDARD = 2 +CMD_VERTICAL_LINE_SCAN = 3 +CMD_SCREENING = 4 +CMD_SUPER_FAST_OMEGA = 5 +CMD_STILL_WEDGE = 6 +CMD_STILLS = 7 +CMD_REPEAT_SINGLE_OSCILLATION = 8 +CMD_SINGLE_OSCILLATION = 9 +CMD_OLD_FASHIONED = 10 +CMD_RASTER_SCAN = 11 +CMD_JET_ROTATION = 12 +CMD_X_HELICAL = 13 +CMD_X_RUNSEQ = 14 +CMD_JUNGFRAU = 15 +CMD_MSOX = 16 +CMD_SLIT_SCAN = 17 +CMD_RASTER_SCAN_STILL = 18 +CMD_SCAN_SASTT = 19 +CMD_SCAN_SASTT_V2 = 20 +CMD_SCAN_SASTT_V3 = 21 + + +class MxFlyscanBase(AsyncFlyScanBase): + """ Base class for MX flyscans + """ + scan_report_hint = "table" + arg_input = {} + arg_bundle_size = {"bundle": len(arg_input), "min": None, "max": None} + + def __init__(self, *args, parameter: dict = None, **kwargs): + """ Just set num_pos=0 to avoid hanging + """ + self.num_pos = 0 + super().__init__(parameter=parameter, **kwargs) + + def stage(self): + """ ToDo: Sot sure if we should call super() here as it'd stage the whole beamline""" + return super().stage() + + def scan_core(self): + """ The actual scan logic comes here. + """ + self.pointID = 0 + # Kick off the run + yield from self.stubs.command("abr", "start_command.set", 1) + + logger.info("Measurement launched on the ABR stage...") + # ToDo: Wait for scan to finish + + def cleanup(self): + """Set scan progress to 1 to finish the scan""" + self.num_pos = 1 + return super().cleanup() + + + + + + +class MeasureStandardWedge(MxFlyscanBase): + """ Measure a standard wedge scan + """ + scan_name = "standard_wedge" + required_kwargs = ["start", "range"] + + def __init__(self, *args, parameter: dict = None, **kwargs): + """ Standard measurement scan + + Measure a standard wedge from `start` to `start` + `wedge` during + `etime` on the Omega axis. + + The scan itself is executed by the scan service running on the Aerotech + controller. Ophyd just configures, launches it and waits for completion. + + Example + ------- + >>> scans.standard_wedge(start=42, range=10, scan_time=20) + + Parameters + ---------- + start : (float, float) + Scan start position of the axis. + range : (float, float) + Scan range of the axis. + scan_time : (float) + Total scan time for the movement [s]. + ready_rate : float, optional + No clue what is this... (default=500) + """ + super().__init__(parameter=parameter, **kwargs) + self.axis = ['omega'] + + self.scan_command = CMD_MEASURE_STANDARD + self.scan_start = self.caller_kwargs.get("start") + self.scan_range = self.caller_kwargs.get("range") + self.scan_scan_time = float(self.caller_kwargs.get("scan_time", 5)) + self.scan_ready_rate = float(self.caller_kwargs.get("ready_rate", 500)) + + def pre_scan(self): + # ToDo: Move roughly to start position + + # Set the global variables + yield from self.stubs.command("abr", "scan_command.set", self.scan_command) + yield from self.stubs.command("abr", "_var_1.set", self.scan_start) + yield from self.stubs.command("abr", "_var_2.set", self.scan_range) + yield from self.stubs.command("abr", "_var_3.set", self.scan_scan_time) + yield from self.stubs.command("abr", "_var_4.set", self.scan_ready_rate) + + # Call super + yield from self.stubs.pre_scan() + + +class MeasureScanSlits(MxFlyscanBase): + """ Measure a slit scan + """ + scan_name = "slit_scan" + required_kwargs = ["start", "range"] + + def __init__(self, *args, parameter: dict = None, **kwargs): + """ Slit scan + + Measure a standard slit scan. + + The scan itself is executed by the scan service running on the Aerotech + controller. Ophyd just configures, launches it and waits for completion. + + Example + ------- + >>> scans.slit_scan(start=42, range=10, scan_time=20) + + Parameters + ---------- + delta_x : (float, float) + Scan range with slit in x. + delta_y : (float, float) + Scan range with slit in y. + velocity : (float) + Scan velocity [mm/s]]. + """ + super().__init__(parameter=parameter, **kwargs) + self.axis = [] + + self.scan_command = CMD_SLIT_SCAN + self.scan_delta_x = self.caller_kwargs.get("delta_x") + self.scan_delta_y = self.caller_kwargs.get("delta_y") + self.scan_velocity = self.caller_kwargs.get("velocity") + + def pre_scan(self): + # ToDo: Move roughly to start position + + # Set the global variables + yield from self.stubs.command("abr", "scan_command.set", self.scan_command) + yield from self.stubs.command("abr", "_var_1.set", self.scan_delta_x) + yield from self.stubs.command("abr", "_var_2.set", self.scan_delta_y) + yield from self.stubs.command("abr", "_var_3.set", self.scan_velocity) + + # Call super + yield from self.stubs.pre_scan() + + +class MeasureRasterSimple(MxFlyscanBase): + """ Measure a grid scan + """ + scan_name = "raster_simple" + required_kwargs = ["exp_time", "cell_width", "cell_height", "ncols", "nrows"] + + def __init__(self, *args, parameter: dict = None, **kwargs): + """ Simnple raster scan + + Measure a simplified raster scan, assuming the goniometer is currently + at the CENTER of TOP-LEFT CELL. + + The scan itself is executed by the scan service running on the Aerotech + controller. Ophyd just configures, launches it and waits for completion. + + Example + ------- + >>> scans.raster_simple(exp_time=0.5, cell_width=0.2, cell_height=0.2, ncols=10, nrows=10) + + Parameters + ---------- + exp_time : (float) + Exposure time per point [s]. + cell_width : (float) + Scan step size [mm]. + cell_height : (float) + Scan step size [mm]. + ncols : (int) + Number of scan steps. + nrows : (int) + Number of scan steps. + """ + super().__init__(parameter=parameter, **kwargs) + self.axis = [] + + self.scan_command = CMD_RASTER_SCAN_SIMPLE + self.scan_exp_time = self.caller_kwargs.get("exp_time") + self.scan_stepsize_x = self.caller_kwargs.get("cell_width") + self.scan_stepsize_y = self.caller_kwargs.get("cell_height") + self.scan_stepnum_x = int(self.caller_kwargs.get("ncols")) + self.scan_stepnum_y = int(self.caller_kwargs.get("nrows")) + + def pre_scan(self): + # ToDo: Move roughly to start position + + # Set the global variables + yield from self.stubs.command("abr", "raster_scan_done.set", 0) + yield from self.stubs.command("abr", "scan_command.set", self.scan_command) + yield from self.stubs.command("abr", "_var_1.set", self.scan_exp_time) + yield from self.stubs.command("abr", "_var_2.set", self.scan_stepsize_x) + yield from self.stubs.command("abr", "_var_3.set", self.scan_stepsize_y) + yield from self.stubs.command("abr", "_var_4.set", self.scan_stepnum_x) + yield from self.stubs.command("abr", "_var_5.set", self.scan_stepnum_y) + yield from self.stubs.command("abr", "_var_6.set", 0) + yield from self.stubs.command("abr", "_var_7.set", 0) + yield from self.stubs.command("abr", "_var_8.set", 0) + + # ToDo: This line was left out + #self.raster._raster_num_rows = nrows + + # Call super + yield from self.stubs.pre_scan() + + +class MeasureSASTT(MxFlyscanBase): + """ Measure a small angle scanning tensor tomography scan + """ + scan_name = "measure_sastt" + required_kwargs = ["exp_time", "cell_width", "cell_height", "ncols", "nrows"] + + def __init__(self, *args, parameter: dict = None, **kwargs): + """ Standard measurement scan + + Measure a simplified grid scan, assuming the goniometer is currently + at the CENTER of the required grid. + + The scan itself is executed by the scan service running on the Aerotech + controller. Ophyd just configures, launches it and waits for completion. + + Example + ------- + >>> scans.measure_sastt(exp_time=1.0, cell_width=0.01, cell_height=0.01, ncols=100, nrows=100) + + Parameters + ---------- + exp_time : (float) + Exposure time per point [s]. + cell_width : (float) + Scan step size [mm]. + cell_height : (float) + Scan step size [mm]. + ncols : (int) + Number of scan steps. + nrows : (int) + Number of scan steps. + odd_tweak : (float) + Distance to be added before the open shutter command [mm]. + Used only in scan version=3. Should be small (default: 0)! + even_tweak : (float) + Distance to be added before the open shutter command [mm]. + Used only in scan version=3. Should be small (default: 0)! + version : (int) + Scanning mode for tensor tomo. + 1 = original snake scan, single PSO window + 2 = scan always from LEFT---RIGHT + 3 = snake scan alternating PSO window for even/odd rows + """ + super().__init__(parameter=parameter, **kwargs) + self.axis = [] + + self.scan_version = int(self.caller_kwargs.get("version", 1)) + if self.scan_version==1: + self.scan_command = CMD_SCAN_SASTT + if self.scan_version==2: + self.scan_command = CMD_SCAN_SASTT_V2 + if self.scan_version==3: + self.scan_command = CMD_SCAN_SASTT_V3 + else: + raise RuntimeError(f"Non existing SAS-TT scan mode: {self.scan_version}") + + self.scan_exp_time = self.caller_kwargs.get("exp_time") + self.scan_stepsize_x = self.caller_kwargs.get("cell_width") + self.scan_stepsize_y = self.caller_kwargs.get("cell_height") + self.scan_stepnum_x = int(self.caller_kwargs.get("ncols")) + self.scan_stepnum_y = int(self.caller_kwargs.get("nrows")) + self.scan_even_tweak = self.caller_kwargs.get("even_tweak", 0) + self.scan_odd_tweak = self.caller_kwargs.get("off_tweak", 0) + + def pre_scan(self): + # ToDo: Move roughly to start position + + # Set the global variables + yield from self.stubs.command("abr", "raster_scan_done.set", 0) + yield from self.stubs.command("abr", "scan_command.set", self.scan_command) + yield from self.stubs.command("abr", "_var_1.set", self.scan_exp_time) + yield from self.stubs.command("abr", "_var_2.set", self.scan_stepsize_x) + yield from self.stubs.command("abr", "_var_3.set", self.scan_stepsize_y) + yield from self.stubs.command("abr", "_var_4.set", self.scan_stepnum_x) + yield from self.stubs.command("abr", "_var_5.set", self.scan_stepnum_y) + yield from self.stubs.command("abr", "_var_6.set", self.scan_even_tweak) + yield from self.stubs.command("abr", "_var_7.set", self.scan_odd_tweak) + yield from self.stubs.command("abr", "_var_8.set", 0) + + # ToDo: This line was left out + # self.raster._raster_num_rows = nrows + + # Call super + yield from self.stubs.pre_scan() + + +class MeasureVerticalLine(MxFlyscanBase): + """ Measure a vertical line using the GMY motor + + Simple fly scan that performs a single scan vertically. + The line is nummCells * cellHeight long and the exposureTime is per cell. + + The scan itself is executed by the scan service running on the Aerotech + controller. Ophyd just configures, launches it and waits for completion. + + Example + ------- + >>> scans.measure_vertical_line(start=42, range=10, scan_time=20) + + Parameters + ---------- + cell_height : (float) + Step size [mm]. + nrows : (int) + Scan range of the axis. + exp_time : (float) + Exposure time per cell [s] + """ + scan_name = "measure_vertical_line" + required_kwargs = ["exp_time", "cell_height", "nrows"] + + def __init__(self, *args, parameter: dict = None, **kwargs): + + super().__init__(parameter=parameter, **kwargs) + self.axis = ['gmy'] + + self.scan_command = CMD_VERTICAL_LINE_SCAN + self.scan_exp_time = self.caller_kwargs.get("exp_time") + self.scan_stepsize_y = self.caller_kwargs.get("cell_height") + self.scan_stepnum_y = int(self.caller_kwargs.get("nrows")) + + def pre_scan(self): + # ToDo: Move roughly to start position + + # Set the global variables + yield from self.stubs.command("abr", "scan_command.set", self.scan_command) + yield from self.stubs.command("abr", "_var_1.set", self.scan_stepsize_y) + yield from self.stubs.command("abr", "_var_2.set", self.scan_stepnum_y) + yield from self.stubs.command("abr", "_var_3.set", self.scan_exp_time) + + # Call super + yield from self.stubs.pre_scan() + + +class MeasureScreening(MxFlyscanBase): + """ Measure a sample screening program + + The scan itself is executed by the scan service running on the Aerotech + controller. Ophyd just configures, launches it and waits for completion. + + Example + ------- + >>> scans.measure_screening(start=42, range=10, scan_time=20, degrees=180, frames=1800) + + Parameters + ---------- + start : (float) + Scan start position of the axis. + range : (float) + Scan range of the axis. + scan_time : (float) + Total scan time for the movement [s]. + degrees : (???) + frames : (???) + delta : (???) + (default: 0.5). + + """ + scan_name = "measure_screening" + required_kwargs = ["start", "range", "scan_time", "degrees", "frames"] + + def __init__(self, *args, parameter: dict = None, **kwargs): + + super().__init__(parameter=parameter, **kwargs) + self.axis = ['gmy'] + + self.scan_command = CMD_SCREENING + self.scan_start = self.caller_kwargs.get("start") + self.scan_range = self.caller_kwargs.get("range") + self.scan_scan_time = self.caller_kwargs.get("scan_time") + self.scan_degrees = self.caller_kwargs.get("degrees") + self.scan_frames = self.caller_kwargs.get("frames") + self.scan_delta = self.caller_kwargs.get("delta", 0.5) + + def pre_scan(self): + # ToDo: Move roughly to start position + + # Set the global variables + yield from self.stubs.command("abr", "scan_command.set", self.scan_command) + yield from self.stubs.command("abr", "_var_1.set", self.scan_start) + yield from self.stubs.command("abr", "_var_2.set", self.scan_range) + yield from self.stubs.command("abr", "_var_3.set", self.scan_scan_time) + yield from self.stubs.command("abr", "_var_4.set", self.scan_degrees) + yield from self.stubs.command("abr", "_var_5.set", self.scan_frames) + yield from self.stubs.command("abr", "_var_6.set", self.scan_delta) + + + # Call super + yield from self.stubs.pre_scan() + + + + + +class MeasureFastOmega(MxFlyscanBase): + """ Measure a fast omega scan + + MEasures a fast rotational scan with the rotation stage (omega). + + The scan itself is executed by the scan service running on the Aerotech + controller. Ophyd just configures, launches it and waits for completion. + + Example + ------- + >>> scans.measure_fast_omega(start=42, range=180, scan_time=20) + + Parameters + ---------- + start : (float) + Scan start position of the axis. + range : (float) + Scan range of the axis. + scan_time : (float) + Total scan time for the movement [s]. + rate : (???) + (default: 200). + """ + scan_name = "measure_fast_omega" + required_kwargs = ["start", "range", "exp_time"] + + def __init__(self, *args, parameter: dict = None, **kwargs): + + super().__init__(parameter=parameter, **kwargs) + self.axis = ['omega'] + + self.scan_command = CMD_SUPER_FAST_OMEGA + self.scan_start = self.caller_kwargs.get("start") + self.scan_range = self.caller_kwargs.get("range") + self.scan_scan_time = self.caller_kwargs.get("scan_time") + self.scan_rate = self.caller_kwargs.get("rate", 200) + + def pre_scan(self): + # ToDo: Move roughly to start position + + # Set the global variables + yield from self.stubs.command("abr", "scan_command.set", self.scan_command) + yield from self.stubs.command("abr", "_var_1.set", self.scan_start) + yield from self.stubs.command("abr", "_var_2.set", self.scan_range) + yield from self.stubs.command("abr", "_var_3.set", self.scan_scan_time) + yield from self.stubs.command("abr", "_var_4.set", self.scan_rate) + + # Call super + yield from self.stubs.pre_scan() + + + + +class MeasureStillWedge(MxFlyscanBase): + """ Measure a still wedge scan + + The scan itself is executed by the scan service running on the Aerotech + controller. Ophyd just configures, launches it and waits for completion. + + Example + ------- + >>> scans.measure_still_wedge(start=42, range=90, scan_time=20, oscrange=15) + + Parameters + ---------- + start : (float) + Scan start position of the axis. + range : (float) + Scan range of the axis. + scan_time : (float) + Total scan time for the movement [s]. + oscrange : (float) + sleep_after_shutter_close : (float) + [s] (default: 0.01). + """ + scan_name = "measure_still_wedge" + required_kwargs = ["start", "range", "exp_time", "oscrange"] + + def __init__(self, *args, parameter: dict = None, **kwargs): + + super().__init__(parameter=parameter, **kwargs) + self.axis = ['omega'] + + self.scan_command = CMD_STILL_WEDGE + self.scan_start = self.caller_kwargs.get("start") + self.scan_range = self.caller_kwargs.get("range") + self.scan_scan_time = self.caller_kwargs.get("scan_time") + self.scan_oscrange = self.caller_kwargs.get("oscrange") + self.scan_shutter_sleep = self.caller_kwargs.get("sleep_after_shutter_close", 0.01) + + def pre_scan(self): + # ToDo: Move roughly to start position + + # Set the global variables + yield from self.stubs.command("abr", "scan_command.set", self.scan_command) + yield from self.stubs.command("abr", "_var_1.set", self.scan_start) + yield from self.stubs.command("abr", "_var_2.set", self.scan_range) + yield from self.stubs.command("abr", "_var_3.set", self.scan_scan_time) + yield from self.stubs.command("abr", "_var_4.set", self.scan_oscrange) + yield from self.stubs.command("abr", "_var_5.set", self.scan_shutter_sleep) + + # Call super + yield from self.stubs.pre_scan() + + + + + + +class MeasureStills(MxFlyscanBase): + """ Measure a still image sequence + + Measures a series of still images without any motor movement. + IMPORTANT: use idle_time=0.0 to prevent shutter open/close + + The scan itself is executed by the scan service running on the Aerotech + controller. Ophyd just configures, launches it and waits for completion. + + Example + ------- + >>> scans.measure_stils(nframes=100, exp_time=0.1, idle_time=30) + + Parameters + ---------- + nframes : (int) + Total number of frames to be recorded. + exposure_time : (float) + Exposure time of each frame [s]. + idle_time : (float) + Sleep time between the frames [s]. + """ + scan_name = "measure_stils" + required_kwargs = ["exp_time", "nframes"] + + def __init__(self, *args, parameter: dict = None, **kwargs): + + super().__init__(parameter=parameter, **kwargs) + self.axis = [] + + self.scan_command = CMD_STILLS + self.scan_exp_time = self.caller_kwargs.get("exp_time") + self.scan_num_frames = int(self.caller_kwargs.get("nframes")) + self.scan_idle_time = self.caller_kwargs.get("idle_time") + + def pre_scan(self): + # ToDo: Move roughly to start position + + # Set the global variables + yield from self.stubs.command("abr", "scan_command.set", self.scan_command) + yield from self.stubs.command("abr", "_var_1.set", self.scan_num_frames) + yield from self.stubs.command("abr", "_var_2.set", self.scan_exp_time) + yield from self.stubs.command("abr", "_var_3.set", self.scan_idle_time) + + # Call super + yield from self.stubs.pre_scan() + + + + + + +class MeasureSingleOscillation(MxFlyscanBase): + """ Measure a single oscillation + + The scan itself is executed by the scan service running on the Aerotech + controller. Ophyd just configures, launches it and waits for completion. + + Example + ------- + >>> scans.measure_single_osc(start=42, range=180, scan_time=20) + + Parameters + ---------- + start : (float) + Scan start position of the axis. + range : (float) + Oscillation range of the axis. + scan_time : (float) + Total scan time for the movement [s]. + delta : (???) + (default: 0). + settle : (???) + (default: 0.5). + """ + scan_name = "measure_single_osc" + required_kwargs = ["start", "range", "scan_time"] + + def __init__(self, *args, parameter: dict = None, **kwargs): + + super().__init__(parameter=parameter, **kwargs) + self.axis = ['omega'] + + self.scan_command = CMD_SINGLE_OSCILLATION + self.scan_start = self.caller_kwargs.get("start") + self.scan_range = self.caller_kwargs.get("range") + self.scan_scan_time = self.caller_kwargs.get("scan_time") + self.scan_delta = self.caller_kwargs.get("delta", 0) + self.scan_settle = self.caller_kwargs.get("settle", 0.5) + + def pre_scan(self): + # ToDo: Move roughly to start position + + # Set the global variables + yield from self.stubs.command("abr", "scan_command.set", self.scan_command) + yield from self.stubs.command("abr", "_var_1.set", self.scan_start) + yield from self.stubs.command("abr", "_var_2.set", self.scan_range) + yield from self.stubs.command("abr", "_var_3.set", self.scan_scan_time) + yield from self.stubs.command("abr", "_var_4.set", self.scan_delta) + yield from self.stubs.command("abr", "_var_5.set", self.scan_settle) + + # Call super + yield from self.stubs.pre_scan() + + + + + +class MeasureRepeatedOscillation(MxFlyscanBase): + """ Measure oscillation repeatedly + + The scan itself is executed by the scan service running on the Aerotech + controller. Ophyd just configures, launches it and waits for completion. + + Example + ------- + >>> scans.measure_multi_osc(start=42, range=180, scan_time=20) + + Parameters + ---------- + nframes : (int) + ??? + scan_time : (float) + Total scan time for the movement [s]. + range : (float) + Oscillation range of the axis. + settle : (???) + (default: 0). + delta : (???) + (default: 0.5). + + """ + scan_name = "measure_multi_osc" + required_kwargs = ["start", "range", "scan_time"] + + def __init__(self, *args, parameter: dict = None, **kwargs): + + super().__init__(parameter=parameter, **kwargs) + self.axis = ['omega'] + + self.scan_command = CMD_REPEAT_SINGLE_OSCILLATION + self.scan_num_frames = self.caller_kwargs.get("nframes") + self.scan_range = self.caller_kwargs.get("range") + self.scan_scan_time = self.caller_kwargs.get("scan_time") + self.scan_sleep = self.caller_kwargs.get("sleep", 0) + self.scan_delta = self.caller_kwargs.get("delta", 0.5) + + def pre_scan(self): + # ToDo: Move roughly to start position + + # Configure the global variables + d = {"scan_command" : self.scan_command, + "var_1" : self.scan_num_frames, + "var_2" : self.scan_scan_time, + "var_3" : self.scan_range, + "var_4" : self.scan_sleep, + "var_5" : self.scan_delta, + } + yield from self.stubs.send_rpc_and_wait("abr", "configure", d) + + # Call super + yield from self.stubs.pre_scan() + + + + + +class MeasureMSOX(MxFlyscanBase): + """ Standard MSOX scan + + The scan itself is executed by the scan service running on the Aerotech + controller. Ophyd just configures, launches it and waits for completion. + + Example + ------- + >>> scans.measure_msox(start=42, range=10, exp_time=0.1) + + Parameters + ---------- + start : (float, float) + Scan start position of the axis. + range : (float, float) + Scan range of the axis. + exp_time : (float) + Exposure time for each point [s]. + num_datasets : int + ??? + rest_time : float + ??? + """ + scan_name = "measure_msox" + required_kwargs = ["start", "range", "exp_time"] + + def __init__(self, *args, parameter: dict = None, **kwargs): + + super().__init__(parameter=parameter, **kwargs) + self.axis = ['omega'] + + self.scan_command = CMD_MSOX + self.scan_start = self.caller_kwargs.get("start") + self.scan_range = self.caller_kwargs.get("range") + self.scan_exp_time = self.caller_kwargs.get("exp_time") + self.scan_num_datasets = int(self.caller_kwargs.get("num_datasets", 1)) + self.scan_rest_time = self.caller_kwargs.get("rest_time", 0) + + + def pre_scan(self): + # ToDo: Move roughly to start position + + # Reset the scan engine + yield from self.stubs.send_rpc_and_wait("abr", "reset") + + # Configure the global variables + d = {"scan_command" : self.scan_command, + "var_1" : self.scan_start, + "var_2" : self.scan_range, + "var_3" : self.scan_exp_time, + "var_4" : self.scan_num_datasets, + "var_5" : self.scan_rest_time, + } + yield from self.stubs.send_rpc_and_wait("abr", "configure", d) + + # Call super + yield from self.stubs.pre_scan() + + + + + + + +class MeasureGrid(MxFlyscanBase): + """ General grid scan + + Note: This was probably never used in its current form, because the + code had an undefined variable 'self.position' + + The scan itself is executed by the scan service running on the Aerotech + controller. Ophyd just configures, launches it and waits for completion. + + Example + ------- + >>> scans.measure_msox(start=42, range=10, exp_time=0.1) + + Parameters + ---------- + positions : 2D-array + Scan position of each axis, i.e. (N, 3) shaped array. + ncols : int + Nmber of columns. + angle: + ??? + exp_time : (float) + Exposure time for each point [s]. + """ + scan_name = "measure_raster" + required_kwargs = ["positions", "ncols", "angle", "exp_time"] + + def __init__(self, *args, parameter: dict = None, **kwargs): + + super().__init__(parameter=parameter, **kwargs) + self.axis = ['omega'] + + self.scan_command = CMD_RASTER_SCAN + self.scan_positions = self.caller_kwargs.get("positions") + self.scan_stepnum_x = int(self.caller_kwargs.get("ncols")) + self.scan_stepnum_y = self.scan_positions / self.scan_stepnum_x + self.scan_angle = self.caller_kwargs.get("angle") + self.scan_exp_time = self.caller_kwargs.get("exp_time") + + if len(self.scan_positions)==1: + raise RuntimeWarning("Raster scan with one cell makes no sense") + + # Good practice: new members only in __init__ + self.scan_ystep = 0.010 + self.scan_zstep = 0.010 + self.scan_gmy_step = 0.010 + + + def prepare_positions(self): + + # Calculate step sizes + pos_start = self.scan_positions[0] # first cell on first row + pos_col_2nd = self.scan_positions[1] # second cell on first row + pos_col_end = self.scan_positions[self.scan_stepnum_x-1] # last cell on first row + + self.scan_xstep = pos_col_2nd[0] - pos_start[0] + half_cell = abs(self.scan_xstep) / 2.0 + self.scan_start_x = pos_start[0] - half_cell + self.scan_end_x = pos_col_end[0] + half_cell + + if self.scan_stepnum_y > 1: + pos_row_2nd = self.scan_positions[self.scan_stepnum_x] # first cell on second row + self.scan_ystep = pos_row_2nd[1] - pos_start[1] + self.scan_zstep = pos_row_2nd[2] - pos_start[2] + self.scan_gmy_step = np.linalg.norm([self.scan_ystep, self.scan_zstep]) + + # ToDo : This was left out + # self._raster_starting_x = start_x + # self._raster_starting_y = y1 + # self._raster_starting_z = z1 + # self._raster_step_y = ystep + # self._raster_step_z = zstep + # self._raster_current_row = 0 + # self._raster_num_rows = rows + + + + + def pre_scan(self): + # ToDo: Move roughly to start position + + # Reset the scan engine + yield from self.stubs.send_rpc_and_wait("abr", "reset") + + # Configure the global variables + d = {"scan_command" : self.scan_command, + "var_1" : self.omega.position, + "var_2" : self.scan_start_x, + "var_3" : self.scan_end_x, + "var_4" : self.scan_stepnum_x, + "var_5" : self.scan_stepnum_y, + "var_6" : self.scan_angle, + "var_7" : self.scan_exp_time, + "var_8" : HALF_PERIOD, + "var_9" : self._gmx_offset.get, + "var_10" : self.scan_gmy_step, + } + yield from self.stubs.send_rpc_and_wait("abr", "configure", d) + + # Call super + yield from self.stubs.pre_scan() + + + -- 2.49.1 From 602317faa8875756cc90858d2c8fa371f9c8132e Mon Sep 17 00:00:00 2001 From: Unknown MX Person Date: Wed, 28 Aug 2024 13:23:06 +0200 Subject: [PATCH 08/25] A3200 starts to get ready for scanning --- pxiii_bec/devices/A3200.py | 517 ++------------------ pxiii_bec/devices/aeroA3200.py | 9 +- pxiii_bec/scans/mx_measurements.py | 746 ++++++++++++++++++----------- 3 files changed, 519 insertions(+), 753 deletions(-) diff --git a/pxiii_bec/devices/A3200.py b/pxiii_bec/devices/A3200.py index 9109243..d91d4c9 100644 --- a/pxiii_bec/devices/A3200.py +++ b/pxiii_bec/devices/A3200.py @@ -4,40 +4,27 @@ This module provides an object to control the Aerotech Abr rotational stage. -The following constants are defined. - -Measurement phases: - - Aerotech.ABR_DONE - Aerotech.ABR_READY - Aerotech.ABR_BUSY - -Axis mode - - Aerotech.DIRECT_MODE - Aerotech.MEASURING_MODE - -Shutter - - Aerotech.SHUTTER_OPEN - Aerotech.SHUTTER_CLOSE - Methods in the Abr class ======================== +Standard bluesky interface: + AerotechAbrStage.configure(d={...}) + AerotechAbrStage.kickoff() + AerotechAbrStage.stop() +Additional bluesky functionality: + + + + + Aerotech.is_homed() Aerotech.do_homing(wait=True) Aerotech.get_ready(ostart=None, orange=None, etime=None, wait=True) Aerotech.is_done() Aerotech.is_ready() Aerotech.is_busy() -Aerotech.stop() Aerotech.start_exposure() Aerotech.wait_status(status) -Aerotech.set_mode(, value) -Aerotech.get_mode() -Aerotech.set_direct_mode() -Aerotech.set_measuring_mode() Aerotech.move(angle, wait=False, speed=None) Aerotech.set_shutter(state) @@ -45,48 +32,38 @@ Attribute Access in Abr class ============================= The Abr class overwrites the getattr and setattr methods to provide a pythonic -way of controlling the Abr. +way of controlling the rootation stage of the Abr. The following properties are implemented: -velo - +velocity Sets the velocity of rotation: ``-ES-DF1:ROTX-SETV`` omega - Move the omega angle without any wait: ``-ES-DF1:ROTX-VAL`` -exposure_time - +exp_time Sets the PV for the measurement's exposure time: ``-ES-OSC:ETIME`` start_angle - Sets the PV for the measurement's starting angle: ``-ES-OSC:START-POS`` oscillation_angle - Sets the PV for the measurement's oscillation angle: ``-ES-OSC:RANGE`` shutter - Controls the shutter: ``-ES-PH1:SET`` and ``-ES-PH1:GET`` -mode - - Controls the axis mode: ``-ES-DF1:AXES-MODE`` - measurement_state + Returns the PV for the measurement state: ``-ES-OSC:DONE`` - Controls the PV for the measurement state: ``-ES-OSC:DONE`` - +axis_mode + Returns the axis mode: ``-ES-DF1:AXES-MODE`` Examples ======== - import Aerotech - abr = Aerotech.Abr() + abr = AerotechAbrStage(prefix="X06DA-ES-DF1", name="abr") # move omega to 270.0 degrees abr.omega = 270.0 @@ -95,7 +72,7 @@ Examples abr.move(180, wait=True) # move omega to 3000 degrees at 360 degrees/s and wait for movement to finish - abr.move(3000, speed=360, wait=True) + abr.move(3000, velocity=360, wait=True) # stop any movement abr.stop() # this function only returns after the STATUS is back to OK @@ -198,341 +175,13 @@ class AerotechAbrStage(Device): # A few PVs still needed from grid raster_scan_done = Component(EpicsSignal, "-ES-GRD:SCAN-DONE", kind=Kind.config) - raster_num_rows = Component(EpicsSignal, "-ES-GRD:SCAN-DONE", kind=Kind.config) - - - # def __init__(self): - # self.__pv_ostart = PV(f"{BEAMLINE}-ES-OSC:START-POS") - # self.__pv_orange = PV(f"{BEAMLINE}-ES-OSC:RANGE") - # self.__pv_etime = PV(f"{BEAMLINE}-ES-OSC:ETIME") - # self.__pv_get_ready = PV(f"{BEAMLINE}-ES-OSC:READY.PROC") - # self.__pv_start = PV(f"{BEAMLINE}-ES-OSC:START") - # self.__pv_phase = PV(f"{BEAMLINE}-ES-OSC:DONE") - - # pend_io(10) - - - - def measure_standard(self, start, wedge, etime, ready_rate=500.0): - """measure a standard wedge from `start` to `start` + `wedge` during `etime` seconds""" - self.command(CMD_MEASURE_STANDARD) - self._var_1.set(start).wait() - self._var_2.set(wedge).wait() - self._var_3.set(etime).wait() - self._var_4.set(ready_rate).wait() - self.start_command() - - def measure_scan_slits(self, delta_x, delta_y, velocity): - """measure a standard wedge from `start` to `start` + `wedge` during `etime` seconds""" - self.command(CMD_SLIT_SCAN) - self._var_1.set(delta_x).wait() - self._var_2.set(delta_y).wait() - self._var_3.set(velocity).wait() - self.start_command() - - def measure_raster_simple(self, etime, cell_width, cell_height, ncols, nrows): - """measures the specified grid assuming the goniometer is currently at the CENTER of TOP-LEFT CELL - - etime: a float in seconds - cell_width: a float in mm - cell_height: a float in mm - ncols: an int - nrows: an int - - """ - # self.reset() - self.command(CMD_RASTER_SCAN_SIMPLE) - self.raster.scan_done.set(0).wait() # make SCAN DONE go into busy state - self._var_1.set(etime).wait() - self._var_2.set(cell_width).wait() - self._var_3.set(cell_height).wait() - self._var_4.set(int(ncols)).wait() - self._var_5.set(int(nrows)).wait() - self._var_6.set(0).wait() - self._var_7.set(0).wait() - self._var_8.set(0).wait() - self.raster._raster_num_rows = nrows - self.start_command() - - def measure_sastt(self, etime, cell_width, cell_height, ncols, nrows, even_tweak=0.0, odd_tweak=0.0, version=1): - """measures the specified grid assuming the goniometer is currently at the CENTER of the required grid - - etime: a float in seconds - cell_width: a float in mm - cell_height: a float in mm - ncols: an int - nrows: an int - - odd_tweak, even_tweak: float, a distance to be added to the start of - the open shutter command. used on in the scan mode - version=3 - - - these values can be either positive/negative - - they should be small, i.e. just to tweak a trriggering event - by minor amounts - version: an int (1, 2, 3) - 1 = original snake scan, single PSO window - 2 = scan always from LEFT---RIGHT - 3 = snake scan alternating PSO window for even/odd rows - - """ - # self.reset() - if version == 1: - self.command(CMD_SCAN_SASTT) - elif version == 2: - self.command(CMD_SCAN_SASTT_V2) - elif version == 3: - self.command(CMD_SCAN_SASTT_V3) - else: - raise RuntimeError("non existing SAS-TT scan mode") - - self.raster.scan_done.set(0).wait() # make SCAN DONE go into busy state - self._var_1.set(etime).wait() - self._var_2.set(cell_width).wait() - self._var_3.set(cell_height).wait() - self._var_4.set(int(ncols)).wait() - self._var_5.set(int(nrows)).wait() - self._var_6.set(even_tweak).wait() - self._var_7.set(odd_tweak).wait() - self._var_8.set(0).wait() - self.raster._raster_num_rows = nrows - self.start_command() - - def measure_vertical_line(self, cellHeight, numCells, exposureTime): - """measure a vertical line using the GMY motor - - The line is nummCells * cellHeight long and the exposureTime is per cell. - - `cellHeight` in mm - `numCells` an integer - `exposureTime` in seconds / per cellHeight - """ - self.command(CMD_VERTICAL_LINE_SCAN) - self._var_1.set(cellHeight).wait() - self._var_2.set(numCells).wait() - self._var_3.set(exposureTime).wait() - self.start_command() - - def measure_screening(self, start, oscangle, etime, degrees, frames, delta=0.5): - self.command(CMD_SCREENING) - self._var_1.set(start).wait() - self._var_2.set(oscangle).wait() - self._var_3.set(etime).wait() - self._var_4.set(degrees).wait() - self._var_5.set(frames).wait() - self._var_6.set(delta).wait() - self.start_command() - - def measure_fast_omega(self, start, wedge, etime, rate=200.0): - self.command(CMD_SUPER_FAST_OMEGA) - self._var_1.set(start).wait() - self._var_2.set(wedge).wait() - self._var_3.set(etime).wait() - self._var_4.set(rate).wait() - self.start_command() - - def measure_still_wedge(self, start, wedge, etime, oscangle, sleep_after_shutter_close=0.010): - self.command(CMD_STILL_WEDGE) - self._var_1.set(start).wait() - self._var_2.set(wedge).wait() - self._var_3.set(etime).wait() - self._var_4.set(oscangle).wait() - self._var_5.set(sleep_after_shutter_close).wait() - self.start_command() - - def measure_stills(self, number_of_frames, exposure_time, idle=0.0): - """collect still images, i.e. no omega rotation - - number_of_frames => an integer - exposure_time => a float - idle => sleep in between each exposure - - IMPORTANT: use idle=0.0 to prevent shutter open/close - - """ - self.command(CMD_STILLS) - self._var_1.set(number_of_frames).wait() - self._var_2.set(exposure_time).wait() - self._var_3.set(idle).wait() - self.start_command() - - def measure_repeat_singles(self, frames, etime, oscangle, sleep=0.0, delta=0.5): - self.command(CMD_REPEAT_SINGLE_OSCILLATION) - self._var_1.set(frames).wait() - self._var_2.set(etime).wait() - self._var_3.set(oscangle).wait() - self._var_4.set(sleep).wait() - self._var_5.set(delta).wait() - self.start_command() - - def measure_single(self, start_angle, oscillation_angle, exposure_time, delta=0.0, settle=0.0): - self.command(CMD_SINGLE_OSCILLATION) - self._var_1.set(start_angle).wait() - self._var_2.set(oscillation_angle).wait() - self._var_3.set(exposure_time).wait() - self._var_4.set(delta).wait() - self._var_5.set(settle).wait() - self.start_command() - - def measure_raster(self, positions, columns, angle, etime): - """raster via generic experiment interface""" - self.reset() - if len(positions) == 1: - raise RuntimeWarning("Raster scan with one cell makes no sense") - self.command(CMD_RASTER_SCAN) - - rows = len(positions) / columns - x1, y1, z1 = tuple(positions[0]) # first cell on first row - x2, y2, z2 = tuple(positions[1]) # second cell on first row - x4, y4, z4 = tuple(positions[columns - 1]) # last cell on first row - - if rows > 1: - x3, y3, z3 = tuple(positions[columns]) # first cell on second row - ystep = y3 - y1 - zstep = z3 - z1 - gmy_step = math.sqrt(pow(y3 - y1, 2) + pow(z3 - z1, 2)) - else: - ystep = 0.010 - zstep = 0.010 - gmy_step = 0.010 - - half_cell = abs(x2 - x1) / 2.0 - start_x = x1 - half_cell - end_x = x4 + half_cell - - self._raster_starting_x = start_x - self._raster_starting_y = y1 - self._raster_starting_z = z1 - self._raster_step_y = ystep - self._raster_step_z = zstep - self._raster_current_row = 0 - self._raster_num_rows = rows - - self._var_1.set(self.position).wait() - self._var_2.set(start_x).wait() - self._var_3.set(end_x).wait() - self._var_4.set(columns).wait() - self._var_5.set(rows).wait() - self._var_6.set(angle).wait() - self._var_7.set(etime).wait() - self._var_8.set(HALF_PERIOD).wait() - self._var_9.set(self.gmx.offset.get()).wait() - self._var_10.set(gmy_step).wait() - - self.start_command() - - def measure_raster_still(self, positions, columns, etime, delay): - """raster via generic experiment interface - - positions: list of coordinates for each cell - columns: how many columns in grid - etime: exposure time / cell_width - delay: delay configured in delay generator - to trigger detector after aerotech; - must be greater than actual shutter delay - """ - self.reset() - if len(positions) == 1: - raise RuntimeWarning("Raster scan with one cell makes no sense") - self.command(CMD_RASTER_SCAN_STILL) - - rows = len(positions) / columns - x1, y1, z1 = tuple(positions[0]) # first cell on first row - x2, y2, z2 = tuple(positions[1]) # second cell on first row - x4, y4, z4 = tuple(positions[columns - 1]) # last cell on first row - - if rows > 1: - x3, y3, z3 = tuple(positions[columns]) # first cell on second row - ystep = y3 - y1 - zstep = z3 - z1 - gmy_step = math.sqrt(pow(y3 - y1, 2) + pow(z3 - z1, 2)) - else: - ystep = 0.010 - zstep = 0.010 - gmy_step = 0.010 - - half_cell = abs(x2 - x1) / 2.0 - start_x = x1 - half_cell - end_x = x4 + half_cell - - self._raster_starting_x = start_x - self._raster_starting_y = y1 - self._raster_starting_z = z1 - self._raster_step_y = ystep - self._raster_step_z = zstep - self._raster_current_row = 0 - self._raster_num_rows = rows - - self._var_1.set(self.position).wait() - self._var_2.set(start_x).wait() - self._var_3.set(end_x).wait() - self._var_4.set(columns).wait() - self._var_5.set(rows).wait() - self._var_6.set(delay).wait() - self._var_7.set(etime).wait() - self._var_8.set(HALF_PERIOD).wait() - self._var_9.set(self.gmx.offset.get()).wait() - self._var_10.set(gmy_step).wait() - - self.start_command() - - def measure_jungfrau(self, columns, rows, width, height, etime, sleep=0.100): - """for Jungfrau, grid from current position""" - self.reset() - self.command(CMD_JUNGFRAU) - self._var_1.set(columns).wait() - self._var_2.set(rows).wait() - self._var_3.set(height).wait() - self._var_4.set(width).wait() - self._var_5.set(etime).wait() - self._var_6.set(sleep).wait() - self._var_7.set(0.0).wait() - self._var_8.set(0.0).wait() - self._var_9.set(0.0).wait() - self._var_10.set(0.0).wait() - - self.start_command() - timeout = None if sleep <= 0 else 5.0 + (rows * sleep) + (columns * rows * etime) - - if timeout: - # Define wait until the busy flag goes down (excluding initial update) - timestamp_ = 0 - def isReady(*args, old_value, value, timestamp, **kwargs): - nonlocal timestamp_ - result = False if (timestamp_== 0) else bool(value==GRID_SCAN_DONE) - print(f"Old {old_value}\tNew: {value}\tResult: {result}") - timestamp_ = timestamp - return result - - # Subscribe and wait for update - status = SubscriptionStatus(self.raster.scan_done, isReady, timeout=timeout, settle_time=0.5) - status.wait() - - self.set_direct_mode() - - def measure_msox(self, start, wedge, etime, num_datasets=1, rest_time=0.0): - """for msox - start = start omega - wedge = total range to measure - etime = total time for wedge - num_datasets = how many times to repeat - rest_time = how many seconds to rest in between datasets - """ - self.reset() - self.command(CMD_MSOX) - self._var_1.set(start).wait() - self._var_2.set(wedge).wait() - self._var_3.set(etime).wait() - self._var_4.set(num_datasets).wait() - self._var_5.set(rest_time).wait() - self._var_6.set(0).wait() - self._var_7.set(0).wait() - self._var_8.set(0).wait() - self._var_9.set(0).wait() - self._var_10.set(0).wait() - - self.start_command() + raster_num_rows = Component(EpicsSignal, "-ES-GRD:ROW-DONE", kind=Kind.config) + def set_axis_mode(self, mode: str, settle_time=0.1) -> None: + if mode=="direct": + self.axisModeDirect.set(37, settle_time=settle_time).wait() + if mode=="measuring": + self.axisAxesMode.set(MEASURING_MODE, settle_time=settle_time).wait() def configure(self, d: dict) -> tuple: @@ -572,9 +221,9 @@ class AerotechAbrStage(Device): """ return self.raster.complete() - def reset(self): - self.command(CMD_NONE) - self.set_direct_mode() + def reset(self, settle_time=0.1): + self.scan_command.set(CMD_NONE, settle_time=settle_time).wait() + self.set_axis_mode("direct", settle_time=settle_time) def is_ioc_ok(self): return 0 == self.status.get() @@ -597,27 +246,27 @@ class AerotechAbrStage(Device): PARAMETERS `wait` true / false if the routine is to wait for the homing to finish. """ - self.omega.home.set(1).wait() + self.omega.home.set(1, settle_time=1).wait() poll(1.0) if not wait: return while not self.omega.is_homed(): - poll(0.2) + time.sleep(0.2) @property - def velo(self): + def velocity(self): return self.omega.velocity.get() - @velo.setter - def velo(self, value): + @velocity.setter + def velocity(self, value): self.omega.velocity.set(value).wait() @property - def etime(self): - return self.osc.etime.get() + def exp_time(self): + return self.osc.exp_time.get() - @etime.setter - def etime(self, value): + @exp_time.setter + def exp_time(self, value): self.osc.etime.set(value).wait() @property @@ -657,17 +306,9 @@ class AerotechAbrStage(Device): return state == self._shutter.get() @property - def mode(self): + def axis_mode(self): return self.axisAxesMode.get() - @mode.setter - def mode(self, value): - self.axisAxesMode.set(value).wait() - - - - - def get_ready(self, ostart=None, orange=None, etime=None, wait=True): self.wait_for_movements() if self.measurement_state == ABR_BUSY: @@ -683,9 +324,7 @@ class AerotechAbrStage(Device): if etime is not None: self.osc.etime.set(etime).wait() - self.osc.get_ready.set(1).wait() - - poll(0.1) + self.osc.get_ready.set(1, settle_time=0.1).wait() if wait: for n in range(10): @@ -707,7 +346,7 @@ class AerotechAbrStage(Device): moving = True while not timeisup and moving: - poll(0.1) + time.sleep(0.1) try: moving = self.is_moving() except: @@ -731,10 +370,9 @@ class AerotechAbrStage(Device): couple of seconds. """ - self.taskStop.put(1, wait=True) - poll(wait_after_reload) + self.taskStop.set(1, settle_time=wait_after_reload).wait() self.reset() - poll(0.1) + time.sleep(0.1) reload_programs = stop @@ -746,79 +384,16 @@ class AerotechAbrStage(Device): try: self.osc.wait_status(ABR_BUSY, timeout=1) except RuntimeWarning as e: - print(str(e), end=" ") - print(" --- trying start again.") - self.osc.taskStart.set(1).wait() + print("%s --- trying start again.", str(e)) + self.osc.kickoff() def move(self, position, wait=False, velocity=None, relative=None, direct=False, **kwargs) -> MoveStatus: - """Move omega to `angle` at `speed` deg/s and `wait` (or not). - - If a `speed` is specified, it will be used for the movement but the speed - setting will be reverted to its old value afterwards. - - If `wait` is true, the routine waits until the omega position is close - to within 0.01 degrees to the target or that a timeout occured. The - timeout is the time required for the movement plus 5 seconds. - - In case of a timeout a RuntimeWarning is raised. - - PARAMETERS: - `position` [degrees] a float specifying where to move to. - No limits. - `velo` [degrees/s] a float specifying speed to move at. - Limits: 0 - 360 - `wait` [boolean] wait or not for movement to finish. + """ Move the omega axis """ return self.omega.move(position, wait=wait, velocity=velocity, relative=relative, direct=direct, **kwargs) - def set_shutter(self, state): - if self.axisAxesMode.get(): - print("ABR is not in direct mode; cannot manipulate shutter") - return False - state = str(state).lower() - if state not in ["1", "0", "closed", "open"]: - print("unknown shutter state requested") - return None - elif state in ["1", "open"]: - state = 1 - elif state == ["0", "closed"]: - state = 0 - self._shutter.put(state, wait=True) - return state == self._shutter.get() if __name__ == "__main__": - aero = AerotechAbrStage(prefix="X06DA-ES-DF1", name="abr") - aero.wait_for_connection() - - # import argparse - # from time import sleep - - # parser = argparse.ArgumentParser(description="aerotech client") - # parser.add_argument("--stop", help="stop/reset/restart aerotech", action="store_true") - # parser.add_argument("--sanitize-speeds", help="reset speeds on axis to sane defaults", action="store_true") - # parser.add_argument("--measure-standard", help="measure a standard dataset", action="store") - # parser.add_argument("--omega", help="move omega to...", action="store") - # parser.add_argument("--gmx", help="move gmx to...", action="store") - # parser.add_argument("--gmy", help="move gmy to...", action="store") - # parser.add_argument("--gmz", help="move gmz to...", action="store") - # args = parser.parse_args() - - # abr = Abr() - # if args.stop: - # abr.stop() - - - # elif args.sanitize_speeds: - # abr.velo(AXIS_GMX, 50.0) - # abr.velo(AXIS_GMY, 10.0) - # abr.velo(AXIS_GMZ, 10.0) - # abr.velo(AXIS_OMEGA, 180.0) - - # elif args.gmx: - # abr.move_x(args.gmx) - - # elif args.measure_standard: - # start_angle, total_range, total_time = args.measure_standard.split(",") - # abr.measure_standard(start_angle, total_range, total_time) - + abr = AerotechAbrStage(prefix="X06DA-ES-DF1", name="abr") + abr.wait_for_connection() \ No newline at end of file diff --git a/pxiii_bec/devices/aeroA3200.py b/pxiii_bec/devices/aeroA3200.py index 11a2bcd..e319038 100644 --- a/pxiii_bec/devices/aeroA3200.py +++ b/pxiii_bec/devices/aeroA3200.py @@ -343,10 +343,8 @@ class A3200RasterScanner(Device): class A3200Oscillator(Device): - """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.""" + """No clue what this does, seems to be redundant with the task based grid scanners... + """ ostart_pos = Component(EpicsSignal, "-ES-OSC:START-POS", put_complete=True, kind=Kind.config) orange = Component(EpicsSignal, "-ES-OSC:RANGE", put_complete=True, kind=Kind.config) @@ -367,6 +365,9 @@ class A3200Oscillator(Device): def is_busy(self): return ABR_BUSY == self.phase.get() + def kickoff(self): + self.osc.taskStart.set(1).wait() + def wait_status(self, target, timeout=60.0) -> SubscriptionStatus: """Wait for the Aertotech IOC to reach the desired `status`. diff --git a/pxiii_bec/scans/mx_measurements.py b/pxiii_bec/scans/mx_measurements.py index 5208f8f..9329772 100644 --- a/pxiii_bec/scans/mx_measurements.py +++ b/pxiii_bec/scans/mx_measurements.py @@ -1,8 +1,4 @@ - -+ 119 -− 0 import time - import numpy as np from bec_lib import bec_logger @@ -11,32 +7,44 @@ from bec_server.scan_server.scans import AsyncFlyScanBase, ScanArgType, ScanBase logger = bec_logger.logger -CMD_NONE = 0 -CMD_RASTER_SCAN_SIMPLE = 1 -CMD_MEASURE_STANDARD = 2 -CMD_VERTICAL_LINE_SCAN = 3 -CMD_SCREENING = 4 -CMD_SUPER_FAST_OMEGA = 5 -CMD_STILL_WEDGE = 6 -CMD_STILLS = 7 -CMD_REPEAT_SINGLE_OSCILLATION = 8 -CMD_SINGLE_OSCILLATION = 9 -CMD_OLD_FASHIONED = 10 -CMD_RASTER_SCAN = 11 -CMD_JET_ROTATION = 12 -CMD_X_HELICAL = 13 -CMD_X_RUNSEQ = 14 -CMD_JUNGFRAU = 15 -CMD_MSOX = 16 -CMD_SLIT_SCAN = 17 -CMD_RASTER_SCAN_STILL = 18 -CMD_SCAN_SASTT = 19 -CMD_SCAN_SASTT_V2 = 20 -CMD_SCAN_SASTT_V3 = 21 +FULL_PERIOD = 0 +HALF_PERIOD = 1 + +class SasttSnakeMode: + SNAKE_SINGLE = 0 + UNIDIRECTIONAL = 1 + SNAKE_DOUBLE_PSO = 2 + + +class AbrCmd: + NONE = 0 + RASTER_SCAN_SIMPLE = 1 + MEASURE_STANDARD = 2 + VERTICAL_LINE_SCAN = 3 + SCREENING = 4 + SUPER_FAST_OMEGA = 5 + STILL_WEDGE = 6 + STILLS = 7 + REPEAT_SINGLE_OSCILLATION = 8 + SINGLE_OSCILLATION = 9 + OLD_FASHIONED = 10 + RASTER_SCAN = 11 + JET_ROTATION = 12 + X_HELICAL = 13 + X_RUNSEQ = 14 + JUNGFRAU = 15 + MSOX = 16 + SLIT_SCAN = 17 + RASTER_SCAN_STILL = 18 + SCAN_SASTT = 19 + SCAN_SASTT_V2 = 20 + SCAN_SASTT_V3 = 21 class MxFlyscanBase(AsyncFlyScanBase): """ Base class for MX flyscans + + I'd be quite easy to improve standardization. """ scan_report_hint = "table" arg_input = {} @@ -69,93 +77,89 @@ class MxFlyscanBase(AsyncFlyScanBase): - - - class MeasureStandardWedge(MxFlyscanBase): - """ Measure a standard wedge scan + """ Standard wedge scan + + Measure a standard continous line scan from `start` to `start` + `range` + during `scan_time` on the Omega axis. + + The scan itself is executed by the scan service running on the Aerotech + controller. Ophyd just configures, launches it and waits for completion. + + Example + ------- + >>> scans.standard_wedge(start=42, range=10, scan_time=20) + + Parameters + ---------- + start : (float, float) + Scan start position of the axis. + range : (float, float) + Scan range of the axis. + scan_time : (float) + Total fly time for the movement [s]. + ready_rate : float, optional + No clue what is this... (default=500) """ scan_name = "standard_wedge" - required_kwargs = ["start", "range"] + required_kwargs = ["start", "range", "scan_time"] def __init__(self, *args, parameter: dict = None, **kwargs): - """ Standard measurement scan - - Measure a standard wedge from `start` to `start` + `wedge` during - `etime` on the Omega axis. - - The scan itself is executed by the scan service running on the Aerotech - controller. Ophyd just configures, launches it and waits for completion. - - Example - ------- - >>> scans.standard_wedge(start=42, range=10, scan_time=20) - - Parameters - ---------- - start : (float, float) - Scan start position of the axis. - range : (float, float) - Scan range of the axis. - scan_time : (float) - Total scan time for the movement [s]. - ready_rate : float, optional - No clue what is this... (default=500) - """ super().__init__(parameter=parameter, **kwargs) self.axis = ['omega'] - self.scan_command = CMD_MEASURE_STANDARD + self.scan_command = AbrCmd.MEASURE_STANDARD self.scan_start = self.caller_kwargs.get("start") self.scan_range = self.caller_kwargs.get("range") - self.scan_scan_time = float(self.caller_kwargs.get("scan_time", 5)) + self.scan_scan_time = float(self.caller_kwargs.get("scan_time")) self.scan_ready_rate = float(self.caller_kwargs.get("ready_rate", 500)) def pre_scan(self): # ToDo: Move roughly to start position - # Set the global variables - yield from self.stubs.command("abr", "scan_command.set", self.scan_command) - yield from self.stubs.command("abr", "_var_1.set", self.scan_start) - yield from self.stubs.command("abr", "_var_2.set", self.scan_range) - yield from self.stubs.command("abr", "_var_3.set", self.scan_scan_time) - yield from self.stubs.command("abr", "_var_4.set", self.scan_ready_rate) + # Configure the global variables + d = {"scan_command" : self.scan_command, + "var_1" : self.scan_start, + "var_2" : self.scan_range, + "var_3" : self.scan_scan_time, + "var_4" : self.scan_ready_rate, + } + yield from self.stubs.send_rpc_and_wait("abr", "configure", d) # Call super - yield from self.stubs.pre_scan() + yield from super().pre_scan() + class MeasureScanSlits(MxFlyscanBase): - """ Measure a slit scan + """ Slit scan + + Measure a standard slit scan. + + The scan itself is executed by the scan service running on the Aerotech + controller. Ophyd just configures, launches it and waits for completion. + + Example + ------- + >>> scans.slit_scan(start=42, range=10, scan_time=20) + + Parameters + ---------- + delta_x : (float, float) + Scan range with slit in x. + delta_y : (float, float) + Scan range with slit in y. + velocity : (float) + Scan velocity [mm/s]. """ scan_name = "slit_scan" - required_kwargs = ["start", "range"] + required_kwargs = ["delta_x", "delta_y", "velocity"] def __init__(self, *args, parameter: dict = None, **kwargs): - """ Slit scan - - Measure a standard slit scan. - - The scan itself is executed by the scan service running on the Aerotech - controller. Ophyd just configures, launches it and waits for completion. - - Example - ------- - >>> scans.slit_scan(start=42, range=10, scan_time=20) - - Parameters - ---------- - delta_x : (float, float) - Scan range with slit in x. - delta_y : (float, float) - Scan range with slit in y. - velocity : (float) - Scan velocity [mm/s]]. - """ super().__init__(parameter=parameter, **kwargs) self.axis = [] - self.scan_command = CMD_SLIT_SCAN + self.scan_command = AbrCmd.SLIT_SCAN self.scan_delta_x = self.caller_kwargs.get("delta_x") self.scan_delta_y = self.caller_kwargs.get("delta_y") self.scan_velocity = self.caller_kwargs.get("velocity") @@ -163,52 +167,53 @@ class MeasureScanSlits(MxFlyscanBase): def pre_scan(self): # ToDo: Move roughly to start position - # Set the global variables - yield from self.stubs.command("abr", "scan_command.set", self.scan_command) - yield from self.stubs.command("abr", "_var_1.set", self.scan_delta_x) - yield from self.stubs.command("abr", "_var_2.set", self.scan_delta_y) - yield from self.stubs.command("abr", "_var_3.set", self.scan_velocity) + # Configure the global variables + d = {"scan_command" : self.scan_command, + "var_1" : self.scan_delta_x, + "var_2" : self.scan_delta_y, + "var_3" : self.scan_velocity, + } + yield from self.stubs.send_rpc_and_wait("abr", "configure", d) # Call super - yield from self.stubs.pre_scan() + yield from super().pre_scan() + class MeasureRasterSimple(MxFlyscanBase): - """ Measure a grid scan + """ Simple raster scan + + Measure a simplified raster scan, assuming the goniometer is currently + at the CENTER of TOP-LEFT CELL. + + The scan itself is executed by the scan service running on the Aerotech + controller. Ophyd just configures, launches it and waits for completion. + + Example + ------- + >>> scans.raster_simple(exp_time=0.5, cell_width=0.2, cell_height=0.2, ncols=10, nrows=10) + + Parameters + ---------- + exp_time : (float) + Exposure time per point [s]. + cell_width : (float) + Scan step size [mm]. + cell_height : (float) + Scan step size [mm]. + ncols : (int) + Number of scan steps. + nrows : (int) + Number of scan steps. """ scan_name = "raster_simple" required_kwargs = ["exp_time", "cell_width", "cell_height", "ncols", "nrows"] def __init__(self, *args, parameter: dict = None, **kwargs): - """ Simnple raster scan - - Measure a simplified raster scan, assuming the goniometer is currently - at the CENTER of TOP-LEFT CELL. - - The scan itself is executed by the scan service running on the Aerotech - controller. Ophyd just configures, launches it and waits for completion. - - Example - ------- - >>> scans.raster_simple(exp_time=0.5, cell_width=0.2, cell_height=0.2, ncols=10, nrows=10) - - Parameters - ---------- - exp_time : (float) - Exposure time per point [s]. - cell_width : (float) - Scan step size [mm]. - cell_height : (float) - Scan step size [mm]. - ncols : (int) - Number of scan steps. - nrows : (int) - Number of scan steps. - """ super().__init__(parameter=parameter, **kwargs) self.axis = [] - self.scan_command = CMD_RASTER_SCAN_SIMPLE + self.scan_command = AbrCmd.RASTER_SCAN_SIMPLE self.scan_exp_time = self.caller_kwargs.get("exp_time") self.scan_stepsize_x = self.caller_kwargs.get("cell_width") self.scan_stepsize_y = self.caller_kwargs.get("cell_height") @@ -218,36 +223,35 @@ class MeasureRasterSimple(MxFlyscanBase): def pre_scan(self): # ToDo: Move roughly to start position - # Set the global variables + # Reset done flag yield from self.stubs.command("abr", "raster_scan_done.set", 0) - yield from self.stubs.command("abr", "scan_command.set", self.scan_command) - yield from self.stubs.command("abr", "_var_1.set", self.scan_exp_time) - yield from self.stubs.command("abr", "_var_2.set", self.scan_stepsize_x) - yield from self.stubs.command("abr", "_var_3.set", self.scan_stepsize_y) - yield from self.stubs.command("abr", "_var_4.set", self.scan_stepnum_x) - yield from self.stubs.command("abr", "_var_5.set", self.scan_stepnum_y) - yield from self.stubs.command("abr", "_var_6.set", 0) - yield from self.stubs.command("abr", "_var_7.set", 0) - yield from self.stubs.command("abr", "_var_8.set", 0) + # Configure the global variables + d = {"scan_command" : self.scan_command, + "var_1" : self.scan_exp_time, + "var_2" : self.scan_stepsize_x, + "var_3" : self.scan_stepsize_y, + "var_4" : self.scan_stepnum_x, + "var_5" : self.scan_stepnum_y, + "var_6" : 0, + "var_7" : 0, + "var_8" : 0, + } + yield from self.stubs.send_rpc_and_wait("abr", "configure", d) # ToDo: This line was left out #self.raster._raster_num_rows = nrows # Call super - yield from self.stubs.pre_scan() + yield from super().pre_scan() + class MeasureSASTT(MxFlyscanBase): - """ Measure a small angle scanning tensor tomography scan - """ - scan_name = "measure_sastt" - required_kwargs = ["exp_time", "cell_width", "cell_height", "ncols", "nrows"] + """ Small angle scanning tensor tomography scan - def __init__(self, *args, parameter: dict = None, **kwargs): - """ Standard measurement scan - - Measure a simplified grid scan, assuming the goniometer is currently - at the CENTER of the required grid. + Measure a Small Angle Scanning Tensor Tomography scan on the specified + grid assuming the goniometer is currently at the CENTER of the required + grid. The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. @@ -280,16 +284,20 @@ class MeasureSASTT(MxFlyscanBase): 2 = scan always from LEFT---RIGHT 3 = snake scan alternating PSO window for even/odd rows """ + scan_name = "measure_sastt" + required_kwargs = ["exp_time", "cell_width", "cell_height", "ncols", "nrows"] + + def __init__(self, *args, parameter: dict = None, **kwargs): super().__init__(parameter=parameter, **kwargs) self.axis = [] self.scan_version = int(self.caller_kwargs.get("version", 1)) if self.scan_version==1: - self.scan_command = CMD_SCAN_SASTT + self.scan_command = AbrCmd.SCAN_SASTT if self.scan_version==2: - self.scan_command = CMD_SCAN_SASTT_V2 + self.scan_command = AbrCmd.SCAN_SASTT_V2 if self.scan_version==3: - self.scan_command = CMD_SCAN_SASTT_V3 + self.scan_command = AbrCmd.SCAN_SASTT_V3 else: raise RuntimeError(f"Non existing SAS-TT scan mode: {self.scan_version}") @@ -304,17 +312,20 @@ class MeasureSASTT(MxFlyscanBase): def pre_scan(self): # ToDo: Move roughly to start position - # Set the global variables + # Reset done flag yield from self.stubs.command("abr", "raster_scan_done.set", 0) - yield from self.stubs.command("abr", "scan_command.set", self.scan_command) - yield from self.stubs.command("abr", "_var_1.set", self.scan_exp_time) - yield from self.stubs.command("abr", "_var_2.set", self.scan_stepsize_x) - yield from self.stubs.command("abr", "_var_3.set", self.scan_stepsize_y) - yield from self.stubs.command("abr", "_var_4.set", self.scan_stepnum_x) - yield from self.stubs.command("abr", "_var_5.set", self.scan_stepnum_y) - yield from self.stubs.command("abr", "_var_6.set", self.scan_even_tweak) - yield from self.stubs.command("abr", "_var_7.set", self.scan_odd_tweak) - yield from self.stubs.command("abr", "_var_8.set", 0) + # Configure the global variables + d = {"scan_command" : self.scan_command, + "var_1" : self.scan_exp_time, + "var_2" : self.scan_stepsize_x, + "var_3" : self.scan_stepsize_y, + "var_4" : self.scan_stepnum_x, + "var_5" : self.scan_stepnum_y, + "var_6" : self.scan_even_tweak, + "var_7" : self.scan_odd_tweak, + "var_8" : 0, + } + yield from self.stubs.send_rpc_and_wait("abr", "configure", d) # ToDo: This line was left out # self.raster._raster_num_rows = nrows @@ -323,10 +334,11 @@ class MeasureSASTT(MxFlyscanBase): yield from self.stubs.pre_scan() -class MeasureVerticalLine(MxFlyscanBase): - """ Measure a vertical line using the GMY motor - Simple fly scan that performs a single scan vertically. +class MeasureVerticalLine(MxFlyscanBase): + """ Vertical line scan using the GMY motor + + Simple fly scan that records a single vertical line. The line is nummCells * cellHeight long and the exposureTime is per cell. The scan itself is executed by the scan service running on the Aerotech @@ -334,45 +346,47 @@ class MeasureVerticalLine(MxFlyscanBase): Example ------- - >>> scans.measure_vertical_line(start=42, range=10, scan_time=20) + >>> scans.measure_vline(cell_height=12, cell_num=10, cell_time=20) Parameters ---------- cell_height : (float) Step size [mm]. - nrows : (int) + cell_num : (int) Scan range of the axis. - exp_time : (float) + cell_time : (float) Exposure time per cell [s] """ - scan_name = "measure_vertical_line" - required_kwargs = ["exp_time", "cell_height", "nrows"] + scan_name = "measure_vline" + required_kwargs = ["cell_time", "cell_height", "cell_num"] def __init__(self, *args, parameter: dict = None, **kwargs): - super().__init__(parameter=parameter, **kwargs) self.axis = ['gmy'] - self.scan_command = CMD_VERTICAL_LINE_SCAN - self.scan_exp_time = self.caller_kwargs.get("exp_time") + self.scan_command = AbrCmd.VERTICAL_LINE_SCAN + self.scan_cell_time = self.caller_kwargs.get("cell_time") self.scan_stepsize_y = self.caller_kwargs.get("cell_height") - self.scan_stepnum_y = int(self.caller_kwargs.get("nrows")) + self.scan_stepnum_y = int(self.caller_kwargs.get("cell_num")) def pre_scan(self): # ToDo: Move roughly to start position - # Set the global variables - yield from self.stubs.command("abr", "scan_command.set", self.scan_command) - yield from self.stubs.command("abr", "_var_1.set", self.scan_stepsize_y) - yield from self.stubs.command("abr", "_var_2.set", self.scan_stepnum_y) - yield from self.stubs.command("abr", "_var_3.set", self.scan_exp_time) + # Configure the global variables + d = {"scan_command" : self.scan_command, + "var_1" : self.scan_stepsize_y, + "var_2" : self.scan_stepnum_y, + "var_3" : self.scan_cell_time, + } + yield from self.stubs.send_rpc_and_wait("abr", "configure", d) # Call super - yield from self.stubs.pre_scan() + yield from super().pre_scan() + class MeasureScreening(MxFlyscanBase): - """ Measure a sample screening program + """ Sample screening scan The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. @@ -383,27 +397,25 @@ class MeasureScreening(MxFlyscanBase): Parameters ---------- - start : (float) + start : float Scan start position of the axis. - range : (float) + range : float Scan range of the axis. - scan_time : (float) + scan_time : float Total scan time for the movement [s]. degrees : (???) frames : (???) delta : (???) (default: 0.5). - """ scan_name = "measure_screening" required_kwargs = ["start", "range", "scan_time", "degrees", "frames"] def __init__(self, *args, parameter: dict = None, **kwargs): - super().__init__(parameter=parameter, **kwargs) self.axis = ['gmy'] - self.scan_command = CMD_SCREENING + self.scan_command = AbrCmd.SCREENING self.scan_start = self.caller_kwargs.get("start") self.scan_range = self.caller_kwargs.get("range") self.scan_scan_time = self.caller_kwargs.get("scan_time") @@ -414,25 +426,24 @@ class MeasureScreening(MxFlyscanBase): def pre_scan(self): # ToDo: Move roughly to start position - # Set the global variables - yield from self.stubs.command("abr", "scan_command.set", self.scan_command) - yield from self.stubs.command("abr", "_var_1.set", self.scan_start) - yield from self.stubs.command("abr", "_var_2.set", self.scan_range) - yield from self.stubs.command("abr", "_var_3.set", self.scan_scan_time) - yield from self.stubs.command("abr", "_var_4.set", self.scan_degrees) - yield from self.stubs.command("abr", "_var_5.set", self.scan_frames) - yield from self.stubs.command("abr", "_var_6.set", self.scan_delta) - + # Configure the global variables + d = {"scan_command" : self.scan_command, + "var_1" : self.scan_start, + "var_2" : self.scan_range, + "var_3" : self.scan_scan_time, + "var_4" : self.scan_degrees, + "var_5" : self.scan_frames, + "var_6" : self.scan_delta, + } + yield from self.stubs.send_rpc_and_wait("abr", "configure", d) # Call super - yield from self.stubs.pre_scan() - - + yield from super().pre_scan() class MeasureFastOmega(MxFlyscanBase): - """ Measure a fast omega scan + """ Fast omega scan MEasures a fast rotational scan with the rotation stage (omega). @@ -445,24 +456,23 @@ class MeasureFastOmega(MxFlyscanBase): Parameters ---------- - start : (float) + start : float Scan start position of the axis. - range : (float) + range : float Scan range of the axis. - scan_time : (float) + scan_time : float Total scan time for the movement [s]. rate : (???) (default: 200). """ scan_name = "measure_fast_omega" - required_kwargs = ["start", "range", "exp_time"] + required_kwargs = ["start", "range", "scan_time"] def __init__(self, *args, parameter: dict = None, **kwargs): - super().__init__(parameter=parameter, **kwargs) self.axis = ['omega'] - self.scan_command = CMD_SUPER_FAST_OMEGA + self.scan_command = AbrCmd.SUPER_FAST_OMEGA self.scan_start = self.caller_kwargs.get("start") self.scan_range = self.caller_kwargs.get("range") self.scan_scan_time = self.caller_kwargs.get("scan_time") @@ -471,21 +481,22 @@ class MeasureFastOmega(MxFlyscanBase): def pre_scan(self): # ToDo: Move roughly to start position - # Set the global variables - yield from self.stubs.command("abr", "scan_command.set", self.scan_command) - yield from self.stubs.command("abr", "_var_1.set", self.scan_start) - yield from self.stubs.command("abr", "_var_2.set", self.scan_range) - yield from self.stubs.command("abr", "_var_3.set", self.scan_scan_time) - yield from self.stubs.command("abr", "_var_4.set", self.scan_rate) + # Configure the global variables + d = {"scan_command" : self.scan_command, + "var_1" : self.scan_start, + "var_2" : self.scan_range, + "var_3" : self.scan_scan_time, + "var_4" : self.scan_rate, + } + yield from self.stubs.send_rpc_and_wait("abr", "configure", d) # Call super yield from self.stubs.pre_scan() - class MeasureStillWedge(MxFlyscanBase): - """ Measure a still wedge scan + """ Still wedge scan The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. @@ -510,11 +521,10 @@ class MeasureStillWedge(MxFlyscanBase): required_kwargs = ["start", "range", "exp_time", "oscrange"] def __init__(self, *args, parameter: dict = None, **kwargs): - super().__init__(parameter=parameter, **kwargs) self.axis = ['omega'] - self.scan_command = CMD_STILL_WEDGE + self.scan_command = AbrCmd.STILL_WEDGE self.scan_start = self.caller_kwargs.get("start") self.scan_range = self.caller_kwargs.get("range") self.scan_scan_time = self.caller_kwargs.get("scan_time") @@ -524,24 +534,23 @@ class MeasureStillWedge(MxFlyscanBase): def pre_scan(self): # ToDo: Move roughly to start position - # Set the global variables - yield from self.stubs.command("abr", "scan_command.set", self.scan_command) - yield from self.stubs.command("abr", "_var_1.set", self.scan_start) - yield from self.stubs.command("abr", "_var_2.set", self.scan_range) - yield from self.stubs.command("abr", "_var_3.set", self.scan_scan_time) - yield from self.stubs.command("abr", "_var_4.set", self.scan_oscrange) - yield from self.stubs.command("abr", "_var_5.set", self.scan_shutter_sleep) + # Configure the global variables + d = {"scan_command" : self.scan_command, + "var_1" : self.scan_start, + "var_2" : self.scan_range, + "var_3" : self.scan_scan_time, + "var_4" : self.scan_oscrange, + "var_5" : self.scan_shutter_sleep, + } + yield from self.stubs.send_rpc_and_wait("abr", "configure", d) # Call super yield from self.stubs.pre_scan() - - - class MeasureStills(MxFlyscanBase): - """ Measure a still image sequence + """ Still image sequence scan Measures a series of still images without any motor movement. IMPORTANT: use idle_time=0.0 to prevent shutter open/close @@ -551,26 +560,25 @@ class MeasureStills(MxFlyscanBase): Example ------- - >>> scans.measure_stils(nframes=100, exp_time=0.1, idle_time=30) + >>> scans.measure_stills(nframes=100, exp_time=0.1, idle_time=30) Parameters ---------- - nframes : (int) + nframes : int Total number of frames to be recorded. - exposure_time : (float) + exposure_time : float Exposure time of each frame [s]. - idle_time : (float) + idle_time : float Sleep time between the frames [s]. """ - scan_name = "measure_stils" - required_kwargs = ["exp_time", "nframes"] + scan_name = "measure_stills" + required_kwargs = ["exp_time", "nframes", "idle_time"] def __init__(self, *args, parameter: dict = None, **kwargs): - super().__init__(parameter=parameter, **kwargs) self.axis = [] - self.scan_command = CMD_STILLS + self.scan_command = AbrCmd.STILLS self.scan_exp_time = self.caller_kwargs.get("exp_time") self.scan_num_frames = int(self.caller_kwargs.get("nframes")) self.scan_idle_time = self.caller_kwargs.get("idle_time") @@ -578,22 +586,21 @@ class MeasureStills(MxFlyscanBase): def pre_scan(self): # ToDo: Move roughly to start position - # Set the global variables - yield from self.stubs.command("abr", "scan_command.set", self.scan_command) - yield from self.stubs.command("abr", "_var_1.set", self.scan_num_frames) - yield from self.stubs.command("abr", "_var_2.set", self.scan_exp_time) - yield from self.stubs.command("abr", "_var_3.set", self.scan_idle_time) + # Configure the global variables + d = {"scan_command" : self.scan_command, + "var_1" : self.scan_num_frames, + "var_2" : self.scan_exp_time, + "var_3" : self.scan_idle_time, + } + yield from self.stubs.send_rpc_and_wait("abr", "configure", d) # Call super yield from self.stubs.pre_scan() - - - class MeasureSingleOscillation(MxFlyscanBase): - """ Measure a single oscillation + """ Single oscillation scan The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. @@ -604,11 +611,11 @@ class MeasureSingleOscillation(MxFlyscanBase): Parameters ---------- - start : (float) + start : float Scan start position of the axis. - range : (float) + range : float Oscillation range of the axis. - scan_time : (float) + scan_time : float Total scan time for the movement [s]. delta : (???) (default: 0). @@ -619,11 +626,10 @@ class MeasureSingleOscillation(MxFlyscanBase): required_kwargs = ["start", "range", "scan_time"] def __init__(self, *args, parameter: dict = None, **kwargs): - super().__init__(parameter=parameter, **kwargs) self.axis = ['omega'] - self.scan_command = CMD_SINGLE_OSCILLATION + self.scan_command = AbrCmd.SINGLE_OSCILLATION self.scan_start = self.caller_kwargs.get("start") self.scan_range = self.caller_kwargs.get("range") self.scan_scan_time = self.caller_kwargs.get("scan_time") @@ -633,23 +639,23 @@ class MeasureSingleOscillation(MxFlyscanBase): def pre_scan(self): # ToDo: Move roughly to start position - # Set the global variables - yield from self.stubs.command("abr", "scan_command.set", self.scan_command) - yield from self.stubs.command("abr", "_var_1.set", self.scan_start) - yield from self.stubs.command("abr", "_var_2.set", self.scan_range) - yield from self.stubs.command("abr", "_var_3.set", self.scan_scan_time) - yield from self.stubs.command("abr", "_var_4.set", self.scan_delta) - yield from self.stubs.command("abr", "_var_5.set", self.scan_settle) + # Configure the global variables + d = {"scan_command" : self.scan_command, + "var_1" : self.scan_start, + "var_2" : self.scan_range, + "var_3" : self.scan_scan_time, + "var_4" : self.scan_delta, + "var_5" : self.scan_settle, + } + yield from self.stubs.send_rpc_and_wait("abr", "configure", d) # Call super yield from self.stubs.pre_scan() - - class MeasureRepeatedOscillation(MxFlyscanBase): - """ Measure oscillation repeatedly + """ Repeated oscillation scan The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. @@ -660,27 +666,25 @@ class MeasureRepeatedOscillation(MxFlyscanBase): Parameters ---------- - nframes : (int) + nframes : int ??? - scan_time : (float) + scan_time : float Total scan time for the movement [s]. - range : (float) + range : float Oscillation range of the axis. settle : (???) (default: 0). delta : (???) (default: 0.5). - """ scan_name = "measure_multi_osc" - required_kwargs = ["start", "range", "scan_time"] + required_kwargs = ["nframes", "range", "scan_time"] def __init__(self, *args, parameter: dict = None, **kwargs): - super().__init__(parameter=parameter, **kwargs) self.axis = ['omega'] - self.scan_command = CMD_REPEAT_SINGLE_OSCILLATION + self.scan_command = AbrCmd.REPEAT_SINGLE_OSCILLATION self.scan_num_frames = self.caller_kwargs.get("nframes") self.scan_range = self.caller_kwargs.get("range") self.scan_scan_time = self.caller_kwargs.get("scan_time") @@ -705,8 +709,6 @@ class MeasureRepeatedOscillation(MxFlyscanBase): - - class MeasureMSOX(MxFlyscanBase): """ Standard MSOX scan @@ -719,11 +721,11 @@ class MeasureMSOX(MxFlyscanBase): Parameters ---------- - start : (float, float) + start : float Scan start position of the axis. - range : (float, float) + range : float Scan range of the axis. - exp_time : (float) + exp_time : float Exposure time for each point [s]. num_datasets : int ??? @@ -734,18 +736,16 @@ class MeasureMSOX(MxFlyscanBase): required_kwargs = ["start", "range", "exp_time"] def __init__(self, *args, parameter: dict = None, **kwargs): - super().__init__(parameter=parameter, **kwargs) self.axis = ['omega'] - self.scan_command = CMD_MSOX + self.scan_command = AbrCmd.MSOX self.scan_start = self.caller_kwargs.get("start") self.scan_range = self.caller_kwargs.get("range") self.scan_exp_time = self.caller_kwargs.get("exp_time") self.scan_num_datasets = int(self.caller_kwargs.get("num_datasets", 1)) self.scan_rest_time = self.caller_kwargs.get("rest_time", 0) - def pre_scan(self): # ToDo: Move roughly to start position @@ -759,6 +759,11 @@ class MeasureMSOX(MxFlyscanBase): "var_3" : self.scan_exp_time, "var_4" : self.scan_num_datasets, "var_5" : self.scan_rest_time, + "var_6" : 0, + "var_7" : 0, + "var_8" : 0, + "var_9" : 0, + "var_10" : 0, } yield from self.stubs.send_rpc_and_wait("abr", "configure", d) @@ -767,10 +772,6 @@ class MeasureMSOX(MxFlyscanBase): - - - - class MeasureGrid(MxFlyscanBase): """ General grid scan @@ -782,7 +783,7 @@ class MeasureGrid(MxFlyscanBase): Example ------- - >>> scans.measure_msox(start=42, range=10, exp_time=0.1) + >>> scans.measure_raster(start=42, range=10, exp_time=0.1) Parameters ---------- @@ -799,11 +800,10 @@ class MeasureGrid(MxFlyscanBase): required_kwargs = ["positions", "ncols", "angle", "exp_time"] def __init__(self, *args, parameter: dict = None, **kwargs): - super().__init__(parameter=parameter, **kwargs) self.axis = ['omega'] - self.scan_command = CMD_RASTER_SCAN + self.scan_command = AbrCmd.RASTER_SCAN self.scan_positions = self.caller_kwargs.get("positions") self.scan_stepnum_x = int(self.caller_kwargs.get("ncols")) self.scan_stepnum_y = self.scan_positions / self.scan_stepnum_x @@ -814,13 +814,14 @@ class MeasureGrid(MxFlyscanBase): raise RuntimeWarning("Raster scan with one cell makes no sense") # Good practice: new members only in __init__ + self.scan_start_x = None + self.scan_end_x = None + self.scan_xstep = 0.010 self.scan_ystep = 0.010 self.scan_zstep = 0.010 self.scan_gmy_step = 0.010 - def prepare_positions(self): - # Calculate step sizes pos_start = self.scan_positions[0] # first cell on first row pos_col_2nd = self.scan_positions[1] # second cell on first row @@ -837,6 +838,10 @@ class MeasureGrid(MxFlyscanBase): self.scan_zstep = pos_row_2nd[2] - pos_start[2] self.scan_gmy_step = np.linalg.norm([self.scan_ystep, self.scan_zstep]) + # ToDo: Check with Zac what are theese parameters + self.scan_omega_position = yield from self.stubs.send_rpc_and_wait("abs", "omega.readback.get") + self.scan_gmx_offset = yield from self.stubs.send_rpc_and_wait("abs", "gmx.readback.get") + # ToDo : This was left out # self._raster_starting_x = start_x # self._raster_starting_y = y1 @@ -846,9 +851,6 @@ class MeasureGrid(MxFlyscanBase): # self._raster_current_row = 0 # self._raster_num_rows = rows - - - def pre_scan(self): # ToDo: Move roughly to start position @@ -857,7 +859,7 @@ class MeasureGrid(MxFlyscanBase): # Configure the global variables d = {"scan_command" : self.scan_command, - "var_1" : self.omega.position, + "var_1" : self.scan_omega_position, "var_2" : self.scan_start_x, "var_3" : self.scan_end_x, "var_4" : self.scan_stepnum_x, @@ -865,7 +867,7 @@ class MeasureGrid(MxFlyscanBase): "var_6" : self.scan_angle, "var_7" : self.scan_exp_time, "var_8" : HALF_PERIOD, - "var_9" : self._gmx_offset.get, + "var_9" : self.scan_gmx_offset, "var_10" : self.scan_gmy_step, } yield from self.stubs.send_rpc_and_wait("abr", "configure", d) @@ -875,3 +877,191 @@ class MeasureGrid(MxFlyscanBase): + + +class MeasureGridStill(MxFlyscanBase): + """ Still grid scan + + Note: This was probably never used in its current form, because the + code had an undefined variable 'self.position' + + The scan itself is executed by the scan service running on the Aerotech + controller. Ophyd just configures, launches it and waits for completion. + + Example + ------- + >>> scans.measure_raster(positions=[[11, 2, 3.4], [11, 2, 3.5], ...], columns=20, exp_time=0.1, delay=0.05) + + Parameters + ---------- + positions : 2D-array + Scan position of each axis, i.e. (N, 3) shaped array. + columns : int + Nmber of columns. + angle: + ??? + exp_time : float + Exposure time for each point [s]. + delay: + ??? + """ + scan_name = "measure_raster_still" + required_kwargs = ["positions", "columns", "exp_time", "delay"] + + def __init__(self, *args, parameter: dict = None, **kwargs): + super().__init__(parameter=parameter, **kwargs) + self.axis = ['omega'] + + self.scan_command = AbrCmd.RASTER_SCAN_STILL + self.scan_positions = self.caller_kwargs.get("positions") + self.scan_stepnum_x = int(self.caller_kwargs.get("columns")) + self.scan_stepnum_y = self.scan_positions / self.scan_stepnum_x + self.scan_exp_time = self.caller_kwargs.get("exp_time") + self.scan_delay = self.caller_kwargs.get("delay") + + if len(self.scan_positions)==1: + raise RuntimeWarning("Raster scan with one cell makes no sense") + + # Good practice: new members only in __init__ + self.scan_start_x = None + self.scan_end_x = None + self.scan_xstep = 0.010 + self.scan_ystep = 0.010 + self.scan_zstep = 0.010 + self.scan_gmy_step = 0.010 + + def prepare_positions(self): + # Calculate step sizes + pos_start = self.scan_positions[0] # first cell on first row + pos_col_2nd = self.scan_positions[1] # second cell on first row + pos_col_end = self.scan_positions[self.scan_stepnum_x-1] # last cell on first row + + self.scan_xstep = pos_col_2nd[0] - pos_start[0] + half_cell = abs(self.scan_xstep) / 2.0 + self.scan_start_x = pos_start[0] - half_cell + self.scan_end_x = pos_col_end[0] + half_cell + + if self.scan_stepnum_y > 1: + pos_row_2nd = self.scan_positions[self.scan_stepnum_x] # first cell on second row + self.scan_ystep = pos_row_2nd[1] - pos_start[1] + self.scan_zstep = pos_row_2nd[2] - pos_start[2] + self.scan_gmy_step = np.linalg.norm([self.scan_ystep, self.scan_zstep]) + + # ToDo: Check with Zac what are theese parameters + self.scan_omega_position = yield from self.stubs.send_rpc_and_wait("abs", "omega.readback.get") + self.scan_gmx_offset = yield from self.stubs.send_rpc_and_wait("abs", "gmx.readback.get") + + # ToDo : This was left out + # self._raster_starting_x = start_x + # self._raster_starting_y = y1 + # self._raster_starting_z = z1 + # self._raster_step_y = ystep + # self._raster_step_z = zstep + # self._raster_current_row = 0 + # self._raster_num_rows = rows + + def pre_scan(self): + # ToDo: Move roughly to start position + + # Reset the scan engine + yield from self.stubs.send_rpc_and_wait("abr", "reset") + + # Configure the global variables + d = {"scan_command" : self.scan_command, + "var_1" : self.scan_omega_position, + "var_2" : self.scan_start_x, + "var_3" : self.scan_end_x, + "var_4" : self.scan_stepnum_x, + "var_5" : self.scan_stepnum_y, + "var_6" : self.scan_delay, + "var_7" : self.scan_exp_time, + "var_8" : HALF_PERIOD, + "var_9" : self.scan_gmx_offset, + "var_10" : self.scan_gmy_step, + } + yield from self.stubs.send_rpc_and_wait("abr", "configure", d) + + # Call super + yield from self.stubs.pre_scan() + + + +class MeasureJungfrau(MxFlyscanBase): + """ Scan with the Jungfrau detector + + The scan itself is executed by the scan service running on the Aerotech + controller. Ophyd just configures, launches it and waits for completion. + + Example + ------- + >>> scans.measure_jungfrau(start=42, range=180, scan_time=20) + + Parameters + ---------- + columns : int + ??? + rows : int + ??? + width : int + ??? + height : int + ??? + exp_time : float + Exposure time per point [s]. + sleep : float + (default: 0.1). + """ + scan_name = "measure_jungfrau" + required_kwargs = ["columns", "rows", "width", "height", "exp_time"] + + def __init__(self, *args, parameter: dict = None, **kwargs): + super().__init__(parameter=parameter, **kwargs) + self.axis = ['omega'] + + self.scan_command = AbrCmd.JUNGFRAU + self.scan_num_cols = self.caller_kwargs.get("columns") + self.scan_num_rows = self.caller_kwargs.get("rows") + self.scan_width = self.caller_kwargs.get("width") + self.scan_height = self.caller_kwargs.get("height") + self.scan_exp_time = self.caller_kwargs.get("exp_time") + self.scan_sleep = self.caller_kwargs.get("sleep", 0.1) + + self.scan_timeout = None if self.scan_sleep <= 0 else 5.0 + (self.scan_num_rows * self.scan_sleep) + (self.scan_num_cols * self.scan_num_rows * self.scan_exp_time) + + def pre_scan(self): + # ToDo: Move roughly to start position + + # Configure the global variables + d = {"scan_command" : self.scan_command, + "var_1" : self.scan_num_cols, + "var_2" : self.scan_num_rows, + "var_3" : self.scan_width, + "var_4" : self.scan_height, + "var_5" : self.scan_exp_time, + "var_6" : self.scan_sleep, + "var_7" : 0, + "var_8" : 0, + } + yield from self.stubs.send_rpc_and_wait("abr", "configure", d) + + # Call super + yield from self.stubs.pre_scan() + + def scan_core(self): + """ The actual scan logic comes here. + """ + self.pointID = 0 + # Kick off the run + yield from self.stubs.command("abr", "start_command.set", 1) + logger.info("Measurement launched on the ABR stage...") + + # Wait for grid scanner to finish + st = yield from self.stubs.send_rpc_and_wait("abr", "complete", self.scan_timeout) + st.wait() + + # Go back to direct mode + st = yield from self.stubs.send_rpc_and_wait("abr", "set_axis_mode", "direct") + st.wait() + + + -- 2.49.1 From 3bf21ff647fafc4979738697c2802a8582bb2a9c Mon Sep 17 00:00:00 2001 From: Unknown MX Person Date: Mon, 9 Sep 2024 13:23:03 +0200 Subject: [PATCH 09/25] Both ABR stage and scan instantiates, need Zac to test it safely --- pxiii_bec/device_configs/x06da_compact.lmay | 19 ++- .../device_configs/x06da_device_config.yaml | 43 ++++-- pxiii_bec/devices/A3200.py | 126 +++++++++++------- pxiii_bec/devices/__init__.py | 2 + pxiii_bec/devices/aeroA3200.py | 58 ++++---- pxiii_bec/scans/__init__.py | 1 + 6 files changed, 151 insertions(+), 98 deletions(-) diff --git a/pxiii_bec/device_configs/x06da_compact.lmay b/pxiii_bec/device_configs/x06da_compact.lmay index 4684593..7ad90d4 100644 --- a/pxiii_bec/device_configs/x06da_compact.lmay +++ b/pxiii_bec/device_configs/x06da_compact.lmay @@ -66,19 +66,19 @@ ssxbpm_try: ssslit_trxr: deviceClass: ophyd.EpicsMotor deviceConfig: - prefix: 'X06DA-ES-SSSH1:TRXR' + prefix: 'X06DA-ES-SSSLH1:TRXR' ssslit_trxw: deviceClass: ophyd.EpicsMotor deviceConfig: - prefix: 'X06DA-ES-SSSH1:TRXW' + prefix: 'X06DA-ES-SSSLH1:TRXW' ssslit_tryt: deviceClass: ophyd.EpicsMotor deviceConfig: - prefix: 'X06DA-ES-SSSV1:TRYT' + prefix: 'X06DA-ES-SSSLV1:TRYT' ssslit_tryb: deviceClass: ophyd.EpicsMotor deviceConfig: - prefix: 'X06DA-ES-SSSV1:TRYB' + prefix: 'X06DA-ES-SSSLV1:TRYB' ssxi1_trx: deviceClass: ophyd.EpicsMotor deviceConfig: @@ -195,5 +195,12 @@ bstop_diode: prefix: 'X06DA-ES-BS:READOUT' - - +# End station +omega: + deviceClass: pxiii_bec.devices.A3200Axis + deviceConfig: + prefix: 'X06DA-ES-DF1:OMEGA' +abr: + deviceClass: pxiii_bec.devices.AerotechAbrStage + deviceConfig: + prefix: 'X06DA-ES' diff --git a/pxiii_bec/device_configs/x06da_device_config.yaml b/pxiii_bec/device_configs/x06da_device_config.yaml index 1893b72..c49d4af 100644 --- a/pxiii_bec/device_configs/x06da_device_config.yaml +++ b/pxiii_bec/device_configs/x06da_device_config.yaml @@ -1,4 +1,3 @@ - slh_trxr: deviceClass: ophyd.EpicsMotor deviceConfig: {prefix: 'X06DA-OP-SLH:TRXR'} @@ -63,6 +62,14 @@ dccm_energy2: readoutPriority: monitored readOnly: false softwareTrigger: false +dccm_xbpm: + deviceClass: ophyd.EpicsSignalRO + deviceConfig: {read_pv: 'X06DA-OP-XBPM1:SumAll:MeanValue_RBV'} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: true + softwareTrigger: false dccm_energy: description: Monochromator energy using ECMC virtual motors deviceClass: ophyd.EpicsMotor @@ -73,7 +80,7 @@ dccm_energy: readOnly: false softwareTrigger: false dccm_eoffset: - description: Monochromator energy offset between crystals using ECMC virtual motors + description: Monochromator energy offset for ECMC virtual motors deviceClass: ophyd.EpicsMotor deviceConfig: {prefix: 'X06DA-OP-DCCM:_EOFFSET'} onFailure: buffer @@ -81,14 +88,6 @@ dccm_eoffset: readoutPriority: monitored readOnly: false softwareTrigger: false -dccm_xbpm: - deviceClass: ophyd.EpicsSignalRO - deviceConfig: {read_pv: 'X06DA-OP-XBPM1:SumAll:MeanValue_RBV'} - onFailure: buffer - enabled: true - readoutPriority: monitored - readOnly: true - softwareTrigger: false ssxbpm_trx: deviceClass: ophyd.EpicsMotor deviceConfig: {prefix: 'X06DA-ES-SSBPM1:TRX'} @@ -107,7 +106,7 @@ ssxbpm_try: softwareTrigger: false ssslit_trxr: deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES-SSSH1:TRXR'} + deviceConfig: {prefix: 'X06DA-ES-SSSLH1:TRXR'} onFailure: buffer enabled: true readoutPriority: monitored @@ -115,7 +114,7 @@ ssslit_trxr: softwareTrigger: false ssslit_trxw: deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES-SSSH1:TRXW'} + deviceConfig: {prefix: 'X06DA-ES-SSSLH1:TRXW'} onFailure: buffer enabled: true readoutPriority: monitored @@ -123,7 +122,7 @@ ssslit_trxw: softwareTrigger: false ssslit_tryt: deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES-SSSV1:TRYT'} + deviceConfig: {prefix: 'X06DA-ES-SSSLV1:TRYT'} onFailure: buffer enabled: true readoutPriority: monitored @@ -131,7 +130,7 @@ ssslit_tryt: softwareTrigger: false ssslit_tryb: deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES-SSSV1:TRYB'} + deviceConfig: {prefix: 'X06DA-ES-SSSLV1:TRYB'} onFailure: buffer enabled: true readoutPriority: monitored @@ -329,3 +328,19 @@ bstop_diode: readoutPriority: monitored readOnly: true softwareTrigger: false +omega: + deviceClass: pxiii_bec.devices.A3200Axis + deviceConfig: {prefix: 'X06DA-ES-DF1:OMEGA'} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: false + softwareTrigger: false +abr: + deviceClass: pxiii_bec.devices.AerotechAbrStage + deviceConfig: {prefix: X06DA-ES} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: false + softwareTrigger: false diff --git a/pxiii_bec/devices/A3200.py b/pxiii_bec/devices/A3200.py index d91d4c9..b606501 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 .aeroA3200 import A3200Axis, A3200RasterScanner, A3200Oscillator ABR_DONE = 0 @@ -136,46 +136,56 @@ AXIS_STZ = 6 class AerotechAbrStage(Device): - - taskStop = Component(EpicsSignal, "-ES-AERO:TSK-STOP", put_complete=True, kind=Kind.omitted) - status = Component(EpicsSignal, "-ES-AERO:STAT", put_complete=True, kind=Kind.omitted) + """ Standard PX stage on A3200 controller + + This is the wrapper class for the standard rotation stage layout for the PX + beamlines at SLS. It wraps the main rotation axis OMEGA and the associated + motion axes GMX, GMY and GMZ. The ophyd class associates to the general PX + measurement procedure, which is that the actual scan script is running as + an AeroBasic program on the controller and we communicate to it via 10+1 + global variables. + """ + taskStop = Component(EpicsSignal, "-AERO:TSK-STOP", put_complete=True, kind=Kind.omitted) + status = Component(EpicsSignal, "-AERO:STAT", put_complete=True, kind=Kind.omitted) # Enable/disable motor movement via the IOC (i.e. make it task-only) - axisModeLocked = Component(EpicsSignal, "-ES-DF1:LOCK", put_complete=True, kind=Kind.omitted) - axisModeDirect = Component(EpicsSignal, "-ES-DF1:MODE-DIRECT", put_complete=True, kind=Kind.omitted) - axisAxesMode = Component(EpicsSignal, "-ES-DF1:AXES-MODE", put_complete=True, kind=Kind.omitted) + axisModeLocked = Component(EpicsSignal, "-DF1:LOCK", put_complete=True, kind=Kind.omitted) + axisModeDirect = Component(EpicsSignal, "-DF1:MODE-DIRECT", put_complete=True, kind=Kind.omitted) + axisAxesMode = Component(EpicsSignal, "-DF1:AXES-MODE", put_complete=True, kind=Kind.omitted) - _shutter = Component( - EpicsSignal, "-ES-PH1:GET", write_pv="-ES-PH1:SET", put_complete=True, kind=Kind.config, - ) + # Shutter box is missing readback so the -GET signal is installed on the VME + # _shutter = Component( + # EpicsSignal, "-PH1:GET", write_pv="-PH1:SET", put_complete=True, kind=Kind.config, + # ) raster = Component(A3200RasterScanner, "", kind=Kind.config) osc = Component(A3200Oscillator, "", kind=Kind.config) - omega = Component(A3200Axis, "-ES-DF1:OMEGA", kind=Kind.hinted) - gmx = Component(A3200Axis, "-ES-DF1:GMX", kind=Kind.hinted) - gmy = Component(A3200Axis, "-ES-DF1:GMY", kind=Kind.hinted) - gmz = Component(A3200Axis, "-ES-DF1:GMZ", kind=Kind.hinted) + # Positioners for the motor axes + omega = Component(A3200Axis, "-DF1:OMEGA", kind=Kind.hinted) + gmx = Component(A3200Axis, "-DF1:GMX", kind=Kind.hinted) + gmy = Component(A3200Axis, "-DF1:GMY", kind=Kind.hinted) + gmz = Component(A3200Axis, "-DF1:GMZ", kind=Kind.hinted) + scan_command = Component(EpicsSignal, "-PSO:CMD", put_complete=True, kind=Kind.omitted) + start_command = Component(EpicsSignal, "-PSO:START-TEST.PROC", put_complete=True, kind=Kind.omitted) + stop_command = Component(EpicsSignal, "-PSO:STOP-TEST.PROC", put_complete=True, kind=Kind.omitted) - scan_command = Component(EpicsSignal, "-ES-PSO:CMD", put_complete=True, kind=Kind.omitted) - start_command = Component(EpicsSignal, "-ES-PSO:START-TEST.PROC", put_complete=True, kind=Kind.omitted) - stop_command = Component(EpicsSignal, "-ES-PSO:STOP-TEST.PROC", put_complete=True, kind=Kind.omitted) - - _var_1 = Component(EpicsSignal, "-ES-PSO:VAR-1", put_complete=True, kind=Kind.omitted) - _var_2 = Component(EpicsSignal, "-ES-PSO:VAR-2", put_complete=True, kind=Kind.omitted) - _var_3 = Component(EpicsSignal, "-ES-PSO:VAR-3", put_complete=True, kind=Kind.omitted) - _var_4 = Component(EpicsSignal, "-ES-PSO:VAR-4", put_complete=True, kind=Kind.omitted) - _var_5 = Component(EpicsSignal, "-ES-PSO:VAR-5", put_complete=True, kind=Kind.omitted) - _var_6 = Component(EpicsSignal, "-ES-PSO:VAR-6", put_complete=True, kind=Kind.omitted) - _var_7 = Component(EpicsSignal, "-ES-PSO:VAR-7", put_complete=True, kind=Kind.omitted) - _var_8 = Component(EpicsSignal, "-ES-PSO:VAR-8", put_complete=True, kind=Kind.omitted) - _var_9 = Component(EpicsSignal, "-ES-PSO:VAR-9", put_complete=True, kind=Kind.omitted) - _var_10 = Component(EpicsSignal, "-ES-PSO:VAR-10", put_complete=True, kind=Kind.omitted) + # Global variables to controll AeroBasic scripts + _var_1 = Component(EpicsSignal, "-PSO:VAR-1", put_complete=True, kind=Kind.omitted) + _var_2 = Component(EpicsSignal, "-PSO:VAR-2", put_complete=True, kind=Kind.omitted) + _var_3 = Component(EpicsSignal, "-PSO:VAR-3", put_complete=True, kind=Kind.omitted) + _var_4 = Component(EpicsSignal, "-PSO:VAR-4", put_complete=True, kind=Kind.omitted) + _var_5 = Component(EpicsSignal, "-PSO:VAR-5", put_complete=True, kind=Kind.omitted) + _var_6 = Component(EpicsSignal, "-PSO:VAR-6", put_complete=True, kind=Kind.omitted) + _var_7 = Component(EpicsSignal, "-PSO:VAR-7", put_complete=True, kind=Kind.omitted) + _var_8 = Component(EpicsSignal, "-PSO:VAR-8", put_complete=True, kind=Kind.omitted) + _var_9 = Component(EpicsSignal, "-PSO:VAR-9", put_complete=True, kind=Kind.omitted) + _var_10 = Component(EpicsSignal, "-PSO:VAR-10", put_complete=True, kind=Kind.omitted) # A few PVs still needed from grid - raster_scan_done = Component(EpicsSignal, "-ES-GRD:SCAN-DONE", kind=Kind.config) - raster_num_rows = Component(EpicsSignal, "-ES-GRD:ROW-DONE", kind=Kind.config) + raster_scan_done = Component(EpicsSignal, "-GRD:SCAN-DONE", kind=Kind.config) + raster_num_rows = Component(EpicsSignal, "-GRD:ROW-DONE", kind=Kind.config) def set_axis_mode(self, mode: str, settle_time=0.1) -> None: if mode=="direct": @@ -184,7 +194,25 @@ class AerotechAbrStage(Device): self.axisAxesMode.set(MEASURING_MODE, settle_time=settle_time).wait() def configure(self, d: dict) -> tuple: + """" Configure the exposure scripts + Performs the configuration of the exposure scrips, i.e. sets the + required global variables. + + Parameters + ---------- + scan_command: + var_1: + var_2: + var_3: + var_4: + var_5: + var_6: + var_7: + var_8: + var_9: + var_10: + """ old = self.read_configuration() # ToDo: Check if idle before reconfiguring @@ -285,25 +313,25 @@ class AerotechAbrStage(Device): def measurement_state(self, value): self.osc.phase.set(value).wait() - @property - def shutter(self): - return self._shutter.get() + # @property + # def shutter(self): + # return self._shutter.get() - @shutter.setter - def shutter(self, value): - if self.axisAxesMode.get(): - print("ABR is not in direct mode; cannot manipulate shutter") - return False - state = str(state).lower() - if state not in ["1", "0", "closed", "open"]: - print("unknown shutter state requested") - return None - elif state in ["1", "open"]: - state = 1 - elif state == ["0", "closed"]: - state = 0 - self._shutter.set(state).wait() - return state == self._shutter.get() + # @shutter.setter + # def shutter(self, value): + # if self.axisAxesMode.get(): + # print("ABR is not in direct mode; cannot manipulate shutter") + # return False + # state = str(state).lower() + # if state not in ["1", "0", "closed", "open"]: + # print("unknown shutter state requested") + # return None + # elif state in ["1", "open"]: + # state = 1 + # elif state == ["0", "closed"]: + # state = 0 + # self._shutter.set(state).wait() + # return state == self._shutter.get() @property def axis_mode(self): @@ -395,5 +423,5 @@ class AerotechAbrStage(Device): if __name__ == "__main__": - abr = AerotechAbrStage(prefix="X06DA-ES-DF1", name="abr") + abr = AerotechAbrStage(prefix="X06DA-ES", name="abr") abr.wait_for_connection() \ No newline at end of file diff --git a/pxiii_bec/devices/__init__.py b/pxiii_bec/devices/__init__.py index e69de29..4031fa7 100644 --- a/pxiii_bec/devices/__init__.py +++ b/pxiii_bec/devices/__init__.py @@ -0,0 +1,2 @@ +from .aeroA3200 import A3200Axis +from .A3200 import AerotechAbrStage \ No newline at end of file diff --git a/pxiii_bec/devices/aeroA3200.py b/pxiii_bec/devices/aeroA3200.py index e319038..34ef135 100644 --- a/pxiii_bec/devices/aeroA3200.py +++ b/pxiii_bec/devices/aeroA3200.py @@ -148,34 +148,34 @@ class A3200RasterScanner(Device): a standard Ophyd positioner for the BEC. It also has some additional functionality for diagnostics.""" - x_start = Component(EpicsSignal, "-ES-GRD:GMX-START", kind=Kind.config) - x_start = Component(EpicsSignal, "-ES-GRD:GMX-END", kind=Kind.config) - omega = Component(EpicsSignal, "-ES-OSC:#M-START", kind=Kind.config) - osc = Component(EpicsSignal, "-ES-GRD:ANGLE", kind=Kind.config) - celltime = Component(EpicsSignal, "-ES-GRD:CELL-TIME", kind=Kind.config) + x_start = Component(EpicsSignal, "-GRD:GMX-START", kind=Kind.config) + x_start = Component(EpicsSignal, "-GRD:GMX-END", kind=Kind.config) + omega = Component(EpicsSignal, "-OSC:#M-START", kind=Kind.config) + osc = Component(EpicsSignal, "-GRD:ANGLE", kind=Kind.config) + celltime = Component(EpicsSignal, "-GRD:CELL-TIME", kind=Kind.config) - y_start = Component(EpicsSignal, "-ES-OSC:#STY-START", kind=Kind.config) - y_end = Component(EpicsSignal, "-ES-OSC:#STY-END", kind=Kind.config) - z_start = Component(EpicsSignal, "-ES-OSC:#STZ-START", kind=Kind.config) - z_end = Component(EpicsSignal, "-ES-OSC:#STZ-END", kind=Kind.config) + y_start = Component(EpicsSignal, "-OSC:#STY-START", kind=Kind.config) + y_end = Component(EpicsSignal, "-OSC:#STY-END", kind=Kind.config) + z_start = Component(EpicsSignal, "-OSC:#STZ-START", kind=Kind.config) + z_end = Component(EpicsSignal, "-OSC:#STZ-END", kind=Kind.config) - columns = Component(EpicsSignal, "-ES-GRD:COLUMNS", kind=Kind.config) - rows = Component(EpicsSignal, "-ES-GRD:ROWS", kind=Kind.config) - delay = Component(EpicsSignal, "-ES-GRD:RAST-DLY", kind=Kind.config) - osc_mode = Component(EpicsSignal, "-ES-GRD:SET-MODE", kind=Kind.config) + columns = Component(EpicsSignal, "-GRD:COLUMNS", kind=Kind.config) + rows = Component(EpicsSignal, "-GRD:ROWS", kind=Kind.config) + delay = Component(EpicsSignal, "-GRD:RAST-DLY", kind=Kind.config) + osc_mode = Component(EpicsSignal, "-GRD:SET-MODE", kind=Kind.config) - velo = Component(EpicsSignal, "-ES-GRD:#X-VEL", kind=Kind.config) - get_ready = Component(EpicsSignal, "-ES-GRD:READY.PROC", kind=Kind.config) - grid_start = Component(EpicsSignal, "-ES-GRD:START.PROC", kind=Kind.config) - grid_next = Component(EpicsSignal, "-ES-GRD:NEXT-ROW", kind=Kind.config) - row_done = Component(EpicsSignal, "-ES-GRD:ROW-DONE", kind=Kind.config) - scan_done = Component(EpicsSignal, "-ES-GRD:SCAN-DONE", kind=Kind.config) - grid_done = Component(EpicsSignal, "-ES-GRD:DONE", kind=Kind.config) + velo = Component(EpicsSignal, "-GRD:#X-VEL", kind=Kind.config) + get_ready = Component(EpicsSignal, "-GRD:READY.PROC", kind=Kind.config) + grid_start = Component(EpicsSignal, "-GRD:START.PROC", kind=Kind.config) + grid_next = Component(EpicsSignal, "-GRD:NEXT-ROW", kind=Kind.config) + row_done = Component(EpicsSignal, "-GRD:ROW-DONE", kind=Kind.config) + scan_done = Component(EpicsSignal, "-GRD:SCAN-DONE", kind=Kind.config) + grid_done = Component(EpicsSignal, "-GRD:DONE", kind=Kind.config) # Also needs the command interface - _zcmd = Component(EpicsSignal, "-ES-PSO:CMD", put_complete=True, kind=Kind.omitted) - _start_command = Component(EpicsSignal, "-ES-PSO:START-TEST.PROC", put_complete=True, kind=Kind.omitted) - _stop_command = Component(EpicsSignal, "-ES-PSO:STOP-TEST.PROC", put_complete=True, kind=Kind.omitted) + _zcmd = Component(EpicsSignal, "-PSO:CMD", put_complete=True, kind=Kind.omitted) + _start_command = Component(EpicsSignal, "-PSO:START-TEST.PROC", put_complete=True, kind=Kind.omitted) + _stop_command = Component(EpicsSignal, "-PSO:STOP-TEST.PROC", put_complete=True, kind=Kind.omitted) def raster_setup(self, positions, columns, angle, etime): @@ -346,12 +346,12 @@ class A3200Oscillator(Device): """No clue what this does, seems to be redundant with the task based grid scanners... """ - ostart_pos = Component(EpicsSignal, "-ES-OSC:START-POS", put_complete=True, kind=Kind.config) - orange = Component(EpicsSignal, "-ES-OSC:RANGE", put_complete=True, kind=Kind.config) - etime = Component(EpicsSignal, "-ES-OSC:ETIME", put_complete=True, kind=Kind.config) - get_ready = Component(EpicsSignal, "-ES-OSC:READY.PROC", put_complete=True, kind=Kind.config) - taskStart = Component(EpicsSignal, "-ES-OSC:START", put_complete=True, kind=Kind.config) - phase = Component(EpicsSignal, "-ES-OSC:DONE", auto_monitor=True, kind=Kind.config) + ostart_pos = Component(EpicsSignal, "-OSC:START-POS", put_complete=True, kind=Kind.config) + orange = Component(EpicsSignal, "-OSC:RANGE", put_complete=True, kind=Kind.config) + etime = Component(EpicsSignal, "-OSC:ETIME", put_complete=True, kind=Kind.config) + get_ready = Component(EpicsSignal, "-OSC:READY.PROC", put_complete=True, kind=Kind.config) + taskStart = Component(EpicsSignal, "-OSC:START", put_complete=True, kind=Kind.config) + phase = Component(EpicsSignal, "-OSC:DONE", auto_monitor=True, kind=Kind.config) @property def is_done(self): diff --git a/pxiii_bec/scans/__init__.py b/pxiii_bec/scans/__init__.py index e69de29..0d5714f 100644 --- a/pxiii_bec/scans/__init__.py +++ b/pxiii_bec/scans/__init__.py @@ -0,0 +1 @@ +from .mx_measurements import MeasureStandardWedge \ No newline at end of file -- 2.49.1 From 23aadabfd1df9d51209d79637a82ff8f163e426d Mon Sep 17 00:00:00 2001 From: Unknown MX Person Date: Wed, 18 Sep 2024 12:49:33 +0200 Subject: [PATCH 10/25] First Aerotech scan works --- pxiii_bec/config_saved.yaml | 2107 ++++++++++++++++++++++++++++ pxiii_bec/config_saved.yaml~ | 2094 +++++++++++++++++++++++++++ pxiii_bec/devices/A3200.py | 20 +- pxiii_bec/scans/__init__.py | 2 +- pxiii_bec/scans/mx_measurements.py | 606 ++++---- 5 files changed, 4470 insertions(+), 359 deletions(-) create mode 100644 pxiii_bec/config_saved.yaml create mode 100644 pxiii_bec/config_saved.yaml~ diff --git a/pxiii_bec/config_saved.yaml b/pxiii_bec/config_saved.yaml new file mode 100644 index 0000000..3a946b8 --- /dev/null +++ b/pxiii_bec/config_saved.yaml @@ -0,0 +1,2107 @@ +aptrx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +aptry: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bim2x: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bim2y: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bm1trx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bm1try: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bm2trx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bm2try: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bm3trx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bm3try: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bm4trx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bm4try: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bm5trx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bm5try: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bm6trx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bm6try: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bpm3a: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm3b: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm3c: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm3d: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm3i: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm3x: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm3y: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm3z: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm4a: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm4b: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm4c: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm4d: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm4i: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm4r: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bpm4s: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm4x: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm4xf: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm4xm: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm4y: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm4yf: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm4ym: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm4z: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm5a: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm5b: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm5c: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm5d: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm5i: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm5r: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bpm5x: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm5y: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm5z: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm6a: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm6b: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm6c: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm6d: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm6i: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm6x: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm6y: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm6z: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bs1x: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bs1y: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bs2x: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bs2y: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +burstn: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +burstr: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +curr: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +ddg1a: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ddg1b: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ddg1c: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ddg1d: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ddg1e: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ddg1f: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ddg1g: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ddg1h: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +dettrx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +di2trx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +di2try: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +diode: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +dtpush: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +dtth: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +dttrx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +dttry: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +dttrz: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +dyn_signals: + deviceClass: ophyd_devices.sim.sim.SynDynamicComponents + deviceConfig: {} + enabled: true + readOnly: false + readoutPriority: baseline +ebcsx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ebcsy: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ebfi1: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ebfi2: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ebfi3: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ebfi4: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ebfzpx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ebfzpy: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ebpmdx: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +ebpmdy: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +ebpmux: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +ebpmuy: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +ebtrx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ebtry: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ebtrz: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +eiger: + deviceClass: ophyd_devices.SimCamera + deviceConfig: + device_access: true + deviceTags: + - detector + enabled: true + readOnly: false + readoutPriority: async + softwareTrigger: true +eyefoc: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +eyex: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +eyey: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +fi1try: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +fi2try: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +fi3try: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +flyer_sim: + deviceClass: ophyd_devices.SynFlyer + deviceConfig: + delay: 1 + device_access: true + update_frequency: 400 + deviceTags: + - flyer + enabled: true + readOnly: false + readoutPriority: on_request +fsh1x: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +fsh2x: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ftp: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +ftrans: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +fttrx1: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +fttrx2: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +fttry1: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +fttry2: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +fttrz: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +hexapod: + deviceClass: ophyd_devices.SynDeviceOPAAS + deviceConfig: {} + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +hrox: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +hroy: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +hroz: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +hx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +hy: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +hz: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +idgap: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mbsx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +mbsy: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +mibd: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mibd1: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mibd2: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +miroll: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mith: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mitrx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mitry: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mitry1: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mitry2: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mitry3: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mobd: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mobdai: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mobdbo: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mobdco: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mobddi: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mokev: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +monitor_async: + deviceClass: ophyd_devices.sim.sim_monitor.SimMonitorAsync + deviceConfig: + sim_init: + model: GaussianModel + params: + amplitude: 500 + center: 0 + sigma: 1 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: async + softwareTrigger: true +mopush1: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mopush2: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +moroll1: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +moroll2: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +moth1: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +moth1e: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +moth2: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +moth2e: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +motrx2: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +motry: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +motry2: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +motrz1: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +motrz1e: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +moyaw2: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +pinx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +piny: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +pinz: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +pseudo_signal1: + deviceClass: ophyd_devices.ComputedSignal + deviceConfig: + compute_method: "def compute_signals(signal1, signal2):\n return signal1.get()*signal2.get()\n" + input_signals: + - bpm4i_readback + - bpm5i_readback + enabled: true + readOnly: false + readoutPriority: baseline +ring_current_sim: + deviceClass: ophyd_devices.ReadOnlySignal + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +samx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +samy: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +samz: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +sl0ch: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl0trxi: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl0trxo: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl0wh: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl1ch: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl1cv: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl1trxi: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl1trxo: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl1tryb: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl1tryt: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl1wh: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl1wv: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl2ch: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl2cv: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl2trxi: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl2trxo: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl2tryb: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl2tryt: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl2wh: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl2wv: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl3ch: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl3cv: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl3trxi: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl3trxo: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl3tryb: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl3tryt: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl3wh: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl3wv: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl4ch: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl4cv: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl4trxi: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl4trxo: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl4tryb: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl4tryt: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl4wh: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl4wv: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl5ch: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl5cv: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl5trxi: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl5trxo: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl5tryb: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl5tryt: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl5wh: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl5wv: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +strox: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +stroy: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +stroz: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sttrx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sttry: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +temp: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +transd: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +# schade eigentlich .... +#gauss_bpm: +# readoutPriority: monitored +# #deviceClass: sim:sim:SynGaussBEC +# deviceClass: ophyd_devices.SimMonitor +# deviceConfig: +# sigma: 1 +# noise: 'uniform' +# noise_multiplier: 0.4 +# deviceTags: +# - beamline +# enabled: true +# readOnly: False diff --git a/pxiii_bec/config_saved.yaml~ b/pxiii_bec/config_saved.yaml~ new file mode 100644 index 0000000..b30580b --- /dev/null +++ b/pxiii_bec/config_saved.yaml~ @@ -0,0 +1,2094 @@ +aptrx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +aptry: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bim2x: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bim2y: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bm1trx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bm1try: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bm2trx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bm2try: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bm3trx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bm3try: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bm4trx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bm4try: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bm5trx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bm5try: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bm6trx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bm6try: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bpm3a: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm3b: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm3c: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm3d: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm3i: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm3x: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm3y: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm3z: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm4a: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm4b: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm4c: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm4d: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm4i: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm4r: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bpm4s: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm4x: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm4xf: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm4xm: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm4y: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm4yf: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm4ym: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm4z: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm5a: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm5b: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm5c: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm5d: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm5i: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm5r: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bpm5x: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm5y: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm5z: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm6a: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm6b: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm6c: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm6d: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm6i: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm6x: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm6y: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bpm6z: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +bs1x: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bs1y: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bs2x: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +bs2y: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +burstn: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +burstr: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +curr: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +ddg1a: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ddg1b: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ddg1c: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ddg1d: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ddg1e: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ddg1f: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ddg1g: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ddg1h: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +dettrx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +di2trx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +di2try: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +diode: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +dtpush: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +dtth: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +dttrx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +dttry: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +dttrz: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +dyn_signals: + deviceClass: ophyd_devices.sim.sim.SynDynamicComponents + deviceConfig: {} + enabled: true + readOnly: false + readoutPriority: baseline +ebcsx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ebcsy: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ebfi1: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ebfi2: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ebfi3: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ebfi4: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ebfzpx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ebfzpy: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ebpmdx: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +ebpmdy: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +ebpmux: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +ebpmuy: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +ebtrx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ebtry: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ebtrz: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +eiger: + deviceClass: ophyd_devices.SimCamera + deviceConfig: + device_access: true + deviceTags: + - detector + enabled: true + readOnly: false + readoutPriority: async + softwareTrigger: true +eyefoc: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +eyex: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +eyey: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +fi1try: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +fi2try: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +fi3try: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +flyer_sim: + deviceClass: ophyd_devices.SynFlyer + deviceConfig: + delay: 1 + device_access: true + update_frequency: 400 + deviceTags: + - flyer + enabled: true + readOnly: false + readoutPriority: on_request +fsh1x: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +fsh2x: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +ftp: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +ftrans: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +fttrx1: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +fttrx2: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +fttry1: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +fttry2: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +fttrz: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +hexapod: + deviceClass: ophyd_devices.SynDeviceOPAAS + deviceConfig: {} + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +hrox: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +hroy: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +hroz: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +hx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +hy: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +hz: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +idgap: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mbsx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +mbsy: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +mibd: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mibd1: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mibd2: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +miroll: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mith: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mitrx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mitry: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mitry1: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mitry2: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mitry3: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mobd: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mobdai: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mobdbo: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mobdco: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mobddi: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mokev: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +monitor_async: + deviceClass: ophyd_devices.sim.sim_monitor.SimMonitorAsync + deviceConfig: + sim_init: + model: GaussianModel + params: + amplitude: 500 + center: 0 + sigma: 1 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: async + softwareTrigger: true +mopush1: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +mopush2: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +moroll1: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +moroll2: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +moth1: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +moth1e: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +moth2: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +moth2e: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +motrx2: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +motry: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +motry2: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +motrz1: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +motrz1e: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +moyaw2: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +pinx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +piny: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +pinz: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +pseudo_signal1: + deviceClass: ophyd_devices.ComputedSignal + deviceConfig: + compute_method: "def compute_signals(signal1, signal2):\n return signal1.get()*signal2.get()\n" + input_signals: + - bpm4i_readback + - bpm5i_readback + enabled: true + readOnly: false + readoutPriority: baseline +ring_current_sim: + deviceClass: ophyd_devices.ReadOnlySignal + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +samx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +samy: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +samz: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + limits: + - -50 + - 50 + tolerance: 0.01 + update_frequency: 400 + deviceTags: + - user motors + enabled: true + readOnly: false + readoutPriority: baseline +sl0ch: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl0trxi: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl0trxo: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl0wh: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl1ch: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl1cv: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl1trxi: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl1trxo: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl1tryb: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl1tryt: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl1wh: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl1wv: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl2ch: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl2cv: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl2trxi: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl2trxo: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl2tryb: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl2tryt: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl2wh: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl2wv: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl3ch: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl3cv: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl3trxi: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl3trxo: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl3tryb: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl3tryt: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl3wh: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl3wv: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl4ch: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl4cv: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl4trxi: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl4trxo: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl4tryb: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl4tryt: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl4wh: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl4wv: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl5ch: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl5cv: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl5trxi: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl5trxo: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl5tryb: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl5tryt: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl5wh: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sl5wv: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +strox: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +stroy: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +stroz: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sttrx: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +sttry: + deviceClass: ophyd_devices.SimPositioner + deviceConfig: + delay: 1 + update_frequency: 400 + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: baseline +temp: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored +transd: + deviceClass: ophyd_devices.SimMonitor + deviceConfig: {} + deviceTags: + - beamline + enabled: true + readOnly: false + readoutPriority: monitored diff --git a/pxiii_bec/devices/A3200.py b/pxiii_bec/devices/A3200.py index b606501..3ead2c6 100644 --- a/pxiii_bec/devices/A3200.py +++ b/pxiii_bec/devices/A3200.py @@ -220,25 +220,25 @@ class AerotechAbrStage(Device): # Set the corresponding global variables if "scan_command" in d: self.scan_command.set(d['scan_command']).wait() - if "var_1" in d: + 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: + if "var_2" in d and d['var_2'] is not None: self._var_2.set(d['var_2']).wait() - if "var_3" in d: + if "var_3" in d and d['var_3'] is not None: self._var_3.set(d['var_3']).wait() - if "var_4" in d: + if "var_4" in d and d['var_4'] is not None: self._var_4.set(d['var_4']).wait() - if "var_5" in d: + if "var_5" in d and d['var_5'] is not None: self._var_5.set(d['var_5']).wait() - if "var_6" in d: + if "var_6" in d and d['var_6'] is not None: self._var_6.set(d['var_6']).wait() - if "var_7" in d: + if "var_7" in d and d['var_7'] is not None: self._var_7.set(d['var_7']).wait() - if "var_8" in d: + if "var_8" in d and d['var_8'] is not None: self._var_8.set(d['var_8']).wait() - if "var_9" in d: + if "var_9" in d and d['var_9'] is not None: self._var_9.set(d['var_9']).wait() - if "var_10" in d: + if "var_10" in d and d['var_10'] is not None: self._var_10.set(d['var_10']).wait() new = self.read_configuration() diff --git a/pxiii_bec/scans/__init__.py b/pxiii_bec/scans/__init__.py index 0d5714f..9ca53ef 100644 --- a/pxiii_bec/scans/__init__.py +++ b/pxiii_bec/scans/__init__.py @@ -1 +1 @@ -from .mx_measurements import MeasureStandardWedge \ No newline at end of file +from .mx_measurements import MeasureFastOmega \ No newline at end of file diff --git a/pxiii_bec/scans/mx_measurements.py b/pxiii_bec/scans/mx_measurements.py index 9329772..dd61fb9 100644 --- a/pxiii_bec/scans/mx_measurements.py +++ b/pxiii_bec/scans/mx_measurements.py @@ -41,21 +41,66 @@ class AbrCmd: SCAN_SASTT_V3 = 21 -class MxFlyscanBase(AsyncFlyScanBase): + +class AerotechFlyscanBase(AsyncFlyScanBase): """ Base class for MX flyscans - I'd be quite easy to improve standardization. + Low-level base class for standard scans at the PX beamlines at SLS. Theese scans use the + A3200 rotation stage and the actual experiment is performed using an AeroBasic script + controlled via global variables. """ scan_report_hint = "table" arg_input = {} arg_bundle_size = {"bundle": len(arg_input), "min": None, "max": None} + # Aerotech stage config + abr_command = None + abr_globals = {} + abr_reset = False + abr_raster_reset =False + abr_complete = False + abr_timeout = None + def __init__(self, *args, parameter: dict = None, **kwargs): - """ Just set num_pos=0 to avoid hanging + """ Just set num_pos=0 to avoid hanging and override defaults if explicitly set from + parameters. """ self.num_pos = 0 super().__init__(parameter=parameter, **kwargs) + # Override if explicitly passed as parameter + if "abr_command" in self.caller_kwargs: + self.abr_command = self.caller_kwargs.get("abr_command") + if "abr_globals" in self.caller_kwargs: + self.abr_globals = self.caller_kwargs.get("abr_globals") + if "abr_reset" in self.caller_kwargs: + self.abr_reset = self.caller_kwargs.get("abr_reset") + if "abr_raster_reset" in self.caller_kwargs: + self.abr_raster_reset = self.caller_kwargs.get("abr_raster_reset") + if "abr_complete" in self.caller_kwargs: + self.abr_complete = self.caller_kwargs.get("abr_complete") + if "abr_timeout" in self.caller_kwargs: + self.abr_timeout = self.caller_kwargs.get("abr_timeout") + + def pre_scan(self): + # ToDo: Move roughly to start position + + # Reset the scan engine + if self.abr_reset: + yield from self.stubs.send_rpc_and_wait("abr", "reset") + if self.abr_raster_reset: + yield from self.stubs.command("abr", "raster_scan_done.set", 0) + + # Configure the global variables + d = {"scan_command" : self.abr_command} + d.update(self.abr_globals) + logger.info(d) + # Configure + yield from self.stubs.send_rpc_and_wait("abr", "configure", d) + + # Call super + yield from super().pre_scan() + def stage(self): """ ToDo: Sot sure if we should call super() here as it'd stage the whole beamline""" return super().stage() @@ -65,10 +110,13 @@ class MxFlyscanBase(AsyncFlyScanBase): """ self.pointID = 0 # Kick off the run - yield from self.stubs.command("abr", "start_command.set", 1) - + yield from self.stubs.send_rpc_and_wait("abr", "start_command.set", 1) logger.info("Measurement launched on the ABR stage...") - # ToDo: Wait for scan to finish + + # Wait for grid scanner to finish + if self.abr_complete and self.abr_timeout is not None: + st = yield from self.stubs.send_rpc_and_wait("abr", "complete", self.abr_timeout) + st.wait() def cleanup(self): """Set scan progress to 1 to finish the scan""" @@ -77,7 +125,7 @@ class MxFlyscanBase(AsyncFlyScanBase): -class MeasureStandardWedge(MxFlyscanBase): +class MeasureStandardWedge(AerotechFlyscanBase): """ Standard wedge scan Measure a standard continous line scan from `start` to `start` + `range` @@ -105,33 +153,25 @@ class MeasureStandardWedge(MxFlyscanBase): required_kwargs = ["start", "range", "scan_time"] def __init__(self, *args, parameter: dict = None, **kwargs): - super().__init__(parameter=parameter, **kwargs) self.axis = ['omega'] - - self.scan_command = AbrCmd.MEASURE_STANDARD - self.scan_start = self.caller_kwargs.get("start") - self.scan_range = self.caller_kwargs.get("range") - self.scan_scan_time = float(self.caller_kwargs.get("scan_time")) - self.scan_ready_rate = float(self.caller_kwargs.get("ready_rate", 500)) - - def pre_scan(self): - # ToDo: Move roughly to start position - - # Configure the global variables - d = {"scan_command" : self.scan_command, + # Set parameters from meaningful inputs + self.scan_start = parameter['kwargs'].get("start") + self.scan_range = parameter['kwargs'].get("range") + self.scan_scan_time = parameter['kwargs'].get("scan_time") + self.scan_ready_rate = parameter['kwargs'].get("ready_rate", 500) + self.abr_command = AbrCmd.MEASURE_STANDARD + self.abr_globals = { "var_1" : self.scan_start, "var_2" : self.scan_range, "var_3" : self.scan_scan_time, "var_4" : self.scan_ready_rate, } - yield from self.stubs.send_rpc_and_wait("abr", "configure", d) - - # Call super - yield from super().pre_scan() + # Call base class + super().__init__(parameter=parameter, **kwargs) -class MeasureScanSlits(MxFlyscanBase): +class MeasureScanSlits(AerotechFlyscanBase): """ Slit scan Measure a standard slit scan. @@ -156,31 +196,23 @@ class MeasureScanSlits(MxFlyscanBase): required_kwargs = ["delta_x", "delta_y", "velocity"] def __init__(self, *args, parameter: dict = None, **kwargs): - super().__init__(parameter=parameter, **kwargs) self.axis = [] - - self.scan_command = AbrCmd.SLIT_SCAN - self.scan_delta_x = self.caller_kwargs.get("delta_x") - self.scan_delta_y = self.caller_kwargs.get("delta_y") - self.scan_velocity = self.caller_kwargs.get("velocity") - - def pre_scan(self): - # ToDo: Move roughly to start position - - # Configure the global variables - d = {"scan_command" : self.scan_command, + # Set parameters from meaningful inputs + self.abr_command = AbrCmd.SLIT_SCAN + self.scan_delta_x = parameter['kwargs'].get("delta_x") + self.scan_delta_y = parameter['kwargs'].get("delta_y") + self.scan_velocity = parameter['kwargs'].get("velocity") + self.abr_globals = { "var_1" : self.scan_delta_x, "var_2" : self.scan_delta_y, "var_3" : self.scan_velocity, } - yield from self.stubs.send_rpc_and_wait("abr", "configure", d) - - # Call super - yield from super().pre_scan() + # Call base class + super().__init__(parameter=parameter, **kwargs) -class MeasureRasterSimple(MxFlyscanBase): +class MeasureRasterSimple(AerotechFlyscanBase): """ Simple raster scan Measure a simplified raster scan, assuming the goniometer is currently @@ -210,23 +242,16 @@ class MeasureRasterSimple(MxFlyscanBase): required_kwargs = ["exp_time", "cell_width", "cell_height", "ncols", "nrows"] def __init__(self, *args, parameter: dict = None, **kwargs): - super().__init__(parameter=parameter, **kwargs) self.axis = [] - - self.scan_command = AbrCmd.RASTER_SCAN_SIMPLE - self.scan_exp_time = self.caller_kwargs.get("exp_time") - self.scan_stepsize_x = self.caller_kwargs.get("cell_width") - self.scan_stepsize_y = self.caller_kwargs.get("cell_height") - self.scan_stepnum_x = int(self.caller_kwargs.get("ncols")) - self.scan_stepnum_y = int(self.caller_kwargs.get("nrows")) - - def pre_scan(self): - # ToDo: Move roughly to start position - - # Reset done flag - yield from self.stubs.command("abr", "raster_scan_done.set", 0) - # Configure the global variables - d = {"scan_command" : self.scan_command, + # Set parameters from meaningful inputs + self.scan_exp_time = parameter['kwargs'].get("exp_time") + self.scan_stepsize_x = parameter['kwargs'].get("cell_width") + self.scan_stepsize_y = parameter['kwargs'].get("cell_height") + self.scan_stepnum_x = parameter['kwargs'].get("ncols") + self.scan_stepnum_y = parameter['kwargs'].get("nrows") + self.abr_command = AbrCmd.RASTER_SCAN_SIMPLE + self.abr_raster_reset = True + self.abr_globals = { "var_1" : self.scan_exp_time, "var_2" : self.scan_stepsize_x, "var_3" : self.scan_stepsize_y, @@ -236,17 +261,12 @@ class MeasureRasterSimple(MxFlyscanBase): "var_7" : 0, "var_8" : 0, } - yield from self.stubs.send_rpc_and_wait("abr", "configure", d) - - # ToDo: This line was left out - #self.raster._raster_num_rows = nrows - - # Call super - yield from super().pre_scan() + # Call base class + super().__init__(parameter=parameter, **kwargs) -class MeasureSASTT(MxFlyscanBase): +class MeasureSASTT(AerotechFlyscanBase): """ Small angle scanning tensor tomography scan Measure a Small Angle Scanning Tensor Tomography scan on the specified @@ -288,54 +308,41 @@ class MeasureSASTT(MxFlyscanBase): required_kwargs = ["exp_time", "cell_width", "cell_height", "ncols", "nrows"] def __init__(self, *args, parameter: dict = None, **kwargs): - super().__init__(parameter=parameter, **kwargs) self.axis = [] - - self.scan_version = int(self.caller_kwargs.get("version", 1)) + # Set parameters from meaningful inputs + self.scan_version = int(parameter['kwargs'].get("version", 1)) if self.scan_version==1: - self.scan_command = AbrCmd.SCAN_SASTT + self.abr_command = AbrCmd.SCAN_SASTT if self.scan_version==2: - self.scan_command = AbrCmd.SCAN_SASTT_V2 + self.abr_command = AbrCmd.SCAN_SASTT_V2 if self.scan_version==3: - self.scan_command = AbrCmd.SCAN_SASTT_V3 + self.abr_command = AbrCmd.SCAN_SASTT_V3 else: raise RuntimeError(f"Non existing SAS-TT scan mode: {self.scan_version}") - self.scan_exp_time = self.caller_kwargs.get("exp_time") - self.scan_stepsize_x = self.caller_kwargs.get("cell_width") - self.scan_stepsize_y = self.caller_kwargs.get("cell_height") - self.scan_stepnum_x = int(self.caller_kwargs.get("ncols")) - self.scan_stepnum_y = int(self.caller_kwargs.get("nrows")) - self.scan_even_tweak = self.caller_kwargs.get("even_tweak", 0) - self.scan_odd_tweak = self.caller_kwargs.get("off_tweak", 0) - - def pre_scan(self): - # ToDo: Move roughly to start position - - # Reset done flag - yield from self.stubs.command("abr", "raster_scan_done.set", 0) - # Configure the global variables - d = {"scan_command" : self.scan_command, - "var_1" : self.scan_exp_time, - "var_2" : self.scan_stepsize_x, - "var_3" : self.scan_stepsize_y, - "var_4" : self.scan_stepnum_x, - "var_5" : self.scan_stepnum_y, - "var_6" : self.scan_even_tweak, - "var_7" : self.scan_odd_tweak, - "var_8" : 0, - } - yield from self.stubs.send_rpc_and_wait("abr", "configure", d) - - # ToDo: This line was left out - # self.raster._raster_num_rows = nrows - - # Call super - yield from self.stubs.pre_scan() + self.scan_exp_time = parameter['kwargs'].get("exp_time") + self.scan_stepsize_x = parameter['kwargs'].get("cell_width") + self.scan_stepsize_y = parameter['kwargs'].get("cell_height") + self.scan_stepnum_x = parameter['kwargs'].get("ncols") + self.scan_stepnum_y = parameter['kwargs'].get("nrows") + self.scan_even_tweak = parameter['kwargs'].get("even_tweak", 0) + self.scan_odd_tweak = parameter['kwargs'].get("odd_tweak", 0) + self.abr_globals = { + 'var_1': self.scan_exp_time, + 'var_2': self.scan_stepsize_x, + 'var_3': self.scan_stepsize_y, + 'var_4': self.scan_stepnum_x, + 'var_5': self.scan_stepnum_y, + 'var_6': self.scan_even_tweak, + 'var_7': self.scan_odd_tweak, + 'var_8': 0, + } + # Call base class + super().__init__(parameter=parameter, **kwargs) -class MeasureVerticalLine(MxFlyscanBase): +class MeasureVerticalLine(AerotechFlyscanBase): """ Vertical line scan using the GMY motor Simple fly scan that records a single vertical line. @@ -361,31 +368,23 @@ class MeasureVerticalLine(MxFlyscanBase): required_kwargs = ["cell_time", "cell_height", "cell_num"] def __init__(self, *args, parameter: dict = None, **kwargs): - super().__init__(parameter=parameter, **kwargs) self.axis = ['gmy'] - - self.scan_command = AbrCmd.VERTICAL_LINE_SCAN - self.scan_cell_time = self.caller_kwargs.get("cell_time") - self.scan_stepsize_y = self.caller_kwargs.get("cell_height") - self.scan_stepnum_y = int(self.caller_kwargs.get("cell_num")) - - def pre_scan(self): - # ToDo: Move roughly to start position - - # Configure the global variables - d = {"scan_command" : self.scan_command, - "var_1" : self.scan_stepsize_y, - "var_2" : self.scan_stepnum_y, - "var_3" : self.scan_cell_time, - } - yield from self.stubs.send_rpc_and_wait("abr", "configure", d) - - # Call super - yield from super().pre_scan() + # Set parameters from meaningful inputs + self.scan_cell_time = parameter['kwargs'].get("cell_time") + self.scan_stepsize_y = parameter['kwargs'].get("cell_height") + self.scan_stepnum_y = parameter['kwargs'].get("cell_num") + self.abr_command = AbrCmd.VERTICAL_LINE_SCAN + self.abr_globals = { + 'var_1': self.scan_cell_time, + 'var_2': self.scan_stepsize_y, + 'var_3': self.scan_stepnum_y, + } + # Call base class + super().__init__(parameter=parameter, **kwargs) -class MeasureScreening(MxFlyscanBase): +class MeasureScreening(AerotechFlyscanBase): """ Sample screening scan The scan itself is executed by the scan service running on the Aerotech @@ -412,22 +411,16 @@ class MeasureScreening(MxFlyscanBase): required_kwargs = ["start", "range", "scan_time", "degrees", "frames"] def __init__(self, *args, parameter: dict = None, **kwargs): - super().__init__(parameter=parameter, **kwargs) self.axis = ['gmy'] - - self.scan_command = AbrCmd.SCREENING - self.scan_start = self.caller_kwargs.get("start") - self.scan_range = self.caller_kwargs.get("range") - self.scan_scan_time = self.caller_kwargs.get("scan_time") - self.scan_degrees = self.caller_kwargs.get("degrees") - self.scan_frames = self.caller_kwargs.get("frames") - self.scan_delta = self.caller_kwargs.get("delta", 0.5) - - def pre_scan(self): - # ToDo: Move roughly to start position - - # Configure the global variables - d = {"scan_command" : self.scan_command, + # Set parameters from meaningful inputs + self.scan_start = parameter['kwargs'].get("start") + self.scan_range = parameter['kwargs'].get("range") + self.scan_scan_time = parameter['kwargs'].get("scan_time") + self.scan_degrees = parameter['kwargs'].get("degrees") + self.scan_frames = parameter['kwargs'].get("frames") + self.scan_delta = parameter['kwargs'].get("delta", 0.5) + self.abr_command = AbrCmd.SCREENING + self.abr_globals = { "var_1" : self.scan_start, "var_2" : self.scan_range, "var_3" : self.scan_scan_time, @@ -435,14 +428,12 @@ class MeasureScreening(MxFlyscanBase): "var_5" : self.scan_frames, "var_6" : self.scan_delta, } - yield from self.stubs.send_rpc_and_wait("abr", "configure", d) - - # Call super - yield from super().pre_scan() + # Call base class + super().__init__(parameter=parameter, **kwargs) -class MeasureFastOmega(MxFlyscanBase): +class MeasureFastOmega(AerotechFlyscanBase): """ Fast omega scan MEasures a fast rotational scan with the rotation stage (omega). @@ -469,33 +460,26 @@ class MeasureFastOmega(MxFlyscanBase): required_kwargs = ["start", "range", "scan_time"] def __init__(self, *args, parameter: dict = None, **kwargs): - super().__init__(parameter=parameter, **kwargs) self.axis = ['omega'] - - self.scan_command = AbrCmd.SUPER_FAST_OMEGA - self.scan_start = self.caller_kwargs.get("start") - self.scan_range = self.caller_kwargs.get("range") - self.scan_scan_time = self.caller_kwargs.get("scan_time") - self.scan_rate = self.caller_kwargs.get("rate", 200) - - def pre_scan(self): - # ToDo: Move roughly to start position - - # Configure the global variables - d = {"scan_command" : self.scan_command, + # Set parameters from meaningful inputs + logger.info(parameter) + self.scan_start = parameter['kwargs'].get("start") + self.scan_range = parameter['kwargs'].get("range") + self.scan_scan_time = parameter['kwargs'].get("scan_time") + self.scan_rate = parameter['kwargs'].get("rate", 200) + self.abr_command = AbrCmd.SUPER_FAST_OMEGA + self.abr_globals = { "var_1" : self.scan_start, "var_2" : self.scan_range, "var_3" : self.scan_scan_time, "var_4" : self.scan_rate, } - yield from self.stubs.send_rpc_and_wait("abr", "configure", d) - - # Call super - yield from self.stubs.pre_scan() + # Call base class + super().__init__(parameter=parameter, **kwargs) -class MeasureStillWedge(MxFlyscanBase): +class MeasureStillWedge(AerotechFlyscanBase): """ Still wedge scan The scan itself is executed by the scan service running on the Aerotech @@ -521,35 +505,27 @@ class MeasureStillWedge(MxFlyscanBase): required_kwargs = ["start", "range", "exp_time", "oscrange"] def __init__(self, *args, parameter: dict = None, **kwargs): - super().__init__(parameter=parameter, **kwargs) self.axis = ['omega'] - - self.scan_command = AbrCmd.STILL_WEDGE - self.scan_start = self.caller_kwargs.get("start") - self.scan_range = self.caller_kwargs.get("range") - self.scan_scan_time = self.caller_kwargs.get("scan_time") - self.scan_oscrange = self.caller_kwargs.get("oscrange") - self.scan_shutter_sleep = self.caller_kwargs.get("sleep_after_shutter_close", 0.01) - - def pre_scan(self): - # ToDo: Move roughly to start position - - # Configure the global variables - d = {"scan_command" : self.scan_command, + # Set parameters from meaningful inputs + self.scan_start = parameter['kwargs'].get("start") + self.scan_range = parameter['kwargs'].get("range") + self.scan_scan_time = parameter['kwargs'].get("scan_time") + self.scan_oscrange = parameter['kwargs'].get("oscrange") + self.scan_shutter_sleep = parameter['kwargs'].get("sleep_after_shutter_close", 0.01) + self.abr_command = AbrCmd.STILL_WEDGE + self.abr_globals = { "var_1" : self.scan_start, "var_2" : self.scan_range, "var_3" : self.scan_scan_time, "var_4" : self.scan_oscrange, "var_5" : self.scan_shutter_sleep, } - yield from self.stubs.send_rpc_and_wait("abr", "configure", d) - - # Call super - yield from self.stubs.pre_scan() + # Call base class + super().__init__(parameter=parameter, **kwargs) -class MeasureStills(MxFlyscanBase): +class MeasureStills(AerotechFlyscanBase): """ Still image sequence scan Measures a series of still images without any motor movement. @@ -575,31 +551,23 @@ class MeasureStills(MxFlyscanBase): required_kwargs = ["exp_time", "nframes", "idle_time"] def __init__(self, *args, parameter: dict = None, **kwargs): - super().__init__(parameter=parameter, **kwargs) self.axis = [] - - self.scan_command = AbrCmd.STILLS - self.scan_exp_time = self.caller_kwargs.get("exp_time") - self.scan_num_frames = int(self.caller_kwargs.get("nframes")) - self.scan_idle_time = self.caller_kwargs.get("idle_time") - - def pre_scan(self): - # ToDo: Move roughly to start position - - # Configure the global variables - d = {"scan_command" : self.scan_command, + # Set parameters from meaningful inputs + self.scan_exp_time = parameter['kwargs'].get("exp_time") + self.scan_num_frames = parameter['kwargs'].get("nframes") + self.scan_idle_time = parameter['kwargs'].get("idle_time") + self.abr_command = AbrCmd.STILLS + self.abr_globals = { "var_1" : self.scan_num_frames, "var_2" : self.scan_exp_time, "var_3" : self.scan_idle_time, } - yield from self.stubs.send_rpc_and_wait("abr", "configure", d) - - # Call super - yield from self.stubs.pre_scan() + # Call base class + super().__init__(parameter=parameter, **kwargs) -class MeasureSingleOscillation(MxFlyscanBase): +class MeasureSingleOscillation(AerotechFlyscanBase): """ Single oscillation scan The scan itself is executed by the scan service running on the Aerotech @@ -626,35 +594,27 @@ class MeasureSingleOscillation(MxFlyscanBase): required_kwargs = ["start", "range", "scan_time"] def __init__(self, *args, parameter: dict = None, **kwargs): - super().__init__(parameter=parameter, **kwargs) self.axis = ['omega'] - - self.scan_command = AbrCmd.SINGLE_OSCILLATION - self.scan_start = self.caller_kwargs.get("start") - self.scan_range = self.caller_kwargs.get("range") - self.scan_scan_time = self.caller_kwargs.get("scan_time") - self.scan_delta = self.caller_kwargs.get("delta", 0) - self.scan_settle = self.caller_kwargs.get("settle", 0.5) - - def pre_scan(self): - # ToDo: Move roughly to start position - - # Configure the global variables - d = {"scan_command" : self.scan_command, + # Set parameters from meaningful inputs + self.scan_start = parameter['kwargs'].get("start") + self.scan_range = parameter['kwargs'].get("range") + self.scan_scan_time = parameter['kwargs'].get("scan_time") + self.scan_delta = parameter['kwargs'].get("delta", 0) + self.scan_settle = parameter['kwargs'].get("settle", 0.5) + self.abr_command = AbrCmd.SINGLE_OSCILLATION + self.abr_globals = { "var_1" : self.scan_start, "var_2" : self.scan_range, "var_3" : self.scan_scan_time, "var_4" : self.scan_delta, "var_5" : self.scan_settle, } - yield from self.stubs.send_rpc_and_wait("abr", "configure", d) - - # Call super - yield from self.stubs.pre_scan() + # Call base class + super().__init__(parameter=parameter, **kwargs) -class MeasureRepeatedOscillation(MxFlyscanBase): +class MeasureRepeatedOscillation(AerotechFlyscanBase): """ Repeated oscillation scan The scan itself is executed by the scan service running on the Aerotech @@ -681,35 +641,27 @@ class MeasureRepeatedOscillation(MxFlyscanBase): required_kwargs = ["nframes", "range", "scan_time"] def __init__(self, *args, parameter: dict = None, **kwargs): - super().__init__(parameter=parameter, **kwargs) self.axis = ['omega'] - - self.scan_command = AbrCmd.REPEAT_SINGLE_OSCILLATION - self.scan_num_frames = self.caller_kwargs.get("nframes") - self.scan_range = self.caller_kwargs.get("range") - self.scan_scan_time = self.caller_kwargs.get("scan_time") - self.scan_sleep = self.caller_kwargs.get("sleep", 0) - self.scan_delta = self.caller_kwargs.get("delta", 0.5) - - def pre_scan(self): - # ToDo: Move roughly to start position - - # Configure the global variables - d = {"scan_command" : self.scan_command, + # Set parameters from meaningful inputs + self.scan_num_frames = parameter['kwargs'].get("nframes") + self.scan_range = parameter['kwargs'].get("range") + self.scan_scan_time = parameter['kwargs'].get("scan_time") + self.scan_sleep = parameter['kwargs'].get("sleep", 0) + self.scan_delta = parameter['kwargs'].get("delta", 0.5) + self.abr_command = AbrCmd.REPEAT_SINGLE_OSCILLATION + self.abr_globals = { "var_1" : self.scan_num_frames, "var_2" : self.scan_scan_time, "var_3" : self.scan_range, "var_4" : self.scan_sleep, "var_5" : self.scan_delta, } - yield from self.stubs.send_rpc_and_wait("abr", "configure", d) - - # Call super - yield from self.stubs.pre_scan() + # Call base class + super().__init__(parameter=parameter, **kwargs) -class MeasureMSOX(MxFlyscanBase): +class MeasureMSOX(AerotechFlyscanBase): """ Standard MSOX scan The scan itself is executed by the scan service running on the Aerotech @@ -736,24 +688,16 @@ class MeasureMSOX(MxFlyscanBase): required_kwargs = ["start", "range", "exp_time"] def __init__(self, *args, parameter: dict = None, **kwargs): - super().__init__(parameter=parameter, **kwargs) self.axis = ['omega'] - - self.scan_command = AbrCmd.MSOX - self.scan_start = self.caller_kwargs.get("start") - self.scan_range = self.caller_kwargs.get("range") - self.scan_exp_time = self.caller_kwargs.get("exp_time") - self.scan_num_datasets = int(self.caller_kwargs.get("num_datasets", 1)) - self.scan_rest_time = self.caller_kwargs.get("rest_time", 0) - - def pre_scan(self): - # ToDo: Move roughly to start position - - # Reset the scan engine - yield from self.stubs.send_rpc_and_wait("abr", "reset") - - # Configure the global variables - d = {"scan_command" : self.scan_command, + # Set parameters from meaningful inputs + self.scan_start = parameter['kwargs'].get("start") + self.scan_range = parameter['kwargs'].get("range") + self.scan_exp_time = parameter['kwargs'].get("exp_time") + self.scan_num_datasets = parameter['kwargs'].get("num_datasets", 1) + self.scan_rest_time = parameter['kwargs'].get("rest_time", 0) + self.abr_command = AbrCmd.MSOX + self.abr_reset = True + self.abr_globals = { "var_1" : self.scan_start, "var_2" : self.scan_range, "var_3" : self.scan_exp_time, @@ -765,14 +709,12 @@ class MeasureMSOX(MxFlyscanBase): "var_9" : 0, "var_10" : 0, } - yield from self.stubs.send_rpc_and_wait("abr", "configure", d) - - # Call super - yield from self.stubs.pre_scan() + # Call base class + super().__init__(parameter=parameter, **kwargs) -class MeasureGrid(MxFlyscanBase): +class MeasureGrid(AerotechFlyscanBase): """ General grid scan Note: This was probably never used in its current form, because the @@ -799,27 +741,30 @@ class MeasureGrid(MxFlyscanBase): scan_name = "measure_raster" required_kwargs = ["positions", "ncols", "angle", "exp_time"] - def __init__(self, *args, parameter: dict = None, **kwargs): - super().__init__(parameter=parameter, **kwargs) - self.axis = ['omega'] + # Default values + scan_start_x = None + scan_end_x = None + scan_xstep = 0.010 + scan_ystep = 0.010 + scan_zstep = 0.010 + scan_gmy_step = 0.010 - self.scan_command = AbrCmd.RASTER_SCAN - self.scan_positions = self.caller_kwargs.get("positions") - self.scan_stepnum_x = int(self.caller_kwargs.get("ncols")) + def __init__(self, *args, parameter: dict = None, **kwargs): + self.axis = ['omega'] + # Set parameters from meaningful inputs + self.abr_command = AbrCmd.RASTER_SCAN + self.abr_reset = True + self.scan_positions = parameter['kwargs'].get("positions") + self.scan_stepnum_x = parameter['kwargs'].get("ncols") self.scan_stepnum_y = self.scan_positions / self.scan_stepnum_x - self.scan_angle = self.caller_kwargs.get("angle") - self.scan_exp_time = self.caller_kwargs.get("exp_time") + self.scan_angle = parameter['kwargs'].get("angle") + self.scan_exp_time = parameter['kwargs'].get("exp_time") if len(self.scan_positions)==1: raise RuntimeWarning("Raster scan with one cell makes no sense") - - # Good practice: new members only in __init__ - self.scan_start_x = None - self.scan_end_x = None - self.scan_xstep = 0.010 - self.scan_ystep = 0.010 - self.scan_zstep = 0.010 - self.scan_gmy_step = 0.010 + + # Call base class + super().__init__(parameter=parameter, **kwargs) def prepare_positions(self): # Calculate step sizes @@ -851,14 +796,8 @@ class MeasureGrid(MxFlyscanBase): # self._raster_current_row = 0 # self._raster_num_rows = rows - def pre_scan(self): - # ToDo: Move roughly to start position - - # Reset the scan engine - yield from self.stubs.send_rpc_and_wait("abr", "reset") - # Configure the global variables - d = {"scan_command" : self.scan_command, + self.abr_globals = { "var_1" : self.scan_omega_position, "var_2" : self.scan_start_x, "var_3" : self.scan_end_x, @@ -870,16 +809,10 @@ class MeasureGrid(MxFlyscanBase): "var_9" : self.scan_gmx_offset, "var_10" : self.scan_gmy_step, } - yield from self.stubs.send_rpc_and_wait("abr", "configure", d) - - # Call super - yield from self.stubs.pre_scan() - - -class MeasureGridStill(MxFlyscanBase): +class MeasureGridStill(AerotechFlyscanBase): """ Still grid scan Note: This was probably never used in its current form, because the @@ -908,27 +841,29 @@ class MeasureGridStill(MxFlyscanBase): scan_name = "measure_raster_still" required_kwargs = ["positions", "columns", "exp_time", "delay"] - def __init__(self, *args, parameter: dict = None, **kwargs): - super().__init__(parameter=parameter, **kwargs) - self.axis = ['omega'] + # Default values + scan_start_x = None + scan_end_x = None + scan_xstep = 0.010 + scan_ystep = 0.010 + scan_zstep = 0.010 + scan_gmy_step = 0.010 - self.scan_command = AbrCmd.RASTER_SCAN_STILL - self.scan_positions = self.caller_kwargs.get("positions") - self.scan_stepnum_x = int(self.caller_kwargs.get("columns")) + def __init__(self, *args, parameter: dict = None, **kwargs): + self.axis = ['omega'] + # Set parameters from meaningful inputs + self.abr_command = AbrCmd.RASTER_SCAN_STILL + self.abr_reset = True + self.scan_positions = parameter['kwargs'].get("positions") + self.scan_stepnum_x = parameter['kwargs'].get("columns") self.scan_stepnum_y = self.scan_positions / self.scan_stepnum_x - self.scan_exp_time = self.caller_kwargs.get("exp_time") - self.scan_delay = self.caller_kwargs.get("delay") + self.scan_exp_time = parameter['kwargs'].get("exp_time") + self.scan_delay = parameter['kwargs'].get("delay") if len(self.scan_positions)==1: raise RuntimeWarning("Raster scan with one cell makes no sense") - - # Good practice: new members only in __init__ - self.scan_start_x = None - self.scan_end_x = None - self.scan_xstep = 0.010 - self.scan_ystep = 0.010 - self.scan_zstep = 0.010 - self.scan_gmy_step = 0.010 + # Call base class + super().__init__(parameter=parameter, **kwargs) def prepare_positions(self): # Calculate step sizes @@ -960,14 +895,7 @@ class MeasureGridStill(MxFlyscanBase): # self._raster_current_row = 0 # self._raster_num_rows = rows - def pre_scan(self): - # ToDo: Move roughly to start position - - # Reset the scan engine - yield from self.stubs.send_rpc_and_wait("abr", "reset") - - # Configure the global variables - d = {"scan_command" : self.scan_command, + self.abr_globals = { "var_1" : self.scan_omega_position, "var_2" : self.scan_start_x, "var_3" : self.scan_end_x, @@ -979,14 +907,10 @@ class MeasureGridStill(MxFlyscanBase): "var_9" : self.scan_gmx_offset, "var_10" : self.scan_gmy_step, } - yield from self.stubs.send_rpc_and_wait("abr", "configure", d) - - # Call super - yield from self.stubs.pre_scan() -class MeasureJungfrau(MxFlyscanBase): +class MeasureJungfrau(AerotechFlyscanBase): """ Scan with the Jungfrau detector The scan itself is executed by the scan service running on the Aerotech @@ -1015,24 +939,19 @@ class MeasureJungfrau(MxFlyscanBase): required_kwargs = ["columns", "rows", "width", "height", "exp_time"] def __init__(self, *args, parameter: dict = None, **kwargs): - super().__init__(parameter=parameter, **kwargs) self.axis = ['omega'] - - self.scan_command = AbrCmd.JUNGFRAU - self.scan_num_cols = self.caller_kwargs.get("columns") - self.scan_num_rows = self.caller_kwargs.get("rows") - self.scan_width = self.caller_kwargs.get("width") - self.scan_height = self.caller_kwargs.get("height") - self.scan_exp_time = self.caller_kwargs.get("exp_time") - self.scan_sleep = self.caller_kwargs.get("sleep", 0.1) - - self.scan_timeout = None if self.scan_sleep <= 0 else 5.0 + (self.scan_num_rows * self.scan_sleep) + (self.scan_num_cols * self.scan_num_rows * self.scan_exp_time) - - def pre_scan(self): - # ToDo: Move roughly to start position - - # Configure the global variables - d = {"scan_command" : self.scan_command, + # Set parameters from meaningful inputs + self.scan_num_cols = parameter['kwargs'].get("columns") + self.scan_num_rows = parameter['kwargs'].get("rows") + self.scan_width = parameter['kwargs'].get("width") + self.scan_height = parameter['kwargs'].get("height") + self.scan_exp_time = parameter['kwargs'].get("exp_time") + self.scan_sleep = parameter['kwargs'].get("sleep", 0.1) + self.abr_timeout = None if self.scan_sleep <= 0 else 5.0 + (self.scan_num_rows * self.scan_sleep) + (self.scan_num_cols * self.scan_num_rows * self.scan_exp_time) + self.abr_command = AbrCmd.JUNGFRAU + self.abr_reset = True + self.abr_complete = True + self.abr_globals = { "var_1" : self.scan_num_cols, "var_2" : self.scan_num_rows, "var_3" : self.scan_width, @@ -1041,23 +960,14 @@ class MeasureJungfrau(MxFlyscanBase): "var_6" : self.scan_sleep, "var_7" : 0, "var_8" : 0, - } - yield from self.stubs.send_rpc_and_wait("abr", "configure", d) - - # Call super - yield from self.stubs.pre_scan() + } + # Call base class + super().__init__(parameter=parameter, **kwargs) def scan_core(self): """ The actual scan logic comes here. """ - self.pointID = 0 - # Kick off the run - yield from self.stubs.command("abr", "start_command.set", 1) - logger.info("Measurement launched on the ABR stage...") - - # Wait for grid scanner to finish - st = yield from self.stubs.send_rpc_and_wait("abr", "complete", self.scan_timeout) - st.wait() + yield from super().scan_core() # Go back to direct mode st = yield from self.stubs.send_rpc_and_wait("abr", "set_axis_mode", "direct") -- 2.49.1 From 2563471ac8c1ade9b69219044a587c51fcbca29e Mon Sep 17 00:00:00 2001 From: Unknown MX Person Date: Wed, 18 Sep 2024 18:26:54 +0200 Subject: [PATCH 11/25] Dailiy commit --- pxiii_bec/scans/mx_measurements.py | 518 ++++++++++++++++------------- 1 file changed, 284 insertions(+), 234 deletions(-) diff --git a/pxiii_bec/scans/mx_measurements.py b/pxiii_bec/scans/mx_measurements.py index dd61fb9..41573c4 100644 --- a/pxiii_bec/scans/mx_measurements.py +++ b/pxiii_bec/scans/mx_measurements.py @@ -48,6 +48,9 @@ class AerotechFlyscanBase(AsyncFlyScanBase): Low-level base class for standard scans at the PX beamlines at SLS. Theese scans use the A3200 rotation stage and the actual experiment is performed using an AeroBasic script controlled via global variables. + + IMPORTANT: The AeroBasic scripts take care of the PSO configuration. + """ scan_report_hint = "table" arg_input = {} @@ -126,17 +129,17 @@ class AerotechFlyscanBase(AsyncFlyScanBase): class MeasureStandardWedge(AerotechFlyscanBase): - """ Standard wedge scan + """ Standard wedge scan using the OMEGA motor - Measure a standard continous line scan from `start` to `start` + `range` - during `scan_time` on the Omega axis. + Measure an absolute continous line scan from `start` to `start` + `range` + during `move_time` on the Omega axis with PSO output. The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. Example ------- - >>> scans.standard_wedge(start=42, range=10, scan_time=20) + >>> scans.standard_wedge(start=42, range=10, move_time=20) Parameters ---------- @@ -144,26 +147,26 @@ class MeasureStandardWedge(AerotechFlyscanBase): Scan start position of the axis. range : (float, float) Scan range of the axis. - scan_time : (float) - Total fly time for the movement [s]. + move_time : (float) + Total travel time for the movement [s]. ready_rate : float, optional No clue what is this... (default=500) """ scan_name = "standard_wedge" - required_kwargs = ["start", "range", "scan_time"] + required_kwargs = ["start", "range", "move_time"] def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ['omega'] + self.axis = ['abr.omega'] # Set parameters from meaningful inputs self.scan_start = parameter['kwargs'].get("start") self.scan_range = parameter['kwargs'].get("range") - self.scan_scan_time = parameter['kwargs'].get("scan_time") + self.scan_move_time = parameter['kwargs'].get("move_time") self.scan_ready_rate = parameter['kwargs'].get("ready_rate", 500) self.abr_command = AbrCmd.MEASURE_STANDARD self.abr_globals = { "var_1" : self.scan_start, "var_2" : self.scan_range, - "var_3" : self.scan_scan_time, + "var_3" : self.scan_move_time, "var_4" : self.scan_ready_rate, } # Call base class @@ -171,40 +174,88 @@ class MeasureStandardWedge(AerotechFlyscanBase): -class MeasureScanSlits(AerotechFlyscanBase): - """ Slit scan - Measure a standard slit scan. +class MeasureVerticalLine(AerotechFlyscanBase): + """ Vertical line scan using the GMY motor + + Simple relative continous line scan that records a single vertical line + with PSO output. There's no actual stepping, it's only used for velocity + calculation. The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. Example ------- - >>> scans.slit_scan(start=42, range=10, scan_time=20) + >>> scans.measure_vline(range_y=12, steps_y=40, exp_time=0.1) Parameters ---------- - delta_x : (float, float) - Scan range with slit in x. - delta_y : (float, float) - Scan range with slit in y. - velocity : (float) - Scan velocity [mm/s]. + range_y : float + Step size [mm]. + steps_y : int + Scan range of the axis. + exp_time : float + Eeffective exposure time per step [s] """ - scan_name = "slit_scan" - required_kwargs = ["delta_x", "delta_y", "velocity"] + scan_name = "measure_vline" + required_kwargs = ["exp_time", "range_y", "steps_y"] def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = [] + self.axis = ['abr.gmy'] + # Set parameters from meaningful inputs + self.scan_exp_time = parameter['kwargs'].get("exp_time") + self.scan_range_y = parameter['kwargs'].get("range_y") + self.scan_stepnum_y = parameter['kwargs'].get("steps_y") + self.scan_stepsize_y = self.scan_range_y / self.scan_stepnum_y + + self.abr_command = AbrCmd.VERTICAL_LINE_SCAN + self.abr_globals = { + 'var_1': self.scan_exp_time, + 'var_2': self.scan_stepsize_y, + 'var_3': self.scan_stepnum_y, + } + # Call base class + super().__init__(parameter=parameter, **kwargs) + + + +class MeasureScanSlits(AerotechFlyscanBase): + """ Coordinated scan + + Measure a hardware coordinated relative line scan with the X- and Y axes. + This is used to accurately track solid supports with long linear grooves, + as theese might be slightly rotated. + + The scan itself is executed by the scan service running on the Aerotech + controller. Ophyd just configures, launches it and waits for completion. + + Example + ------- + >>> scans.slit_scan(range=[10, 0.3], scan_time=20) + + Parameters + ---------- + range_x : float + Move distance in X [mm]. + range_y : float + Move distance in Y [mm]. + velocity : float + Effective movement velocity [mm/s]. + """ + scan_name = "slit_scan" + required_kwargs = ["range", "velocity"] + + def __init__(self, *args, parameter: dict = None, **kwargs): + self.axis = ["abr.gmx", "abr.gmy"] # Set parameters from meaningful inputs self.abr_command = AbrCmd.SLIT_SCAN - self.scan_delta_x = parameter['kwargs'].get("delta_x") - self.scan_delta_y = parameter['kwargs'].get("delta_y") + self.scan_range_x = parameter['kwargs'].get("range_x") + self.scan_range_y = parameter['kwargs'].get("range_y") self.scan_velocity = parameter['kwargs'].get("velocity") self.abr_globals = { - "var_1" : self.scan_delta_x, - "var_2" : self.scan_delta_y, + "var_1" : self.scan_range_x, + "var_2" : self.scan_range_y, "var_3" : self.scan_velocity, } # Call base class @@ -215,40 +266,45 @@ class MeasureScanSlits(AerotechFlyscanBase): class MeasureRasterSimple(AerotechFlyscanBase): """ Simple raster scan - Measure a simplified raster scan, assuming the goniometer is currently - at the CENTER of TOP-LEFT CELL. + Measure a simplified relative zigzag raster scan in the X-Y plane. + The scan is relative assumes the goniometer is currently at the CENTER of + the first cell (i.e. TOP-LEFT). Each line is executed as a single continous + movement, i.e. there's no actual stepping in the X direction. The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. Example ------- - >>> scans.raster_simple(exp_time=0.5, cell_width=0.2, cell_height=0.2, ncols=10, nrows=10) + >>> scans.raster_simple(exp_time=0.1, range_x=4, range_y=4, steps_x=80, steps_y=80) Parameters ---------- - exp_time : (float) - Exposure time per point [s]. - cell_width : (float) + exp_time : float + Effective exposure time for each cell along the X axis [s]. + range_x : float Scan step size [mm]. - cell_height : (float) + range_y : float Scan step size [mm]. - ncols : (int) - Number of scan steps. - nrows : (int) - Number of scan steps. + steps_x : int + Number of scan steps in X (fast). Only used for velocity calculation. + steps_y : int + Number of scan steps in Y (slow). """ scan_name = "raster_simple" - required_kwargs = ["exp_time", "cell_width", "cell_height", "ncols", "nrows"] + required_kwargs = ["exp_time", "range_x", "range_y", "steps_x", "steps_y"] def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = [] + self.axis = ['abr.gmx', 'abr.gmy'] # Set parameters from meaningful inputs self.scan_exp_time = parameter['kwargs'].get("exp_time") - self.scan_stepsize_x = parameter['kwargs'].get("cell_width") - self.scan_stepsize_y = parameter['kwargs'].get("cell_height") - self.scan_stepnum_x = parameter['kwargs'].get("ncols") - self.scan_stepnum_y = parameter['kwargs'].get("nrows") + self.scan_range_x = parameter['kwargs'].get("range_x") + self.scan_range_y = parameter['kwargs'].get("range_y") + self.scan_stepnum_x = parameter['kwargs'].get("steps_x") + self.scan_stepnum_y = parameter['kwargs'].get("steps_y") + self.scan_stepsize_x = self.scan_range_x / self.scan_stepnum_x + self.scan_stepsize_y = self.scan_range_y / self.scan_stepnum_y + self.abr_command = AbrCmd.RASTER_SCAN_SIMPLE self.abr_raster_reset = True self.abr_globals = { @@ -269,29 +325,33 @@ class MeasureRasterSimple(AerotechFlyscanBase): class MeasureSASTT(AerotechFlyscanBase): """ Small angle scanning tensor tomography scan - Measure a Small Angle Scanning Tensor Tomography scan on the specified - grid assuming the goniometer is currently at the CENTER of the required - grid. + Measure a single projection for Small Angle Scanning Tensor Tomography. + It's essentially a separate grid scan, that can be modified independently. + The scan is relative and assumes the goniometer is currently at the CENTER + of the first cell (i.e. TOP-LEFT). Each line is executed as a single + continous movement, there's no actual stepping in the X direction. + + NOTE: Not all beamlines have all SASTT modes implemented. The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. Example ------- - >>> scans.measure_sastt(exp_time=1.0, cell_width=0.01, cell_height=0.01, ncols=100, nrows=100) + >>> scans.measure_sastt(exp_time=0.01, range_x=1, range_y=1, steps_x=200, steps_y=200) Parameters ---------- - exp_time : (float) - Exposure time per point [s]. - cell_width : (float) + exp_time : float + Effective exposure time for each cell along the X axis [s]. + range_x : float Scan step size [mm]. - cell_height : (float) + range_y : float Scan step size [mm]. - ncols : (int) - Number of scan steps. - nrows : (int) - Number of scan steps. + steps_x : int + Number of scan steps in X (fast). Only used for velocity calculation. + steps_y : int + Number of scan steps in Y (slow). odd_tweak : (float) Distance to be added before the open shutter command [mm]. Used only in scan version=3. Should be small (default: 0)! @@ -305,10 +365,10 @@ class MeasureSASTT(AerotechFlyscanBase): 3 = snake scan alternating PSO window for even/odd rows """ scan_name = "measure_sastt" - required_kwargs = ["exp_time", "cell_width", "cell_height", "ncols", "nrows"] + required_kwargs = ["exp_time", "range_x", "range_y", "steps_x", "steps_y"] def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = [] + self.axis = ['abr.gmx', 'abr.gmy'] # Set parameters from meaningful inputs self.scan_version = int(parameter['kwargs'].get("version", 1)) if self.scan_version==1: @@ -319,14 +379,18 @@ class MeasureSASTT(AerotechFlyscanBase): self.abr_command = AbrCmd.SCAN_SASTT_V3 else: raise RuntimeError(f"Non existing SAS-TT scan mode: {self.scan_version}") - + + # Set parameters from meaningful inputs self.scan_exp_time = parameter['kwargs'].get("exp_time") - self.scan_stepsize_x = parameter['kwargs'].get("cell_width") - self.scan_stepsize_y = parameter['kwargs'].get("cell_height") - self.scan_stepnum_x = parameter['kwargs'].get("ncols") - self.scan_stepnum_y = parameter['kwargs'].get("nrows") + self.scan_range_x = parameter['kwargs'].get("range_x") + self.scan_range_y = parameter['kwargs'].get("range_y") + self.scan_stepnum_x = parameter['kwargs'].get("steps_x") + self.scan_stepnum_y = parameter['kwargs'].get("steps_y") self.scan_even_tweak = parameter['kwargs'].get("even_tweak", 0) self.scan_odd_tweak = parameter['kwargs'].get("odd_tweak", 0) + self.scan_stepsize_x = self.scan_range_x / self.scan_stepnum_x + self.scan_stepsize_y = self.scan_range_y / self.scan_stepnum_y + self.abr_globals = { 'var_1': self.scan_exp_time, 'var_2': self.scan_stepsize_x, @@ -342,90 +406,57 @@ class MeasureSASTT(AerotechFlyscanBase): -class MeasureVerticalLine(AerotechFlyscanBase): - """ Vertical line scan using the GMY motor - - Simple fly scan that records a single vertical line. - The line is nummCells * cellHeight long and the exposureTime is per cell. - - The scan itself is executed by the scan service running on the Aerotech - controller. Ophyd just configures, launches it and waits for completion. - - Example - ------- - >>> scans.measure_vline(cell_height=12, cell_num=10, cell_time=20) - - Parameters - ---------- - cell_height : (float) - Step size [mm]. - cell_num : (int) - Scan range of the axis. - cell_time : (float) - Exposure time per cell [s] - """ - scan_name = "measure_vline" - required_kwargs = ["cell_time", "cell_height", "cell_num"] - - def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ['gmy'] - # Set parameters from meaningful inputs - self.scan_cell_time = parameter['kwargs'].get("cell_time") - self.scan_stepsize_y = parameter['kwargs'].get("cell_height") - self.scan_stepnum_y = parameter['kwargs'].get("cell_num") - self.abr_command = AbrCmd.VERTICAL_LINE_SCAN - self.abr_globals = { - 'var_1': self.scan_cell_time, - 'var_2': self.scan_stepsize_y, - 'var_3': self.scan_stepnum_y, - } - # Call base class - super().__init__(parameter=parameter, **kwargs) - class MeasureScreening(AerotechFlyscanBase): """ Sample screening scan + Sample screening scan that scans intervals on the rotation axis taking + 1 image/interval. This makes sure that we hit diffraction peaks if there + are any crystals. + The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. Example ------- - >>> scans.measure_screening(start=42, range=10, scan_time=20, degrees=180, frames=1800) + >>> scans.measure_screening(start=42, range=180, steps=18, exp_time=0.1, oscrange=2.0) Parameters ---------- start : float - Scan start position of the axis. + Absolute scan start position of the omega axis [deg]. range : float - Scan range of the axis. - scan_time : float - Total scan time for the movement [s]. - degrees : (???) - frames : (???) - delta : (???) - (default: 0.5). + Total screened range of the omega axis relative to 'start' [deg]. + steps : int + Number of blurred intervals. + exp_time : float + Exposure time per blurred interval [s]. + oscrange : float + Motion blurring of each interval [deg] + delta : float + Safety margin for sub-range movements (default: 0.5) [deg]. """ scan_name = "measure_screening" - required_kwargs = ["start", "range", "scan_time", "degrees", "frames"] + required_kwargs = ["start", "range", "steps", "exp_time", "oscrange"] def __init__(self, *args, parameter: dict = None, **kwargs): self.axis = ['gmy'] # Set parameters from meaningful inputs self.scan_start = parameter['kwargs'].get("start") - self.scan_range = parameter['kwargs'].get("range") - self.scan_scan_time = parameter['kwargs'].get("scan_time") - self.scan_degrees = parameter['kwargs'].get("degrees") - self.scan_frames = parameter['kwargs'].get("frames") + self.scan_range_o = parameter['kwargs'].get("range") + self.scan_stepnum_o = parameter['kwargs'].get("steps") + self.scan_exp_time = parameter['kwargs'].get("exp_time") + self.scan_oscrange = parameter['kwargs'].get("oscrange") self.scan_delta = parameter['kwargs'].get("delta", 0.5) + self.scan_stepsize_o = self.scan_range_o / self.scan_stepnum_o self.abr_command = AbrCmd.SCREENING self.abr_globals = { "var_1" : self.scan_start, - "var_2" : self.scan_range, - "var_3" : self.scan_scan_time, - "var_4" : self.scan_degrees, - "var_5" : self.scan_frames, + "var_2" : self.scan_oscrange, + "var_3" : self.scan_exp_time, + "var_4" : self.scan_stepsize_o, + "var_5" : self.scan_stepnum_o, "var_6" : self.scan_delta, } # Call base class @@ -436,28 +467,32 @@ class MeasureScreening(AerotechFlyscanBase): class MeasureFastOmega(AerotechFlyscanBase): """ Fast omega scan - MEasures a fast rotational scan with the rotation stage (omega). + Performs a fast rotational scan with the rotation stage (omega). It ramps + up to constant speed and sets off PSO for the expected travel time. I.e. + not really a PSO output (as it ignores acceleration distance) but it's + expected to trigger at-speed for 'range' distance. The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. Example ------- - >>> scans.measure_fast_omega(start=42, range=180, scan_time=20) + >>> scans.measure_fast_omega(start=42, range=180, move_time=1.0) Parameters ---------- start : float - Scan start position of the axis. + Scan start position of the axis [deg]. range : float - Scan range of the axis. - scan_time : float - Total scan time for the movement [s]. + At-speed range of the axis relative to 'start' + acceleration + distance [deg]. + move_time : float + Total time for the at-speed movement and trigger [s]. rate : (???) (default: 200). """ scan_name = "measure_fast_omega" - required_kwargs = ["start", "range", "scan_time"] + required_kwargs = ["start", "range", "move_time"] def __init__(self, *args, parameter: dict = None, **kwargs): self.axis = ['omega'] @@ -465,14 +500,14 @@ class MeasureFastOmega(AerotechFlyscanBase): logger.info(parameter) self.scan_start = parameter['kwargs'].get("start") self.scan_range = parameter['kwargs'].get("range") - self.scan_scan_time = parameter['kwargs'].get("scan_time") - self.scan_rate = parameter['kwargs'].get("rate", 200) + self.scan_move_time = parameter['kwargs'].get("move_time") + self.scan_acceleration = parameter['kwargs'].get("acceleration", 200) self.abr_command = AbrCmd.SUPER_FAST_OMEGA self.abr_globals = { "var_1" : self.scan_start, "var_2" : self.scan_range, - "var_3" : self.scan_scan_time, - "var_4" : self.scan_rate, + "var_3" : self.scan_move_time, + "var_4" : self.scan_acceleration, } # Call base class super().__init__(parameter=parameter, **kwargs) @@ -482,42 +517,47 @@ class MeasureFastOmega(AerotechFlyscanBase): class MeasureStillWedge(AerotechFlyscanBase): """ Still wedge scan + Measure a simple step scan with the omega stage with PSO triggering. + The scan is similar to the continous wedge scan. + The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. Example ------- - >>> scans.measure_still_wedge(start=42, range=90, scan_time=20, oscrange=15) + >>> scans.measure_still_wedge(start=42, range=180, exp_time=0.1, steps=60) Parameters ---------- start : (float) - Scan start position of the axis. + Scan start position of the axis [mm]. range : (float) - Scan range of the axis. - scan_time : (float) - Total scan time for the movement [s]. - oscrange : (float) + Scan range of the omega axis [mm]. + exp_time : (float) + Exposure time per point [s]. + steps : int + Number of actual steps. sleep_after_shutter_close : (float) [s] (default: 0.01). """ scan_name = "measure_still_wedge" - required_kwargs = ["start", "range", "exp_time", "oscrange"] + required_kwargs = ["start", "range", "exp_time", "steps"] def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ['omega'] + self.axis = ['abr.omega'] # Set parameters from meaningful inputs self.scan_start = parameter['kwargs'].get("start") self.scan_range = parameter['kwargs'].get("range") - self.scan_scan_time = parameter['kwargs'].get("scan_time") - self.scan_oscrange = parameter['kwargs'].get("oscrange") + self.scan_stepnum_o = parameter['kwargs'].get("steps") + self.scan_exp_time = parameter['kwargs'].get("exp_time") self.scan_shutter_sleep = parameter['kwargs'].get("sleep_after_shutter_close", 0.01) + self.scan_stepsize_o = self.scan_stepnum_o / self.scan_stepnum_o self.abr_command = AbrCmd.STILL_WEDGE self.abr_globals = { "var_1" : self.scan_start, "var_2" : self.scan_range, - "var_3" : self.scan_scan_time, - "var_4" : self.scan_oscrange, + "var_3" : self.scan_exp_time, + "var_4" : self.scan_stepsize_o, "var_5" : self.scan_shutter_sleep, } # Call base class @@ -528,37 +568,37 @@ class MeasureStillWedge(AerotechFlyscanBase): class MeasureStills(AerotechFlyscanBase): """ Still image sequence scan - Measures a series of still images without any motor movement. - IMPORTANT: use idle_time=0.0 to prevent shutter open/close + Measures a series of PSO triggered images without any motor movement. + NOTE: Use idle_time=0.0 to prevent shutter open/close. The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. Example ------- - >>> scans.measure_stills(nframes=100, exp_time=0.1, idle_time=30) + >>> scans.measure_stills(steps=100, exp_time=0.1, idle_time=3) Parameters ---------- - nframes : int + steps : int Total number of frames to be recorded. - exposure_time : float + exp_time : float Exposure time of each frame [s]. idle_time : float Sleep time between the frames [s]. """ scan_name = "measure_stills" - required_kwargs = ["exp_time", "nframes", "idle_time"] + required_kwargs = ["exp_time", "steps", "idle_time"] def __init__(self, *args, parameter: dict = None, **kwargs): self.axis = [] # Set parameters from meaningful inputs self.scan_exp_time = parameter['kwargs'].get("exp_time") - self.scan_num_frames = parameter['kwargs'].get("nframes") + self.scan_numsteps = parameter['kwargs'].get("steps") self.scan_idle_time = parameter['kwargs'].get("idle_time") self.abr_command = AbrCmd.STILLS self.abr_globals = { - "var_1" : self.scan_num_frames, + "var_1" : self.scan_numsteps, "var_2" : self.scan_exp_time, "var_3" : self.scan_idle_time, } @@ -570,42 +610,44 @@ class MeasureStills(AerotechFlyscanBase): class MeasureSingleOscillation(AerotechFlyscanBase): """ Single oscillation scan + Short line scan with the omega axis with PSO trigger. + The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. Example ------- - >>> scans.measure_single_osc(start=42, range=180, scan_time=20) + >>> scans.measure_single_osc(start=42, range=180, move_time=20) Parameters ---------- start : float - Scan start position of the axis. + Scan start position of the omegaaxis [mm]. range : float - Oscillation range of the axis. - scan_time : float + Oscillation range of the axis [mm]. + move_time : float Total scan time for the movement [s]. delta : (???) - (default: 0). + Safety margin for movement (default: 0). settle : (???) (default: 0.5). """ scan_name = "measure_single_osc" - required_kwargs = ["start", "range", "scan_time"] + required_kwargs = ["start", "range", "move_time"] def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ['omega'] + self.axis = ['abr.omega'] # Set parameters from meaningful inputs self.scan_start = parameter['kwargs'].get("start") self.scan_range = parameter['kwargs'].get("range") - self.scan_scan_time = parameter['kwargs'].get("scan_time") + self.scan_move_time = parameter['kwargs'].get("move_time") self.scan_delta = parameter['kwargs'].get("delta", 0) self.scan_settle = parameter['kwargs'].get("settle", 0.5) self.abr_command = AbrCmd.SINGLE_OSCILLATION self.abr_globals = { "var_1" : self.scan_start, "var_2" : self.scan_range, - "var_3" : self.scan_scan_time, + "var_3" : self.scan_move_time, "var_4" : self.scan_delta, "var_5" : self.scan_settle, } @@ -617,41 +659,44 @@ class MeasureSingleOscillation(AerotechFlyscanBase): class MeasureRepeatedOscillation(AerotechFlyscanBase): """ Repeated oscillation scan + Repeated relative line scans with the omega axis with PSO enable trigger. + The lines are performed in zigzag mode with PSO triggering. + The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. Example ------- - >>> scans.measure_multi_osc(start=42, range=180, scan_time=20) + >>> scans.measure_multi_osc(steps=50, range=30, move_time=3) Parameters ---------- - nframes : int - ??? - scan_time : float + steps : int + Number of cycles to repeat the scan + move_time : float Total scan time for the movement [s]. range : float - Oscillation range of the axis. + Relative oscillation range of the omega axis [deg]. settle : (???) - (default: 0). + Movement settle time after each line (default: 0) [s]. delta : (???) - (default: 0.5). + Safety margin for movement (default: 0.5) [deg]. """ scan_name = "measure_multi_osc" - required_kwargs = ["nframes", "range", "scan_time"] + required_kwargs = ["steps", "range", "scan_time"] def __init__(self, *args, parameter: dict = None, **kwargs): self.axis = ['omega'] # Set parameters from meaningful inputs - self.scan_num_frames = parameter['kwargs'].get("nframes") + self.scan_stepnum = parameter['kwargs'].get("steps") self.scan_range = parameter['kwargs'].get("range") - self.scan_scan_time = parameter['kwargs'].get("scan_time") + self.scan_move_time = parameter['kwargs'].get("move_time") self.scan_sleep = parameter['kwargs'].get("sleep", 0) self.scan_delta = parameter['kwargs'].get("delta", 0.5) self.abr_command = AbrCmd.REPEAT_SINGLE_OSCILLATION self.abr_globals = { - "var_1" : self.scan_num_frames, - "var_2" : self.scan_scan_time, + "var_1" : self.scan_stepnum, + "var_2" : self.scan_move_time, "var_3" : self.scan_range, "var_4" : self.scan_sleep, "var_5" : self.scan_delta, @@ -664,12 +709,15 @@ class MeasureRepeatedOscillation(AerotechFlyscanBase): class MeasureMSOX(AerotechFlyscanBase): """ Standard MSOX scan + MSOX experiment for Florian, i.e. a glorified oscillation scan this time + with absolute positions. The lines are unidirectional with PSO triggering. + The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. Example ------- - >>> scans.measure_msox(start=42, range=10, exp_time=0.1) + >>> scans.measure_msox(start=42, range=30, move_time=3.0) Parameters ---------- @@ -677,32 +725,32 @@ class MeasureMSOX(AerotechFlyscanBase): Scan start position of the axis. range : float Scan range of the axis. - exp_time : float + move_time : float Exposure time for each point [s]. - num_datasets : int - ??? - rest_time : float - ??? + steps : int + Number of repeats. + settle_time : float + Settle time before line start (default=0) [s]. """ scan_name = "measure_msox" - required_kwargs = ["start", "range", "exp_time"] + required_kwargs = ["start", "range", "move_time"] def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ['omega'] + self.axis = ['abr.omega'] # Set parameters from meaningful inputs self.scan_start = parameter['kwargs'].get("start") self.scan_range = parameter['kwargs'].get("range") - self.scan_exp_time = parameter['kwargs'].get("exp_time") - self.scan_num_datasets = parameter['kwargs'].get("num_datasets", 1) - self.scan_rest_time = parameter['kwargs'].get("rest_time", 0) + self.scan_move_time = parameter['kwargs'].get("move_time") + self.scan_stepnum = parameter['kwargs'].get("steps", 1) + self.scan_settle_time = parameter['kwargs'].get("settle_time", 0) self.abr_command = AbrCmd.MSOX self.abr_reset = True self.abr_globals = { "var_1" : self.scan_start, "var_2" : self.scan_range, - "var_3" : self.scan_exp_time, - "var_4" : self.scan_num_datasets, - "var_5" : self.scan_rest_time, + "var_3" : self.scan_move_time, + "var_4" : self.scan_stepnum, + "var_5" : self.scan_settle_time, "var_6" : 0, "var_7" : 0, "var_8" : 0, @@ -717,6 +765,11 @@ class MeasureMSOX(AerotechFlyscanBase): class MeasureGrid(AerotechFlyscanBase): """ General grid scan + Performs an X-Y-Omega coordinated grid scan: + X axis is absolute (fast axis) + Y axis is relative to start + Omega starts at the current value and is synchronized to X + Note: This was probably never used in its current form, because the code had an undefined variable 'self.position' @@ -731,12 +784,14 @@ class MeasureGrid(AerotechFlyscanBase): ---------- positions : 2D-array Scan position of each axis, i.e. (N, 3) shaped array. - ncols : int - Nmber of columns. + steps_x : int + Number of points along the X axis (fast). + steps_y : int + Number of points in the Y axis (slow). angle: - ??? + Triggered angular range on the synchronized omega [deg] exp_time : (float) - Exposure time for each point [s]. + Exposure time for each point, only used for number of points and velocity [s]. """ scan_name = "measure_raster" required_kwargs = ["positions", "ncols", "angle", "exp_time"] @@ -750,16 +805,19 @@ class MeasureGrid(AerotechFlyscanBase): scan_gmy_step = 0.010 def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ['omega'] + self.axis = ['abr.omega', 'abr.gmx', 'abr.gmy'] # Set parameters from meaningful inputs self.abr_command = AbrCmd.RASTER_SCAN self.abr_reset = True self.scan_positions = parameter['kwargs'].get("positions") - self.scan_stepnum_x = parameter['kwargs'].get("ncols") - self.scan_stepnum_y = self.scan_positions / self.scan_stepnum_x - self.scan_angle = parameter['kwargs'].get("angle") + self.scan_stepnum_x = parameter['kwargs'].get("steps_x") + self.scan_stepnum_y = parameter['kwargs'].get("steps_y") + self.scan_anglerange = parameter['kwargs'].get("angle") self.scan_exp_time = parameter['kwargs'].get("exp_time") + if len(self.scan_positions) != self.scan_stepnum_x * self.scan_stepnum_y: + raise RuntimeError("Number of points and positions do not match") + if len(self.scan_positions)==1: raise RuntimeWarning("Raster scan with one cell makes no sense") @@ -784,26 +842,17 @@ class MeasureGrid(AerotechFlyscanBase): self.scan_gmy_step = np.linalg.norm([self.scan_ystep, self.scan_zstep]) # ToDo: Check with Zac what are theese parameters - self.scan_omega_position = yield from self.stubs.send_rpc_and_wait("abs", "omega.readback.get") + self.scan_start_o = yield from self.stubs.send_rpc_and_wait("abs", "omega.readback.get") self.scan_gmx_offset = yield from self.stubs.send_rpc_and_wait("abs", "gmx.readback.get") - # ToDo : This was left out - # self._raster_starting_x = start_x - # self._raster_starting_y = y1 - # self._raster_starting_z = z1 - # self._raster_step_y = ystep - # self._raster_step_z = zstep - # self._raster_current_row = 0 - # self._raster_num_rows = rows - # Configure the global variables self.abr_globals = { - "var_1" : self.scan_omega_position, + "var_1" : self.scan_start_o, "var_2" : self.scan_start_x, "var_3" : self.scan_end_x, "var_4" : self.scan_stepnum_x, "var_5" : self.scan_stepnum_y, - "var_6" : self.scan_angle, + "var_6" : self.scan_anglerange, "var_7" : self.scan_exp_time, "var_8" : HALF_PERIOD, "var_9" : self.scan_gmx_offset, @@ -815,6 +864,11 @@ class MeasureGrid(AerotechFlyscanBase): class MeasureGridStill(AerotechFlyscanBase): """ Still grid scan + Performs an X-Y-Omega coordinated grid scan in stepping mode: + X axis is absolute + Y axis is relative to start + Omega starts at the current value and is synchronized to X + Note: This was probably never used in its current form, because the code had an undefined variable 'self.position' @@ -823,7 +877,7 @@ class MeasureGridStill(AerotechFlyscanBase): Example ------- - >>> scans.measure_raster(positions=[[11, 2, 3.4], [11, 2, 3.5], ...], columns=20, exp_time=0.1, delay=0.05) + >>> scans.measure_raster(positions=[[11, 2, 3.4], [11, 2, 3.5], ...], steps_x=20, steps_y=20, exp_time=0.1, delay=0.05) Parameters ---------- @@ -839,7 +893,7 @@ class MeasureGridStill(AerotechFlyscanBase): ??? """ scan_name = "measure_raster_still" - required_kwargs = ["positions", "columns", "exp_time", "delay"] + required_kwargs = ["positions", "steps_x", "steps_y", "exp_time", "delay"] # Default values scan_start_x = None @@ -850,16 +904,19 @@ class MeasureGridStill(AerotechFlyscanBase): scan_gmy_step = 0.010 def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ['omega'] + self.axis = ['abr.gmx', 'abr.gmy', 'abr.omega'] # Set parameters from meaningful inputs self.abr_command = AbrCmd.RASTER_SCAN_STILL self.abr_reset = True self.scan_positions = parameter['kwargs'].get("positions") - self.scan_stepnum_x = parameter['kwargs'].get("columns") - self.scan_stepnum_y = self.scan_positions / self.scan_stepnum_x + self.scan_stepnum_x = parameter['kwargs'].get("steps_x") + self.scan_stepnum_y = parameter['kwargs'].get("steps_y") self.scan_exp_time = parameter['kwargs'].get("exp_time") self.scan_delay = parameter['kwargs'].get("delay") + if len(self.scan_positions) != self.scan_stepnum_x * self.scan_stepnum_y: + raise RuntimeError("Number of points and positions do not match") + if len(self.scan_positions)==1: raise RuntimeWarning("Raster scan with one cell makes no sense") # Call base class @@ -886,15 +943,6 @@ class MeasureGridStill(AerotechFlyscanBase): self.scan_omega_position = yield from self.stubs.send_rpc_and_wait("abs", "omega.readback.get") self.scan_gmx_offset = yield from self.stubs.send_rpc_and_wait("abs", "gmx.readback.get") - # ToDo : This was left out - # self._raster_starting_x = start_x - # self._raster_starting_y = y1 - # self._raster_starting_z = z1 - # self._raster_step_y = ystep - # self._raster_step_z = zstep - # self._raster_current_row = 0 - # self._raster_num_rows = rows - self.abr_globals = { "var_1" : self.scan_omega_position, "var_2" : self.scan_start_x, @@ -922,13 +970,13 @@ class MeasureJungfrau(AerotechFlyscanBase): Parameters ---------- - columns : int + steps_x : int ??? - rows : int + steps_y : int ??? - width : int + range_x : int ??? - height : int + range_y : int ??? exp_time : float Exposure time per point [s]. @@ -936,15 +984,17 @@ class MeasureJungfrau(AerotechFlyscanBase): (default: 0.1). """ scan_name = "measure_jungfrau" - required_kwargs = ["columns", "rows", "width", "height", "exp_time"] + required_kwargs = ["steps_x", "steps_y", "range_x", "range_y", "exp_time"] def __init__(self, *args, parameter: dict = None, **kwargs): self.axis = ['omega'] # Set parameters from meaningful inputs - self.scan_num_cols = parameter['kwargs'].get("columns") - self.scan_num_rows = parameter['kwargs'].get("rows") - self.scan_width = parameter['kwargs'].get("width") - self.scan_height = parameter['kwargs'].get("height") + self.scan_stepnum_x = parameter['kwargs'].get("steps_x") + self.scan_stepnum_y = parameter['kwargs'].get("steps_y") + self.scan_range_x = parameter['kwargs'].get("range_x") + self.scan_range_y = parameter['kwargs'].get("range_y") + self.scan_stepsize_x = self.scan_range_x / self.scan_stepnum_x + self.scan_stepsize_y = self.scan_range_y / self.scan_stepnum_y self.scan_exp_time = parameter['kwargs'].get("exp_time") self.scan_sleep = parameter['kwargs'].get("sleep", 0.1) self.abr_timeout = None if self.scan_sleep <= 0 else 5.0 + (self.scan_num_rows * self.scan_sleep) + (self.scan_num_cols * self.scan_num_rows * self.scan_exp_time) @@ -952,10 +1002,10 @@ class MeasureJungfrau(AerotechFlyscanBase): self.abr_reset = True self.abr_complete = True self.abr_globals = { - "var_1" : self.scan_num_cols, - "var_2" : self.scan_num_rows, - "var_3" : self.scan_width, - "var_4" : self.scan_height, + "var_1" : self.scan_stepnum_x, + "var_2" : self.scan_stepnum_y, + "var_3" : self.scan_stepsize_x, + "var_4" : self.scan_stepsize_y, "var_5" : self.scan_exp_time, "var_6" : self.scan_sleep, "var_7" : 0, -- 2.49.1 From 14ca9bd74a4f77151bf8c2e362a0566e1accbe07 Mon Sep 17 00:00:00 2001 From: Unknown MX Person Date: Thu, 19 Sep 2024 17:30:13 +0200 Subject: [PATCH 12/25] Daily commit mit some patching --- .../device_configs/x06da_device_config.yaml | 9 +- pxiii_bec/devices/A3200.py | 25 +++-- .../devices/{aeroA3200.py => A3200utils.py} | 93 +++++++++++-------- pxiii_bec/devices/__init__.py | 4 +- pxiii_bec/scans/mx_measurements.py | 24 +++-- 5 files changed, 96 insertions(+), 59 deletions(-) rename pxiii_bec/devices/{aeroA3200.py => A3200utils.py} (84%) 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: -- 2.49.1 From b0703552f29069d4d8b6ca5c7dc36eb2616672ea Mon Sep 17 00:00:00 2001 From: Unknown MX Person Date: Fri, 20 Sep 2024 14:38:04 +0200 Subject: [PATCH 13/25] Aerotech scans work nicely --- .../device_configs/x06da_device_config.yaml | 2 +- pxiii_bec/devices/A3200.py | 22 +++++++- pxiii_bec/devices/A3200utils.py | 29 +++++++++-- pxiii_bec/scans/__init__.py | 2 +- pxiii_bec/scans/mx_measurements.py | 52 +++++++++++-------- 5 files changed, 77 insertions(+), 30 deletions(-) diff --git a/pxiii_bec/device_configs/x06da_device_config.yaml b/pxiii_bec/device_configs/x06da_device_config.yaml index e312153..039b5e7 100644 --- a/pxiii_bec/device_configs/x06da_device_config.yaml +++ b/pxiii_bec/device_configs/x06da_device_config.yaml @@ -345,7 +345,7 @@ omega: abr: description: Aerotech ABR motion system deviceClass: pxiii_bec.devices.AerotechAbrStage - deviceConfig: {prefix: X06DA-ES} + deviceConfig: {prefix: 'X06DA-ES'} onFailure: buffer enabled: true readoutPriority: monitored diff --git a/pxiii_bec/devices/A3200.py b/pxiii_bec/devices/A3200.py index 467d29c..b43f18f 100644 --- a/pxiii_bec/devices/A3200.py +++ b/pxiii_bec/devices/A3200.py @@ -81,7 +81,7 @@ Examples import time import math -from ophyd import Component, Device, EpicsSignal, EpicsSignalRO, Kind +from ophyd import Component, Device, EpicsSignal, EpicsSignalRO, Kind, SignalRO from ophyd.status import MoveStatus, SubscriptionStatus, DeviceStatus @@ -185,6 +185,12 @@ class AerotechAbrStage(Device): _var_9 = Component(EpicsSignal, "-PSO:VAR-9", put_complete=True, kind=Kind.omitted) _var_10 = Component(EpicsSignal, "-PSO:VAR-10", put_complete=True, kind=Kind.omitted) + # Task status PVs (programs always run on task 1) + task1 = Component(EpicsSignalRO, "-AERO:TSK1-DONE", auto_monitor=True, kind=Kind.hinted) + task2 = Component(EpicsSignalRO, "-AERO:TSK2-DONE", auto_monitor=True) + task3 = Component(EpicsSignalRO, "-AERO:TSK3-DONE", auto_monitor=True) + task4 = Component(EpicsSignalRO, "-AERO:TSK4-DONE", auto_monitor=True) + # A few PVs still needed from grid raster_scan_done = Component(EpicsSignal, "-GRD:SCAN-DONE", kind=Kind.config) raster_num_rows = Component(EpicsSignal, "-GRD:ROW-DONE", kind=Kind.config) @@ -255,6 +261,20 @@ class AerotechAbrStage(Device): """ return self.raster.complete() + + def complete(self, timeout=None) -> SubscriptionStatus: + """ Waits for task execution + + NOTE: Original complete was raster scanner complete... + """ + # Define wait until the busy flag goes down (excluding initial update) + def isIdle(*args, old_value, value, timestamp, **kwargs): + return bool(value==1) + + # Subscribe and wait for update + status = SubscriptionStatus(self.task1, isIdle, timeout=timeout, settle_time=0.5) + return status + def reset(self, settle_time=0.1): self.scan_command.set(CMD_NONE, settle_time=settle_time).wait() self.set_axis_mode("direct", settle_time=settle_time) diff --git a/pxiii_bec/devices/A3200utils.py b/pxiii_bec/devices/A3200utils.py index 57b11f9..4ab5af8 100644 --- a/pxiii_bec/devices/A3200utils.py +++ b/pxiii_bec/devices/A3200utils.py @@ -25,10 +25,29 @@ MEASURING_MODE = 1 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. + """ Positioner wrapper for A3200 axes + + + 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 error checking and diagnostics. + + Examples + -------- + omega = A3200Axis('X06DA-ES-DF1:OMEGA', base_pv='X06DA-ES') + + class abr(Device): + omega = Component(A3200Axis, '-DF1:OMEGA') + gmx = Component(A3200Axis, '-DF1:GMX') + gmy = Component(A3200Axis, '-DF1:GMY') + + Parameters + ---------- + prefix : str + Axis PV name root. + base_pv : str (situational) + IOC PV name root, i.e. X06DA-ES if standalone class. """ USER_ACCESS = ['omove'] @@ -74,7 +93,7 @@ class A3200Axis(PVPositioner): """ 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""" + """ Patched not to enforce parent prefix when no parent""" if kw in self.add_prefix: return suffix return suffix diff --git a/pxiii_bec/scans/__init__.py b/pxiii_bec/scans/__init__.py index 9ca53ef..2b4690a 100644 --- a/pxiii_bec/scans/__init__.py +++ b/pxiii_bec/scans/__init__.py @@ -1 +1 @@ -from .mx_measurements import MeasureFastOmega \ No newline at end of file +from .mx_measurements import MeasureFastOmega, MeasureStandardWedge, MeasureVerticalLine, MeasureScanSlits, MeasureRasterSimple, MeasureScreening, MeasureRepeatedOscillation, MeasureMSOX, MeasureGrid \ No newline at end of file diff --git a/pxiii_bec/scans/mx_measurements.py b/pxiii_bec/scans/mx_measurements.py index a4dcad3..5cd99a7 100644 --- a/pxiii_bec/scans/mx_measurements.py +++ b/pxiii_bec/scans/mx_measurements.py @@ -45,12 +45,19 @@ class AbrCmd: class AerotechFlyscanBase(AsyncFlyScanBase): """ Base class for MX flyscans - Low-level base class for standard scans at the PX beamlines at SLS. Theese scans use the - A3200 rotation stage and the actual experiment is performed using an AeroBasic script - controlled via global variables. + Low-level base class for standard scans at the PX beamlines at SLS. Theese + scans use the A3200 rotation stage and the actual experiment is performed + using an AeroBasic script controlled via global variables. The base class + has some basic safety features like checking status then sets globals and + fires off the scan. Implementations can choose to set the corresponding + configurations in child classes or pass it as command line parameters. IMPORTANT: The AeroBasic scripts take care of the PSO configuration. - + + Parameters: + ----------- + abr_complete : bool + Wait for the launched ABR task to complete. """ scan_report_hint = "table" arg_input = {} @@ -59,7 +66,6 @@ class AerotechFlyscanBase(AsyncFlyScanBase): # Aerotech stage config abr_command = None abr_globals = {} - abr_reset = False abr_raster_reset =False abr_complete = False abr_timeout = None @@ -76,8 +82,6 @@ class AerotechFlyscanBase(AsyncFlyScanBase): self.abr_command = self.caller_kwargs.get("abr_command") if "abr_globals" in self.caller_kwargs: self.abr_globals = self.caller_kwargs.get("abr_globals") - if "abr_reset" in self.caller_kwargs: - self.abr_reset = self.caller_kwargs.get("abr_reset") if "abr_raster_reset" in self.caller_kwargs: self.abr_raster_reset = self.caller_kwargs.get("abr_raster_reset") if "abr_complete" in self.caller_kwargs: @@ -86,17 +90,19 @@ class AerotechFlyscanBase(AsyncFlyScanBase): self.abr_timeout = self.caller_kwargs.get("abr_timeout") def pre_scan(self): - # ToDo: Move roughly to start position + # TODO: Move roughly to start position??? + + # ABR status checking 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") + task = yield from self.stubs.send_rpc_and_wait("abr","task1.get") + # From what I got values are copied to local vars at the start of scan, + # so only kickoff should be forbidden. + if task not in (1, "OK"): + raise RuntimeError("Aerotech ABR task #1 seems to busy") - if self.abr_reset: - yield from self.stubs.send_rpc_and_wait("abr", "reset") - - # Reset the scan engine - if self.abr_reset: - yield from self.stubs.send_rpc_and_wait("abr", "reset") + # Reset the raster scan engine if self.abr_raster_reset: yield from self.stubs.command("abr", "raster_scan_done.set", 0) @@ -123,9 +129,13 @@ class AerotechFlyscanBase(AsyncFlyScanBase): logger.info("Measurement launched on the ABR stage...") # Wait for grid scanner to finish - if self.abr_complete and self.abr_timeout is not None: - st = yield from self.stubs.send_rpc_and_wait("abr", "complete", self.abr_timeout) - st.wait() + if self.abr_complete: + if self.abr_timeout is not None: + st = yield from self.stubs.send_rpc_and_wait("abr", "complete", self.abr_timeout) + st.wait() + else: + st = yield from self.stubs.send_rpc_and_wait("abr", "complete") + st.wait() def cleanup(self): """Set scan progress to 1 to finish the scan""" @@ -175,6 +185,7 @@ class MeasureStandardWedge(AerotechFlyscanBase): "var_3" : self.scan_move_time, "var_4" : self.scan_ready_rate, } + logger.info(self.abr_globals) # Call base class super().__init__(parameter=parameter, **kwargs) @@ -204,7 +215,7 @@ class MeasureVerticalLine(AerotechFlyscanBase): exp_time : float Eeffective exposure time per step [s] """ - scan_name = "measure_vline" + scan_name = "vertical_line" required_kwargs = ["exp_time", "range_y", "steps_y"] def __init__(self, *args, parameter: dict = None, **kwargs): @@ -515,6 +526,7 @@ class MeasureFastOmega(AerotechFlyscanBase): "var_3" : self.scan_move_time, "var_4" : self.scan_acceleration, } + logger.info(self.abr_globals) # Call base class super().__init__(parameter=parameter, **kwargs) @@ -750,7 +762,6 @@ class MeasureMSOX(AerotechFlyscanBase): self.scan_stepnum = parameter['kwargs'].get("steps", 1) self.scan_settle_time = parameter['kwargs'].get("settle_time", 0) self.abr_command = AbrCmd.MSOX - self.abr_reset = True self.abr_globals = { "var_1" : self.scan_start, "var_2" : self.scan_range, @@ -814,7 +825,6 @@ class MeasureGrid(AerotechFlyscanBase): self.axis = ['abr.omega', 'abr.gmx', 'abr.gmy'] # Set parameters from meaningful inputs self.abr_command = AbrCmd.RASTER_SCAN - self.abr_reset = True self.scan_positions = parameter['kwargs'].get("positions") self.scan_stepnum_x = parameter['kwargs'].get("steps_x") self.scan_stepnum_y = parameter['kwargs'].get("steps_y") @@ -913,7 +923,6 @@ class MeasureGridStill(AerotechFlyscanBase): self.axis = ['abr.gmx', 'abr.gmy', 'abr.omega'] # Set parameters from meaningful inputs self.abr_command = AbrCmd.RASTER_SCAN_STILL - self.abr_reset = True self.scan_positions = parameter['kwargs'].get("positions") self.scan_stepnum_x = parameter['kwargs'].get("steps_x") self.scan_stepnum_y = parameter['kwargs'].get("steps_y") @@ -1005,7 +1014,6 @@ class MeasureJungfrau(AerotechFlyscanBase): self.scan_sleep = parameter['kwargs'].get("sleep", 0.1) self.abr_timeout = None if self.scan_sleep <= 0 else 5.0 + (self.scan_num_rows * self.scan_sleep) + (self.scan_num_cols * self.scan_num_rows * self.scan_exp_time) self.abr_command = AbrCmd.JUNGFRAU - self.abr_reset = True self.abr_complete = True self.abr_globals = { "var_1" : self.scan_stepnum_x, -- 2.49.1 From 78c75b17693fb23ee083072e9f3275b705835aa2 Mon Sep 17 00:00:00 2001 From: Unknown MX Person Date: Wed, 13 Nov 2024 14:30:57 +0100 Subject: [PATCH 14/25] Moving towards beamline startup --- pxiii_bec/device_configs/DbGenerator.py | 55 ----- pxiii_bec/device_configs/x06da_compact.lmay | 206 ------------------ .../device_configs/x06da_device_config.yaml | 195 ++++++++++++++--- pxiii_bec/devices/A3200.py | 60 +++-- pxiii_bec/devices/A3200utils.py | 111 +++++----- pxiii_bec/scans/__init__.py | 4 +- pxiii_bec/scans/mx_measurements.py | 40 ++-- 7 files changed, 281 insertions(+), 390 deletions(-) delete mode 100644 pxiii_bec/device_configs/DbGenerator.py delete mode 100644 pxiii_bec/device_configs/x06da_compact.lmay diff --git a/pxiii_bec/device_configs/DbGenerator.py b/pxiii_bec/device_configs/DbGenerator.py deleted file mode 100644 index 62b95f8..0000000 --- a/pxiii_bec/device_configs/DbGenerator.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Aug 31 10:27:18 2022 - -Database migration from a sensible format to the BEC YAML file format - -@author: mohacsi_i -""" - -import yaml -import yaml.representer - - - -def MigrateYamlFile(filein, fileout): - """ Migrates an absolutely minimal YAML config file to the format - required by the BEC (i.e. adding default fields). - """ - fp = open(filein, "r") - lut = yaml.load(fp, Loader=yaml.Loader) - - # Allocate empty database - db = dict() - - for k,v in lut.items(): - new = v - - # Adding defaults - if 'onFailure' not in new: - new['onFailure'] = "buffer" - if 'enabled' not in new: - new['enabled'] = True - if 'readoutPriority' not in new: - new['readoutPriority'] = "monitored" - if 'readOnly' not in new: - new['readOnly'] = bool(new['deviceClass'] in ('ophyd.EpicsSignalRO')) - if 'softwareTrigger' not in new: - new['softwareTrigger'] = False - - if new['deviceClass'] == "ophyd.EpicsSignalRO": - if "read_pv" not in new['deviceConfig']: - new["deviceConfig"]["read_pv"] = new["deviceConfig"]["prefix"] - del new["deviceConfig"]["prefix"] - - - db[k] = new - - with open(fileout, 'w') as stream: - yaml.dump(db, stream, default_flow_style=None, sort_keys=False) - - - -# Automatically start simulation if directly invoked -if __name__ == "__main__": - MigrateYamlFile("./x06da_compact.lmay", "x06da_device_config.yaml") diff --git a/pxiii_bec/device_configs/x06da_compact.lmay b/pxiii_bec/device_configs/x06da_compact.lmay deleted file mode 100644 index 7ad90d4..0000000 --- a/pxiii_bec/device_configs/x06da_compact.lmay +++ /dev/null @@ -1,206 +0,0 @@ -# OP before mono -slh_trxr: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-OP-SLH:TRXR' -slh_trxw: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-OP-SLH:TRXW' -fi1_try: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-OP-FI1:TRY' - -# DCCM crystal 1 -dccm_pitch1: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-OP-DCCM:PITCH1' -dccm_energy1: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-OP-DCCM:ENERGY1' -dccm_diode: - deviceClass: ophyd.EpicsSignalRO - deviceConfig: - prefix: 'X06DA-OP-XPM1:BOT:READOUT' - -# DCCM crystal 2 -dccm_pitch2: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-OP-DCCM:PITCH2' -dccm_energy2: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-OP-DCCM:ENERGY2' -dccm_xbpm: - deviceClass: ophyd.EpicsSignalRO - deviceConfig: - prefix: 'X06DA-OP-XBPM1:SumAll:MeanValue_RBV' - -# DCCM common - -dccm_energy: - description: Monochromator energy using ECMC virtual motors - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-OP-DCCM:_ENERGY' -dccm_eoffset: - description: Monochromator energy offset for ECMC virtual motors - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-OP-DCCM:_EOFFSET' - -# Secondary source XBPM -ssxbpm_trx: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES-SSBPM1:TRX' -ssxbpm_try: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES-SSBPM1:TRY' - -ssslit_trxr: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES-SSSLH1:TRXR' -ssslit_trxw: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES-SSSLH1:TRXW' -ssslit_tryt: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES-SSSLV1:TRYT' -ssslit_tryb: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES-SSSLV1:TRYB' -ssxi1_trx: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES-SSXI1:TRX' -ssxi1_try: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES-SSXI1:TRY' - -# Vertical focusing mirror -vfm_trxu: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES1-VFM:TRXU' - enabled: false -vfm_trxd: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES1-VFM:TRXD' - enabled: false -vfm_tryuw: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES1-VFM:TRYUW' -vfm_tryr: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES1-VFM:TRYR' -vfm_trydw: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES1-VFM:TRYDW' -vfm_pitch: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES1-VFM:PITCH' -vfm_yaw: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES1-VFM:YAW' - enabled: false -vfm_roll: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES1-VFM:ROLL' - enabled: false -vfm_trx: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES1-VFM:TRX' - enabled: false -vfm_try: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES1-VFM:TRY' - -# Horizontal focusing mirror -hfm_trxu: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES1-HFM:TRXU' - enabled: false -hfm_trxd: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES1-HFM:TRXD' - enabled: false -hfm_tryur: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES1-HFM:TRYUR' -hfm_tryw: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES1-HFM:TRYW' -hfm_trydr: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES1-HFM:TRYDR' -hfm_pitch: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES1-HFM:PITCH' - enabled: false -hfm_yaw: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES1-HFM:YAW' - enabled: false -hfm_roll: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES1-HFM:ROLL' - enabled: false -hfm_trx: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES1-HFM:TRX' - enabled: false -hfm_try: - deviceClass: ophyd.EpicsMotor - deviceConfig: - prefix: 'X06DA-ES1-HFM:TRY' - - -# Exposure box signals -xbox_diode: - deviceClass: ophyd.EpicsSignalRO - deviceConfig: - prefix: 'X06DA-ES-DI1:READOUT' -bstop_diode: - deviceClass: ophyd.EpicsSignalRO - deviceConfig: - prefix: 'X06DA-ES-BS:READOUT' - - -# End station -omega: - deviceClass: pxiii_bec.devices.A3200Axis - deviceConfig: - prefix: 'X06DA-ES-DF1:OMEGA' -abr: - deviceClass: pxiii_bec.devices.AerotechAbrStage - deviceConfig: - prefix: 'X06DA-ES' diff --git a/pxiii_bec/device_configs/x06da_device_config.yaml b/pxiii_bec/device_configs/x06da_device_config.yaml index 039b5e7..00c4f8b 100644 --- a/pxiii_bec/device_configs/x06da_device_config.yaml +++ b/pxiii_bec/device_configs/x06da_device_config.yaml @@ -1,4 +1,5 @@ slh_trxr: + description: OP slit inner blade motion deviceClass: ophyd.EpicsMotor deviceConfig: {prefix: 'X06DA-OP-SLH:TRXR'} onFailure: buffer @@ -7,6 +8,7 @@ slh_trxr: readOnly: false softwareTrigger: false slh_trxw: + description: OP slit outer blade motion deviceClass: ophyd.EpicsMotor deviceConfig: {prefix: 'X06DA-OP-SLH:TRXW'} onFailure: buffer @@ -15,15 +17,16 @@ slh_trxw: readOnly: false softwareTrigger: false fi1_try: - description: Beam attenuator in OP + description: Beam attenuator motion before mono deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-OP-FI1:TRY'} + deviceConfig: {prefix: 'X06DA-OP-FI1:TRY1'} onFailure: buffer enabled: true readoutPriority: monitored readOnly: false softwareTrigger: false dccm_pitch1: + description: Monochromator pitch 1 deviceClass: ophyd.EpicsMotor deviceConfig: {prefix: 'X06DA-OP-DCCM:PITCH1'} onFailure: buffer @@ -32,6 +35,7 @@ dccm_pitch1: readOnly: false softwareTrigger: false dccm_energy1: + description: Monochromator energy 1 deviceClass: ophyd.EpicsMotor deviceConfig: {prefix: 'X06DA-OP-DCCM:ENERGY1'} onFailure: buffer @@ -40,6 +44,7 @@ dccm_energy1: readOnly: false softwareTrigger: false dccm_diode: + description: Diode between mono crystals deviceClass: ophyd.EpicsSignalRO deviceConfig: {read_pv: 'X06DA-OP-XPM1:BOT:READOUT'} onFailure: buffer @@ -48,6 +53,7 @@ dccm_diode: readOnly: true softwareTrigger: false dccm_pitch2: + description: Monochromator pitch 2 deviceClass: ophyd.EpicsMotor deviceConfig: {prefix: 'X06DA-OP-DCCM:PITCH2'} onFailure: buffer @@ -56,6 +62,7 @@ dccm_pitch2: readOnly: false softwareTrigger: false dccm_energy2: + description: Monochromator energy 2 deviceClass: ophyd.EpicsMotor deviceConfig: {prefix: 'X06DA-OP-DCCM:ENERGY2'} onFailure: buffer @@ -64,6 +71,7 @@ dccm_energy2: readOnly: false softwareTrigger: false dccm_xbpm: + description: XBPM total intensity after monochromator deviceClass: ophyd.EpicsSignalRO deviceConfig: {read_pv: 'X06DA-OP-XBPM1:SumAll:MeanValue_RBV'} onFailure: buffer @@ -71,25 +79,26 @@ dccm_xbpm: readoutPriority: monitored readOnly: true softwareTrigger: false -dccm_energy: - description: Monochromator energy using ECMC virtual motors - deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-OP-DCCM:_ENERGY'} - onFailure: buffer - enabled: true - readoutPriority: monitored - readOnly: false - softwareTrigger: false -dccm_eoffset: - description: Monochromator energy offset for ECMC virtual motors - deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-OP-DCCM:_EOFFSET'} - onFailure: buffer - enabled: true - readoutPriority: monitored - readOnly: false - softwareTrigger: false +# dccm_energy: +# description: Monochromator energy using ECMC virtual motors +# deviceClass: ophyd.EpicsMotor +# deviceConfig: {prefix: 'X06DA-OP-DCCM:_ENERGY'} +# onFailure: buffer +# enabled: true +# readoutPriority: monitored +# readOnly: false +# softwareTrigger: false +# dccm_eoffset: +# description: Monochromator energy offset for ECMC virtual motors +# deviceClass: ophyd.EpicsMotor +# deviceConfig: {prefix: 'X06DA-OP-DCCM:_EOFFSET'} +# onFailure: buffer +# enabled: true +# readoutPriority: monitored +# readOnly: false +# softwareTrigger: false ssxbpm_trx: + description: XBPM motion before secondary source deviceClass: ophyd.EpicsMotor deviceConfig: {prefix: 'X06DA-ES-SSBPM1:TRX'} onFailure: buffer @@ -98,6 +107,7 @@ ssxbpm_trx: readOnly: false softwareTrigger: false ssxbpm_try: + description: XBPM motion before secondary source deviceClass: ophyd.EpicsMotor deviceConfig: {prefix: 'X06DA-ES-SSBPM1:TRY'} onFailure: buffer @@ -105,7 +115,17 @@ ssxbpm_try: readoutPriority: monitored readOnly: false softwareTrigger: false +# ssxbpm: +# description: XBPM before secondary source +# deviceClass: ophyd.EpicsSignalRO +# deviceConfig: {read_pv: 'X06DA-ES-SSBPM1:SumAll:MeanValue_RBV'} +# onFailure: buffer +# enabled: true +# readoutPriority: monitored +# readOnly: true +# softwareTrigger: false ssslit_trxr: + description: Secondary source blade motion deviceClass: ophyd.EpicsMotor deviceConfig: {prefix: 'X06DA-ES-SSSLH1:TRXR'} onFailure: buffer @@ -114,6 +134,7 @@ ssslit_trxr: readOnly: false softwareTrigger: false ssslit_trxw: + description: Secondary source blade motion deviceClass: ophyd.EpicsMotor deviceConfig: {prefix: 'X06DA-ES-SSSLH1:TRXW'} onFailure: buffer @@ -122,6 +143,7 @@ ssslit_trxw: readOnly: false softwareTrigger: false ssslit_tryt: + description: Secondary source blade motion deviceClass: ophyd.EpicsMotor deviceConfig: {prefix: 'X06DA-ES-SSSLV1:TRYT'} onFailure: buffer @@ -130,6 +152,7 @@ ssslit_tryt: readOnly: false softwareTrigger: false ssslit_tryb: + description: Secondary source blade motion deviceClass: ophyd.EpicsMotor deviceConfig: {prefix: 'X06DA-ES-SSSLV1:TRYB'} onFailure: buffer @@ -138,6 +161,7 @@ ssslit_tryb: readOnly: false softwareTrigger: false ssxi1_trx: + description: Secondary source diagnostic screen motion deviceClass: ophyd.EpicsMotor deviceConfig: {prefix: 'X06DA-ES-SSXI1:TRX'} onFailure: buffer @@ -146,6 +170,7 @@ ssxi1_trx: readOnly: false softwareTrigger: false ssxi1_try: + description: Secondary source diagnostic screen motion deviceClass: ophyd.EpicsMotor deviceConfig: {prefix: 'X06DA-ES-SSXI1:TRY'} onFailure: buffer @@ -155,7 +180,7 @@ ssxi1_try: softwareTrigger: false vfm_trxu: deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES1-VFM:TRXU'} + deviceConfig: {prefix: 'X06DA-ES-VFM:TRXU'} enabled: false onFailure: buffer readoutPriority: monitored @@ -163,7 +188,7 @@ vfm_trxu: softwareTrigger: false vfm_trxd: deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES1-VFM:TRXD'} + deviceConfig: {prefix: 'X06DA-ES-VFM:TRXD'} enabled: false onFailure: buffer readoutPriority: monitored @@ -171,7 +196,7 @@ vfm_trxd: softwareTrigger: false vfm_tryuw: deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES1-VFM:TRYUW'} + deviceConfig: {prefix: 'X06DA-ES-VFM:TRYUW'} onFailure: buffer enabled: true readoutPriority: monitored @@ -179,7 +204,7 @@ vfm_tryuw: softwareTrigger: false vfm_tryr: deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES1-VFM:TRYR'} + deviceConfig: {prefix: 'X06DA-ES-VFM:TRYR'} onFailure: buffer enabled: true readoutPriority: monitored @@ -187,7 +212,7 @@ vfm_tryr: softwareTrigger: false vfm_trydw: deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES1-VFM:TRYDW'} + deviceConfig: {prefix: 'X06DA-ES-VFM:TRYDW'} onFailure: buffer enabled: true readoutPriority: monitored @@ -236,7 +261,7 @@ vfm_try: softwareTrigger: false hfm_trxu: deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES1-HFM:TRXU'} + deviceConfig: {prefix: 'X06DA-ES-HFM:TRXU'} enabled: false onFailure: buffer readoutPriority: monitored @@ -244,7 +269,7 @@ hfm_trxu: softwareTrigger: false hfm_trxd: deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES1-HFM:TRXD'} + deviceConfig: {prefix: 'X06DA-ES-HFM:TRXD'} enabled: false onFailure: buffer readoutPriority: monitored @@ -252,7 +277,7 @@ hfm_trxd: softwareTrigger: false hfm_tryur: deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES1-HFM:TRYUR'} + deviceConfig: {prefix: 'X06DA-ES-HFM:TRYUR'} onFailure: buffer enabled: true readoutPriority: monitored @@ -260,7 +285,7 @@ hfm_tryur: softwareTrigger: false hfm_tryw: deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES1-HFM:TRYW'} + deviceConfig: {prefix: 'X06DA-ES-HFM:TRYW'} onFailure: buffer enabled: true readoutPriority: monitored @@ -268,7 +293,7 @@ hfm_tryw: softwareTrigger: false hfm_trydr: deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES1-HFM:TRYDR'} + deviceConfig: {prefix: 'X06DA-ES-HFM:TRYDR'} onFailure: buffer enabled: true readoutPriority: monitored @@ -315,6 +340,51 @@ hfm_try: readoutPriority: monitored readOnly: false softwareTrigger: false +# xbox_xbpm: +# description: Exposure box XBPM +# deviceClass: ophyd.EpicsSignalRO +# deviceConfig: {read_pv: 'X06DA-ES-XBBPM1:SumAll:MeanValue_RBV'} +# onFailure: buffer +# enabled: true +# readoutPriority: monitored +# readOnly: true +# softwareTrigger: false +xbox_fil1: + description: Exposure box filter wheel 1 + deviceClass: ophyd.EpicsMotor + deviceConfig: {prefix: 'X06DA-ES-FI1:ROZ1'} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: false + softwareTrigger: false +xbox_fil2: + description: Exposure box filter wheel 2 + deviceClass: ophyd.EpicsMotor + deviceConfig: {prefix: 'X06DA-ES-FI2:ROZ1'} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: false + softwareTrigger: false +xbox_fil3: + description: Exposure box filter wheel 3 + deviceClass: ophyd.EpicsMotor + deviceConfig: {prefix: 'X06DA-ES-FI3:ROZ1'} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: false + softwareTrigger: false +xbox_fil4: + description: Exposure box filter wheel 4 + deviceClass: ophyd.EpicsMotor + deviceConfig: {prefix: 'X06DA-ES-FI4:ROZ1'} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: false + softwareTrigger: false xbox_diode: description: Exposure box diode deviceClass: ophyd.EpicsSignalRO @@ -324,6 +394,71 @@ xbox_diode: readoutPriority: monitored readOnly: true softwareTrigger: false +ms_focus: + description: Sample microscope focus + deviceClass: ophyd.EpicsMotor + deviceConfig: {prefix: 'X06DA-ES-MS:FOCUS'} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: false + softwareTrigger: false +ms_zoom: + description: Sample microscope zoom + deviceClass: ophyd.EpicsMotor + deviceConfig: {prefix: 'X06DA-ES-MS:ZOOM'} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: false + softwareTrigger: false +ms_try: + description: Sample microscope translation + deviceClass: ophyd.EpicsMotor + deviceConfig: {prefix: 'X06DA-ES-MS:TRY1'} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: false + softwareTrigger: false + + +bstop_pneum: + description: Beamstop pneumatic in-out + deviceClass: ophyd.EpicsSignal + deviceConfig: {read_pv: 'X06DA-ES-BS:GET-POS', write_pv: 'X06DA-ES-BS:SET-POS'} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: false + softwareTrigger: false +bstop_x: + description: Beamstop translation + deviceClass: ophyd.EpicsMotor + deviceConfig: {prefix: 'X06DA-ES-BS:TRX1'} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: false + softwareTrigger: false +bstop_y: + description: Beamstop translation + deviceClass: ophyd.EpicsMotor + deviceConfig: {prefix: 'X06DA-ES-BS:TRY1'} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: false + softwareTrigger: false +bstop_z: + description: Beamstop translation + deviceClass: ophyd.EpicsMotor + deviceConfig: {prefix: 'X06DA-ES-BS:TRZ1'} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: false + softwareTrigger: false bstop_diode: description: Beamstop diode deviceClass: ophyd.EpicsSignalRO diff --git a/pxiii_bec/devices/A3200.py b/pxiii_bec/devices/A3200.py index b43f18f..7dff5bd 100644 --- a/pxiii_bec/devices/A3200.py +++ b/pxiii_bec/devices/A3200.py @@ -79,12 +79,9 @@ Examples """ import time -import math - -from ophyd import Component, Device, EpicsSignal, EpicsSignalRO, Kind, SignalRO +from ophyd import Component, Device, EpicsSignal, EpicsSignalRO, Kind from ophyd.status import MoveStatus, SubscriptionStatus, DeviceStatus - from .A3200utils import A3200Axis, A3200RasterScanner, A3200Oscillator @@ -147,13 +144,18 @@ class AerotechAbrStage(Device): """ 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) + taskStop = Component( + EpicsSignal, "-AERO:TSK-STOP", put_complete=True, kind=Kind.omitted) + status = Component( + EpicsSignal, "-AERO:STAT", put_complete=True, kind=Kind.omitted) # Enable/disable motor movement via the IOC (i.e. make it task-only) - axisModeLocked = Component(EpicsSignal, "-DF1:LOCK", put_complete=True, kind=Kind.omitted) - axisModeDirect = Component(EpicsSignal, "-DF1:MODE-DIRECT", put_complete=True, kind=Kind.omitted) - axisAxesMode = Component(EpicsSignal, "-DF1:AXES-MODE", put_complete=True, kind=Kind.omitted) + axisModeLocked = Component( + EpicsSignal, "-DF1:LOCK", put_complete=True, kind=Kind.omitted) + axisModeDirect = Component( + EpicsSignal, "-DF1:MODE-DIRECT", put_complete=True, kind=Kind.omitted) + axisAxesMode = Component( + EpicsSignal, "-DF1:AXES-MODE", put_complete=True, kind=Kind.omitted) # Shutter box is missing readback so the -GET signal is installed on the VME # _shutter = Component( @@ -169,9 +171,12 @@ class AerotechAbrStage(Device): gmy = Component(A3200Axis, "-DF1:GMY", kind=Kind.hinted) gmz = Component(A3200Axis, "-DF1:GMZ", kind=Kind.hinted) - scan_command = Component(EpicsSignal, "-PSO:CMD", put_complete=True, kind=Kind.omitted) - start_command = Component(EpicsSignal, "-PSO:START-TEST.PROC", put_complete=True, kind=Kind.omitted) - stop_command = Component(EpicsSignal, "-PSO:STOP-TEST.PROC", put_complete=True, kind=Kind.omitted) + scan_command = Component( + EpicsSignal, "-PSO:CMD", put_complete=True, kind=Kind.omitted) + start_command = Component( + EpicsSignal, "-PSO:START-TEST.PROC", put_complete=True, kind=Kind.omitted) + stop_command = Component( + EpicsSignal, "-PSO:STOP-TEST.PROC", put_complete=True, kind=Kind.omitted) # Global variables to controll AeroBasic scripts _var_1 = Component(EpicsSignal, "-PSO:VAR-1", put_complete=True, kind=Kind.omitted) @@ -186,7 +191,8 @@ class AerotechAbrStage(Device): _var_10 = Component(EpicsSignal, "-PSO:VAR-10", put_complete=True, kind=Kind.omitted) # Task status PVs (programs always run on task 1) - task1 = Component(EpicsSignalRO, "-AERO:TSK1-DONE", auto_monitor=True, kind=Kind.hinted) + task1 = Component( + EpicsSignalRO, "-AERO:TSK1-DONE", auto_monitor=True, kind=Kind.hinted) task2 = Component(EpicsSignalRO, "-AERO:TSK2-DONE", auto_monitor=True) task3 = Component(EpicsSignalRO, "-AERO:TSK3-DONE", auto_monitor=True) task4 = Component(EpicsSignalRO, "-AERO:TSK4-DONE", auto_monitor=True) @@ -196,6 +202,14 @@ class AerotechAbrStage(Device): raster_num_rows = Component(EpicsSignal, "-GRD:ROW-DONE", kind=Kind.config) def set_axis_mode(self, mode: str, settle_time=0.1) -> None: + """ Set acix mode to direct/measurement mode. + Measurement mode blocks axis commands from the IOC. + + Parameters: + ----------- + mode : str + Valid values are 'direct' and 'measuring'. + """ if mode=="direct": self.axisModeDirect.set(37, settle_time=settle_time).wait() if mode=="measuring": @@ -259,20 +273,20 @@ class AerotechAbrStage(Device): def complete(self, timeout=None) -> DeviceStatus: """ ToDo: WTF was this device doing here? Whatever... """ - return self.raster.complete() + return self.raster.complete() def complete(self, timeout=None) -> SubscriptionStatus: """ Waits for task execution - + NOTE: Original complete was raster scanner complete... """ # Define wait until the busy flag goes down (excluding initial update) - def isIdle(*args, old_value, value, timestamp, **kwargs): - return bool(value==1) - + def is_idle(*args, old_value, value, timestamp, **kwargs): + return bool(value==1) + # Subscribe and wait for update - status = SubscriptionStatus(self.task1, isIdle, timeout=timeout, settle_time=0.5) + status = SubscriptionStatus(self.task1, is_idle, timeout=timeout, settle_time=0.5) return status def reset(self, settle_time=0.1): @@ -442,13 +456,15 @@ class AerotechAbrStage(Device): print("%s --- trying start again.", str(e)) self.osc.kickoff() - def move(self, position, wait=False, velocity=None, relative=None, direct=False, **kwargs) -> MoveStatus: + def move(self, position, wait=False, velocity=None, relative=None, direct=False, + **kwargs) -> MoveStatus: """ Move the omega axis """ - return self.omega.move(position, wait=wait, velocity=velocity, relative=relative, direct=direct, **kwargs) + return self.omega.move(position, wait=wait, velocity=velocity, relative=relative, + direct=direct, **kwargs) if __name__ == "__main__": abr = AerotechAbrStage(prefix="X06DA-ES", name="abr") - abr.wait_for_connection() \ No newline at end of file + abr.wait_for_connection() diff --git a/pxiii_bec/devices/A3200utils.py b/pxiii_bec/devices/A3200utils.py index 4ab5af8..07242a9 100644 --- a/pxiii_bec/devices/A3200utils.py +++ b/pxiii_bec/devices/A3200utils.py @@ -5,7 +5,8 @@ Created on Tue Jun 11 11:28:38 2024 @author: mohacsi_i """ import types -from ophyd import Component, PVPositioner, Device, Signal, EpicsSignal, EpicsSignalRO, Kind, PositionerBase +from ophyd import (Component, PVPositioner, Device, Signal, EpicsSignal, EpicsSignalRO, Kind, + PositionerBase) from ophyd.status import Status, MoveStatus, SubscriptionStatus, DeviceStatus @@ -51,8 +52,10 @@ class A3200Axis(PVPositioner): """ 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) + 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) @@ -87,34 +90,41 @@ class A3200Axis(PVPositioner): vmax = Component(Signal, kind=Kind.config) offset = Component(EpicsSignal, "-OFF", put_complete=True, kind=Kind.config) - 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): + 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""" + # 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 when no parent""" + # Patched not to enforce parent prefix when no parent 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"]) + 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) + 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, timeout=None, **kwargs) -> MoveStatus: + 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.abr_mode.value not in (DIRECT_MODE, "DIRECT"): - if direct: + if direct: self.abr_mode_direct.set(1).wait() else: raise RuntimeError(f"ABR axis not in direct mode: {self.abr_mode.value}") @@ -150,11 +160,12 @@ class A3200Axis(PVPositioner): status.wait() except KeyboardInterrupt: self.stop() - raise + raise return status - def omove(self, position, velocity=None, wait=True, relative=False, direct=False, **kwargs) -> MoveStatus: + 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) @@ -210,8 +221,10 @@ class A3200RasterScanner(Device): # Also needs the command interface _zcmd = Component(EpicsSignal, "-PSO:CMD", put_complete=True, kind=Kind.omitted) - _start_command = Component(EpicsSignal, "-PSO:START-TEST.PROC", put_complete=True, kind=Kind.omitted) - _stop_command = Component(EpicsSignal, "-PSO:STOP-TEST.PROC", put_complete=True, kind=Kind.omitted) + _start_command = Component( + EpicsSignal, "-PSO:START-TEST.PROC", put_complete=True, kind=Kind.omitted) + _stop_command = Component( + EpicsSignal, "-PSO:STOP-TEST.PROC", put_complete=True, kind=Kind.omitted) def raster_setup(self, positions, columns, angle, etime): @@ -276,8 +289,7 @@ class A3200RasterScanner(Device): if r < self._raster_num_rows: return (y, z) - else: - return (None, None) + return (None, None) def next_row(self): """start rastering a new row""" @@ -289,19 +301,19 @@ class A3200RasterScanner(Device): """ self.get_ready.set(1).wait() - for n in range(10): + for _ in range(10): try: # Define wait until the busy flag goes down (excluding initial update) timestamp_ = 0 def isReady(*args, old_value, value, timestamp, **kwargs): - nonlocal timestamp_ - result = False if (timestamp_== 0) else bool(value==ABR_READY) + nonlocal timestamp_ + result = False if (timestamp_== 0) else bool(value==ABR_READY) print(f"Old {old_value}\tNew: {value}\tResult: {result}") timestamp_ = timestamp return result - + # Subscribe and wait for update - status = SubscriptionStatus(self.grid_done, isReady, timeout=2, settle_time=0.5) + status = SubscriptionStatus(self.grid_done, isReady, timeout=2, settle_time=0.5) status.wait() except TimeoutError as e: print(str(e), end=" ") @@ -315,22 +327,21 @@ class A3200RasterScanner(Device): else: self.grid_next.set(1) - def is_scan_done(self): - return 1 == scan_done.get() + return 1 == self.scan_done.get() def wait_scan_done(self, timeout=60.0) -> SubscriptionStatus: # Define wait until the busy flag goes down (excluding initial update) timestamp_ = 0 def isReady(*args, old_value, value, timestamp, **kwargs): - nonlocal timestamp_ - result = False if (timestamp_== 0) else bool(value==1) + nonlocal timestamp_ + result = False if (timestamp_== 0) else bool(value==1) print(f"Old {old_value}\tNew: {value}\tResult: {result}") timestamp_ = timestamp return result - + # Subscribe and wait for update - status = SubscriptionStatus(self.scan_done, isReady, timeout=timeout, settle_time=0.5) + status = SubscriptionStatus(self.scan_done, isReady, timeout=timeout, settle_time=0.5) status.wait() return status @@ -341,32 +352,36 @@ class A3200RasterScanner(Device): def raster_wait_for_row(self, timeout=60) -> SubscriptionStatus: # Define wait subscription timestamp_ = 0 - def isReady(*args, old_value, value, timestamp, **kwargs): - nonlocal timestamp_ - result = False if (timestamp_== 0) else bool(value==1) + def is_ready(*args, old_value, value, timestamp, **kwargs): + nonlocal timestamp_ + result = False if (timestamp_== 0) else bool(value==1) print(f"Old {old_value}\tNew: {value}\tResult: {result}") timestamp_ = timestamp return result - + # Subscribe and wait for update - status = SubscriptionStatus(self.row_done, isReady, timeout=timeout, settle_time=0.5) + status = SubscriptionStatus(self.row_done, is_ready, timeout=timeout, settle_time=0.5) status.wait() return status def complete(self, timeout=None) -> DeviceStatus: + """ Wait for the grid scanner to finish + + TODO: Probably redundant with task status wait? + """ if timeout is not None: # Define wait until the busy flag goes down (excluding initial update) timestamp_ = 0 def isReady(*args, old_value, value, timestamp, **kwargs): - nonlocal timestamp_ - result = False if (timestamp_== 0) else bool(value==GRID_SCAN_DONE) + nonlocal timestamp_ + result = False if (timestamp_== 0) else bool(value==GRID_SCAN_DONE) print(f"Old {old_value}\tNew: {value}\tResult: {result}") timestamp_ = timestamp return result - + # Subscribe and wait for update - status = SubscriptionStatus(self.scan_done, isReady, timeout=timeout, settle_time=0.5) + status = SubscriptionStatus(self.scan_done, isReady, timeout=timeout, settle_time=0.5) return status status = DeviceStatus(self, settle_time=0.5) status.set_finished() @@ -374,14 +389,9 @@ class A3200RasterScanner(Device): - - - - class A3200Oscillator(Device): """No clue what this does, seems to be redundant with the task based grid scanners... """ - ostart_pos = Component(EpicsSignal, "-OSC:START-POS", put_complete=True, kind=Kind.config) orange = Component(EpicsSignal, "-OSC:RANGE", put_complete=True, kind=Kind.config) etime = Component(EpicsSignal, "-OSC:ETIME", put_complete=True, kind=Kind.config) @@ -415,27 +425,16 @@ class A3200Oscillator(Device): """ # Define wait until the busy flag goes down (excluding initial update) def inStatus(*args, old_value, value, timestamp, **kwargs): - result = bool(value==target) + result = bool(value==target) print(f"Old {old_value}\tNew: {value}\tResult: {result}") return result - + # Subscribe and wait for update - status = SubscriptionStatus(self.phase, inStatus, timeout=timeout, settle_time=0.5) + status = SubscriptionStatus(self.phase, inStatus, timeout=timeout, settle_time=0.5) return status - - - - - - - - - - - # Automatically start an axis if directly invoked if __name__ == "__main__": omega = A3200Axis(prefix="X06DA-ES-DF1:OMEGA", name="omega") diff --git a/pxiii_bec/scans/__init__.py b/pxiii_bec/scans/__init__.py index 2b4690a..6d3422d 100644 --- a/pxiii_bec/scans/__init__.py +++ b/pxiii_bec/scans/__init__.py @@ -1 +1,3 @@ -from .mx_measurements import MeasureFastOmega, MeasureStandardWedge, MeasureVerticalLine, MeasureScanSlits, MeasureRasterSimple, MeasureScreening, MeasureRepeatedOscillation, MeasureMSOX, MeasureGrid \ No newline at end of file +from .mx_measurements import (MeasureFastOmega, MeasureStandardWedge, MeasureVerticalLine, + MeasureScanSlits, MeasureRasterSimple, MeasureScreening, MeasureRepeatedOscillation, + MeasureMSOX, MeasureGrid) diff --git a/pxiii_bec/scans/mx_measurements.py b/pxiii_bec/scans/mx_measurements.py index 5cd99a7..dbb4348 100644 --- a/pxiii_bec/scans/mx_measurements.py +++ b/pxiii_bec/scans/mx_measurements.py @@ -1,4 +1,8 @@ -import time +""" MX measurements module + +Scan primitives for standard BEC scans at the PX beamlines at SLS. +Theese scans define the event model and can be called from higher levels. +""" import numpy as np from bec_lib import bec_logger @@ -10,13 +14,9 @@ logger = bec_logger.logger FULL_PERIOD = 0 HALF_PERIOD = 1 -class SasttSnakeMode: - SNAKE_SINGLE = 0 - UNIDIRECTIONAL = 1 - SNAKE_DOUBLE_PSO = 2 - class AbrCmd: + """ Valid Aerotech ABR scan commands from the AeroBasic files""" NONE = 0 RASTER_SCAN_SIMPLE = 1 MEASURE_STANDARD = 2 @@ -69,12 +69,13 @@ class AerotechFlyscanBase(AsyncFlyScanBase): abr_raster_reset =False abr_complete = False abr_timeout = None + pointID = 0 + num_pos = 0 def __init__(self, *args, parameter: dict = None, **kwargs): """ Just set num_pos=0 to avoid hanging and override defaults if explicitly set from parameters. """ - self.num_pos = 0 super().__init__(parameter=parameter, **kwargs) # Override if explicitly passed as parameter @@ -104,7 +105,7 @@ class AerotechFlyscanBase(AsyncFlyScanBase): # Reset the raster scan engine if self.abr_raster_reset: - yield from self.stubs.command("abr", "raster_scan_done.set", 0) + yield from self.stubs.send_rpc_and_wait("abr", "raster_scan_done.set", 0) # Configure the global variables d = {"scan_command" : self.abr_command} @@ -116,14 +117,13 @@ class AerotechFlyscanBase(AsyncFlyScanBase): # Call super yield from super().pre_scan() - def stage(self): - """ ToDo: Sot sure if we should call super() here as it'd stage the whole beamline""" - return super().stage() + # def stage(self): + # """ ToDo: Sot sure if we should call super() here as it'd stage the whole beamline""" + # return super().stage() def scan_core(self): """ The actual scan logic comes here. """ - self.pointID = 0 # Kick off the run yield from self.stubs.send_rpc_and_wait("abr", "start_command.set", 1) logger.info("Measurement launched on the ABR stage...") @@ -836,7 +836,7 @@ class MeasureGrid(AerotechFlyscanBase): if len(self.scan_positions)==1: raise RuntimeWarning("Raster scan with one cell makes no sense") - + # Call base class super().__init__(parameter=parameter, **kwargs) @@ -893,7 +893,8 @@ class MeasureGridStill(AerotechFlyscanBase): Example ------- - >>> scans.measure_raster(positions=[[11, 2, 3.4], [11, 2, 3.5], ...], steps_x=20, steps_y=20, exp_time=0.1, delay=0.05) + >>> pts = [[11, 2, 3.4], [11, 2, 3.5], ...] + >>> scans.measure_raster(positions=pts, steps_x=20, steps_y=20, exp_time=0.1, delay=0.05) Parameters ---------- @@ -1012,7 +1013,9 @@ class MeasureJungfrau(AerotechFlyscanBase): self.scan_stepsize_y = self.scan_range_y / self.scan_stepnum_y self.scan_exp_time = parameter['kwargs'].get("exp_time") self.scan_sleep = parameter['kwargs'].get("sleep", 0.1) - self.abr_timeout = None if self.scan_sleep <= 0 else 5.0 + (self.scan_num_rows * self.scan_sleep) + (self.scan_num_cols * self.scan_num_rows * self.scan_exp_time) + sleep_time = 5.0 + (self.scan_stepnum_y * self.scan_sleep) + ( + self.scan_stepnum_x * self.scan_stepnum_y * self.scan_exp_time) + self.abr_timeout = None if self.scan_sleep <= 0 else sleep_time self.abr_command = AbrCmd.JUNGFRAU self.abr_complete = True self.abr_globals = { @@ -1024,7 +1027,7 @@ class MeasureJungfrau(AerotechFlyscanBase): "var_6" : self.scan_sleep, "var_7" : 0, "var_8" : 0, - } + } # Call base class super().__init__(parameter=parameter, **kwargs) @@ -1035,7 +1038,4 @@ class MeasureJungfrau(AerotechFlyscanBase): # Go back to direct mode st = yield from self.stubs.send_rpc_and_wait("abr", "set_axis_mode", "direct") - st.wait() - - - + st.wait() \ No newline at end of file -- 2.49.1 From add46d8b0dc883f7a4ec5d12c28924c79857f17b Mon Sep 17 00:00:00 2001 From: Unknown MX Person Date: Fri, 13 Dec 2024 17:44:52 +0100 Subject: [PATCH 15/25] A3200 cleanup --- pxiii_bec/devices/A3200.py | 144 +++++++------------------------- pxiii_bec/devices/A3200enums.py | 113 +++++++++++++++++++++++++ pxiii_bec/devices/A3200utils.py | 65 ++++++++++---- pxiii_bec/devices/__init__.py | 6 ++ 4 files changed, 202 insertions(+), 126 deletions(-) create mode 100644 pxiii_bec/devices/A3200enums.py diff --git a/pxiii_bec/devices/A3200.py b/pxiii_bec/devices/A3200.py index 7dff5bd..44b1437 100644 --- a/pxiii_bec/devices/A3200.py +++ b/pxiii_bec/devices/A3200.py @@ -80,56 +80,20 @@ Examples """ import time from ophyd import Component, Device, EpicsSignal, EpicsSignalRO, Kind -from ophyd.status import MoveStatus, SubscriptionStatus, DeviceStatus +from ophyd.status import SubscriptionStatus -from .A3200utils import A3200Axis, A3200RasterScanner, A3200Oscillator +from .A3200utils import A3200RasterScanner, A3200Oscillator +from .A3200enums import * +try: + from bec_lib import bec_logger + logger = bec_logger.logger +except ModuleNotFoundError: + import logging + logger = logging.getLogger("A3200") -ABR_DONE = 0 -ABR_READY = 1 -ABR_BUSY = 2 +# pylint: disable=logging-fstring-interpolation -GRID_SCAN_BUSY = 0 -GRID_SCAN_DONE = 1 - -DIRECT_MODE = 0 -MEASURING_MODE = 1 - -SHUTTER_CLOSE = 0 -SHUTTER_OPEN = 1 - -FULL_PERIOD = 0 -HALF_PERIOD = 1 - -CMD_NONE = 0 -CMD_RASTER_SCAN_SIMPLE = 1 -CMD_MEASURE_STANDARD = 2 -CMD_VERTICAL_LINE_SCAN = 3 -CMD_SCREENING = 4 -CMD_SUPER_FAST_OMEGA = 5 -CMD_STILL_WEDGE = 6 -CMD_STILLS = 7 -CMD_REPEAT_SINGLE_OSCILLATION = 8 -CMD_SINGLE_OSCILLATION = 9 -CMD_OLD_FASHIONED = 10 -CMD_RASTER_SCAN = 11 -CMD_JET_ROTATION = 12 -CMD_X_HELICAL = 13 -CMD_X_RUNSEQ = 14 -CMD_JUNGFRAU = 15 -CMD_MSOX = 16 -CMD_SLIT_SCAN = 17 -CMD_RASTER_SCAN_STILL = 18 -CMD_SCAN_SASTT = 19 -CMD_SCAN_SASTT_V2 = 20 -CMD_SCAN_SASTT_V3 = 21 - -AXIS_OMEGA = 1 -AXIS_GMX = 2 -AXIS_GMY = 3 -AXIS_GMZ = 4 -AXIS_STY = 5 -AXIS_STZ = 6 class AerotechAbrStage(Device): @@ -165,11 +129,13 @@ class AerotechAbrStage(Device): raster = Component(A3200RasterScanner, "", kind=Kind.config) osc = Component(A3200Oscillator, "", kind=Kind.config) - # Positioners for the motor axes - omega = Component(A3200Axis, "-DF1:OMEGA", kind=Kind.hinted) - gmx = Component(A3200Axis, "-DF1:GMX", kind=Kind.hinted) - gmy = Component(A3200Axis, "-DF1:GMY", kind=Kind.hinted) - gmz = Component(A3200Axis, "-DF1:GMZ", kind=Kind.hinted) + # Status flags for all axes + omega_done = Component(EpicsSignalRO, "-DF1:OMEGA-DONE", kind=Kind.normal) + gmx_done = Component(EpicsSignalRO, "-DF1:GMX-DONE", kind=Kind.normal) + gmy_done = Component(EpicsSignalRO, "-DF1:GMY-DONE", kind=Kind.normal) + gmz_done = Component(EpicsSignalRO, "-DF1:GMZ-DONE", kind=Kind.normal) + + scan_command = Component( EpicsSignal, "-PSO:CMD", put_complete=True, kind=Kind.omitted) @@ -270,19 +236,13 @@ class AerotechAbrStage(Device): new = self.read_configuration() return old, new - def complete(self, timeout=None) -> DeviceStatus: - """ ToDo: WTF was this device doing here? Whatever... - """ - return self.raster.complete() - - def complete(self, timeout=None) -> SubscriptionStatus: """ Waits for task execution NOTE: Original complete was raster scanner complete... """ # Define wait until the busy flag goes down (excluding initial update) - def is_idle(*args, old_value, value, timestamp, **kwargs): + def is_idle(*args, value, _, **kwargs): return bool(value==1) # Subscribe and wait for update @@ -290,46 +250,14 @@ class AerotechAbrStage(Device): return status def reset(self, settle_time=0.1): + """Attempts to reset the currently running measurement task on the A3200""" self.scan_command.set(CMD_NONE, settle_time=settle_time).wait() self.set_axis_mode("direct", settle_time=settle_time) def is_ioc_ok(self): + """Checks execution status""" return 0 == self.status.get() - def is_ready_and_willing(self): - return self.is_ioc_ok() and self.omega.is_homed() - - def is_omega_ok(self): - return 0 == self.self.omega.status.get() - - def is_homed(self): - """Are the motors homed?""" - return 1 == self.omega.is_homed.get() - - def do_homing(self, wait=True): - """Execute the homing procedure. - - 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() - if not wait: - return - while not self.omega.is_homed(): - time.sleep(0.2) - - @property - def velocity(self): - return self.omega.velocity.get() - - @velocity.setter - def velocity(self, value): - self.omega.velocity.set(value).wait() - @property def exp_time(self): return self.osc.exp_time.get() @@ -396,12 +324,11 @@ class AerotechAbrStage(Device): self.osc.get_ready.set(1, settle_time=0.1).wait() if wait: - for n in range(10): + for _ in range(10): try: self.wait_status(ABR_READY, timeout=5).wait() - except RuntimeWarning as e: - print(str(e), end=" ") - print(" --- trying ready again.") + except RuntimeWarning as ex: + logger.warning(f"{ex} --- trying ready again.") self.osc.get_ready.set(1).wait() @@ -418,8 +345,8 @@ class AerotechAbrStage(Device): time.sleep(0.1) try: moving = self.is_moving() - except: - print("Aerotech() Failed to retrieve moving state for axes omega, gmx, gmy, gmz") + except Exception as ex: + logger.error(f"Failed to retrieve moving state for axes: {ex}") moving = True timeisup = timeout < time.time() @@ -427,9 +354,10 @@ class AerotechAbrStage(Device): raise RuntimeWarning("timeout waiting for all axis to stop moving") def is_moving(self): - return ( - self.omega.moving - and self.gmx.moving and self.gmy.moving and self.gmz.moving ) + """Chechs if all axes are DONE""" + return not ( + self.omega_done.get() + and self.gmx_done.get() and self.gmy_done.get() and self.gmz_done.get() ) def stop(self, wait_after_reload=1.0): """Stops current motions; reloads aerobasic programs; goes DIRECT @@ -449,21 +377,13 @@ class AerotechAbrStage(Device): """Starts the previously configured exposure.""" self.wait_for_movements() self.osc.taskStart.set(1).wait() - for n in range(10): + for _ in range(10): try: self.osc.wait_status(ABR_BUSY, timeout=1) - except RuntimeWarning as e: - print("%s --- trying start again.", str(e)) + except RuntimeWarning as ex: + logger.error(f"{ex} --- trying start again.") self.osc.kickoff() - def move(self, position, wait=False, velocity=None, relative=None, direct=False, - **kwargs) -> MoveStatus: - """ Move the omega axis - """ - return self.omega.move(position, wait=wait, velocity=velocity, relative=relative, - direct=direct, **kwargs) - - if __name__ == "__main__": abr = AerotechAbrStage(prefix="X06DA-ES", name="abr") diff --git a/pxiii_bec/devices/A3200enums.py b/pxiii_bec/devices/A3200enums.py new file mode 100644 index 0000000..c898e94 --- /dev/null +++ b/pxiii_bec/devices/A3200enums.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +""" +Enumerations for the MX specific Aerotech A3200 stage. + +@author: mohacsi_i +""" +# pylint: disable=too-few-public-methods + +class AbrStatus: + """ABR measurement task status""" + DONE = 0 + READY = 1 + BUSY = 2 + +ABR_DONE = 0 +ABR_READY = 1 +ABR_BUSY = 2 + +class AbrGridStatus: + """ABR grid scan status""" + BUSY = 0 + DONE = 1 + +GRID_SCAN_BUSY = 0 +GRID_SCAN_DONE = 1 + +class AbrMode: + """ABR mode status""" + DIRECT = 0 + MEASURING = 1 + +DIRECT_MODE = 0 +MEASURING_MODE = 1 + +class AbrShutterStatus: + """ABR shutter status""" + CLOSE = 0 + OPEN = 1 + +SHUTTER_CLOSE = 0 +SHUTTER_OPEN = 1 + +class AbrGridPeriod: + """ABR grid period""" + FULL = 0 + HALF = 1 + +FULL_PERIOD = 0 +HALF_PERIOD = 1 + +class AbrCmd: + """ABR command table""" + NONE = 0 + RASTER_SCAN_SIMPLE = 1 + MEASURE_STANDARD = 2 + VERTICAL_LINE_SCAN = 3 + SCREENING = 4 + SUPER_FAST_OMEGA = 5 + STILL_WEDGE = 6 + STILLS = 7 + REPEAT_SINGLE_OSCILLATION = 8 + SINGLE_OSCILLATION = 9 + OLD_FASHIONED = 10 + RASTER_SCAN = 11 + JET_ROTATION = 12 + X_HELICAL = 13 + X_RUNSEQ = 14 + JUNGFRAU = 15 + MSOX = 16 + SLIT_SCAN = 17 + RASTER_SCAN_STILL = 18 + SCAN_SASTT = 19 + SCAN_SASTT_V2 = 20 + SCAN_SASTT_V3 = 21 + +CMD_NONE = 0 +CMD_RASTER_SCAN_SIMPLE = 1 +CMD_MEASURE_STANDARD = 2 +CMD_VERTICAL_LINE_SCAN = 3 +CMD_SCREENING = 4 +CMD_SUPER_FAST_OMEGA = 5 +CMD_STILL_WEDGE = 6 +CMD_STILLS = 7 +CMD_REPEAT_SINGLE_OSCILLATION = 8 +CMD_SINGLE_OSCILLATION = 9 +CMD_OLD_FASHIONED = 10 +CMD_RASTER_SCAN = 11 +CMD_JET_ROTATION = 12 +CMD_X_HELICAL = 13 +CMD_X_RUNSEQ = 14 +CMD_JUNGFRAU = 15 +CMD_MSOX = 16 +CMD_SLIT_SCAN = 17 +CMD_RASTER_SCAN_STILL = 18 +CMD_SCAN_SASTT = 19 +CMD_SCAN_SASTT_V2 = 20 +CMD_SCAN_SASTT_V3 = 21 + +class AbrAxis: + """ABR axis index""" + OMEGA = 1 + GMX = 2 + GMY = 3 + GMZ = 4 + STY = 5 + STZ = 6 + +AXIS_OMEGA = 1 +AXIS_GMX = 2 +AXIS_GMY = 3 +AXIS_GMZ = 4 +AXIS_STY = 5 +AXIS_STZ = 6 \ No newline at end of file diff --git a/pxiii_bec/devices/A3200utils.py b/pxiii_bec/devices/A3200utils.py index 07242a9..ae0025f 100644 --- a/pxiii_bec/devices/A3200utils.py +++ b/pxiii_bec/devices/A3200utils.py @@ -5,6 +5,7 @@ Created on Tue Jun 11 11:28:38 2024 @author: mohacsi_i """ import types +import math from ophyd import (Component, PVPositioner, Device, Signal, EpicsSignal, EpicsSignalRO, Kind, PositionerBase) from ophyd.status import Status, MoveStatus, SubscriptionStatus, DeviceStatus @@ -14,6 +15,11 @@ from bec_lib import bec_logger logger = bec_logger.logger +from .A3200enums import * + + + + ABR_DONE = 0 ABR_READY = 1 ABR_BUSY = 2 @@ -118,8 +124,8 @@ class A3200Axis(PVPositioner): self.vmin.set(vmin).wait() self.vmax.set(vmax).wait() - def move(self, position, velocity=None, wait=True, relative=False, direct=False, timeout=None, - **kwargs) -> MoveStatus: + def omove(self, position, wait=True, timeout=None, moved_cb=None, velocity=None, relative=False, + direct=False, **kwargs) -> MoveStatus: """ Native absolute/relative movement on the A3200 """ # Check if we're in direct movement mode @@ -164,10 +170,9 @@ class A3200Axis(PVPositioner): return status - def omove(self, position, velocity=None, wait=True, relative=False, direct=False, - **kwargs) -> MoveStatus: + def move(self, position, wait=True, timeout=None, moved_cb=None, **kwargs) -> MoveStatus: """ Exposes the ophyd move command through BEC abstraction""" - return self.move(position, velocity, wait, relative, direct, **kwargs) + return self.omove(position, wait=wait, timeout=timeout, moved_cb=moved_cb, **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""" @@ -186,6 +191,38 @@ class A3200Axis(PVPositioner): return status + # def is_omega_ok(self): + # """Checks omega axis status""" + # return 0 == self.self.omega.status.get() + + # def is_homed(self): + # """Checks if omega is homed""" + # return 1 == self.omega.is_homed.get() + + # def do_homing(self, wait=True): + # """Execute the homing procedure. + + # Executes the homing procedure on omega 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() + # if not wait: + # return + # while not self.omega.is_homed(): + # time.sleep(0.2) + + + + + + + + + @@ -305,7 +342,7 @@ class A3200RasterScanner(Device): try: # Define wait until the busy flag goes down (excluding initial update) timestamp_ = 0 - def isReady(*args, old_value, value, timestamp, **kwargs): + def is_ready(*args, old_value, value, timestamp, **kwargs): nonlocal timestamp_ result = False if (timestamp_== 0) else bool(value==ABR_READY) print(f"Old {old_value}\tNew: {value}\tResult: {result}") @@ -313,7 +350,7 @@ class A3200RasterScanner(Device): return result # Subscribe and wait for update - status = SubscriptionStatus(self.grid_done, isReady, timeout=2, settle_time=0.5) + status = SubscriptionStatus(self.grid_done, is_ready, timeout=2, settle_time=0.5) status.wait() except TimeoutError as e: print(str(e), end=" ") @@ -333,7 +370,7 @@ class A3200RasterScanner(Device): def wait_scan_done(self, timeout=60.0) -> SubscriptionStatus: # Define wait until the busy flag goes down (excluding initial update) timestamp_ = 0 - def isReady(*args, old_value, value, timestamp, **kwargs): + def is_ready(*args, old_value, value, timestamp, **kwargs): nonlocal timestamp_ result = False if (timestamp_== 0) else bool(value==1) print(f"Old {old_value}\tNew: {value}\tResult: {result}") @@ -341,7 +378,7 @@ class A3200RasterScanner(Device): return result # Subscribe and wait for update - status = SubscriptionStatus(self.scan_done, isReady, timeout=timeout, settle_time=0.5) + status = SubscriptionStatus(self.scan_done, is_ready, timeout=timeout, settle_time=0.5) status.wait() return status @@ -373,7 +410,7 @@ class A3200RasterScanner(Device): if timeout is not None: # Define wait until the busy flag goes down (excluding initial update) timestamp_ = 0 - def isReady(*args, old_value, value, timestamp, **kwargs): + def is_ready(*args, old_value, value, timestamp, **kwargs): nonlocal timestamp_ result = False if (timestamp_== 0) else bool(value==GRID_SCAN_DONE) print(f"Old {old_value}\tNew: {value}\tResult: {result}") @@ -381,7 +418,7 @@ class A3200RasterScanner(Device): return result # Subscribe and wait for update - status = SubscriptionStatus(self.scan_done, isReady, timeout=timeout, settle_time=0.5) + status = SubscriptionStatus(self.scan_done, is_ready, timeout=timeout, settle_time=0.5) return status status = DeviceStatus(self, settle_time=0.5) status.set_finished() @@ -424,13 +461,13 @@ class A3200Oscillator(Device): SubscriptionStatus : A waitable status subscribed on 'phase' updates. """ # Define wait until the busy flag goes down (excluding initial update) - def inStatus(*args, old_value, value, timestamp, **kwargs): + def in_status(*args, old_value, value, _, **kwargs): result = bool(value==target) - print(f"Old {old_value}\tNew: {value}\tResult: {result}") + # print(f"Old {old_value}\tNew: {value}\tResult: {result}") return result # Subscribe and wait for update - status = SubscriptionStatus(self.phase, inStatus, timeout=timeout, settle_time=0.5) + status = SubscriptionStatus(self.phase, in_status, timeout=timeout, settle_time=0.5) return status diff --git a/pxiii_bec/devices/__init__.py b/pxiii_bec/devices/__init__.py index 76f5548..d09a6fd 100644 --- a/pxiii_bec/devices/__init__.py +++ b/pxiii_bec/devices/__init__.py @@ -1,2 +1,8 @@ +# -*- coding: utf-8 -*- +""" +Ophyd devices for the PX III beamline, including the MX specific Aerotech A3200 stage. + +@author: mohacsi_i +""" from .A3200 import AerotechAbrStage from .A3200utils import A3200Axis -- 2.49.1 From 0b99a82ae9ae18cef0c70826ea7945f64dad603b Mon Sep 17 00:00:00 2001 From: gac-x06da Date: Tue, 17 Dec 2024 17:41:47 +0100 Subject: [PATCH 16/25] Waiting for raster scan works --- .../device_configs/x06da_device_config.yaml | 21 +------------------ pxiii_bec/devices/A3200.py | 5 +++-- 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/pxiii_bec/device_configs/x06da_device_config.yaml b/pxiii_bec/device_configs/x06da_device_config.yaml index 00c4f8b..74d2a1e 100644 --- a/pxiii_bec/device_configs/x06da_device_config.yaml +++ b/pxiii_bec/device_configs/x06da_device_config.yaml @@ -394,34 +394,15 @@ xbox_diode: readoutPriority: monitored readOnly: true softwareTrigger: false -ms_focus: - description: Sample microscope focus - deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES-MS:FOCUS'} - onFailure: buffer - enabled: true - readoutPriority: monitored - readOnly: false - softwareTrigger: false ms_zoom: description: Sample microscope zoom deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES-MS:ZOOM'} + deviceConfig: {prefix: 'X06DA-ES-SAMCAM:ZOOM'} onFailure: buffer enabled: true readoutPriority: monitored readOnly: false softwareTrigger: false -ms_try: - description: Sample microscope translation - deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES-MS:TRY1'} - onFailure: buffer - enabled: true - readoutPriority: monitored - readOnly: false - softwareTrigger: false - bstop_pneum: description: Beamstop pneumatic in-out diff --git a/pxiii_bec/devices/A3200.py b/pxiii_bec/devices/A3200.py index 44b1437..be02d1b 100644 --- a/pxiii_bec/devices/A3200.py +++ b/pxiii_bec/devices/A3200.py @@ -242,11 +242,12 @@ class AerotechAbrStage(Device): NOTE: Original complete was raster scanner complete... """ # Define wait until the busy flag goes down (excluding initial update) - def is_idle(*args, value, _, **kwargs): + def is_idle(*args, value, **kwargs): return bool(value==1) # Subscribe and wait for update - status = SubscriptionStatus(self.task1, is_idle, timeout=timeout, settle_time=0.5) + # status = SubscriptionStatus(self.task1, is_idle, timeout=timeout, settle_time=0.5) + status = SubscriptionStatus(self.raster_scan_done, is_idle, timeout=timeout, settle_time=0.5) return status def reset(self, settle_time=0.1): -- 2.49.1 From d3d016108e0d6fa028ee6174cef5c68d9b647e01 Mon Sep 17 00:00:00 2001 From: gac-x06da Date: Wed, 15 Jan 2025 14:46:51 +0100 Subject: [PATCH 17/25] First tries with ABR --- pxiii_bec/devices/SmarGon.py | 94 +++++++ pxiii_bec/devices/SmarGon_orig.py | 391 ++++++++++++++++++++++++++++++ 2 files changed, 485 insertions(+) create mode 100644 pxiii_bec/devices/SmarGon.py create mode 100644 pxiii_bec/devices/SmarGon_orig.py diff --git a/pxiii_bec/devices/SmarGon.py b/pxiii_bec/devices/SmarGon.py new file mode 100644 index 0000000..aaa2b34 --- /dev/null +++ b/pxiii_bec/devices/SmarGon.py @@ -0,0 +1,94 @@ +import time +from ophyd import Component, Device, EpicsSignal, EpicsSignalRO, Kind, Device, Signal +from ophyd.status import SubscriptionStatus + +import requests + +try: + from bec_lib import bec_logger + logger = bec_logger.logger +except ModuleNotFoundError: + import logging + logger = logging.getLogger("A3200") + + +class SmarGonSignal(Signal): + """Small helper class to read PVs that need to be processed first.""" + + def __init__(self, group, addr, *args, **kwargs): + super().__init__(*args, **kwargs) + self.group = group + self.addr = addr + # self.get() + + def put(self, value, *args, **kwargs): + r = self.parent._go_n_put(f"target{self.group}?{self.addr}={value}") + print(r) + return super().put(*args, **kwargs) + + def get(self, *args, **kwargs): + r = self.parent._go_n_get(f"target{self.group}?{self.addr}") + print(r) + return super().get(*args, **kwargs) + + +class SmarGonClient(Device): + """SmarGon client deice + + This class controls the SmarGon goniometer via the REST interface. + """ + # pylint: disable=too-many-instance-attributes + USER_ACCESS = ["set_daq_config", "get_daq_config", "nuke", "connect", "message", "state", "bluestage", "blueunstage"] + + # Status attributes + sg_url = Component(Signal, kind=Kind.config, metadata={'write_access': False}) + + # Axis parameters + shx = Component(SmarGonSignal, group="SCS", name="shx", kind=Kind.config) + # shy = Component(SmarGonSignal, group="SCS", name="shy", kind=Kind.config) + # shz = Component(SmarGonSignal, group="SCS", name="shz", kind=Kind.config) + # chi = Component(SmarGonSignal, group="SCS", name="chi", kind=Kind.config) + # phi = Component(SmarGonSignal, group="SCS", name="phi", kind=Kind.config) + + def __init__( + self, + prefix="", + *, + name, + kind=None, + read_attrs=None, + configuration_attrs=None, + parent=None, + device_manager=None, + sg_url: str = "http://x06da-smargopolo.psi.ch:3000", + **kwargs, + ) -> None: + # super().__init__(prefix=prefix, name=name, kind=kind, read_attrs=read_attrs, configuration_attrs=configuration_attrs, parent=parent, device_manager=device_manager, **kwargs) + + super().__init__(prefix=prefix, name=name, kind=kind, read_attrs=read_attrs, configuration_attrs=configuration_attrs, parent=parent, **kwargs) + self.sg_url._metadata["write_access"] = False + self.sg_url.set(sg_url, force=True).wait() + + def _go_n_get(self, name, **kwargs): + cmd = f"{self.sg_url.get()}/{name}" + r = requests.get(cmd, timeout=1) + if not r.ok: + raise RuntimeError(f"[self.name] Error getting {name}; server returned {r.status_code} => {r.reason}") + return r.json() + + def _go_n_put(self, name, **kwargs): + cmd = f"{self.sg_url.get()}/{name}" + r = requests.put(cmd, timeout=1) + if not r.ok: + raise RuntimeError(f"[self.name] Error putting {name}; server returned {r.status_code} => {r.reason}") + + + + + +if __name__ == "__main__": + sg = SmarGonClient(prefix="X06DA-ES", name="smargon") + sg.wait_for_connection() + + + diff --git a/pxiii_bec/devices/SmarGon_orig.py b/pxiii_bec/devices/SmarGon_orig.py new file mode 100644 index 0000000..bde885f --- /dev/null +++ b/pxiii_bec/devices/SmarGon_orig.py @@ -0,0 +1,391 @@ +#!/usr/bin/env python3 + +from time import sleep, time +from typing import Tuple + +from requests import get, put + +from beamline import beamline +from mx_redis import SMARGON + +try: + from mx_preferences import get_config + + host = get_config(beamline)["smargon"]["host"] + port = get_config(beamline)["smargon"]["port"] +except Exception: + host = "x06da-smargopolo.psi.ch" + port = 3000 +base = f"http://{host}:{port}" + + +def gonget(thing: str, **kwargs) -> dict: + """issue a GET for some API component on the smargopolo server""" + cmd = f"{base}/{thing}" + if kwargs.get("verbose", False): + print(cmd) + r = get(cmd) + if not r.ok: + raise Exception(f"error getting {thing}; server returned {r.status_code} => {r.reason}") + return r.json() + + +def gonput(thing: str, **kwargs): + """issue a PUT for some API component on the smargopolo server""" + cmd = f"{base}/{thing}" + if kwargs.get("verbose", False): + print(cmd) + put(cmd) + + +def scsput(**kwargs): + """ + Issue a new absolute target in the SH coordinate system. + + The key "verbose" may be passed in kwargs with any true + value for verbose behaviour. + + + :param kwargs: a dict containing keys ("shx", "shy", "shz", "chi", "phi") + :type kwargs: dict + :return: + :rtype: + """ + xyz = {k.upper(): v for k, v in kwargs.items() if k.lower() in ("shx", "shy", "shz", "chi", "phi")} + thing = "&".join([f"{k.upper()}={float(v):.5f}" for k, v in xyz.items()]) + cmd = f"{base}/targetSCS?{thing}" + if kwargs.get("verbose", False): + print(cmd) + put(cmd) + + +def bcsput(**kwargs): + """ + Issue a new absolute target in the beamline coordinate system. + + The key "verbose" may be passed in kwargs with any true + value for verbose behaviour. + + + :param kwargs: a dict containing keys ("bx", "by", "bz", "chi", "phi") + :return: + :rtype: + """ + xyz = {k.upper(): v for k, v in kwargs.items() if k.lower() in ("bx", "by", "bz", "chi", "phi")} + thing = "&".join([f"{k.upper()}={float(v):.5f}" for k, v in xyz.items()]) + cmd = f"{base}/targetBCS?{thing}" + if kwargs.get("verbose", False): + print(cmd) + put(cmd) + + +def scsrelput(**kwargs) -> None: + """ + Issue relative increments to current SH coordinate system. + + The key "verbose" may be passed in kwargs with any true + value for verbose behaviour. + + + :param kwargs: a dict containing keys ("shx", "shy", "shz", "chi", "phi") + :type kwargs: dict + :return: + :rtype: + """ + xyz = {k.upper(): v for k, v in kwargs.items() if k.lower() in ("shx", "shy", "shz", "chi", "phi")} + thing = "&".join([f"{k.upper()}={float(v):.5f}" for k, v in xyz.items()]) + cmd = f"{base}/targetSCS_rel?{thing}" + if kwargs.get("verbose", False): + print(cmd) + put(cmd) + + +def bcsrelput(**kwargs): + """ + Issue relative increments to current beamline coordinate system. + + The key "verbose" may be passed in kwargs with any true + value for verbose behaviour. + + :param kwargs: a dict containing keys ("bx", "by", "bz") + :type kwargs: dict + :return: + :rtype: + """ + xyz = {k.upper(): v for k, v in kwargs.items() if k.lower() in ("bx", "by", "bz")} + thing = "&".join([f"{k.upper()}={float(v):.5f}" for k, v in xyz.items()]) + cmd = f"{base}/targetBCS_rel?{thing}" + if kwargs.get("verbose", False): + print(cmd) + put(cmd) + + +# url_redis = f"{beamline}-cons-705.psi.ch" +# print(f"connecting to redis DB #3 on host: {url_redis}") +# redis_handle = redis.StrictRedis(host=url_redis, db=3) +# pubsub = redis_handle.pubsub() + +MODE_UNINITIALIZED = 0 +MODE_INITIALIZING = 1 +MODE_READY = 2 +MODE_ERROR = 99 + + +class SmarGon(object): + def __init__(self): + super(SmarGon, self).__init__() + self.__dict__.update(target=None) + self.__dict__.update(bookmarks={}) + self.__dict__.update(_latest_message={}) + # pubsub.psubscribe(**{f"__keyspace@{SMARGON.value}__:*": self._cb_readbackSCS}) + # pubsub.run_in_thread(sleep_time=0.5, daemon=True) + + def __repr__(self): + BX, BY, BZ, OMEGA, CHI, PHI, a, b, c = self.readback_bcs().values() + return f"<{self.__class__.__name__} X={BX:.3f}, Y={BY:.3f}, Z={BZ:.3f}, CHI={CHI:.3f}, PHI={PHI:.3f}, OMEGA={OMEGA:.3f}>" + + def _cb_readbackSCS(self, msg): + if msg["data"] in ["hset"]: + self._latest_message = msg + + def move_home(self, wait=False) -> None: + """move to beamline coordinate system X, Y, Z, Chi, Phi = 0 0 0 0 0""" + self.apply_bookmark_sh({"shx": 0.0, "shy": 0.0, "shz": 18.0, "chi": 0.0, "phi": 0.0}) + if wait: + self.wait_home() + + def xyz(self, coords: Tuple[float, float, float], wait: bool = True) -> None: + """ + Move smargon in absolute beamline coordinates + + :param coords: a tuple of floats representing X, Y, Z coordinates + :type coords: + :param wait: + :type wait: + :return: + :rtype: + """ + x, y, z = coords + # the two steps below are necessary otherwise the control system + # remembers *a* previous CHI + bcs = self.bcs + bcs.update({"BX": x, "BY": y, "BZ": z}) + self.bcs = bcs + if wait: + self.wait() + + def wait_home(self, timeout: float = 20.0) -> None: + """ + wait for the smargon to reach its home position: + SHX = 0.0 + SHY = 0.0 + SHZ = 18.0 + CHI = 0.0 + PHI = 0.0 + + :param timeout: time to wait for positions to be reached raises TimeoutError if timeout reached + :type timeout: float + :return: + :rtype: + """ + tout = timeout + time() + in_place = [False, False] + rbv = -999.0 + while not all(in_place) and time() < tout: + rbv = self.readback_scs() + in_place = [] + for k, v in { + "SHX": 0.0, + "SHY": 0.0, + "SHZ": 18.0, + "CHI": 0.0, + "PHI": 0.0, + }.items(): + in_place.append(abs(rbv[k] - v) < 0.01) + if time() > tout: + raise TimeoutError(f"timeout waiting for smargon to reach home position: {rbv}") + + def push_bookmark(self): + """ + save current absolute coordinates in FIFO stack + :return: + :rtype: + """ + t = round(time()) + self.bookmarks[t] = self.readback_scs() + + def pop_bookmark(self): + return self.bookmarks.popitem()[1] + + def apply_bookmark_sh(self, scs): + scsput(**scs) + + def apply_last_bookmark_sh(self): + scs = self.pop_bookmark() + scsput(**scs) + + def readback_mcs(self): + """current motor positions of the smargon sliders""" + return gonget("readbackMCS") + + def readback_scs(self): + """current SH coordinates of the smargon model""" + return gonget("readbackSCS") + + def readback_bcs(self): + """current beamline coordinates of the smargon""" + return gonget("readbackBCS") + + def target_scs(self): + """currently assigned targets for the smargon control system""" + return gonget("targetSCS") + + def initialize(self): + """initialize the smargon""" + self.set_mode(MODE_UNINITIALIZED) + sleep(0.1) + self.set_mode(MODE_INITIALIZING) + + def set_mode(self, mode: int): + """put smargon control system in a given mode + MODE_UNINITIALIZED = 0 + MODE_INITIALIZING = 1 + MODE_READY = 2 + MODE_ERROR = 99 + """ + gonput(f"mode?mode={mode}") + + def enable_correction(self): + """enable calibration based corrections""" + gonput("corr_type?corr_type=1") + + def disable_correction(self): + """disable calibration based corrections""" + gonput("corr_type?corr_type=0") + + def chi(self, val=None, wait=False): + if val is None: + return self.readback_scs()["CHI"] + scsput(CHI=val) + if wait: + timeout = 10 + time() + while time() < timeout: + if abs(val - self.readback_scs()["CHI"]) < 0.1: + break + if time() > timeout: + raise RuntimeError(f"SmarGon CHI did not reach requested target {val} in time") + + def phi(self, val=None, wait=False): + if val is None: + return self.readback_scs()["PHI"] + scsput(PHI=val) + if wait: + timeout = 70 + time() + while time() < timeout: + if abs(val - self.readback_scs()["PHI"]) < 0.1: + break + if time() > timeout: + raise RuntimeError(f"SmarGon PHI did not reach requested target {val} in time") + + def wait(self, timeout=60.0): + """waits up to `timeout` seconds for smargon to reach target""" + target = { + k.upper(): v for k, v in self.target_scs().items() if k.lower() in ("shx", "shy", "shz", "chi", "phi") + } + + timeout = timeout + time() + while time() < timeout: + s = { + k: (abs(v - target[k]) < 0.01) + for k, v in self.readback_scs().items() + if k.upper() in ("SHX", "SHY", "SHZ", "CHI", "PHI") + } + if all(list(s.values())): + break + if time() > timeout: + raise TimeoutError("timed out waiting for smargon to reach target") + + def __setattr__(self, key, value): + key = key.lower() + if key == "mode": + self.set_mode(value) + elif key == "correction": + assert value in ( + 0, + 1, + False, + True, + ), "correction is either 1 or True (enabled) or 0 (disabled)" + gonput(f"corr_type?corr_type?{value}") + elif key == "scs": + scsput(**value) + elif key == "bcs": + bcsput(**value) + elif key == "target": + if not isinstance(value, dict): + raise Exception(f"expected a dict with target axis and values got something else: {value}") + for k in value.keys(): + if k.lower() not in "shx shy shz chi phi ox oy oz".split(): + raise Exception(f'unknown axis in target "{k}"') + scsput(**value) + elif key in "shx shy shz chi phi ox oy oz".split(): + scsput(**{key: value}) + elif key in "bx by bz".split(): + bcs = self.readback_bcs() + bcs[key] = value + bcsput(**bcs) + else: + self.__dict__[key].update(value) + + def __getattr__(self, key): + key = key.lower() + if key == "mode": + return self.readback_mcs()["mode"] + elif key == "correction": + return gonget("corr_type") + elif key == "bcs": + return self.readback_bcs() + elif key == "mcs": + return self.readback_mcs() + elif key == "scs": + return self.readback_scs() + elif key in "shx shy shz chi phi ox oy oz".split(): + return self.readback_scs()[key.upper()] + elif key in "bx by bz".split(): + return self.readback_bcs()[key.upper()] + else: + return self.__getattribute__(key) + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser(description="SmarGon client") + parser.add_argument("-i", "--initialize", help="initialize smargon", action="store_true") + args = parser.parse_args() + + smargon = SmarGon() + + if args.initialize: + print("initializing smargon device") + import Aerotech + + print("moving aerotech back by 50mm") + abr = Aerotech.Abr() + + abr.incr_x(-50.0, wait=True, velo=100.0) + + print("issuing init command to smargon") + smargon.initialize() + sleep(0.5) + + print("waiting for init routine to complete") + while MODE_READY != smargon.mode: + sleep(0.5) + + print("moving smargon to HOME position") + smargon.move_home() + + print("moving aerotech to its previous position") + abr.incr_x(50.0, wait=True, velo=100.0) + exit(0) -- 2.49.1 From 03a5850bbfe68e30727b6067ace137271eb91aff Mon Sep 17 00:00:00 2001 From: gac-x06da Date: Wed, 15 Jan 2025 17:44:46 +0100 Subject: [PATCH 18/25] SmarGon reads --- pxiii_bec/devices/A3200.py | 122 ++++++++++++++--------------------- pxiii_bec/devices/SmarGon.py | 104 +++++++++++++++++++++++++---- 2 files changed, 139 insertions(+), 87 deletions(-) diff --git a/pxiii_bec/devices/A3200.py b/pxiii_bec/devices/A3200.py index be02d1b..a5e6ddb 100644 --- a/pxiii_bec/devices/A3200.py +++ b/pxiii_bec/devices/A3200.py @@ -100,11 +100,11 @@ class AerotechAbrStage(Device): """ Standard PX stage on A3200 controller This is the wrapper class for the standard rotation stage layout for the PX - beamlines at SLS. It wraps the main rotation axis OMEGA and the associated - motion axes GMX, GMY and GMZ. The ophyd class associates to the general PX - measurement procedure, which is that the actual scan script is running as - an AeroBasic program on the controller and we communicate to it via 10+1 - global variables. + beamlines at SLS. It wraps the main rotation axis OMEGA (Aerotech ABR)and + the associated motion axes GMX, GMY and GMZ. The ophyd class associates to + the general PX measurement procedure, which is that the actual scan script + is running as an AeroBasic program on the controller and we communicate to + it via 10+1 global variables. """ USER_ACCESS = ['reset', 'kickoff', 'complete'] @@ -135,8 +135,7 @@ class AerotechAbrStage(Device): gmy_done = Component(EpicsSignalRO, "-DF1:GMY-DONE", kind=Kind.normal) gmz_done = Component(EpicsSignalRO, "-DF1:GMZ-DONE", kind=Kind.normal) - - + # For some reason the task interface is called PSO... scan_command = Component( EpicsSignal, "-PSO:CMD", put_complete=True, kind=Kind.omitted) start_command = Component( @@ -157,8 +156,7 @@ class AerotechAbrStage(Device): _var_10 = Component(EpicsSignal, "-PSO:VAR-10", put_complete=True, kind=Kind.omitted) # Task status PVs (programs always run on task 1) - task1 = Component( - EpicsSignalRO, "-AERO:TSK1-DONE", auto_monitor=True, kind=Kind.hinted) + task1 = Component(EpicsSignalRO, "-AERO:TSK1-DONE", auto_monitor=True) task2 = Component(EpicsSignalRO, "-AERO:TSK2-DONE", auto_monitor=True) task3 = Component(EpicsSignalRO, "-AERO:TSK3-DONE", auto_monitor=True) task4 = Component(EpicsSignalRO, "-AERO:TSK4-DONE", auto_monitor=True) @@ -168,8 +166,10 @@ class AerotechAbrStage(Device): raster_num_rows = Component(EpicsSignal, "-GRD:ROW-DONE", kind=Kind.config) def set_axis_mode(self, mode: str, settle_time=0.1) -> None: - """ Set acix mode to direct/measurement mode. - Measurement mode blocks axis commands from the IOC. + """ Set axis mode to direct/measurement mode. + + Measurement ensures that the scrips run undisturbed by blocking axis + motion commands from the IOC (i.e. internal only). Parameters: ----------- @@ -250,9 +250,31 @@ class AerotechAbrStage(Device): status = SubscriptionStatus(self.raster_scan_done, is_idle, timeout=timeout, settle_time=0.5) return status - def reset(self, settle_time=0.1): - """Attempts to reset the currently running measurement task on the A3200""" + def reset(self, settle_time=0.1, wait_after_reload=1) -> None: + """ Resets the Aerotech controller state + + Attempts to reset the currently running measurement task on the A3200 + by stopping current motions, reloading aerobasic programs and going + to DIRECT mode. + + This will stop movements in both DIRECT and MEASURING modes. During the + stop the `status` temporarely goes to ERROR but reverts to OK after a + couple of seconds. + """ + # Disarm commands self.scan_command.set(CMD_NONE, settle_time=settle_time).wait() + self.stop_command.set(1, settle_time=settle_time) + # Reload tasks + self.taskStop.set(1, settle_time=wait_after_reload).wait() + # Go to direct mode + self.set_axis_mode("direct", settle_time=settle_time) + + def stop(self, settle_time=1.0) -> None: + """ Stops current motions + """ + # Disarm commands + self.scan_command.set(CMD_NONE, settle_time=settle_time).wait() + # Go to direct mode self.set_axis_mode("direct", settle_time=settle_time) def is_ioc_ok(self): @@ -283,6 +305,10 @@ class AerotechAbrStage(Device): def measurement_state(self, value): self.osc.phase.set(value).wait() + @property + def axis_mode(self): + return self.axisAxesMode.get() + # @property # def shutter(self): # return self._shutter.get() @@ -303,56 +329,16 @@ class AerotechAbrStage(Device): # self._shutter.set(state).wait() # return state == self._shutter.get() - @property - def axis_mode(self): - return self.axisAxesMode.get() - - def get_ready(self, ostart=None, orange=None, etime=None, wait=True): - self.wait_for_movements() - if self.measurement_state == ABR_BUSY: - raise RuntimeError("ABR is not DONE!!!!") - - if self.measurement_state == ABR_READY: - self.measurement_state = ABR_DONE - - if ostart is not None: - self.osc.ostart.set(ostart).wait() - if orange is not None: - self.osc.orange.set(orange).wait() - if etime is not None: - self.osc.etime.set(etime).wait() - - self.osc.get_ready.set(1, settle_time=0.1).wait() - - if wait: - for _ in range(10): - try: - self.wait_status(ABR_READY, timeout=5).wait() - except RuntimeWarning as ex: - logger.warning(f"{ex} --- trying ready again.") - self.osc.get_ready.set(1).wait() - - def wait_for_movements(self, timeout=60.0): - timeout = timeout + time.time() - timeisup = False + """ Waits for all motor movements""" + t_start = time.time() + t_elapsed = 0 - try: - moving = self.is_moving() - except: - moving = True - - while not timeisup and moving: - time.sleep(0.1) - try: - moving = self.is_moving() - except Exception as ex: - logger.error(f"Failed to retrieve moving state for axes: {ex}") - moving = True - timeisup = timeout < time.time() - - if timeisup: - raise RuntimeWarning("timeout waiting for all axis to stop moving") + while self.is_moving() and t_elapsed < timeout: + t_elapsed = time.time() - t_start + if timeout is not None and t_elapsed > timeout: + raise TimeoutError("Timeout waiting for all axis to stop moving") + time.sleep(0.5) def is_moving(self): """Chechs if all axes are DONE""" @@ -360,20 +346,6 @@ class AerotechAbrStage(Device): self.omega_done.get() and self.gmx_done.get() and self.gmy_done.get() and self.gmz_done.get() ) - def stop(self, wait_after_reload=1.0): - """Stops current motions; reloads aerobasic programs; goes DIRECT - - This will stop movements in both DIRECT and MEASURING modes. During the - stop the `status` temporarely goes to ERROR but reverts to OK after a - couple of seconds. - - """ - self.taskStop.set(1, settle_time=wait_after_reload).wait() - self.reset() - time.sleep(0.1) - - reload_programs = stop - def start_exposure(self): """Starts the previously configured exposure.""" self.wait_for_movements() diff --git a/pxiii_bec/devices/SmarGon.py b/pxiii_bec/devices/SmarGon.py index aaa2b34..9888725 100644 --- a/pxiii_bec/devices/SmarGon.py +++ b/pxiii_bec/devices/SmarGon.py @@ -15,22 +15,64 @@ except ModuleNotFoundError: class SmarGonSignal(Signal): """Small helper class to read PVs that need to be processed first.""" - def __init__(self, group, addr, *args, **kwargs): + def __init__(self, prefix, *args, **kwargs): super().__init__(*args, **kwargs) - self.group = group - self.addr = addr + self.prefix = prefix + self.addr = self.parent.name # self.get() def put(self, value, *args, **kwargs): - r = self.parent._go_n_put(f"target{self.group}?{self.addr}={value}") - print(r) - return super().put(*args, **kwargs) + self._go_n_put(f"target{self.prefix}?{self.addr}={value}") + return super().put(value, *args, force=True, **kwargs) def get(self, *args, **kwargs): - r = self.parent._go_n_get(f"target{self.group}?{self.addr}") + r = self._go_n_get(f"target{self.prefix}") print(r) + self.value = r[self.addr.upper()] return super().get(*args, **kwargs) + def _go_n_get(self, name, **kwargs): + cmd = f"{self.parent.sg_url.get()}/{name}" + r = requests.get(cmd, timeout=1) + if not r.ok: + raise RuntimeError(f"[self.name] Error getting {name}; server returned {r.status_code} => {r.reason}") + return r.json() + + def _go_n_put(self, name, **kwargs): + cmd = f"{self.parent.sg_url.get()}/{name}" + r = requests.put(cmd, timeout=1) + if not r.ok: + raise RuntimeError(f"[self.name] Error putting {name}; server returned {r.status_code} => {r.reason}") + + + + + +class SmarGonSignalRO(Signal): + """Small helper class to read PVs that need to be processed first. + + TODO: Add monitoring + """ + + def __init__(self, prefix, *args, **kwargs): + super().__init__(*args, **kwargs) + self._metadata["write_access"] = False + self.prefix = prefix + self.addr = self.parent.name + + def get(self, *args, **kwargs): + r = self._go_n_get(f"readback{self.prefix}") + print(r) + self.put(r[self.addr.upper()], force=True) + return super().get(*args, **kwargs) + + def _go_n_get(self, name, **kwargs): + cmd = f"{self.parent.sg_url.get()}/{name}" + r = requests.get(cmd, timeout=1) + if not r.ok: + raise RuntimeError(f"[self.name] Error getting {name}; server returned {r.status_code} => {r.reason}") + return r.json() + class SmarGonClient(Device): """SmarGon client deice @@ -44,11 +86,11 @@ class SmarGonClient(Device): sg_url = Component(Signal, kind=Kind.config, metadata={'write_access': False}) # Axis parameters - shx = Component(SmarGonSignal, group="SCS", name="shx", kind=Kind.config) - # shy = Component(SmarGonSignal, group="SCS", name="shy", kind=Kind.config) - # shz = Component(SmarGonSignal, group="SCS", name="shz", kind=Kind.config) - # chi = Component(SmarGonSignal, group="SCS", name="chi", kind=Kind.config) - # phi = Component(SmarGonSignal, group="SCS", name="phi", kind=Kind.config) + shx = Component(SmarGonSignal, group="SCS", addr="shx", kind=Kind.config) + # shy = Component(SmarGonSignal, group="SCS", addr="shy", kind=Kind.config) + # shz = Component(SmarGonSignal, group="SCS", addr="shz", kind=Kind.config) + # chi = Component(SmarGonSignal, group="SCS", addr="chi", kind=Kind.config) + # phi = Component(SmarGonSignal, group="SCS", addr="phi", kind=Kind.config) def __init__( self, @@ -84,9 +126,47 @@ class SmarGonClient(Device): +class SmarGonAxis(Device): + """SmarGon client deice + + This class controls the SmarGon goniometer via the REST interface. + """ + # Status attributes + sg_url = Component(Signal, kind=Kind.config, metadata={'write_access': False}) + + # Axis parameters + readback = Component(SmarGonSignalRO, kind=Kind.config) + setpoint = Component(SmarGonSignal, kind=Kind.config) + + def __init__( + self, + prefix="SCS", + *, + name, + kind=None, + read_attrs=None, + configuration_attrs=None, + parent=None, + device_manager=None, + sg_url: str = "http://x06da-smargopolo.psi.ch:3000", + **kwargs, + ) -> None: + self.__class__.__dict__['readback'].kwargs['prefix'] = prefix + self.__class__.__dict__['readback'].kwargs['name'] = name + self.__class__.__dict__['setpoint'].kwargs['prefix'] = prefix + self.__class__.__dict__['setpoint'].kwargs['name'] = name + + + # super().__init__(prefix=prefix, name=name, kind=kind, read_attrs=read_attrs, configuration_attrs=configuration_attrs, parent=parent, device_manager=device_manager, **kwargs) + + super().__init__(prefix=prefix, name=name, kind=kind, read_attrs=read_attrs, configuration_attrs=configuration_attrs, parent=parent, **kwargs) + self.sg_url._metadata["write_access"] = False + self.sg_url.set(sg_url, force=True).wait() + if __name__ == "__main__": + shz = SmarGonAxis(prefix="SCS", name="shz", sg_url="http://x06da-smargopolo.psi.ch:3000") sg = SmarGonClient(prefix="X06DA-ES", name="smargon") sg.wait_for_connection() -- 2.49.1 From 8da2ed4102289c26afed785e209ad926d4a418eb Mon Sep 17 00:00:00 2001 From: gac-x06da Date: Mon, 20 Jan 2025 18:41:04 +0100 Subject: [PATCH 19/25] BEC style A3200 seems working --- .../device_configs/x06da_device_config.yaml | 132 +-- pxiii_bec/devices/A3200.py | 216 ++++- pxiii_bec/scans/__init__.py | 5 +- pxiii_bec/scans/mx_measurements.py | 843 +----------------- 4 files changed, 271 insertions(+), 925 deletions(-) diff --git a/pxiii_bec/device_configs/x06da_device_config.yaml b/pxiii_bec/device_configs/x06da_device_config.yaml index 74d2a1e..07f543c 100644 --- a/pxiii_bec/device_configs/x06da_device_config.yaml +++ b/pxiii_bec/device_configs/x06da_device_config.yaml @@ -100,7 +100,7 @@ dccm_xbpm: ssxbpm_trx: description: XBPM motion before secondary source deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES-SSBPM1:TRX'} + deviceConfig: {prefix: 'X06DA-ES-SSBPM1:TRX1'} onFailure: buffer enabled: true readoutPriority: monitored @@ -109,7 +109,7 @@ ssxbpm_trx: ssxbpm_try: description: XBPM motion before secondary source deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES-SSBPM1:TRY'} + deviceConfig: {prefix: 'X06DA-ES-SSBPM1:TRY1'} onFailure: buffer enabled: true readoutPriority: monitored @@ -127,7 +127,7 @@ ssxbpm_try: ssslit_trxr: description: Secondary source blade motion deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES-SSSLH1:TRXR'} + deviceConfig: {prefix: 'X06DA-ES-SSSH1:TRXR'} onFailure: buffer enabled: true readoutPriority: monitored @@ -136,7 +136,7 @@ ssslit_trxr: ssslit_trxw: description: Secondary source blade motion deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES-SSSLH1:TRXW'} + deviceConfig: {prefix: 'X06DA-ES-SSSH1:TRXW'} onFailure: buffer enabled: true readoutPriority: monitored @@ -145,7 +145,7 @@ ssslit_trxw: ssslit_tryt: description: Secondary source blade motion deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES-SSSLV1:TRYT'} + deviceConfig: {prefix: 'X06DA-ES-SSSV1:TRYT'} onFailure: buffer enabled: true readoutPriority: monitored @@ -154,7 +154,7 @@ ssslit_tryt: ssslit_tryb: description: Secondary source blade motion deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES-SSSLV1:TRYB'} + deviceConfig: {prefix: 'X06DA-ES-SSSV1:TRYB'} onFailure: buffer enabled: true readoutPriority: monitored @@ -163,7 +163,7 @@ ssslit_tryb: ssxi1_trx: description: Secondary source diagnostic screen motion deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES-SSXI1:TRX'} + deviceConfig: {prefix: 'X06DA-ES-SSXI1:TRX1'} onFailure: buffer enabled: true readoutPriority: monitored @@ -172,7 +172,7 @@ ssxi1_trx: ssxi1_try: description: Secondary source diagnostic screen motion deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES-SSXI1:TRY'} + deviceConfig: {prefix: 'X06DA-ES-SSXI1:TRY1'} onFailure: buffer enabled: true readoutPriority: monitored @@ -194,34 +194,34 @@ vfm_trxd: readoutPriority: monitored readOnly: false softwareTrigger: false -vfm_tryuw: - deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES-VFM:TRYUW'} - onFailure: buffer - enabled: true - readoutPriority: monitored - readOnly: false - softwareTrigger: false -vfm_tryr: - deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES-VFM:TRYR'} - onFailure: buffer - enabled: true - readoutPriority: monitored - readOnly: false - softwareTrigger: false -vfm_trydw: - deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES-VFM:TRYDW'} - onFailure: buffer - enabled: true - readoutPriority: monitored - readOnly: false - softwareTrigger: false +# vfm_tryuw: +# deviceClass: ophyd.EpicsMotor +# deviceConfig: {prefix: 'X06DA-ES-VFM:TRYUW'} +# onFailure: buffer +# enabled: true +# readoutPriority: monitored +# readOnly: false +# softwareTrigger: false +# vfm_tryr: +# deviceClass: ophyd.EpicsMotor +# deviceConfig: {prefix: 'X06DA-ES-VFM:TRYR'} +# onFailure: buffer +# enabled: true +# readoutPriority: monitored +# readOnly: false +# softwareTrigger: false +# vfm_trydw: +# deviceClass: ophyd.EpicsMotor +# deviceConfig: {prefix: 'X06DA-ES-VFM:TRYDW'} +# onFailure: buffer +# enabled: true +# readoutPriority: monitored +# readOnly: false +# softwareTrigger: false vfm_pitch: description: KB mirror vertical steering deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES1-VFM:PITCH'} + deviceConfig: {prefix: 'X06DA-ES-VFM:PITCH'} onFailure: buffer enabled: true readoutPriority: monitored @@ -229,7 +229,7 @@ vfm_pitch: softwareTrigger: false vfm_yaw: deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES1-VFM:YAW'} + deviceConfig: {prefix: 'X06DA-ES-VFM:YAW'} enabled: false onFailure: buffer readoutPriority: monitored @@ -237,7 +237,7 @@ vfm_yaw: softwareTrigger: false vfm_roll: deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES1-VFM:ROLL'} + deviceConfig: {prefix: 'X06DA-ES-VFM:ROLL'} enabled: false onFailure: buffer readoutPriority: monitored @@ -245,7 +245,7 @@ vfm_roll: softwareTrigger: false vfm_trx: deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES1-VFM:TRX'} + deviceConfig: {prefix: 'X06DA-ES-VFM:TRX'} enabled: false onFailure: buffer readoutPriority: monitored @@ -253,7 +253,7 @@ vfm_trx: softwareTrigger: false vfm_try: deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES1-VFM:TRY'} + deviceConfig: {prefix: 'X06DA-ES-VFM:TRY'} onFailure: buffer enabled: true readoutPriority: monitored @@ -275,34 +275,34 @@ hfm_trxd: readoutPriority: monitored readOnly: false softwareTrigger: false -hfm_tryur: - deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES-HFM:TRYUR'} - onFailure: buffer - enabled: true - readoutPriority: monitored - readOnly: false - softwareTrigger: false -hfm_tryw: - deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES-HFM:TRYW'} - onFailure: buffer - enabled: true - readoutPriority: monitored - readOnly: false - softwareTrigger: false -hfm_trydr: - deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES-HFM:TRYDR'} - onFailure: buffer - enabled: true - readoutPriority: monitored - readOnly: false - softwareTrigger: false +# hfm_tryur: +# deviceClass: ophyd.EpicsMotor +# deviceConfig: {prefix: 'X06DA-ES-HFM:TRYUR'} +# onFailure: buffer +# enabled: true +# readoutPriority: monitored +# readOnly: false +# softwareTrigger: false +# hfm_tryw: +# deviceClass: ophyd.EpicsMotor +# deviceConfig: {prefix: 'X06DA-ES-HFM:TRYW'} +# onFailure: buffer +# enabled: true +# readoutPriority: monitored +# readOnly: false +# softwareTrigger: false +# hfm_trydr: +# deviceClass: ophyd.EpicsMotor +# deviceConfig: {prefix: 'X06DA-ES-HFM:TRYDR'} +# onFailure: buffer +# enabled: true +# readoutPriority: monitored +# readOnly: false +# softwareTrigger: false hfm_pitch: description: KB mirror horizontal steering deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES1-HFM:PITCH'} + deviceConfig: {prefix: 'X06DA-ES-HFM:PITCH'} enabled: false onFailure: buffer readoutPriority: monitored @@ -310,7 +310,7 @@ hfm_pitch: softwareTrigger: false hfm_yaw: deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES1-HFM:YAW'} + deviceConfig: {prefix: 'X06DA-ES-HFM:YAW'} enabled: false onFailure: buffer readoutPriority: monitored @@ -318,7 +318,7 @@ hfm_yaw: softwareTrigger: false hfm_roll: deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES1-HFM:ROLL'} + deviceConfig: {prefix: 'X06DA-ES-HFM:ROLL'} enabled: false onFailure: buffer readoutPriority: monitored @@ -326,7 +326,7 @@ hfm_roll: softwareTrigger: false hfm_trx: deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES1-HFM:TRX'} + deviceConfig: {prefix: 'X06DA-ES-HFM:TRX'} enabled: false onFailure: buffer readoutPriority: monitored @@ -334,7 +334,7 @@ hfm_trx: softwareTrigger: false hfm_try: deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES1-HFM:TRY'} + deviceConfig: {prefix: 'X06DA-ES-HFM:TRY'} onFailure: buffer enabled: true readoutPriority: monitored diff --git a/pxiii_bec/devices/A3200.py b/pxiii_bec/devices/A3200.py index a5e6ddb..686778b 100644 --- a/pxiii_bec/devices/A3200.py +++ b/pxiii_bec/devices/A3200.py @@ -79,11 +79,16 @@ Examples """ import time -from ophyd import Component, Device, EpicsSignal, EpicsSignalRO, Kind +from ophyd import Component, EpicsSignal, EpicsSignalRO, Kind from ophyd.status import SubscriptionStatus +from ophyd_devices.interfaces.base_classes.psi_detector_base import PSIDetectorBase as PsiDeviceBase +from ophyd_devices.interfaces.base_classes.psi_detector_base import CustomDetectorMixin as CustomDeviceMixin + +try: + from .A3200enums import AbrCmd, AbrMode +except ImportError: + from A3200enums import AbrCmd, AbrMode -from .A3200utils import A3200RasterScanner, A3200Oscillator -from .A3200enums import * try: from bec_lib import bec_logger @@ -92,11 +97,114 @@ except ModuleNotFoundError: import logging logger = logging.getLogger("A3200") + # pylint: disable=logging-fstring-interpolation +class AerotechAbrMixin(CustomDeviceMixin): + """ Configuration class for the Aerotech A3200 controller for the ABR stage""" -class AerotechAbrStage(Device): + + + def on_stage(self): + """ + + NOTE: Zac's request is that stage is essentially ARM, i.e. get ready and don't do anything. + """ + + logger.warning(f"Configuring {self.parent.scaninfo.scan_msg.info['scan_name']} on ABR") + + d = {} + if self.parent.scaninfo.scan_type in ("measure", "measurement"): + scanargs = self.parent.scaninfo.scan_msg.info['kwargs'] + scanname = self.parent.scaninfo.scan_msg.info['scan_name'] + + if scanname in ("standardscan"): + scan_start = scanargs["start"] + scan_range = scanargs["range"] + scan_move_time = scanargs["move_time"] + scan_ready_rate = scanargs.get("ready_rate", 500) + d["scan_command"] = AbrCmd.MEASURE_STANDARD + d["var_1"] = scan_start + d["var_2"] = scan_range + d["var_3"] = scan_move_time + d["var_4"] = scan_ready_rate + d["var_5"] = 0 + d["var_6"] = 0 + d["var_7"] = 0 + d["var_8"] = 0 + d["var_9"] = 0 + if scanname in ("verticallinescan", "vlinescan"): + scan_exp_time = scanargs["exp_time"] + scan_range_y = scanargs["range"] + scan_steps_y = scanargs["steps"] + d["scan_command"] = AbrCmd.VERTICAL_LINE_SCAN + d["var_1"] = scan_exp_time + d["var_2"] = scan_range_y + d["var_3"] = scan_steps_y + d["var_4"] = 0 + d["var_5"] = 0 + d["var_6"] = 0 + d["var_7"] = 0 + d["var_8"] = 0 + d["var_9"] = 0 + if scanname in ("screeningscan"): + scan_start = scanargs["start"] + scan_range = scanargs["range"] + scan_stepnum_o = scanargs["steps"] + scan_exp_time = scanargs["exp_time"] + scan_oscrange = scanargs["oscrange"] + scan_delta = scanargs.get("delta", 0.5) + scan_stepsize_o = scan_range / scan_stepnum_o + d["scan_command"] = AbrCmd.SCREENING + d["var_1"] = scan_start + d["var_2"] = scan_oscrange + d["var_3"] = scan_exp_time + d["var_4"] = scan_stepsize_o + d["var_5"] = scan_stepnum_o + d["var_6"] = scan_delta + d["var_7"] = 0 + d["var_8"] = 0 + d["var_9"] = 0 + if scanname in ("rasterscan", "rastersimplescan"): + scan_exp_time = scanargs["exp_time"] + scan_range_x = scanargs["range_x"] + scan_range_y = scanargs["range_y"] + scan_stepnum_x = scanargs["steps_x"] + scan_stepnum_y = scanargs["steps_y"] + scan_stepsize_x = scan_range_x / scan_stepnum_x + scan_stepsize_y = scan_range_y / scan_stepnum_y + d["scan_command"] = AbrCmd.RASTER_SCAN_SIMPLE + d["var_1"] = scan_exp_time + d["var_2"] = scan_stepsize_x + d["var_3"] = scan_stepsize_y + d["var_4"] = scan_stepnum_x + d["var_5"] = scan_stepnum_y + d["var_6"] = 0 + d["var_7"] = 0 + d["var_8"] = 0 + d["var_9"] = 0 + + # Reconfigure if got a valid scan config + if len(d)>0: + self.parent.configure(d) + + # Stage the parent + self.parent.bluestage() + + def on_kickoff(self): + """ Kick off parent """ + self.parent.bluekickoff() + + def on_unstage(self): + """ Unstage the ABR controller""" + self.parent.blueunstage() + + + + + +class AerotechAbrStage(PsiDeviceBase): """ Standard PX stage on A3200 controller This is the wrapper class for the standard rotation stage layout for the PX @@ -106,12 +214,15 @@ class AerotechAbrStage(Device): is running as an AeroBasic program on the controller and we communicate to it via 10+1 global variables. """ + custom_prepare_cls = AerotechAbrMixin 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) + clear = Component( + EpicsSignal, "-AERO:CTRL-CLFT", put_complete=True, kind=Kind.omitted) # Enable/disable motor movement via the IOC (i.e. make it task-only) axisModeLocked = Component( @@ -126,9 +237,6 @@ class AerotechAbrStage(Device): # EpicsSignal, "-PH1:GET", write_pv="-PH1:SET", put_complete=True, kind=Kind.config, # ) - raster = Component(A3200RasterScanner, "", kind=Kind.config) - osc = Component(A3200Oscillator, "", kind=Kind.config) - # Status flags for all axes omega_done = Component(EpicsSignalRO, "-DF1:OMEGA-DONE", kind=Kind.normal) gmx_done = Component(EpicsSignalRO, "-DF1:GMX-DONE", kind=Kind.normal) @@ -179,7 +287,7 @@ class AerotechAbrStage(Device): if mode=="direct": self.axisModeDirect.set(37, settle_time=settle_time).wait() if mode=="measuring": - self.axisAxesMode.set(MEASURING_MODE, settle_time=settle_time).wait() + self.axisAxesMode.set(AbrMode.MEASURING, settle_time=settle_time).wait() def configure(self, d: dict) -> tuple: """" Configure the exposure scripts @@ -209,9 +317,8 @@ class AerotechAbrStage(Device): old = self.read_configuration() # ToDo: Check if idle before reconfiguring - - # Set the corresponding global variables self.scan_command.set(d['scan_command']).wait() + # Set the corresponding global variables 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: @@ -236,6 +343,34 @@ class AerotechAbrStage(Device): new = self.read_configuration() return old, new + def bluestage(self): + """ Bluesky-style stage + + Since configuration synchronization is not guaranteed, this does + nothing. The script launched by kickoff(). + """ + pass + + def bluekickoff(self, timeout=1) -> SubscriptionStatus: + """ Kick off the set program """ + self.start_command.set(1).wait() + + # Define wait until the busy flag goes high + def is_busy(*args, value, **kwargs): + return bool(value==0) + + # Subscribe and wait for update + status = SubscriptionStatus(self.raster_scan_done, is_busy, timeout=timeout, settle_time=0.1) + return status + + def blueunstage(self, settle_time=0.1): + """ Stops current script and releases the axes""" + # Disarm commands + self.scan_command.set(AbrCmd.NONE, settle_time=settle_time).wait() + self.stop_command.set(1).wait() + # Go to direct mode + self.set_axis_mode("direct", settle_time=settle_time) + def complete(self, timeout=None) -> SubscriptionStatus: """ Waits for task execution @@ -262,7 +397,7 @@ class AerotechAbrStage(Device): couple of seconds. """ # Disarm commands - self.scan_command.set(CMD_NONE, settle_time=settle_time).wait() + self.scan_command.set(AbrCmd.NONE, settle_time=settle_time).wait() self.stop_command.set(1, settle_time=settle_time) # Reload tasks self.taskStop.set(1, settle_time=wait_after_reload).wait() @@ -273,7 +408,8 @@ class AerotechAbrStage(Device): """ Stops current motions """ # Disarm commands - self.scan_command.set(CMD_NONE, settle_time=settle_time).wait() + self.scan_command.set(AbrCmd.NONE, settle_time=settle_time).wait() + self.stop_command.set(1).wait() # Go to direct mode self.set_axis_mode("direct", settle_time=settle_time) @@ -281,29 +417,29 @@ class AerotechAbrStage(Device): """Checks execution status""" return 0 == self.status.get() - @property - def exp_time(self): - return self.osc.exp_time.get() + # @property + # def exp_time(self): + # return self.osc.exp_time.get() - @exp_time.setter - def exp_time(self, value): - self.osc.etime.set(value).wait() + # @exp_time.setter + # def exp_time(self, value): + # self.osc.etime.set(value).wait() - @property - def start_angle(self): - return self.osc.ostart_pos.get() + # @property + # def start_angle(self): + # return self.osc.ostart_pos.get() - @start_angle.setter - def start_angle(self, value): - self.osc.ostart_pos(value).wait() + # @start_angle.setter + # def start_angle(self, value): + # self.osc.ostart_pos(value).wait() - @property - def measurement_state(self): - return self.osc.phase.get() + # @property + # def measurement_state(self): + # return self.osc.phase.get() - @measurement_state.setter - def measurement_state(self, value): - self.osc.phase.set(value).wait() + # @measurement_state.setter + # def measurement_state(self, value): + # self.osc.phase.set(value).wait() @property def axis_mode(self): @@ -346,16 +482,16 @@ class AerotechAbrStage(Device): self.omega_done.get() and self.gmx_done.get() and self.gmy_done.get() and self.gmz_done.get() ) - def start_exposure(self): - """Starts the previously configured exposure.""" - self.wait_for_movements() - self.osc.taskStart.set(1).wait() - for _ in range(10): - try: - self.osc.wait_status(ABR_BUSY, timeout=1) - except RuntimeWarning as ex: - logger.error(f"{ex} --- trying start again.") - self.osc.kickoff() + # def start_exposure(self): + # """Starts the previously configured exposure.""" + # self.wait_for_movements() + # self.osc.taskStart.set(1).wait() + # for _ in range(10): + # try: + # self.osc.wait_status(ABR_BUSY, timeout=1) + # except RuntimeWarning as ex: + # logger.error(f"{ex} --- trying start again.") + # self.osc.kickoff() if __name__ == "__main__": diff --git a/pxiii_bec/scans/__init__.py b/pxiii_bec/scans/__init__.py index 6d3422d..b874c1d 100644 --- a/pxiii_bec/scans/__init__.py +++ b/pxiii_bec/scans/__init__.py @@ -1,3 +1,2 @@ -from .mx_measurements import (MeasureFastOmega, MeasureStandardWedge, MeasureVerticalLine, - MeasureScanSlits, MeasureRasterSimple, MeasureScreening, MeasureRepeatedOscillation, - MeasureMSOX, MeasureGrid) +from .mx_measurements import (MeasureStandardWedge, MeasureVerticalLine, + MeasureRasterSimple, MeasureScreening) diff --git a/pxiii_bec/scans/mx_measurements.py b/pxiii_bec/scans/mx_measurements.py index dbb4348..d03a9ca 100644 --- a/pxiii_bec/scans/mx_measurements.py +++ b/pxiii_bec/scans/mx_measurements.py @@ -11,9 +11,6 @@ from bec_server.scan_server.scans import AsyncFlyScanBase, ScanArgType, ScanBase logger = bec_logger.logger -FULL_PERIOD = 0 -HALF_PERIOD = 1 - class AbrCmd: """ Valid Aerotech ABR scan commands from the AeroBasic files""" @@ -22,23 +19,23 @@ class AbrCmd: MEASURE_STANDARD = 2 VERTICAL_LINE_SCAN = 3 SCREENING = 4 - SUPER_FAST_OMEGA = 5 - STILL_WEDGE = 6 # NOTE: Unused - STILLS = 7 # NOTE: Unused - REPEAT_SINGLE_OSCILLATION = 8 # NOTE: Unused - SINGLE_OSCILLATION = 9 - OLD_FASHIONED = 10 # NOTE: Unused - RASTER_SCAN = 11 - JET_ROTATION = 12 # NOTE: Unused - X_HELICAL = 13 # NOTE: Unused - X_RUNSEQ = 14 # NOTE: Unused - JUNGFRAU = 15 - MSOX = 16 # NOTE: Unused - SLIT_SCAN = 17 # NOTE: Unused - RASTER_SCAN_STILL = 18 - SCAN_SASTT = 19 - SCAN_SASTT_V2 = 20 - SCAN_SASTT_V3 = 21 + # SUPER_FAST_OMEGA = 5 # Some Japanese wanted to measure samples in capilaries at high RPMs + # STILL_WEDGE = 6 # NOTE: Unused Step scan + # STILLS = 7 # NOTE: Unused Just send triggers to detector + # REPEAT_SINGLE_OSCILLATION = 8 # NOTE: Unused + # SINGLE_OSCILLATION = 9 + # OLD_FASHIONED = 10 # NOTE: Unused + # RASTER_SCAN = 11 + # JET_ROTATION = 12 # NOTE: Unused + # X_HELICAL = 13 # NOTE: Unused + # X_RUNSEQ = 14 # NOTE: Unused + # JUNGFRAU = 15 + # MSOX = 16 # NOTE: Unused + # SLIT_SCAN = 17 # NOTE: Unused + # RASTER_SCAN_STILL = 18 + # SCAN_SASTT = 19 + # SCAN_SASTT_V2 = 20 + # SCAN_SASTT_V3 = 21 @@ -59,13 +56,12 @@ class AerotechFlyscanBase(AsyncFlyScanBase): abr_complete : bool Wait for the launched ABR task to complete. """ + scan_type = "measure" scan_report_hint = "table" arg_input = {} arg_bundle_size = {"bundle": len(arg_input), "min": None, "max": None} # Aerotech stage config - abr_command = None - abr_globals = {} abr_raster_reset =False abr_complete = False abr_timeout = None @@ -77,12 +73,6 @@ class AerotechFlyscanBase(AsyncFlyScanBase): parameters. """ super().__init__(parameter=parameter, **kwargs) - - # Override if explicitly passed as parameter - if "abr_command" in self.caller_kwargs: - self.abr_command = self.caller_kwargs.get("abr_command") - if "abr_globals" in self.caller_kwargs: - self.abr_globals = self.caller_kwargs.get("abr_globals") if "abr_raster_reset" in self.caller_kwargs: self.abr_raster_reset = self.caller_kwargs.get("abr_raster_reset") if "abr_complete" in self.caller_kwargs: @@ -91,6 +81,7 @@ class AerotechFlyscanBase(AsyncFlyScanBase): self.abr_timeout = self.caller_kwargs.get("abr_timeout") def pre_scan(self): + """ Mostly just checking if ABR stage is ok...""" # TODO: Move roughly to start position??? # ABR status checking @@ -107,13 +98,6 @@ class AerotechFlyscanBase(AsyncFlyScanBase): if self.abr_raster_reset: yield from self.stubs.send_rpc_and_wait("abr", "raster_scan_done.set", 0) - # Configure the global variables - d = {"scan_command" : self.abr_command} - d.update(self.abr_globals) - logger.info(d) - # Configure - yield from self.stubs.send_rpc_and_wait("abr", "configure", d) - # Call super yield from super().pre_scan() @@ -125,7 +109,7 @@ class AerotechFlyscanBase(AsyncFlyScanBase): """ The actual scan logic comes here. """ # Kick off the run - yield from self.stubs.send_rpc_and_wait("abr", "start_command.set", 1) + yield from self.stubs.send_rpc_and_wait("abr", "bluekickoff") logger.info("Measurement launched on the ABR stage...") # Wait for grid scanner to finish @@ -168,28 +152,9 @@ class MeasureStandardWedge(AerotechFlyscanBase): ready_rate : float, optional No clue what is this... (default=500) """ - scan_name = "standard_wedge" + scan_name = "standardscan" required_kwargs = ["start", "range", "move_time"] - def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ['abr.omega'] - # Set parameters from meaningful inputs - self.scan_start = parameter['kwargs'].get("start") - self.scan_range = parameter['kwargs'].get("range") - self.scan_move_time = parameter['kwargs'].get("move_time") - self.scan_ready_rate = parameter['kwargs'].get("ready_rate", 500) - self.abr_command = AbrCmd.MEASURE_STANDARD - self.abr_globals = { - "var_1" : self.scan_start, - "var_2" : self.scan_range, - "var_3" : self.scan_move_time, - "var_4" : self.scan_ready_rate, - } - logger.info(self.abr_globals) - # Call base class - super().__init__(parameter=parameter, **kwargs) - - class MeasureVerticalLine(AerotechFlyscanBase): @@ -208,75 +173,15 @@ class MeasureVerticalLine(AerotechFlyscanBase): Parameters ---------- - range_y : float + range : float Step size [mm]. - steps_y : int + steps : int Scan range of the axis. exp_time : float Eeffective exposure time per step [s] """ - scan_name = "vertical_line" - required_kwargs = ["exp_time", "range_y", "steps_y"] - - def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ['abr.gmy'] - # Set parameters from meaningful inputs - self.scan_exp_time = parameter['kwargs'].get("exp_time") - self.scan_range_y = parameter['kwargs'].get("range_y") - self.scan_stepnum_y = parameter['kwargs'].get("steps_y") - self.scan_stepsize_y = self.scan_range_y / self.scan_stepnum_y - - self.abr_command = AbrCmd.VERTICAL_LINE_SCAN - self.abr_globals = { - 'var_1': self.scan_exp_time, - 'var_2': self.scan_stepsize_y, - 'var_3': self.scan_stepnum_y, - } - # Call base class - super().__init__(parameter=parameter, **kwargs) - - - -class MeasureScanSlits(AerotechFlyscanBase): - """ Coordinated scan - - Measure a hardware coordinated relative line scan with the X- and Y axes. - This is used to accurately track solid supports with long linear grooves, - as theese might be slightly rotated. - - The scan itself is executed by the scan service running on the Aerotech - controller. Ophyd just configures, launches it and waits for completion. - - Example - ------- - >>> scans.slit_scan(range=[10, 0.3], scan_time=20) - - Parameters - ---------- - range_x : float - Move distance in X [mm]. - range_y : float - Move distance in Y [mm]. - velocity : float - Effective movement velocity [mm/s]. - """ - scan_name = "slit_scan" - required_kwargs = ["range", "velocity"] - - def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ["abr.gmx", "abr.gmy"] - # Set parameters from meaningful inputs - self.abr_command = AbrCmd.SLIT_SCAN - self.scan_range_x = parameter['kwargs'].get("range_x") - self.scan_range_y = parameter['kwargs'].get("range_y") - self.scan_velocity = parameter['kwargs'].get("velocity") - self.abr_globals = { - "var_1" : self.scan_range_x, - "var_2" : self.scan_range_y, - "var_3" : self.scan_velocity, - } - # Call base class - super().__init__(parameter=parameter, **kwargs) + scan_name = "vlinescan" + required_kwargs = ["exp_time", "range", "steps"] @@ -308,120 +213,9 @@ class MeasureRasterSimple(AerotechFlyscanBase): steps_y : int Number of scan steps in Y (slow). """ - scan_name = "raster_simple" + scan_name = "rasterscan" required_kwargs = ["exp_time", "range_x", "range_y", "steps_x", "steps_y"] - def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ['abr.gmx', 'abr.gmy'] - # Set parameters from meaningful inputs - self.scan_exp_time = parameter['kwargs'].get("exp_time") - self.scan_range_x = parameter['kwargs'].get("range_x") - self.scan_range_y = parameter['kwargs'].get("range_y") - self.scan_stepnum_x = parameter['kwargs'].get("steps_x") - self.scan_stepnum_y = parameter['kwargs'].get("steps_y") - self.scan_stepsize_x = self.scan_range_x / self.scan_stepnum_x - self.scan_stepsize_y = self.scan_range_y / self.scan_stepnum_y - - self.abr_command = AbrCmd.RASTER_SCAN_SIMPLE - self.abr_raster_reset = True - self.abr_globals = { - "var_1" : self.scan_exp_time, - "var_2" : self.scan_stepsize_x, - "var_3" : self.scan_stepsize_y, - "var_4" : self.scan_stepnum_x, - "var_5" : self.scan_stepnum_y, - "var_6" : 0, - "var_7" : 0, - "var_8" : 0, - } - # Call base class - super().__init__(parameter=parameter, **kwargs) - - - -class MeasureSASTT(AerotechFlyscanBase): - """ Small angle scanning tensor tomography scan - - Measure a single projection for Small Angle Scanning Tensor Tomography. - It's essentially a separate grid scan, that can be modified independently. - The scan is relative and assumes the goniometer is currently at the CENTER - of the first cell (i.e. TOP-LEFT). Each line is executed as a single - continous movement, there's no actual stepping in the X direction. - - NOTE: Not all beamlines have all SASTT modes implemented. - - The scan itself is executed by the scan service running on the Aerotech - controller. Ophyd just configures, launches it and waits for completion. - - Example - ------- - >>> scans.measure_sastt(exp_time=0.01, range_x=1, range_y=1, steps_x=200, steps_y=200) - - Parameters - ---------- - exp_time : float - Effective exposure time for each cell along the X axis [s]. - range_x : float - Scan step size [mm]. - range_y : float - Scan step size [mm]. - steps_x : int - Number of scan steps in X (fast). Only used for velocity calculation. - steps_y : int - Number of scan steps in Y (slow). - odd_tweak : (float) - Distance to be added before the open shutter command [mm]. - Used only in scan version=3. Should be small (default: 0)! - even_tweak : (float) - Distance to be added before the open shutter command [mm]. - Used only in scan version=3. Should be small (default: 0)! - version : (int) - Scanning mode for tensor tomo. - 1 = original snake scan, single PSO window - 2 = scan always from LEFT---RIGHT - 3 = snake scan alternating PSO window for even/odd rows - """ - scan_name = "measure_sastt" - required_kwargs = ["exp_time", "range_x", "range_y", "steps_x", "steps_y"] - - def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ['abr.gmx', 'abr.gmy'] - # Set parameters from meaningful inputs - self.scan_version = int(parameter['kwargs'].get("version", 1)) - if self.scan_version==1: - self.abr_command = AbrCmd.SCAN_SASTT - if self.scan_version==2: - self.abr_command = AbrCmd.SCAN_SASTT_V2 - if self.scan_version==3: - self.abr_command = AbrCmd.SCAN_SASTT_V3 - else: - raise RuntimeError(f"Non existing SAS-TT scan mode: {self.scan_version}") - - # Set parameters from meaningful inputs - self.scan_exp_time = parameter['kwargs'].get("exp_time") - self.scan_range_x = parameter['kwargs'].get("range_x") - self.scan_range_y = parameter['kwargs'].get("range_y") - self.scan_stepnum_x = parameter['kwargs'].get("steps_x") - self.scan_stepnum_y = parameter['kwargs'].get("steps_y") - self.scan_even_tweak = parameter['kwargs'].get("even_tweak", 0) - self.scan_odd_tweak = parameter['kwargs'].get("odd_tweak", 0) - self.scan_stepsize_x = self.scan_range_x / self.scan_stepnum_x - self.scan_stepsize_y = self.scan_range_y / self.scan_stepnum_y - - self.abr_globals = { - 'var_1': self.scan_exp_time, - 'var_2': self.scan_stepsize_x, - 'var_3': self.scan_stepsize_y, - 'var_4': self.scan_stepnum_x, - 'var_5': self.scan_stepnum_y, - 'var_6': self.scan_even_tweak, - 'var_7': self.scan_odd_tweak, - 'var_8': 0, - } - # Call base class - super().__init__(parameter=parameter, **kwargs) - - @@ -454,588 +248,5 @@ class MeasureScreening(AerotechFlyscanBase): delta : float Safety margin for sub-range movements (default: 0.5) [deg]. """ - scan_name = "measure_screening" + scan_name = "screeningscan" required_kwargs = ["start", "range", "steps", "exp_time", "oscrange"] - - def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ['gmy'] - # Set parameters from meaningful inputs - self.scan_start = parameter['kwargs'].get("start") - self.scan_range_o = parameter['kwargs'].get("range") - self.scan_stepnum_o = parameter['kwargs'].get("steps") - self.scan_exp_time = parameter['kwargs'].get("exp_time") - self.scan_oscrange = parameter['kwargs'].get("oscrange") - self.scan_delta = parameter['kwargs'].get("delta", 0.5) - self.scan_stepsize_o = self.scan_range_o / self.scan_stepnum_o - self.abr_command = AbrCmd.SCREENING - self.abr_globals = { - "var_1" : self.scan_start, - "var_2" : self.scan_oscrange, - "var_3" : self.scan_exp_time, - "var_4" : self.scan_stepsize_o, - "var_5" : self.scan_stepnum_o, - "var_6" : self.scan_delta, - } - # Call base class - super().__init__(parameter=parameter, **kwargs) - - - -class MeasureFastOmega(AerotechFlyscanBase): - """ Fast omega scan - - Performs a fast rotational scan with the rotation stage (omega). It ramps - up to constant speed and sets off PSO for the expected travel time. I.e. - not really a PSO output (as it ignores acceleration distance) but it's - expected to trigger at-speed for 'range' distance. - - The scan itself is executed by the scan service running on the Aerotech - controller. Ophyd just configures, launches it and waits for completion. - - Example - ------- - >>> scans.measure_fast_omega(start=42, range=180, move_time=1.0) - - Parameters - ---------- - start : float - Scan start position of the axis [deg]. - range : float - At-speed range of the axis relative to 'start' + acceleration - distance [deg]. - move_time : float - Total time for the at-speed movement and trigger [s]. - rate : (???) - (default: 200). - """ - scan_name = "measure_fast_omega" - required_kwargs = ["start", "range", "move_time"] - - def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ['omega'] - # Set parameters from meaningful inputs - logger.info(parameter) - self.scan_start = parameter['kwargs'].get("start") - self.scan_range = parameter['kwargs'].get("range") - self.scan_move_time = parameter['kwargs'].get("move_time") - self.scan_acceleration = parameter['kwargs'].get("acceleration", 200) - self.abr_command = AbrCmd.SUPER_FAST_OMEGA - self.abr_globals = { - "var_1" : self.scan_start, - "var_2" : self.scan_range, - "var_3" : self.scan_move_time, - "var_4" : self.scan_acceleration, - } - logger.info(self.abr_globals) - # Call base class - super().__init__(parameter=parameter, **kwargs) - - - -class MeasureStillWedge(AerotechFlyscanBase): - """ Still wedge scan - - Measure a simple step scan with the omega stage with PSO triggering. - The scan is similar to the continous wedge scan. - - The scan itself is executed by the scan service running on the Aerotech - controller. Ophyd just configures, launches it and waits for completion. - - Example - ------- - >>> scans.measure_still_wedge(start=42, range=180, exp_time=0.1, steps=60) - - Parameters - ---------- - start : (float) - Scan start position of the axis [mm]. - range : (float) - Scan range of the omega axis [mm]. - exp_time : (float) - Exposure time per point [s]. - steps : int - Number of actual steps. - sleep_after_shutter_close : (float) - [s] (default: 0.01). - """ - scan_name = "measure_still_wedge" - required_kwargs = ["start", "range", "exp_time", "steps"] - - def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ['abr.omega'] - # Set parameters from meaningful inputs - self.scan_start = parameter['kwargs'].get("start") - self.scan_range = parameter['kwargs'].get("range") - self.scan_stepnum_o = parameter['kwargs'].get("steps") - self.scan_exp_time = parameter['kwargs'].get("exp_time") - self.scan_shutter_sleep = parameter['kwargs'].get("sleep_after_shutter_close", 0.01) - self.scan_stepsize_o = self.scan_stepnum_o / self.scan_stepnum_o - self.abr_command = AbrCmd.STILL_WEDGE - self.abr_globals = { - "var_1" : self.scan_start, - "var_2" : self.scan_range, - "var_3" : self.scan_exp_time, - "var_4" : self.scan_stepsize_o, - "var_5" : self.scan_shutter_sleep, - } - # Call base class - super().__init__(parameter=parameter, **kwargs) - - - -class MeasureStills(AerotechFlyscanBase): - """ Still image sequence scan - - Measures a series of PSO triggered images without any motor movement. - NOTE: Use idle_time=0.0 to prevent shutter open/close. - - The scan itself is executed by the scan service running on the Aerotech - controller. Ophyd just configures, launches it and waits for completion. - - Example - ------- - >>> scans.measure_stills(steps=100, exp_time=0.1, idle_time=3) - - Parameters - ---------- - steps : int - Total number of frames to be recorded. - exp_time : float - Exposure time of each frame [s]. - idle_time : float - Sleep time between the frames [s]. - """ - scan_name = "measure_stills" - required_kwargs = ["exp_time", "steps", "idle_time"] - - def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = [] - # Set parameters from meaningful inputs - self.scan_exp_time = parameter['kwargs'].get("exp_time") - self.scan_numsteps = parameter['kwargs'].get("steps") - self.scan_idle_time = parameter['kwargs'].get("idle_time") - self.abr_command = AbrCmd.STILLS - self.abr_globals = { - "var_1" : self.scan_numsteps, - "var_2" : self.scan_exp_time, - "var_3" : self.scan_idle_time, - } - # Call base class - super().__init__(parameter=parameter, **kwargs) - - - -class MeasureSingleOscillation(AerotechFlyscanBase): - """ Single oscillation scan - - Short line scan with the omega axis with PSO trigger. - - The scan itself is executed by the scan service running on the Aerotech - controller. Ophyd just configures, launches it and waits for completion. - - Example - ------- - >>> scans.measure_single_osc(start=42, range=180, move_time=20) - - Parameters - ---------- - start : float - Scan start position of the omegaaxis [mm]. - range : float - Oscillation range of the axis [mm]. - move_time : float - Total scan time for the movement [s]. - delta : (???) - Safety margin for movement (default: 0). - settle : (???) - (default: 0.5). - """ - scan_name = "measure_single_osc" - required_kwargs = ["start", "range", "move_time"] - - def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ['abr.omega'] - # Set parameters from meaningful inputs - self.scan_start = parameter['kwargs'].get("start") - self.scan_range = parameter['kwargs'].get("range") - self.scan_move_time = parameter['kwargs'].get("move_time") - self.scan_delta = parameter['kwargs'].get("delta", 0) - self.scan_settle = parameter['kwargs'].get("settle", 0.5) - self.abr_command = AbrCmd.SINGLE_OSCILLATION - self.abr_globals = { - "var_1" : self.scan_start, - "var_2" : self.scan_range, - "var_3" : self.scan_move_time, - "var_4" : self.scan_delta, - "var_5" : self.scan_settle, - } - # Call base class - super().__init__(parameter=parameter, **kwargs) - - - -class MeasureRepeatedOscillation(AerotechFlyscanBase): - """ Repeated oscillation scan - - Repeated relative line scans with the omega axis with PSO enable trigger. - The lines are performed in zigzag mode with PSO triggering. - - The scan itself is executed by the scan service running on the Aerotech - controller. Ophyd just configures, launches it and waits for completion. - - Example - ------- - >>> scans.measure_multi_osc(steps=50, range=30, move_time=3) - - Parameters - ---------- - steps : int - Number of cycles to repeat the scan - move_time : float - Total scan time for the movement [s]. - range : float - Relative oscillation range of the omega axis [deg]. - settle : (???) - Movement settle time after each line (default: 0) [s]. - delta : (???) - Safety margin for movement (default: 0.5) [deg]. - """ - scan_name = "measure_multi_osc" - required_kwargs = ["steps", "range", "scan_time"] - - def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ['omega'] - # Set parameters from meaningful inputs - self.scan_stepnum = parameter['kwargs'].get("steps") - self.scan_range = parameter['kwargs'].get("range") - self.scan_move_time = parameter['kwargs'].get("move_time") - self.scan_sleep = parameter['kwargs'].get("sleep", 0) - self.scan_delta = parameter['kwargs'].get("delta", 0.5) - self.abr_command = AbrCmd.REPEAT_SINGLE_OSCILLATION - self.abr_globals = { - "var_1" : self.scan_stepnum, - "var_2" : self.scan_move_time, - "var_3" : self.scan_range, - "var_4" : self.scan_sleep, - "var_5" : self.scan_delta, - } - # Call base class - super().__init__(parameter=parameter, **kwargs) - - - -class MeasureMSOX(AerotechFlyscanBase): - """ Standard MSOX scan - - MSOX experiment for Florian, i.e. a glorified oscillation scan this time - with absolute positions. The lines are unidirectional with PSO triggering. - - The scan itself is executed by the scan service running on the Aerotech - controller. Ophyd just configures, launches it and waits for completion. - - Example - ------- - >>> scans.measure_msox(start=42, range=30, move_time=3.0) - - Parameters - ---------- - start : float - Scan start position of the axis. - range : float - Scan range of the axis. - move_time : float - Exposure time for each point [s]. - steps : int - Number of repeats. - settle_time : float - Settle time before line start (default=0) [s]. - """ - scan_name = "measure_msox" - required_kwargs = ["start", "range", "move_time"] - - def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ['abr.omega'] - # Set parameters from meaningful inputs - self.scan_start = parameter['kwargs'].get("start") - self.scan_range = parameter['kwargs'].get("range") - self.scan_move_time = parameter['kwargs'].get("move_time") - self.scan_stepnum = parameter['kwargs'].get("steps", 1) - self.scan_settle_time = parameter['kwargs'].get("settle_time", 0) - self.abr_command = AbrCmd.MSOX - self.abr_globals = { - "var_1" : self.scan_start, - "var_2" : self.scan_range, - "var_3" : self.scan_move_time, - "var_4" : self.scan_stepnum, - "var_5" : self.scan_settle_time, - "var_6" : 0, - "var_7" : 0, - "var_8" : 0, - "var_9" : 0, - "var_10" : 0, - } - # Call base class - super().__init__(parameter=parameter, **kwargs) - - - -class MeasureGrid(AerotechFlyscanBase): - """ General grid scan - - Performs an X-Y-Omega coordinated grid scan: - X axis is absolute (fast axis) - Y axis is relative to start - Omega starts at the current value and is synchronized to X - - Note: This was probably never used in its current form, because the - code had an undefined variable 'self.position' - - The scan itself is executed by the scan service running on the Aerotech - controller. Ophyd just configures, launches it and waits for completion. - - Example - ------- - >>> scans.measure_raster(start=42, range=10, exp_time=0.1) - - Parameters - ---------- - positions : 2D-array - Scan position of each axis, i.e. (N, 3) shaped array. - steps_x : int - Number of points along the X axis (fast). - steps_y : int - Number of points in the Y axis (slow). - angle: - Triggered angular range on the synchronized omega [deg] - exp_time : (float) - Exposure time for each point, only used for number of points and velocity [s]. - """ - scan_name = "measure_raster" - required_kwargs = ["positions", "ncols", "angle", "exp_time"] - - # Default values - scan_start_x = None - scan_end_x = None - scan_xstep = 0.010 - scan_ystep = 0.010 - scan_zstep = 0.010 - scan_gmy_step = 0.010 - - def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ['abr.omega', 'abr.gmx', 'abr.gmy'] - # Set parameters from meaningful inputs - self.abr_command = AbrCmd.RASTER_SCAN - self.scan_positions = parameter['kwargs'].get("positions") - self.scan_stepnum_x = parameter['kwargs'].get("steps_x") - self.scan_stepnum_y = parameter['kwargs'].get("steps_y") - self.scan_anglerange = parameter['kwargs'].get("angle") - self.scan_exp_time = parameter['kwargs'].get("exp_time") - - if len(self.scan_positions) != self.scan_stepnum_x * self.scan_stepnum_y: - raise RuntimeError("Number of points and positions do not match") - - if len(self.scan_positions)==1: - raise RuntimeWarning("Raster scan with one cell makes no sense") - - # Call base class - super().__init__(parameter=parameter, **kwargs) - - def prepare_positions(self): - # Calculate step sizes - pos_start = self.scan_positions[0] # first cell on first row - pos_col_2nd = self.scan_positions[1] # second cell on first row - pos_col_end = self.scan_positions[self.scan_stepnum_x-1] # last cell on first row - - self.scan_xstep = pos_col_2nd[0] - pos_start[0] - half_cell = abs(self.scan_xstep) / 2.0 - self.scan_start_x = pos_start[0] - half_cell - self.scan_end_x = pos_col_end[0] + half_cell - - if self.scan_stepnum_y > 1: - pos_row_2nd = self.scan_positions[self.scan_stepnum_x] # first cell on second row - self.scan_ystep = pos_row_2nd[1] - pos_start[1] - self.scan_zstep = pos_row_2nd[2] - pos_start[2] - self.scan_gmy_step = np.linalg.norm([self.scan_ystep, self.scan_zstep]) - - # ToDo: Check with Zac what are theese parameters - self.scan_start_o = yield from self.stubs.send_rpc_and_wait("abs", "omega.readback.get") - self.scan_gmx_offset = yield from self.stubs.send_rpc_and_wait("abs", "gmx.readback.get") - - # Configure the global variables - self.abr_globals = { - "var_1" : self.scan_start_o, - "var_2" : self.scan_start_x, - "var_3" : self.scan_end_x, - "var_4" : self.scan_stepnum_x, - "var_5" : self.scan_stepnum_y, - "var_6" : self.scan_anglerange, - "var_7" : self.scan_exp_time, - "var_8" : HALF_PERIOD, - "var_9" : self.scan_gmx_offset, - "var_10" : self.scan_gmy_step, - } - - - -class MeasureGridStill(AerotechFlyscanBase): - """ Still grid scan - - Performs an X-Y-Omega coordinated grid scan in stepping mode: - X axis is absolute - Y axis is relative to start - Omega starts at the current value and is synchronized to X - - Note: This was probably never used in its current form, because the - code had an undefined variable 'self.position' - - The scan itself is executed by the scan service running on the Aerotech - controller. Ophyd just configures, launches it and waits for completion. - - Example - ------- - >>> pts = [[11, 2, 3.4], [11, 2, 3.5], ...] - >>> scans.measure_raster(positions=pts, steps_x=20, steps_y=20, exp_time=0.1, delay=0.05) - - Parameters - ---------- - positions : 2D-array - Scan position of each axis, i.e. (N, 3) shaped array. - columns : int - Nmber of columns. - angle: - ??? - exp_time : float - Exposure time for each point [s]. - delay: - ??? - """ - scan_name = "measure_raster_still" - required_kwargs = ["positions", "steps_x", "steps_y", "exp_time", "delay"] - - # Default values - scan_start_x = None - scan_end_x = None - scan_xstep = 0.010 - scan_ystep = 0.010 - scan_zstep = 0.010 - scan_gmy_step = 0.010 - - def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ['abr.gmx', 'abr.gmy', 'abr.omega'] - # Set parameters from meaningful inputs - self.abr_command = AbrCmd.RASTER_SCAN_STILL - self.scan_positions = parameter['kwargs'].get("positions") - self.scan_stepnum_x = parameter['kwargs'].get("steps_x") - self.scan_stepnum_y = parameter['kwargs'].get("steps_y") - self.scan_exp_time = parameter['kwargs'].get("exp_time") - self.scan_delay = parameter['kwargs'].get("delay") - - if len(self.scan_positions) != self.scan_stepnum_x * self.scan_stepnum_y: - raise RuntimeError("Number of points and positions do not match") - - if len(self.scan_positions)==1: - raise RuntimeWarning("Raster scan with one cell makes no sense") - # Call base class - super().__init__(parameter=parameter, **kwargs) - - def prepare_positions(self): - # Calculate step sizes - pos_start = self.scan_positions[0] # first cell on first row - pos_col_2nd = self.scan_positions[1] # second cell on first row - pos_col_end = self.scan_positions[self.scan_stepnum_x-1] # last cell on first row - - self.scan_xstep = pos_col_2nd[0] - pos_start[0] - half_cell = abs(self.scan_xstep) / 2.0 - self.scan_start_x = pos_start[0] - half_cell - self.scan_end_x = pos_col_end[0] + half_cell - - if self.scan_stepnum_y > 1: - pos_row_2nd = self.scan_positions[self.scan_stepnum_x] # first cell on second row - self.scan_ystep = pos_row_2nd[1] - pos_start[1] - self.scan_zstep = pos_row_2nd[2] - pos_start[2] - self.scan_gmy_step = np.linalg.norm([self.scan_ystep, self.scan_zstep]) - - # ToDo: Check with Zac what are theese parameters - self.scan_omega_position = yield from self.stubs.send_rpc_and_wait("abs", "omega.readback.get") - self.scan_gmx_offset = yield from self.stubs.send_rpc_and_wait("abs", "gmx.readback.get") - - self.abr_globals = { - "var_1" : self.scan_omega_position, - "var_2" : self.scan_start_x, - "var_3" : self.scan_end_x, - "var_4" : self.scan_stepnum_x, - "var_5" : self.scan_stepnum_y, - "var_6" : self.scan_delay, - "var_7" : self.scan_exp_time, - "var_8" : HALF_PERIOD, - "var_9" : self.scan_gmx_offset, - "var_10" : self.scan_gmy_step, - } - - - -class MeasureJungfrau(AerotechFlyscanBase): - """ Scan with the Jungfrau detector - - The scan itself is executed by the scan service running on the Aerotech - controller. Ophyd just configures, launches it and waits for completion. - - Example - ------- - >>> scans.measure_jungfrau(start=42, range=180, scan_time=20) - - Parameters - ---------- - steps_x : int - ??? - steps_y : int - ??? - range_x : int - ??? - range_y : int - ??? - exp_time : float - Exposure time per point [s]. - sleep : float - (default: 0.1). - """ - scan_name = "measure_jungfrau" - required_kwargs = ["steps_x", "steps_y", "range_x", "range_y", "exp_time"] - - def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ['omega'] - # Set parameters from meaningful inputs - self.scan_stepnum_x = parameter['kwargs'].get("steps_x") - self.scan_stepnum_y = parameter['kwargs'].get("steps_y") - self.scan_range_x = parameter['kwargs'].get("range_x") - self.scan_range_y = parameter['kwargs'].get("range_y") - self.scan_stepsize_x = self.scan_range_x / self.scan_stepnum_x - self.scan_stepsize_y = self.scan_range_y / self.scan_stepnum_y - self.scan_exp_time = parameter['kwargs'].get("exp_time") - self.scan_sleep = parameter['kwargs'].get("sleep", 0.1) - sleep_time = 5.0 + (self.scan_stepnum_y * self.scan_sleep) + ( - self.scan_stepnum_x * self.scan_stepnum_y * self.scan_exp_time) - self.abr_timeout = None if self.scan_sleep <= 0 else sleep_time - self.abr_command = AbrCmd.JUNGFRAU - self.abr_complete = True - self.abr_globals = { - "var_1" : self.scan_stepnum_x, - "var_2" : self.scan_stepnum_y, - "var_3" : self.scan_stepsize_x, - "var_4" : self.scan_stepsize_y, - "var_5" : self.scan_exp_time, - "var_6" : self.scan_sleep, - "var_7" : 0, - "var_8" : 0, - } - # Call base class - super().__init__(parameter=parameter, **kwargs) - - def scan_core(self): - """ The actual scan logic comes here. - """ - yield from super().scan_core() - - # Go back to direct mode - st = yield from self.stubs.send_rpc_and_wait("abr", "set_axis_mode", "direct") - st.wait() \ No newline at end of file -- 2.49.1 From a8990f8de2bf7ff3643323bcc093ef7d153ea6ec Mon Sep 17 00:00:00 2001 From: gac-x06da Date: Wed, 22 Jan 2025 17:42:50 +0100 Subject: [PATCH 20/25] axis client getting ready --- pxiii_bec/devices/SmarGon.py | 212 ++++++++++++++++++----------------- 1 file changed, 112 insertions(+), 100 deletions(-) diff --git a/pxiii_bec/devices/SmarGon.py b/pxiii_bec/devices/SmarGon.py index 9888725..489e613 100644 --- a/pxiii_bec/devices/SmarGon.py +++ b/pxiii_bec/devices/SmarGon.py @@ -1,5 +1,5 @@ import time -from ophyd import Component, Device, EpicsSignal, EpicsSignalRO, Kind, Device, Signal +from ophyd import Component, Device, Kind, Device, Signal, SignalRO from ophyd.status import SubscriptionStatus import requests @@ -9,121 +9,101 @@ try: logger = bec_logger.logger except ModuleNotFoundError: import logging - logger = logging.getLogger("A3200") + logger = logging.getLogger("SmarGon") class SmarGonSignal(Signal): - """Small helper class to read PVs that need to be processed first.""" + """ SmarGonSignal (R/W) + + Small helper class to read/write parameters from SmarGon. As there is no + motion status readback from smargopolo, this should be substituted with + setting with 'settle_time'. + """ - def __init__(self, prefix, *args, **kwargs): + def __init__(self, *args, write_addr="targetSCS", low_limit=None, high_limit=None, **kwargs): super().__init__(*args, **kwargs) - self.prefix = prefix + self.write_addr = write_addr self.addr = self.parent.name + self._limits = (low_limit, high_limit) # self.get() - def put(self, value, *args, **kwargs): - self._go_n_put(f"target{self.prefix}?{self.addr}={value}") - return super().put(value, *args, force=True, **kwargs) + def put(self, value, *args, timestamp=None, **kwargs): + """ Overriden put to add communication with smargopolo""" + # Validate new value + self.check_value(value) + + if timestamp is None: + timestamp = time.time() + + # Perform the actual write to SmargoPolo + r = self.parent._go_n_put(f"{self.write_addr}?{self.addr.upper()}={value}") + + old_value = self._readback + self._timestamp = timestamp + self._readback = r[self.addr.upper()] + self.value = r[self.addr.upper()] + + # Notify subscribers + self._run_subs(sub_type=self.SUB_VALUE, old_value=old_value, + value=value, timestamp=self._timestamp) + + @property + def limits(self): + return self._limits + + def check_value(self, value, **kwargs): + """ Check if value falls within limits""" + lol = self.limits[0] + if lol is not None: + if value < lol: + raise ValueError(f"Target {value} outside of limits {self.limits}") + hil = self.limits[1] + if hil is not None: + if value > hil: + raise ValueError(f"Target {value} outside of limits {self.limits}") def get(self, *args, **kwargs): - r = self._go_n_get(f"target{self.prefix}") + r = self.parent._go_n_get(self.write_addr) print(r) self.value = r[self.addr.upper()] return super().get(*args, **kwargs) - def _go_n_get(self, name, **kwargs): - cmd = f"{self.parent.sg_url.get()}/{name}" - r = requests.get(cmd, timeout=1) - if not r.ok: - raise RuntimeError(f"[self.name] Error getting {name}; server returned {r.status_code} => {r.reason}") - return r.json() - - def _go_n_put(self, name, **kwargs): - cmd = f"{self.parent.sg_url.get()}/{name}" - r = requests.put(cmd, timeout=1) - if not r.ok: - raise RuntimeError(f"[self.name] Error putting {name}; server returned {r.status_code} => {r.reason}") - - - - class SmarGonSignalRO(Signal): - """Small helper class to read PVs that need to be processed first. - - TODO: Add monitoring + """ Small helper class for read-only parameters PVs from SmarGon. + + TODO: Add monitoring """ - def __init__(self, prefix, *args, **kwargs): + def __init__(self, *args, read_addr="readbackSCS", **kwargs): super().__init__(*args, **kwargs) self._metadata["write_access"] = False - self.prefix = prefix + self.read_addr = read_addr self.addr = self.parent.name def get(self, *args, **kwargs): - r = self._go_n_get(f"readback{self.prefix}") + r = self.parent._go_n_get(self.read_addr) print(r) self.put(r[self.addr.upper()], force=True) return super().get(*args, **kwargs) - def _go_n_get(self, name, **kwargs): - cmd = f"{self.parent.sg_url.get()}/{name}" - r = requests.get(cmd, timeout=1) - if not r.ok: - raise RuntimeError(f"[self.name] Error getting {name}; server returned {r.status_code} => {r.reason}") - return r.json() +# class SmarGonMovingSignalRO(Signal): +# """Small helper class to read PVs that need to be processed first. +# TODO: Add monitoring +# """ -class SmarGonClient(Device): - """SmarGon client deice - - This class controls the SmarGon goniometer via the REST interface. - """ - # pylint: disable=too-many-instance-attributes - USER_ACCESS = ["set_daq_config", "get_daq_config", "nuke", "connect", "message", "state", "bluestage", "blueunstage"] - - # Status attributes - sg_url = Component(Signal, kind=Kind.config, metadata={'write_access': False}) - - # Axis parameters - shx = Component(SmarGonSignal, group="SCS", addr="shx", kind=Kind.config) - # shy = Component(SmarGonSignal, group="SCS", addr="shy", kind=Kind.config) - # shz = Component(SmarGonSignal, group="SCS", addr="shz", kind=Kind.config) - # chi = Component(SmarGonSignal, group="SCS", addr="chi", kind=Kind.config) - # phi = Component(SmarGonSignal, group="SCS", addr="phi", kind=Kind.config) - - def __init__( - self, - prefix="", - *, - name, - kind=None, - read_attrs=None, - configuration_attrs=None, - parent=None, - device_manager=None, - sg_url: str = "http://x06da-smargopolo.psi.ch:3000", - **kwargs, - ) -> None: - # super().__init__(prefix=prefix, name=name, kind=kind, read_attrs=read_attrs, configuration_attrs=configuration_attrs, parent=parent, device_manager=device_manager, **kwargs) - - super().__init__(prefix=prefix, name=name, kind=kind, read_attrs=read_attrs, configuration_attrs=configuration_attrs, parent=parent, **kwargs) - self.sg_url._metadata["write_access"] = False - self.sg_url.set(sg_url, force=True).wait() - - def _go_n_get(self, name, **kwargs): - cmd = f"{self.sg_url.get()}/{name}" - r = requests.get(cmd, timeout=1) - if not r.ok: - raise RuntimeError(f"[self.name] Error getting {name}; server returned {r.status_code} => {r.reason}") - return r.json() - - def _go_n_put(self, name, **kwargs): - cmd = f"{self.sg_url.get()}/{name}" - r = requests.put(cmd, timeout=1) - if not r.ok: - raise RuntimeError(f"[self.name] Error putting {name}; server returned {r.status_code} => {r.reason}") +# def __init__(self, *args, **kwargs): +# super().__init__(*args, **kwargs) +# self._metadata["write_access"] = False +# def get(self, *args, **kwargs): +# r = self.parent._go_n_get("readbackMCS") +# print(r['state']) +# moving_str = r["state"]["q1"] + r["state"]["q2"] + r["state"]["q3"] + r["state"]["q4"] + r["state"]["q5"][0] +# moving = int(moving_str) +# self.put(moving_str, force=True) +# return super().get(*args, **kwargs) class SmarGonAxis(Device): @@ -133,10 +113,15 @@ class SmarGonAxis(Device): """ # Status attributes sg_url = Component(Signal, kind=Kind.config, metadata={'write_access': False}) + corr = Component(SmarGonSignalRO, read_addr="corr_type", kind=Kind.config) + mode = Component(SmarGonSignalRO, read_addr="mode", kind=Kind.config) # Axis parameters - readback = Component(SmarGonSignalRO, kind=Kind.config) - setpoint = Component(SmarGonSignal, kind=Kind.config) + readback = Component(SmarGonSignalRO, kind=Kind.hinted) + setpoint = Component(SmarGonSignal, kind=Kind.normal) + done = Component(SignalRO, value=1, kind=Kind.normal) + # moving = Component(SmarGonMovingSignalRO, kind=Kind.config) + moving = 1 def __init__( self, @@ -149,26 +134,53 @@ class SmarGonAxis(Device): parent=None, device_manager=None, sg_url: str = "http://x06da-smargopolo.psi.ch:3000", + low_limit = None, + high_limit = None, **kwargs, ) -> None: - self.__class__.__dict__['readback'].kwargs['prefix'] = prefix - self.__class__.__dict__['readback'].kwargs['name'] = name - self.__class__.__dict__['setpoint'].kwargs['prefix'] = prefix - self.__class__.__dict__['setpoint'].kwargs['name'] = name - + self.__class__.__dict__['readback'].kwargs['read_addr'] = f"readback{prefix}" + self.__class__.__dict__['setpoint'].kwargs['write_addr'] = f"target{prefix}" + self.__class__.__dict__['setpoint'].kwargs['low_limit'] = low_limit + self.__class__.__dict__['setpoint'].kwargs['high_limit'] = high_limit # super().__init__(prefix=prefix, name=name, kind=kind, read_attrs=read_attrs, configuration_attrs=configuration_attrs, parent=parent, device_manager=device_manager, **kwargs) - super().__init__(prefix=prefix, name=name, kind=kind, read_attrs=read_attrs, configuration_attrs=configuration_attrs, parent=parent, **kwargs) self.sg_url._metadata["write_access"] = False self.sg_url.set(sg_url, force=True).wait() + def initialize(self): + """Helper function for initial readings""" + # self.corr.get() + # self.mode.get() + r = self._go_n_get("corr_type") + print(r) + + def _go_n_get(self, address, **kwargs): + """Helper function to connect to smargopolo""" + cmd = f"{self.sg_url.get()}/{address}" + r = requests.get(cmd, timeout=1) + if not r.ok: + raise RuntimeError(f"[{self.name}] Error getting {address}; server returned {r.status_code} => {r.reason}") + return r.json() + + def _go_n_put(self, address, **kwargs): + """Helper function to connect to smargopolo""" + cmd = f"{self.sg_url.get()}/{address}" + r = requests.put(cmd, timeout=1) + if not r.ok: + raise RuntimeError(f"[{self.name}] Error putting {address}; server returned {r.status_code} => {r.reason}") + return r.json() + + + + + if __name__ == "__main__": - shz = SmarGonAxis(prefix="SCS", name="shz", sg_url="http://x06da-smargopolo.psi.ch:3000") - sg = SmarGonClient(prefix="X06DA-ES", name="smargon") - sg.wait_for_connection() - - - + shx = SmarGonAxis(prefix="SCS", name="shx", sg_url="http://x06da-smargopolo.psi.ch:3000") + shy = SmarGonAxis(prefix="SCS", name="shy", sg_url="http://x06da-smargopolo.psi.ch:3000") + shz = SmarGonAxis(prefix="SCS", name="shz", low_limit=10, high_limit=22, sg_url="http://x06da-smargopolo.psi.ch:3000") + shx.wait_for_connection() + shy.wait_for_connection() + shz.wait_for_connection() -- 2.49.1 From 24302c244df3fac91092b5f49959312d4b940a8b Mon Sep 17 00:00:00 2001 From: gac-x06da Date: Fri, 24 Jan 2025 15:06:45 +0100 Subject: [PATCH 21/25] Fix for the new scan type validation --- .../device_configs/x06da_device_config.yaml | 102 ++++++++++++++++-- pxiii_bec/devices/A3200.py | 2 +- pxiii_bec/scans/mx_measurements.py | 2 +- 3 files changed, 98 insertions(+), 8 deletions(-) diff --git a/pxiii_bec/device_configs/x06da_device_config.yaml b/pxiii_bec/device_configs/x06da_device_config.yaml index 07f543c..da92c58 100644 --- a/pxiii_bec/device_configs/x06da_device_config.yaml +++ b/pxiii_bec/device_configs/x06da_device_config.yaml @@ -46,7 +46,7 @@ dccm_energy1: dccm_diode: description: Diode between mono crystals deviceClass: ophyd.EpicsSignalRO - deviceConfig: {read_pv: 'X06DA-OP-XPM1:BOT:READOUT'} + deviceConfig: {read_pv: 'X06DA-OP-XPM1:BOT:READOUT', auto_monitor: true} onFailure: buffer enabled: true readoutPriority: monitored @@ -73,7 +73,7 @@ dccm_energy2: dccm_xbpm: description: XBPM total intensity after monochromator deviceClass: ophyd.EpicsSignalRO - deviceConfig: {read_pv: 'X06DA-OP-XBPM1:SumAll:MeanValue_RBV'} + deviceConfig: {read_pv: 'X06DA-OP-XBPM1:SumAll:MeanValue_RBV', auto_monitor: true} onFailure: buffer enabled: true readoutPriority: monitored @@ -403,6 +403,34 @@ ms_zoom: readoutPriority: monitored readOnly: false softwareTrigger: false +samdist: + description: Sample distance + deviceClass: ophyd.EpicsSignalRO + deviceConfig: {read_pv: 'X06DA-ES-DF1:CBOX-USER1', auto_monitor: true} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: true + softwareTrigger: false +samrange: + description: Sample in valid distance + deviceClass: ophyd.EpicsSignalRO + deviceConfig: {read_pv: 'X06DA-ES-DF1:CBOX-CMP1', auto_monitor: true} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: true + softwareTrigger: false +samcam: + description: Sample camera device + deviceClass: ophyd_devices.devices.areadetector.cam.GenICam + deviceConfig: {prefix: 'X06DA-SAMCAM:cam1:'} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: true + softwareTrigger: false + bstop_pneum: description: Beamstop pneumatic in-out @@ -440,15 +468,65 @@ bstop_z: readoutPriority: monitored readOnly: false softwareTrigger: false -bstop_diode: - description: Beamstop diode - deviceClass: ophyd.EpicsSignalRO - deviceConfig: {read_pv: 'X06DA-ES-BS:READOUT'} +bstop_pneum: + description: Beamstop pneumatic + deviceClass: ophyd.EpicsSignal + deviceConfig: {read_pv: 'X06DA-ES-BS:GET-POS', write_pv: 'X06DA-ES-BS:SET-POS', put_complete: true} onFailure: buffer enabled: true readoutPriority: monitored readOnly: true softwareTrigger: false +bstop_diode: + description: Beamstop diode + deviceClass: ophyd.EpicsSignalRO + deviceConfig: {read_pv: 'X06DA-ES-BS:READOUT', auto_monitor: true} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: true + softwareTrigger: false +frontlight: + description: Microscope frontlight + deviceClass: ophyd.EpicsSignal + deviceConfig: {read_pv: 'X06DA-ES-FL:SET-BRGHT', put_complete: true} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: true + softwareTrigger: false +backlight: + description: Backlight reflector + deviceClass: ophyd.EpicsSignal + deviceConfig: {read_pv: 'X06DA-ES-BL:GET-POS', write_pv: 'X06DA-ES-BL:SET-POS', put_complete: true} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: true + softwareTrigger: false +det_y: + description: Pilatus height + deviceClass: ophyd.EpicsMotor + deviceConfig: {prefix: 'X06DA-ES-DET:TRY1'} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: false + softwareTrigger: false +det_z: + description: Pilatus translation + deviceClass: ophyd.EpicsMotor + deviceConfig: {prefix: 'X06DA-ES-DET:TRZ1'} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: false + softwareTrigger: false + + + + + omega: description: ABR rotation stage deviceClass: pxiii_bec.devices.A3200Axis @@ -467,3 +545,15 @@ abr: readoutPriority: monitored readOnly: false softwareTrigger: false + + +samimg: + description: Sample camera image + deviceClass: ophyd_devices.devices.areadetector.plugins.ImagePlugin_V35 + deviceConfig: {prefix: 'X06DA-SAMCAM:image1:'} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: true + softwareTrigger: false + diff --git a/pxiii_bec/devices/A3200.py b/pxiii_bec/devices/A3200.py index 686778b..f02ed7d 100644 --- a/pxiii_bec/devices/A3200.py +++ b/pxiii_bec/devices/A3200.py @@ -115,7 +115,7 @@ class AerotechAbrMixin(CustomDeviceMixin): logger.warning(f"Configuring {self.parent.scaninfo.scan_msg.info['scan_name']} on ABR") d = {} - if self.parent.scaninfo.scan_type in ("measure", "measurement"): + if self.parent.scaninfo.scan_type in ("measure", "measurement", "fly"): scanargs = self.parent.scaninfo.scan_msg.info['kwargs'] scanname = self.parent.scaninfo.scan_msg.info['scan_name'] diff --git a/pxiii_bec/scans/mx_measurements.py b/pxiii_bec/scans/mx_measurements.py index d03a9ca..86af521 100644 --- a/pxiii_bec/scans/mx_measurements.py +++ b/pxiii_bec/scans/mx_measurements.py @@ -56,7 +56,7 @@ class AerotechFlyscanBase(AsyncFlyScanBase): abr_complete : bool Wait for the launched ABR task to complete. """ - scan_type = "measure" + scan_type = "fly" scan_report_hint = "table" arg_input = {} arg_bundle_size = {"bundle": len(arg_input), "min": None, "max": None} -- 2.49.1 From 015bf2ee3b84367204083e16978f5ce33615a27c Mon Sep 17 00:00:00 2001 From: gac-x06da Date: Fri, 24 Jan 2025 15:13:24 +0100 Subject: [PATCH 22/25] Blacking --- pxiii_bec/devices/A3200.py | 161 +++++++++++++++-------------- pxiii_bec/devices/A3200enums.py | 23 ++++- pxiii_bec/devices/A3200utils.py | 154 ++++++++++++++++----------- pxiii_bec/devices/SmarGon.py | 67 +++++++----- pxiii_bec/scans/mx_measurements.py | 53 +++++----- 5 files changed, 266 insertions(+), 192 deletions(-) diff --git a/pxiii_bec/devices/A3200.py b/pxiii_bec/devices/A3200.py index f02ed7d..c305ef9 100644 --- a/pxiii_bec/devices/A3200.py +++ b/pxiii_bec/devices/A3200.py @@ -78,11 +78,14 @@ Examples abr.stop() # this function only returns after the STATUS is back to OK """ + import time from ophyd import Component, EpicsSignal, EpicsSignalRO, Kind from ophyd.status import SubscriptionStatus from ophyd_devices.interfaces.base_classes.psi_detector_base import PSIDetectorBase as PsiDeviceBase -from ophyd_devices.interfaces.base_classes.psi_detector_base import CustomDetectorMixin as CustomDeviceMixin +from ophyd_devices.interfaces.base_classes.psi_detector_base import ( + CustomDetectorMixin as CustomDeviceMixin, +) try: from .A3200enums import AbrCmd, AbrMode @@ -92,23 +95,21 @@ except ImportError: try: from bec_lib import bec_logger + logger = bec_logger.logger except ModuleNotFoundError: import logging + logger = logging.getLogger("A3200") # pylint: disable=logging-fstring-interpolation class AerotechAbrMixin(CustomDeviceMixin): - """ Configuration class for the Aerotech A3200 controller for the ABR stage""" - - - - + """Configuration class for the Aerotech A3200 controller for the ABR stage""" def on_stage(self): """ - + NOTE: Zac's request is that stage is essentially ARM, i.e. get ready and don't do anything. """ @@ -116,8 +117,8 @@ class AerotechAbrMixin(CustomDeviceMixin): d = {} if self.parent.scaninfo.scan_type in ("measure", "measurement", "fly"): - scanargs = self.parent.scaninfo.scan_msg.info['kwargs'] - scanname = self.parent.scaninfo.scan_msg.info['scan_name'] + scanargs = self.parent.scaninfo.scan_msg.info["kwargs"] + scanname = self.parent.scaninfo.scan_msg.info["scan_name"] if scanname in ("standardscan"): scan_start = scanargs["start"] @@ -186,27 +187,24 @@ class AerotechAbrMixin(CustomDeviceMixin): d["var_9"] = 0 # Reconfigure if got a valid scan config - if len(d)>0: + if len(d) > 0: self.parent.configure(d) # Stage the parent self.parent.bluestage() def on_kickoff(self): - """ Kick off parent """ + """Kick off parent""" self.parent.bluekickoff() - + def on_unstage(self): - """ Unstage the ABR controller""" + """Unstage the ABR controller""" self.parent.blueunstage() - - - class AerotechAbrStage(PsiDeviceBase): - """ Standard PX stage on A3200 controller - + """Standard PX stage on A3200 controller + This is the wrapper class for the standard rotation stage layout for the PX beamlines at SLS. It wraps the main rotation axis OMEGA (Aerotech ABR)and the associated motion axes GMX, GMY and GMZ. The ophyd class associates to @@ -214,23 +212,20 @@ class AerotechAbrStage(PsiDeviceBase): is running as an AeroBasic program on the controller and we communicate to it via 10+1 global variables. """ - custom_prepare_cls = AerotechAbrMixin - 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) - clear = Component( - EpicsSignal, "-AERO:CTRL-CLFT", put_complete=True, kind=Kind.omitted) + custom_prepare_cls = AerotechAbrMixin + 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) + clear = Component(EpicsSignal, "-AERO:CTRL-CLFT", put_complete=True, kind=Kind.omitted) # Enable/disable motor movement via the IOC (i.e. make it task-only) - axisModeLocked = Component( - EpicsSignal, "-DF1:LOCK", put_complete=True, kind=Kind.omitted) + axisModeLocked = Component(EpicsSignal, "-DF1:LOCK", put_complete=True, kind=Kind.omitted) axisModeDirect = Component( - EpicsSignal, "-DF1:MODE-DIRECT", put_complete=True, kind=Kind.omitted) - axisAxesMode = Component( - EpicsSignal, "-DF1:AXES-MODE", put_complete=True, kind=Kind.omitted) + EpicsSignal, "-DF1:MODE-DIRECT", put_complete=True, kind=Kind.omitted + ) + axisAxesMode = Component(EpicsSignal, "-DF1:AXES-MODE", put_complete=True, kind=Kind.omitted) # Shutter box is missing readback so the -GET signal is installed on the VME # _shutter = Component( @@ -244,12 +239,13 @@ class AerotechAbrStage(PsiDeviceBase): gmz_done = Component(EpicsSignalRO, "-DF1:GMZ-DONE", kind=Kind.normal) # For some reason the task interface is called PSO... - scan_command = Component( - EpicsSignal, "-PSO:CMD", put_complete=True, kind=Kind.omitted) + scan_command = Component(EpicsSignal, "-PSO:CMD", put_complete=True, kind=Kind.omitted) start_command = Component( - EpicsSignal, "-PSO:START-TEST.PROC", put_complete=True, kind=Kind.omitted) + EpicsSignal, "-PSO:START-TEST.PROC", put_complete=True, kind=Kind.omitted + ) stop_command = Component( - EpicsSignal, "-PSO:STOP-TEST.PROC", put_complete=True, kind=Kind.omitted) + EpicsSignal, "-PSO:STOP-TEST.PROC", put_complete=True, kind=Kind.omitted + ) # Global variables to controll AeroBasic scripts _var_1 = Component(EpicsSignal, "-PSO:VAR-1", put_complete=True, kind=Kind.omitted) @@ -274,23 +270,23 @@ class AerotechAbrStage(PsiDeviceBase): raster_num_rows = Component(EpicsSignal, "-GRD:ROW-DONE", kind=Kind.config) def set_axis_mode(self, mode: str, settle_time=0.1) -> None: - """ Set axis mode to direct/measurement mode. + """Set axis mode to direct/measurement mode. Measurement ensures that the scrips run undisturbed by blocking axis - motion commands from the IOC (i.e. internal only). - + motion commands from the IOC (i.e. internal only). + Parameters: ----------- mode : str Valid values are 'direct' and 'measuring'. """ - if mode=="direct": + if mode == "direct": self.axisModeDirect.set(37, settle_time=settle_time).wait() - if mode=="measuring": + if mode == "measuring": self.axisAxesMode.set(AbrMode.MEASURING, settle_time=settle_time).wait() def configure(self, d: dict) -> tuple: - """" Configure the exposure scripts + """ " Configure the exposure scripts Script execution at the PX beamlines is based on scripts that are always running on the controller that execute commands when commanded by @@ -317,54 +313,56 @@ class AerotechAbrStage(PsiDeviceBase): old = self.read_configuration() # ToDo: Check if idle before reconfiguring - self.scan_command.set(d['scan_command']).wait() + self.scan_command.set(d["scan_command"]).wait() # Set the corresponding global variables - 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: - self._var_2.set(d['var_2']).wait() - if "var_3" in d and d['var_3'] is not None: - self._var_3.set(d['var_3']).wait() - if "var_4" in d and d['var_4'] is not None: - self._var_4.set(d['var_4']).wait() - if "var_5" in d and d['var_5'] is not None: - self._var_5.set(d['var_5']).wait() - if "var_6" in d and d['var_6'] is not None: - self._var_6.set(d['var_6']).wait() - if "var_7" in d and d['var_7'] is not None: - self._var_7.set(d['var_7']).wait() - if "var_8" in d and d['var_8'] is not None: - self._var_8.set(d['var_8']).wait() - if "var_9" in d and d['var_9'] is not None: - self._var_9.set(d['var_9']).wait() - if "var_10" in d and d['var_10'] is not None: - self._var_10.set(d['var_10']).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: + self._var_2.set(d["var_2"]).wait() + if "var_3" in d and d["var_3"] is not None: + self._var_3.set(d["var_3"]).wait() + if "var_4" in d and d["var_4"] is not None: + self._var_4.set(d["var_4"]).wait() + if "var_5" in d and d["var_5"] is not None: + self._var_5.set(d["var_5"]).wait() + if "var_6" in d and d["var_6"] is not None: + self._var_6.set(d["var_6"]).wait() + if "var_7" in d and d["var_7"] is not None: + self._var_7.set(d["var_7"]).wait() + if "var_8" in d and d["var_8"] is not None: + self._var_8.set(d["var_8"]).wait() + if "var_9" in d and d["var_9"] is not None: + self._var_9.set(d["var_9"]).wait() + if "var_10" in d and d["var_10"] is not None: + self._var_10.set(d["var_10"]).wait() new = self.read_configuration() return old, new def bluestage(self): - """ Bluesky-style stage - - Since configuration synchronization is not guaranteed, this does - nothing. The script launched by kickoff(). + """Bluesky-style stage + + Since configuration synchronization is not guaranteed, this does + nothing. The script launched by kickoff(). """ pass def bluekickoff(self, timeout=1) -> SubscriptionStatus: - """ Kick off the set program """ + """Kick off the set program""" self.start_command.set(1).wait() # Define wait until the busy flag goes high def is_busy(*args, value, **kwargs): - return bool(value==0) + return bool(value == 0) # Subscribe and wait for update - status = SubscriptionStatus(self.raster_scan_done, is_busy, timeout=timeout, settle_time=0.1) + status = SubscriptionStatus( + self.raster_scan_done, is_busy, timeout=timeout, settle_time=0.1 + ) return status def blueunstage(self, settle_time=0.1): - """ Stops current script and releases the axes""" + """Stops current script and releases the axes""" # Disarm commands self.scan_command.set(AbrCmd.NONE, settle_time=settle_time).wait() self.stop_command.set(1).wait() @@ -372,22 +370,25 @@ class AerotechAbrStage(PsiDeviceBase): self.set_axis_mode("direct", settle_time=settle_time) def complete(self, timeout=None) -> SubscriptionStatus: - """ Waits for task execution + """Waits for task execution NOTE: Original complete was raster scanner complete... """ + # Define wait until the busy flag goes down (excluding initial update) def is_idle(*args, value, **kwargs): - return bool(value==1) + return bool(value == 1) # Subscribe and wait for update # status = SubscriptionStatus(self.task1, is_idle, timeout=timeout, settle_time=0.5) - status = SubscriptionStatus(self.raster_scan_done, is_idle, timeout=timeout, settle_time=0.5) + status = SubscriptionStatus( + self.raster_scan_done, is_idle, timeout=timeout, settle_time=0.5 + ) return status def reset(self, settle_time=0.1, wait_after_reload=1) -> None: - """ Resets the Aerotech controller state - + """Resets the Aerotech controller state + Attempts to reset the currently running measurement task on the A3200 by stopping current motions, reloading aerobasic programs and going to DIRECT mode. @@ -405,8 +406,7 @@ class AerotechAbrStage(PsiDeviceBase): self.set_axis_mode("direct", settle_time=settle_time) def stop(self, settle_time=1.0) -> None: - """ Stops current motions - """ + """Stops current motions""" # Disarm commands self.scan_command.set(AbrCmd.NONE, settle_time=settle_time).wait() self.stop_command.set(1).wait() @@ -466,7 +466,7 @@ class AerotechAbrStage(PsiDeviceBase): # return state == self._shutter.get() def wait_for_movements(self, timeout=60.0): - """ Waits for all motor movements""" + """Waits for all motor movements""" t_start = time.time() t_elapsed = 0 @@ -480,7 +480,10 @@ class AerotechAbrStage(PsiDeviceBase): """Chechs if all axes are DONE""" return not ( self.omega_done.get() - and self.gmx_done.get() and self.gmy_done.get() and self.gmz_done.get() ) + and self.gmx_done.get() + and self.gmy_done.get() + and self.gmz_done.get() + ) # def start_exposure(self): # """Starts the previously configured exposure.""" diff --git a/pxiii_bec/devices/A3200enums.py b/pxiii_bec/devices/A3200enums.py index c898e94..26df02f 100644 --- a/pxiii_bec/devices/A3200enums.py +++ b/pxiii_bec/devices/A3200enums.py @@ -6,50 +6,67 @@ Enumerations for the MX specific Aerotech A3200 stage. """ # pylint: disable=too-few-public-methods + class AbrStatus: """ABR measurement task status""" + DONE = 0 READY = 1 BUSY = 2 + ABR_DONE = 0 ABR_READY = 1 ABR_BUSY = 2 + class AbrGridStatus: """ABR grid scan status""" + BUSY = 0 DONE = 1 + GRID_SCAN_BUSY = 0 GRID_SCAN_DONE = 1 + class AbrMode: """ABR mode status""" + DIRECT = 0 MEASURING = 1 + DIRECT_MODE = 0 MEASURING_MODE = 1 + class AbrShutterStatus: """ABR shutter status""" + CLOSE = 0 OPEN = 1 + SHUTTER_CLOSE = 0 SHUTTER_OPEN = 1 + class AbrGridPeriod: """ABR grid period""" + FULL = 0 HALF = 1 + FULL_PERIOD = 0 HALF_PERIOD = 1 + class AbrCmd: """ABR command table""" + NONE = 0 RASTER_SCAN_SIMPLE = 1 MEASURE_STANDARD = 2 @@ -73,6 +90,7 @@ class AbrCmd: SCAN_SASTT_V2 = 20 SCAN_SASTT_V3 = 21 + CMD_NONE = 0 CMD_RASTER_SCAN_SIMPLE = 1 CMD_MEASURE_STANDARD = 2 @@ -96,8 +114,10 @@ CMD_SCAN_SASTT = 19 CMD_SCAN_SASTT_V2 = 20 CMD_SCAN_SASTT_V3 = 21 + class AbrAxis: """ABR axis index""" + OMEGA = 1 GMX = 2 GMY = 3 @@ -105,9 +125,10 @@ class AbrAxis: STY = 5 STZ = 6 + AXIS_OMEGA = 1 AXIS_GMX = 2 AXIS_GMY = 3 AXIS_GMZ = 4 AXIS_STY = 5 -AXIS_STZ = 6 \ No newline at end of file +AXIS_STZ = 6 diff --git a/pxiii_bec/devices/A3200utils.py b/pxiii_bec/devices/A3200utils.py index ae0025f..d92027a 100644 --- a/pxiii_bec/devices/A3200utils.py +++ b/pxiii_bec/devices/A3200utils.py @@ -6,20 +6,27 @@ Created on Tue Jun 11 11:28:38 2024 """ import types import math -from ophyd import (Component, PVPositioner, Device, Signal, EpicsSignal, EpicsSignalRO, Kind, - PositionerBase) +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 from .A3200enums import * - - ABR_DONE = 0 ABR_READY = 1 ABR_BUSY = 2 @@ -32,12 +39,12 @@ MEASURING_MODE = 1 class A3200Axis(PVPositioner): - """ Positioner wrapper for A3200 axes - - + """Positioner wrapper for A3200 axes + + 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 + standard Ophyd positioner for the BEC. It also has some additional functionality for error checking and diagnostics. Examples @@ -56,19 +63,22 @@ class A3200Axis(PVPositioner): base_pv : str (situational) IOC PV name root, i.e. X06DA-ES if standalone class. """ - USER_ACCESS = ['omove'] + + USER_ACCESS = ["omove"] abr_mode_direct = Component( - EpicsSignal, "-DF1:MODE-DIRECT", put_complete=True, kind=Kind.omitted) + 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) + 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) # Setpoint is one of the two... setpoint = Component(EpicsSignal, "-SETP", kind=Kind.config) - #setpoint = Component(EpicsSignal, "-VAL", kind=Kind.config) + # setpoint = Component(EpicsSignal, "-VAL", kind=Kind.config) velocity = Component(EpicsSignal, "-SETV", kind=Kind.config) status = Component(EpicsSignalRO, "-STAT", auto_monitor=True, kind=Kind.config) @@ -88,7 +98,7 @@ class A3200Axis(PVPositioner): _rock_dist = Component(EpicsSignal, "-RINCP", put_complete=True, kind=Kind.config) _rock_velo = Component(EpicsSignal, "-RSETV", put_complete=True, kind=Kind.config) _rock_count = Component(EpicsSignal, "-COUNT", put_complete=True, kind=Kind.config) - #_rock_accel = Component(EpicsSignal, "-RRATE", put_complete=True, kind=Kind.config) + # _rock_accel = Component(EpicsSignal, "-RRATE", put_complete=True, kind=Kind.config) hlm = Component(Signal, kind=Kind.config) llm = Component(Signal, kind=Kind.config) @@ -96,37 +106,69 @@ class A3200Axis(PVPositioner): vmax = Component(Signal, kind=Kind.config) offset = Component(EpicsSignal, "-OFF", put_complete=True, kind=Kind.config) - 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""" + 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 when no parent 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"]) + 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"]) + 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) + 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 omove(self, position, wait=True, timeout=None, moved_cb=None, velocity=None, relative=False, - direct=False, **kwargs) -> MoveStatus: - """ Native absolute/relative movement on the A3200 """ + def omove( + self, + position, + wait=True, + timeout=None, + moved_cb=None, + velocity=None, + relative=False, + direct=False, + **kwargs, + ) -> MoveStatus: + """Native absolute/relative movement on the A3200""" # Check if we're in direct movement mode if self.abr_mode.value not in (DIRECT_MODE, "DIRECT"): @@ -169,19 +211,18 @@ class A3200Axis(PVPositioner): raise return status - def move(self, position, wait=True, timeout=None, moved_cb=None, **kwargs) -> MoveStatus: - """ Exposes the ophyd move command through BEC abstraction""" + """Exposes the ophyd move command through BEC abstraction""" return self.omove(position, wait=wait, timeout=timeout, moved_cb=moved_cb, **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""" + """Repeated single axis zigzag scan I guess PSO should be configured for this""" self._rock_dist.put(distance) self._rock_count.put(counts) if velocity is not None: self._rock_velo.put(velocity) - #if acceleration is not None: + # if acceleration is not None: # self._rock_accel.put(acceleration) self._rock.put(1) @@ -190,7 +231,6 @@ class A3200Axis(PVPositioner): status.wait() return status - # def is_omega_ok(self): # """Checks omega axis status""" # return 0 == self.self.omega.status.get() @@ -216,21 +256,11 @@ class A3200Axis(PVPositioner): # time.sleep(0.2) - - - - - - - - - - class A3200RasterScanner(Device): - """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.""" + """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.""" x_start = Component(EpicsSignal, "-GRD:GMX-START", kind=Kind.config) x_start = Component(EpicsSignal, "-GRD:GMX-END", kind=Kind.config) @@ -259,10 +289,11 @@ class A3200RasterScanner(Device): # Also needs the command interface _zcmd = Component(EpicsSignal, "-PSO:CMD", put_complete=True, kind=Kind.omitted) _start_command = Component( - EpicsSignal, "-PSO:START-TEST.PROC", put_complete=True, kind=Kind.omitted) + EpicsSignal, "-PSO:START-TEST.PROC", put_complete=True, kind=Kind.omitted + ) _stop_command = Component( - EpicsSignal, "-PSO:STOP-TEST.PROC", put_complete=True, kind=Kind.omitted) - + EpicsSignal, "-PSO:STOP-TEST.PROC", put_complete=True, kind=Kind.omitted + ) def raster_setup(self, positions, columns, angle, etime): """configures parameters for a raster scan @@ -334,7 +365,7 @@ class A3200RasterScanner(Device): def raster_get_ready(self): """ - WTF is this??? + WTF is this??? """ self.get_ready.set(1).wait() @@ -342,9 +373,10 @@ class A3200RasterScanner(Device): try: # Define wait until the busy flag goes down (excluding initial update) timestamp_ = 0 + def is_ready(*args, old_value, value, timestamp, **kwargs): nonlocal timestamp_ - result = False if (timestamp_== 0) else bool(value==ABR_READY) + result = False if (timestamp_ == 0) else bool(value == ABR_READY) print(f"Old {old_value}\tNew: {value}\tResult: {result}") timestamp_ = timestamp return result @@ -370,9 +402,10 @@ class A3200RasterScanner(Device): def wait_scan_done(self, timeout=60.0) -> SubscriptionStatus: # Define wait until the busy flag goes down (excluding initial update) timestamp_ = 0 + def is_ready(*args, old_value, value, timestamp, **kwargs): nonlocal timestamp_ - result = False if (timestamp_== 0) else bool(value==1) + result = False if (timestamp_ == 0) else bool(value == 1) print(f"Old {old_value}\tNew: {value}\tResult: {result}") timestamp_ = timestamp return result @@ -389,9 +422,10 @@ class A3200RasterScanner(Device): def raster_wait_for_row(self, timeout=60) -> SubscriptionStatus: # Define wait subscription timestamp_ = 0 + def is_ready(*args, old_value, value, timestamp, **kwargs): nonlocal timestamp_ - result = False if (timestamp_== 0) else bool(value==1) + result = False if (timestamp_ == 0) else bool(value == 1) print(f"Old {old_value}\tNew: {value}\tResult: {result}") timestamp_ = timestamp return result @@ -403,16 +437,17 @@ class A3200RasterScanner(Device): return status def complete(self, timeout=None) -> DeviceStatus: - """ Wait for the grid scanner to finish - + """Wait for the grid scanner to finish + TODO: Probably redundant with task status wait? """ if timeout is not None: # Define wait until the busy flag goes down (excluding initial update) timestamp_ = 0 + def is_ready(*args, old_value, value, timestamp, **kwargs): nonlocal timestamp_ - result = False if (timestamp_== 0) else bool(value==GRID_SCAN_DONE) + result = False if (timestamp_ == 0) else bool(value == GRID_SCAN_DONE) print(f"Old {old_value}\tNew: {value}\tResult: {result}") timestamp_ = timestamp return result @@ -425,11 +460,10 @@ class A3200RasterScanner(Device): return status - class A3200Oscillator(Device): - """No clue what this does, seems to be redundant with the task based grid scanners... - """ - ostart_pos = Component(EpicsSignal, "-OSC:START-POS", put_complete=True, kind=Kind.config) + """No clue what this does, seems to be redundant with the task based grid scanners...""" + + ostart_pos = Component(EpicsSignal, "-OSC:START-POS", put_complete=True, kind=Kind.config) orange = Component(EpicsSignal, "-OSC:RANGE", put_complete=True, kind=Kind.config) etime = Component(EpicsSignal, "-OSC:ETIME", put_complete=True, kind=Kind.config) get_ready = Component(EpicsSignal, "-OSC:READY.PROC", put_complete=True, kind=Kind.config) @@ -460,9 +494,10 @@ class A3200Oscillator(Device): RETURNS SubscriptionStatus : A waitable status subscribed on 'phase' updates. """ + # Define wait until the busy flag goes down (excluding initial update) def in_status(*args, old_value, value, _, **kwargs): - result = bool(value==target) + result = bool(value == target) # print(f"Old {old_value}\tNew: {value}\tResult: {result}") return result @@ -471,7 +506,6 @@ class A3200Oscillator(Device): return status - # Automatically start an axis if directly invoked if __name__ == "__main__": omega = A3200Axis(prefix="X06DA-ES-DF1:OMEGA", name="omega") diff --git a/pxiii_bec/devices/SmarGon.py b/pxiii_bec/devices/SmarGon.py index 489e613..15d2a7a 100644 --- a/pxiii_bec/devices/SmarGon.py +++ b/pxiii_bec/devices/SmarGon.py @@ -6,18 +6,20 @@ import requests try: from bec_lib import bec_logger + logger = bec_logger.logger except ModuleNotFoundError: import logging + logger = logging.getLogger("SmarGon") class SmarGonSignal(Signal): - """ SmarGonSignal (R/W) - + """SmarGonSignal (R/W) + Small helper class to read/write parameters from SmarGon. As there is no motion status readback from smargopolo, this should be substituted with - setting with 'settle_time'. + setting with 'settle_time'. """ def __init__(self, *args, write_addr="targetSCS", low_limit=None, high_limit=None, **kwargs): @@ -28,7 +30,7 @@ class SmarGonSignal(Signal): # self.get() def put(self, value, *args, timestamp=None, **kwargs): - """ Overriden put to add communication with smargopolo""" + """Overriden put to add communication with smargopolo""" # Validate new value self.check_value(value) @@ -44,15 +46,16 @@ class SmarGonSignal(Signal): self.value = r[self.addr.upper()] # Notify subscribers - self._run_subs(sub_type=self.SUB_VALUE, old_value=old_value, - value=value, timestamp=self._timestamp) + self._run_subs( + sub_type=self.SUB_VALUE, old_value=old_value, value=value, timestamp=self._timestamp + ) @property def limits(self): return self._limits def check_value(self, value, **kwargs): - """ Check if value falls within limits""" + """Check if value falls within limits""" lol = self.limits[0] if lol is not None: if value < lol: @@ -70,9 +73,9 @@ class SmarGonSignal(Signal): class SmarGonSignalRO(Signal): - """ Small helper class for read-only parameters PVs from SmarGon. + """Small helper class for read-only parameters PVs from SmarGon. - TODO: Add monitoring + TODO: Add monitoring """ def __init__(self, *args, read_addr="readbackSCS", **kwargs): @@ -111,8 +114,9 @@ class SmarGonAxis(Device): This class controls the SmarGon goniometer via the REST interface. """ + # Status attributes - sg_url = Component(Signal, kind=Kind.config, metadata={'write_access': False}) + sg_url = Component(Signal, kind=Kind.config, metadata={"write_access": False}) corr = Component(SmarGonSignalRO, read_addr="corr_type", kind=Kind.config) mode = Component(SmarGonSignalRO, read_addr="mode", kind=Kind.config) @@ -134,17 +138,25 @@ class SmarGonAxis(Device): parent=None, device_manager=None, sg_url: str = "http://x06da-smargopolo.psi.ch:3000", - low_limit = None, - high_limit = None, + low_limit=None, + high_limit=None, **kwargs, ) -> None: - self.__class__.__dict__['readback'].kwargs['read_addr'] = f"readback{prefix}" - self.__class__.__dict__['setpoint'].kwargs['write_addr'] = f"target{prefix}" - self.__class__.__dict__['setpoint'].kwargs['low_limit'] = low_limit - self.__class__.__dict__['setpoint'].kwargs['high_limit'] = high_limit + self.__class__.__dict__["readback"].kwargs["read_addr"] = f"readback{prefix}" + self.__class__.__dict__["setpoint"].kwargs["write_addr"] = f"target{prefix}" + self.__class__.__dict__["setpoint"].kwargs["low_limit"] = low_limit + self.__class__.__dict__["setpoint"].kwargs["high_limit"] = high_limit # super().__init__(prefix=prefix, name=name, kind=kind, read_attrs=read_attrs, configuration_attrs=configuration_attrs, parent=parent, device_manager=device_manager, **kwargs) - super().__init__(prefix=prefix, name=name, kind=kind, read_attrs=read_attrs, configuration_attrs=configuration_attrs, parent=parent, **kwargs) + super().__init__( + prefix=prefix, + name=name, + kind=kind, + read_attrs=read_attrs, + configuration_attrs=configuration_attrs, + parent=parent, + **kwargs, + ) self.sg_url._metadata["write_access"] = False self.sg_url.set(sg_url, force=True).wait() @@ -160,7 +172,9 @@ class SmarGonAxis(Device): cmd = f"{self.sg_url.get()}/{address}" r = requests.get(cmd, timeout=1) if not r.ok: - raise RuntimeError(f"[{self.name}] Error getting {address}; server returned {r.status_code} => {r.reason}") + raise RuntimeError( + f"[{self.name}] Error getting {address}; server returned {r.status_code} => {r.reason}" + ) return r.json() def _go_n_put(self, address, **kwargs): @@ -168,19 +182,22 @@ class SmarGonAxis(Device): cmd = f"{self.sg_url.get()}/{address}" r = requests.put(cmd, timeout=1) if not r.ok: - raise RuntimeError(f"[{self.name}] Error putting {address}; server returned {r.status_code} => {r.reason}") + raise RuntimeError( + f"[{self.name}] Error putting {address}; server returned {r.status_code} => {r.reason}" + ) return r.json() - - - - - if __name__ == "__main__": shx = SmarGonAxis(prefix="SCS", name="shx", sg_url="http://x06da-smargopolo.psi.ch:3000") shy = SmarGonAxis(prefix="SCS", name="shy", sg_url="http://x06da-smargopolo.psi.ch:3000") - shz = SmarGonAxis(prefix="SCS", name="shz", low_limit=10, high_limit=22, sg_url="http://x06da-smargopolo.psi.ch:3000") + shz = SmarGonAxis( + prefix="SCS", + name="shz", + low_limit=10, + high_limit=22, + sg_url="http://x06da-smargopolo.psi.ch:3000", + ) shx.wait_for_connection() shy.wait_for_connection() shz.wait_for_connection() diff --git a/pxiii_bec/scans/mx_measurements.py b/pxiii_bec/scans/mx_measurements.py index 86af521..17c0f40 100644 --- a/pxiii_bec/scans/mx_measurements.py +++ b/pxiii_bec/scans/mx_measurements.py @@ -3,6 +3,7 @@ Scan primitives for standard BEC scans at the PX beamlines at SLS. Theese scans define the event model and can be called from higher levels. """ + import numpy as np from bec_lib import bec_logger @@ -11,9 +12,9 @@ from bec_server.scan_server.scans import AsyncFlyScanBase, ScanArgType, ScanBase logger = bec_logger.logger - class AbrCmd: - """ Valid Aerotech ABR scan commands from the AeroBasic files""" + """Valid Aerotech ABR scan commands from the AeroBasic files""" + NONE = 0 RASTER_SCAN_SIMPLE = 1 MEASURE_STANDARD = 2 @@ -38,9 +39,8 @@ class AbrCmd: # SCAN_SASTT_V3 = 21 - class AerotechFlyscanBase(AsyncFlyScanBase): - """ Base class for MX flyscans + """Base class for MX flyscans Low-level base class for standard scans at the PX beamlines at SLS. Theese scans use the A3200 rotation stage and the actual experiment is performed @@ -50,26 +50,27 @@ class AerotechFlyscanBase(AsyncFlyScanBase): configurations in child classes or pass it as command line parameters. IMPORTANT: The AeroBasic scripts take care of the PSO configuration. - + Parameters: ----------- abr_complete : bool Wait for the launched ABR task to complete. """ + scan_type = "fly" scan_report_hint = "table" arg_input = {} arg_bundle_size = {"bundle": len(arg_input), "min": None, "max": None} # Aerotech stage config - abr_raster_reset =False + abr_raster_reset = False abr_complete = False abr_timeout = None pointID = 0 num_pos = 0 def __init__(self, *args, parameter: dict = None, **kwargs): - """ Just set num_pos=0 to avoid hanging and override defaults if explicitly set from + """Just set num_pos=0 to avoid hanging and override defaults if explicitly set from parameters. """ super().__init__(parameter=parameter, **kwargs) @@ -81,14 +82,14 @@ class AerotechFlyscanBase(AsyncFlyScanBase): self.abr_timeout = self.caller_kwargs.get("abr_timeout") def pre_scan(self): - """ Mostly just checking if ABR stage is ok...""" + """Mostly just checking if ABR stage is ok...""" # TODO: Move roughly to start position??? # ABR status checking - stat = yield from self.stubs.send_rpc_and_wait("abr","status.get") + 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") - task = yield from self.stubs.send_rpc_and_wait("abr","task1.get") + task = yield from self.stubs.send_rpc_and_wait("abr", "task1.get") # From what I got values are copied to local vars at the start of scan, # so only kickoff should be forbidden. if task not in (1, "OK"): @@ -106,8 +107,7 @@ class AerotechFlyscanBase(AsyncFlyScanBase): # return super().stage() def scan_core(self): - """ The actual scan logic comes here. - """ + """The actual scan logic comes here.""" # Kick off the run yield from self.stubs.send_rpc_and_wait("abr", "bluekickoff") logger.info("Measurement launched on the ABR stage...") @@ -127,9 +127,8 @@ class AerotechFlyscanBase(AsyncFlyScanBase): return super().cleanup() - class MeasureStandardWedge(AerotechFlyscanBase): - """ Standard wedge scan using the OMEGA motor + """Standard wedge scan using the OMEGA motor Measure an absolute continous line scan from `start` to `start` + `range` during `move_time` on the Omega axis with PSO output. @@ -148,22 +147,22 @@ class MeasureStandardWedge(AerotechFlyscanBase): range : (float, float) Scan range of the axis. move_time : (float) - Total travel time for the movement [s]. + Total travel time for the movement [s]. ready_rate : float, optional No clue what is this... (default=500) """ + scan_name = "standardscan" required_kwargs = ["start", "range", "move_time"] - class MeasureVerticalLine(AerotechFlyscanBase): - """ Vertical line scan using the GMY motor + """Vertical line scan using the GMY motor Simple relative continous line scan that records a single vertical line with PSO output. There's no actual stepping, it's only used for velocity calculation. - + The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. @@ -180,19 +179,19 @@ class MeasureVerticalLine(AerotechFlyscanBase): exp_time : float Eeffective exposure time per step [s] """ + scan_name = "vlinescan" required_kwargs = ["exp_time", "range", "steps"] - class MeasureRasterSimple(AerotechFlyscanBase): - """ Simple raster scan + """Simple raster scan Measure a simplified relative zigzag raster scan in the X-Y plane. The scan is relative assumes the goniometer is currently at the CENTER of the first cell (i.e. TOP-LEFT). Each line is executed as a single continous movement, i.e. there's no actual stepping in the X direction. - + The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. @@ -213,16 +212,15 @@ class MeasureRasterSimple(AerotechFlyscanBase): steps_y : int Number of scan steps in Y (slow). """ + scan_name = "rasterscan" required_kwargs = ["exp_time", "range_x", "range_y", "steps_x", "steps_y"] - - class MeasureScreening(AerotechFlyscanBase): - """ Sample screening scan - - Sample screening scan that scans intervals on the rotation axis taking + """Sample screening scan + + Sample screening scan that scans intervals on the rotation axis taking 1 image/interval. This makes sure that we hit diffraction peaks if there are any crystals. @@ -242,11 +240,12 @@ class MeasureScreening(AerotechFlyscanBase): steps : int Number of blurred intervals. exp_time : float - Exposure time per blurred interval [s]. + Exposure time per blurred interval [s]. oscrange : float Motion blurring of each interval [deg] delta : float Safety margin for sub-range movements (default: 0.5) [deg]. """ + scan_name = "screeningscan" required_kwargs = ["start", "range", "steps", "exp_time", "oscrange"] -- 2.49.1 From 82d51649ee5c8097b45e96e29683953a80d79705 Mon Sep 17 00:00:00 2001 From: gac-x06da Date: Fri, 24 Jan 2025 15:21:59 +0100 Subject: [PATCH 23/25] Blacking and cleanup of unused code --- pxiii_bec/devices/A3200utils.py | 250 ----------------------------- pxiii_bec/devices/SmarGon_orig.py | 24 +-- pxiii_bec/scans/__init__.py | 8 +- pxiii_bec/scans/mx_measurements.py | 6 +- 4 files changed, 21 insertions(+), 267 deletions(-) diff --git a/pxiii_bec/devices/A3200utils.py b/pxiii_bec/devices/A3200utils.py index d92027a..0de97a9 100644 --- a/pxiii_bec/devices/A3200utils.py +++ b/pxiii_bec/devices/A3200utils.py @@ -256,256 +256,6 @@ class A3200Axis(PVPositioner): # time.sleep(0.2) -class A3200RasterScanner(Device): - """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.""" - - x_start = Component(EpicsSignal, "-GRD:GMX-START", kind=Kind.config) - x_start = Component(EpicsSignal, "-GRD:GMX-END", kind=Kind.config) - omega = Component(EpicsSignal, "-OSC:#M-START", kind=Kind.config) - osc = Component(EpicsSignal, "-GRD:ANGLE", kind=Kind.config) - celltime = Component(EpicsSignal, "-GRD:CELL-TIME", kind=Kind.config) - - y_start = Component(EpicsSignal, "-OSC:#STY-START", kind=Kind.config) - y_end = Component(EpicsSignal, "-OSC:#STY-END", kind=Kind.config) - z_start = Component(EpicsSignal, "-OSC:#STZ-START", kind=Kind.config) - z_end = Component(EpicsSignal, "-OSC:#STZ-END", kind=Kind.config) - - columns = Component(EpicsSignal, "-GRD:COLUMNS", kind=Kind.config) - rows = Component(EpicsSignal, "-GRD:ROWS", kind=Kind.config) - delay = Component(EpicsSignal, "-GRD:RAST-DLY", kind=Kind.config) - osc_mode = Component(EpicsSignal, "-GRD:SET-MODE", kind=Kind.config) - - velo = Component(EpicsSignal, "-GRD:#X-VEL", kind=Kind.config) - get_ready = Component(EpicsSignal, "-GRD:READY.PROC", kind=Kind.config) - grid_start = Component(EpicsSignal, "-GRD:START.PROC", kind=Kind.config) - grid_next = Component(EpicsSignal, "-GRD:NEXT-ROW", kind=Kind.config) - row_done = Component(EpicsSignal, "-GRD:ROW-DONE", kind=Kind.config) - scan_done = Component(EpicsSignal, "-GRD:SCAN-DONE", kind=Kind.config) - grid_done = Component(EpicsSignal, "-GRD:DONE", kind=Kind.config) - - # Also needs the command interface - _zcmd = Component(EpicsSignal, "-PSO:CMD", put_complete=True, kind=Kind.omitted) - _start_command = Component( - EpicsSignal, "-PSO:START-TEST.PROC", put_complete=True, kind=Kind.omitted - ) - _stop_command = Component( - EpicsSignal, "-PSO:STOP-TEST.PROC", put_complete=True, kind=Kind.omitted - ) - - def raster_setup(self, positions, columns, angle, etime): - """configures parameters for a raster scan - - positions : a list of xyz indicating the positions of each cell - columns: an integer with how many columns - angle : the oscillation angle for each row - etime : the exposure time per cell - """ - self.parent.axisModeDirect.set(1).wait() - if columns == 1: - raise RuntimeWarning("Fast scans are not available with vertical line scans.") - - rows = len(positions) / columns - x1, y1, z1 = tuple(positions[0]) # first cell on first row - x2, y2, z2 = tuple(positions[1]) # second cell on first row - x4, y4, z4 = tuple(positions[columns - 1]) # last cell on first row - - if rows > 1: - x3, y3, z3 = tuple(positions[columns]) # first cell on second row - ystep = y3 - y1 - zstep = z3 - z1 - gmy_step = math.sqrt(pow(y3 - y1, 2) + pow(z3 - z1, 2)) - else: - ystep = 0.010 - zstep = 0.010 - gmy_step = 0.010 - - half_cell = abs(x2 - x1) / 2.0 - start_x = x1 - half_cell - end_x = x4 + half_cell - - self._raster_starting_x = start_x - self._raster_starting_y = y1 - self._raster_starting_z = z1 - self._raster_step_y = ystep - self._raster_step_z = zstep - self._raster_current_row = 0 - self._raster_num_rows = rows - - self.x_start.set(start_x).wait() - self.x_end.set(end_x).wait() - self.omega.set(self.position).wait() - self.osc.set(angle).wait() - self.celltime.set(etime).wait() - self.y_start.set(y1).wait() - self.z_start.set(z1).wait() - self.y_step.set(ystep).wait() - self.z_step.set(zstep).wait() - self.columns.set(columns).wait() - self.rows.set(rows).wait() - - def raster_next_row_yz(self): - r = self._raster_current_row - self._raster_current_row = r + 1 - - y, z = ( - r * self.step_y.value + self._raster_starting_y, - r * self.step_z.value + self._raster_starting_z, - ) - - if r < self._raster_num_rows: - return (y, z) - return (None, None) - - def next_row(self): - """start rastering a new row""" - self.grid_next.set(1).wait() - - def raster_get_ready(self): - """ - WTF is this??? - """ - - self.get_ready.set(1).wait() - for _ in range(10): - try: - # Define wait until the busy flag goes down (excluding initial update) - timestamp_ = 0 - - def is_ready(*args, old_value, value, timestamp, **kwargs): - nonlocal timestamp_ - result = False if (timestamp_ == 0) else bool(value == ABR_READY) - print(f"Old {old_value}\tNew: {value}\tResult: {result}") - timestamp_ = timestamp - return result - - # Subscribe and wait for update - status = SubscriptionStatus(self.grid_done, is_ready, timeout=2, settle_time=0.5) - status.wait() - except TimeoutError as e: - print(str(e), end=" ") - print(" --- trying again.") - self.get_ready.put(1, wait=True) - - def raster_scan_row(self): - """start rastering a new row, either first or following rows""" - if ABR_READY == self._grid_done.get(): - self.grid_start.set(1) - else: - self.grid_next.set(1) - - def is_scan_done(self): - return 1 == self.scan_done.get() - - def wait_scan_done(self, timeout=60.0) -> SubscriptionStatus: - # Define wait until the busy flag goes down (excluding initial update) - timestamp_ = 0 - - def is_ready(*args, old_value, value, timestamp, **kwargs): - nonlocal timestamp_ - result = False if (timestamp_ == 0) else bool(value == 1) - print(f"Old {old_value}\tNew: {value}\tResult: {result}") - timestamp_ = timestamp - return result - - # Subscribe and wait for update - status = SubscriptionStatus(self.scan_done, is_ready, timeout=timeout, settle_time=0.5) - status.wait() - - return status - - def raster_is_row_scanning(self): - return 1 == self.scan_done.get() - - def raster_wait_for_row(self, timeout=60) -> SubscriptionStatus: - # Define wait subscription - timestamp_ = 0 - - def is_ready(*args, old_value, value, timestamp, **kwargs): - nonlocal timestamp_ - result = False if (timestamp_ == 0) else bool(value == 1) - print(f"Old {old_value}\tNew: {value}\tResult: {result}") - timestamp_ = timestamp - return result - - # Subscribe and wait for update - status = SubscriptionStatus(self.row_done, is_ready, timeout=timeout, settle_time=0.5) - status.wait() - - return status - - def complete(self, timeout=None) -> DeviceStatus: - """Wait for the grid scanner to finish - - TODO: Probably redundant with task status wait? - """ - if timeout is not None: - # Define wait until the busy flag goes down (excluding initial update) - timestamp_ = 0 - - def is_ready(*args, old_value, value, timestamp, **kwargs): - nonlocal timestamp_ - result = False if (timestamp_ == 0) else bool(value == GRID_SCAN_DONE) - print(f"Old {old_value}\tNew: {value}\tResult: {result}") - timestamp_ = timestamp - return result - - # Subscribe and wait for update - status = SubscriptionStatus(self.scan_done, is_ready, timeout=timeout, settle_time=0.5) - return status - status = DeviceStatus(self, settle_time=0.5) - status.set_finished() - return status - - -class A3200Oscillator(Device): - """No clue what this does, seems to be redundant with the task based grid scanners...""" - - ostart_pos = Component(EpicsSignal, "-OSC:START-POS", put_complete=True, kind=Kind.config) - orange = Component(EpicsSignal, "-OSC:RANGE", put_complete=True, kind=Kind.config) - etime = Component(EpicsSignal, "-OSC:ETIME", put_complete=True, kind=Kind.config) - get_ready = Component(EpicsSignal, "-OSC:READY.PROC", put_complete=True, kind=Kind.config) - taskStart = Component(EpicsSignal, "-OSC:START", put_complete=True, kind=Kind.config) - phase = Component(EpicsSignal, "-OSC:DONE", auto_monitor=True, kind=Kind.config) - - @property - def is_done(self): - return ABR_DONE == self.phase.get() - - @property - def is_ready(self): - return ABR_READY == self.phase.get() - - @property - def is_busy(self): - return ABR_BUSY == self.phase.get() - - def kickoff(self): - self.osc.taskStart.set(1).wait() - - def wait_status(self, target, timeout=60.0) -> SubscriptionStatus: - """Wait for the Aertotech IOC to reach the desired `status`. - - PARAMETERS - `status` can be any the three ABR_DONE, ABR_READY or ABR_BUSY. - - RETURNS - SubscriptionStatus : A waitable status subscribed on 'phase' updates. - """ - - # Define wait until the busy flag goes down (excluding initial update) - def in_status(*args, old_value, value, _, **kwargs): - result = bool(value == target) - # print(f"Old {old_value}\tNew: {value}\tResult: {result}") - return result - - # Subscribe and wait for update - status = SubscriptionStatus(self.phase, in_status, timeout=timeout, settle_time=0.5) - return status - - # Automatically start an axis if directly invoked if __name__ == "__main__": omega = A3200Axis(prefix="X06DA-ES-DF1:OMEGA", name="omega") diff --git a/pxiii_bec/devices/SmarGon_orig.py b/pxiii_bec/devices/SmarGon_orig.py index bde885f..926c653 100644 --- a/pxiii_bec/devices/SmarGon_orig.py +++ b/pxiii_bec/devices/SmarGon_orig.py @@ -51,7 +51,9 @@ def scsput(**kwargs): :return: :rtype: """ - xyz = {k.upper(): v for k, v in kwargs.items() if k.lower() in ("shx", "shy", "shz", "chi", "phi")} + xyz = { + k.upper(): v for k, v in kwargs.items() if k.lower() in ("shx", "shy", "shz", "chi", "phi") + } thing = "&".join([f"{k.upper()}={float(v):.5f}" for k, v in xyz.items()]) cmd = f"{base}/targetSCS?{thing}" if kwargs.get("verbose", False): @@ -92,7 +94,9 @@ def scsrelput(**kwargs) -> None: :return: :rtype: """ - xyz = {k.upper(): v for k, v in kwargs.items() if k.lower() in ("shx", "shy", "shz", "chi", "phi")} + xyz = { + k.upper(): v for k, v in kwargs.items() if k.lower() in ("shx", "shy", "shz", "chi", "phi") + } thing = "&".join([f"{k.upper()}={float(v):.5f}" for k, v in xyz.items()]) cmd = f"{base}/targetSCS_rel?{thing}" if kwargs.get("verbose", False): @@ -194,13 +198,7 @@ class SmarGon(object): while not all(in_place) and time() < tout: rbv = self.readback_scs() in_place = [] - for k, v in { - "SHX": 0.0, - "SHY": 0.0, - "SHZ": 18.0, - "CHI": 0.0, - "PHI": 0.0, - }.items(): + for k, v in {"SHX": 0.0, "SHY": 0.0, "SHZ": 18.0, "CHI": 0.0, "PHI": 0.0}.items(): in_place.append(abs(rbv[k] - v) < 0.01) if time() > tout: raise TimeoutError(f"timeout waiting for smargon to reach home position: {rbv}") @@ -290,7 +288,9 @@ class SmarGon(object): def wait(self, timeout=60.0): """waits up to `timeout` seconds for smargon to reach target""" target = { - k.upper(): v for k, v in self.target_scs().items() if k.lower() in ("shx", "shy", "shz", "chi", "phi") + k.upper(): v + for k, v in self.target_scs().items() + if k.lower() in ("shx", "shy", "shz", "chi", "phi") } timeout = timeout + time() @@ -323,7 +323,9 @@ class SmarGon(object): bcsput(**value) elif key == "target": if not isinstance(value, dict): - raise Exception(f"expected a dict with target axis and values got something else: {value}") + raise Exception( + f"expected a dict with target axis and values got something else: {value}" + ) for k in value.keys(): if k.lower() not in "shx shy shz chi phi ox oy oz".split(): raise Exception(f'unknown axis in target "{k}"') diff --git a/pxiii_bec/scans/__init__.py b/pxiii_bec/scans/__init__.py index b874c1d..f95b6ed 100644 --- a/pxiii_bec/scans/__init__.py +++ b/pxiii_bec/scans/__init__.py @@ -1,2 +1,6 @@ -from .mx_measurements import (MeasureStandardWedge, MeasureVerticalLine, - MeasureRasterSimple, MeasureScreening) +from .mx_measurements import ( + MeasureStandardWedge, + MeasureVerticalLine, + MeasureRasterSimple, + MeasureScreening, +) diff --git a/pxiii_bec/scans/mx_measurements.py b/pxiii_bec/scans/mx_measurements.py index 17c0f40..539ee62 100644 --- a/pxiii_bec/scans/mx_measurements.py +++ b/pxiii_bec/scans/mx_measurements.py @@ -4,10 +4,8 @@ Scan primitives for standard BEC scans at the PX beamlines at SLS. Theese scans define the event model and can be called from higher levels. """ -import numpy as np - from bec_lib import bec_logger -from bec_server.scan_server.scans import AsyncFlyScanBase, ScanArgType, ScanBase +from bec_server.scan_server.scans import AsyncFlyScanBase logger = bec_logger.logger @@ -20,7 +18,7 @@ class AbrCmd: MEASURE_STANDARD = 2 VERTICAL_LINE_SCAN = 3 SCREENING = 4 - # SUPER_FAST_OMEGA = 5 # Some Japanese wanted to measure samples in capilaries at high RPMs + # SUPER_FAST_OMEGA = 5 # Some Japanese measured samples in capilaries at high RPMs # STILL_WEDGE = 6 # NOTE: Unused Step scan # STILLS = 7 # NOTE: Unused Just send triggers to detector # REPEAT_SINGLE_OSCILLATION = 8 # NOTE: Unused -- 2.49.1 From 21bd57393fbf7724d50c722e039f9f6b34f72300 Mon Sep 17 00:00:00 2001 From: gac-x06da Date: Fri, 24 Jan 2025 15:42:46 +0100 Subject: [PATCH 24/25] Blacking and cleanup of unused code --- pxiii_bec/devices/A3200.py | 4 ++-- pxiii_bec/devices/A3200utils.py | 38 +++++++++++---------------------- pxiii_bec/devices/SmarGon.py | 36 +++++++------------------------ 3 files changed, 23 insertions(+), 55 deletions(-) diff --git a/pxiii_bec/devices/A3200.py b/pxiii_bec/devices/A3200.py index c305ef9..f9958c7 100644 --- a/pxiii_bec/devices/A3200.py +++ b/pxiii_bec/devices/A3200.py @@ -352,7 +352,7 @@ class AerotechAbrStage(PsiDeviceBase): self.start_command.set(1).wait() # Define wait until the busy flag goes high - def is_busy(*args, value, **kwargs): + def is_busy(*, value, **_): return bool(value == 0) # Subscribe and wait for update @@ -376,7 +376,7 @@ class AerotechAbrStage(PsiDeviceBase): """ # Define wait until the busy flag goes down (excluding initial update) - def is_idle(*args, value, **kwargs): + def is_idle(*, value, **_): return bool(value == 1) # Subscribe and wait for update diff --git a/pxiii_bec/devices/A3200utils.py b/pxiii_bec/devices/A3200utils.py index 0de97a9..60a4cee 100644 --- a/pxiii_bec/devices/A3200utils.py +++ b/pxiii_bec/devices/A3200utils.py @@ -5,37 +5,25 @@ Created on Tue Jun 11 11:28:38 2024 @author: mohacsi_i """ import types -import math -from ophyd import ( - Component, - PVPositioner, - Device, - Signal, - EpicsSignal, - EpicsSignalRO, - Kind, - PositionerBase, -) -from ophyd.status import Status, MoveStatus, SubscriptionStatus, DeviceStatus +from ophyd import Component, PVPositioner, Signal, EpicsSignal, EpicsSignalRO, Kind, PositionerBase +from ophyd.status import Status, MoveStatus +from .A3200enums import AbrMode from bec_lib import bec_logger logger = bec_logger.logger -from .A3200enums import * +# ABR_DONE = 0 +# ABR_READY = 1 +# ABR_BUSY = 2 +# GRID_SCAN_BUSY = 0 +# GRID_SCAN_DONE = 1 -ABR_DONE = 0 -ABR_READY = 1 -ABR_BUSY = 2 - -GRID_SCAN_BUSY = 0 -GRID_SCAN_DONE = 1 - -DIRECT_MODE = 0 -MEASURING_MODE = 1 +# DIRECT_MODE = 0 +# MEASURING_MODE = 1 class A3200Axis(PVPositioner): @@ -162,7 +150,7 @@ class A3200Axis(PVPositioner): position, wait=True, timeout=None, - moved_cb=None, + # moved_cb=None, velocity=None, relative=False, direct=False, @@ -171,7 +159,7 @@ class A3200Axis(PVPositioner): """Native absolute/relative movement on the A3200""" # Check if we're in direct movement mode - if self.abr_mode.value not in (DIRECT_MODE, "DIRECT"): + if self.abr_mode.value not in (AbrMode.DIRECT, "DIRECT"): if direct: self.abr_mode_direct.set(1).wait() else: @@ -189,7 +177,7 @@ class A3200Axis(PVPositioner): self.check_value(position) # Get MoveStatus from parent of parent - status = PositionerBase.move(self, position=position, timeout=timeout) + status = PositionerBase.move(self, position=position, timeout=timeout, **kwargs) has_done = self.done is not None if not has_done: diff --git a/pxiii_bec/devices/SmarGon.py b/pxiii_bec/devices/SmarGon.py index 15d2a7a..0796c36 100644 --- a/pxiii_bec/devices/SmarGon.py +++ b/pxiii_bec/devices/SmarGon.py @@ -1,8 +1,6 @@ import time -from ophyd import Component, Device, Kind, Device, Signal, SignalRO -from ophyd.status import SubscriptionStatus - import requests +from ophyd import Component, Device, Kind, Signal, SignalRO try: from bec_lib import bec_logger @@ -29,7 +27,7 @@ class SmarGonSignal(Signal): self._limits = (low_limit, high_limit) # self.get() - def put(self, value, *args, timestamp=None, **kwargs): + def put(self, value, *, timestamp=None, **kwargs): """Overriden put to add communication with smargopolo""" # Validate new value self.check_value(value) @@ -38,7 +36,7 @@ class SmarGonSignal(Signal): timestamp = time.time() # Perform the actual write to SmargoPolo - r = self.parent._go_n_put(f"{self.write_addr}?{self.addr.upper()}={value}") + r = self.parent._go_n_put(f"{self.write_addr}?{self.addr.upper()}={value}", **kwargs) old_value = self._readback self._timestamp = timestamp @@ -91,24 +89,6 @@ class SmarGonSignalRO(Signal): return super().get(*args, **kwargs) -# class SmarGonMovingSignalRO(Signal): -# """Small helper class to read PVs that need to be processed first. -# TODO: Add monitoring -# """ - -# def __init__(self, *args, **kwargs): -# super().__init__(*args, **kwargs) -# self._metadata["write_access"] = False - -# def get(self, *args, **kwargs): -# r = self.parent._go_n_get("readbackMCS") -# print(r['state']) -# moving_str = r["state"]["q1"] + r["state"]["q2"] + r["state"]["q3"] + r["state"]["q4"] + r["state"]["q5"][0] -# moving = int(moving_str) -# self.put(moving_str, force=True) -# return super().get(*args, **kwargs) - - class SmarGonAxis(Device): """SmarGon client deice @@ -147,7 +127,6 @@ class SmarGonAxis(Device): self.__class__.__dict__["setpoint"].kwargs["low_limit"] = low_limit self.__class__.__dict__["setpoint"].kwargs["high_limit"] = high_limit - # super().__init__(prefix=prefix, name=name, kind=kind, read_attrs=read_attrs, configuration_attrs=configuration_attrs, parent=parent, device_manager=device_manager, **kwargs) super().__init__( prefix=prefix, name=name, @@ -155,6 +134,7 @@ class SmarGonAxis(Device): read_attrs=read_attrs, configuration_attrs=configuration_attrs, parent=parent, + # device_manager=device_manager, **kwargs, ) self.sg_url._metadata["write_access"] = False @@ -170,20 +150,20 @@ class SmarGonAxis(Device): def _go_n_get(self, address, **kwargs): """Helper function to connect to smargopolo""" cmd = f"{self.sg_url.get()}/{address}" - r = requests.get(cmd, timeout=1) + r = requests.get(cmd, timeout=1, **kwargs) if not r.ok: raise RuntimeError( - f"[{self.name}] Error getting {address}; server returned {r.status_code} => {r.reason}" + f"[{self.name}] Error getting {address}; reply was {r.status_code} => {r.reason}" ) return r.json() def _go_n_put(self, address, **kwargs): """Helper function to connect to smargopolo""" cmd = f"{self.sg_url.get()}/{address}" - r = requests.put(cmd, timeout=1) + r = requests.put(cmd, timeout=1, **kwargs) if not r.ok: raise RuntimeError( - f"[{self.name}] Error putting {address}; server returned {r.status_code} => {r.reason}" + f"[{self.name}] Error putting {address}; reply was {r.status_code} => {r.reason}" ) return r.json() -- 2.49.1 From 023e0aab2e2f2d0f00c7c4590642a41cd33f5ef0 Mon Sep 17 00:00:00 2001 From: gac-x06da Date: Fri, 24 Jan 2025 16:17:04 +0100 Subject: [PATCH 25/25] Fixing SmarGon axes --- .../device_configs/x06da_device_config.yaml | 94 ++++++++++++++++--- pxiii_bec/devices/A3200.py | 6 +- pxiii_bec/devices/SmarGon.py | 16 +++- pxiii_bec/devices/__init__.py | 1 + 4 files changed, 94 insertions(+), 23 deletions(-) diff --git a/pxiii_bec/device_configs/x06da_device_config.yaml b/pxiii_bec/device_configs/x06da_device_config.yaml index da92c58..cb1464e 100644 --- a/pxiii_bec/device_configs/x06da_device_config.yaml +++ b/pxiii_bec/device_configs/x06da_device_config.yaml @@ -394,15 +394,6 @@ xbox_diode: readoutPriority: monitored readOnly: true softwareTrigger: false -ms_zoom: - description: Sample microscope zoom - deviceClass: ophyd.EpicsMotor - deviceConfig: {prefix: 'X06DA-ES-SAMCAM:ZOOM'} - onFailure: buffer - enabled: true - readoutPriority: monitored - readOnly: false - softwareTrigger: false samdist: description: Sample distance deviceClass: ophyd.EpicsSignalRO @@ -421,6 +412,15 @@ samrange: readoutPriority: monitored readOnly: true softwareTrigger: false +samzoom: + description: Sample microscope zoom + deviceClass: ophyd.EpicsMotor + deviceConfig: {prefix: 'X06DA-ES-SAMCAM:ZOOM'} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: false + softwareTrigger: false samcam: description: Sample camera device deviceClass: ophyd_devices.devices.areadetector.cam.GenICam @@ -428,7 +428,7 @@ samcam: onFailure: buffer enabled: true readoutPriority: monitored - readOnly: true + readOnly: false softwareTrigger: false @@ -475,7 +475,7 @@ bstop_pneum: onFailure: buffer enabled: true readoutPriority: monitored - readOnly: true + readOnly: false softwareTrigger: false bstop_diode: description: Beamstop diode @@ -493,7 +493,7 @@ frontlight: onFailure: buffer enabled: true readoutPriority: monitored - readOnly: true + readOnly: false softwareTrigger: false backlight: description: Backlight reflector @@ -502,7 +502,7 @@ backlight: onFailure: buffer enabled: true readoutPriority: monitored - readOnly: true + readOnly: false softwareTrigger: false det_y: description: Pilatus height @@ -526,7 +526,24 @@ det_z: - +gmx: + description: ABR horizontal stage + deviceClass: pxiii_bec.devices.A3200Axis + deviceConfig: {prefix: 'X06DA-ES-DF1:GMX', base_pv: 'X06DA-ES'} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: false + softwareTrigger: false +gmy: + description: ABR vertical stage + deviceClass: pxiii_bec.devices.A3200Axis + deviceConfig: {prefix: 'X06DA-ES-DF1:GMY', base_pv: 'X06DA-ES'} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: false + softwareTrigger: false omega: description: ABR rotation stage deviceClass: pxiii_bec.devices.A3200Axis @@ -545,6 +562,53 @@ abr: readoutPriority: monitored readOnly: false softwareTrigger: false +shx: + description: SmarGon X axis + deviceClass: pxiii_bec.devices.SmarGonAxis + deviceConfig: {prefix: 'SCS', sg_url: 'http://x06da-smargopolo.psi.ch:3000'} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: false + softwareTrigger: false +shy: + description: SmarGon Y axis + deviceClass: pxiii_bec.devices.SmarGonAxis + deviceConfig: {prefix: 'SCS', sg_url: 'http://x06da-smargopolo.psi.ch:3000'} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: false + softwareTrigger: false +shz: + description: SmarGon Z axis + deviceClass: pxiii_bec.devices.SmarGonAxis + deviceConfig: {prefix: 'SCS', low_limit: 10, high_limit: 22, sg_url: 'http://x06da-smargopolo.psi.ch:3000'} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: false + softwareTrigger: false +chi: + description: SmarGon CHI axis + deviceClass: pxiii_bec.devices.SmarGonAxis + deviceConfig: {prefix: 'SCS', sg_url: 'http://x06da-smargopolo.psi.ch:3000'} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: false + softwareTrigger: false +phi: + description: SmarGon PHI axis + deviceClass: pxiii_bec.devices.SmarGonAxis + deviceConfig: {prefix: 'SCS', sg_url: 'http://x06da-smargopolo.psi.ch:3000'} + onFailure: buffer + enabled: true + readoutPriority: monitored + readOnly: false + softwareTrigger: false + + samimg: @@ -552,7 +616,7 @@ samimg: deviceClass: ophyd_devices.devices.areadetector.plugins.ImagePlugin_V35 deviceConfig: {prefix: 'X06DA-SAMCAM:image1:'} onFailure: buffer - enabled: true + enabled: false readoutPriority: monitored readOnly: true softwareTrigger: false diff --git a/pxiii_bec/devices/A3200.py b/pxiii_bec/devices/A3200.py index f9958c7..ea67224 100644 --- a/pxiii_bec/devices/A3200.py +++ b/pxiii_bec/devices/A3200.py @@ -140,9 +140,9 @@ class AerotechAbrMixin(CustomDeviceMixin): scan_range_y = scanargs["range"] scan_steps_y = scanargs["steps"] d["scan_command"] = AbrCmd.VERTICAL_LINE_SCAN - d["var_1"] = scan_exp_time - d["var_2"] = scan_range_y - d["var_3"] = scan_steps_y + d["var_1"] = scan_range_y / scan_steps_y + d["var_2"] = scan_steps_y + d["var_3"] = scan_exp_time d["var_4"] = 0 d["var_5"] = 0 d["var_6"] = 0 diff --git a/pxiii_bec/devices/SmarGon.py b/pxiii_bec/devices/SmarGon.py index 0796c36..87f9562 100644 --- a/pxiii_bec/devices/SmarGon.py +++ b/pxiii_bec/devices/SmarGon.py @@ -41,7 +41,7 @@ class SmarGonSignal(Signal): old_value = self._readback self._timestamp = timestamp self._readback = r[self.addr.upper()] - self.value = r[self.addr.upper()] + self._value = r[self.addr.upper()] # Notify subscribers self._run_subs( @@ -65,8 +65,11 @@ class SmarGonSignal(Signal): def get(self, *args, **kwargs): r = self.parent._go_n_get(self.write_addr) - print(r) - self.value = r[self.addr.upper()] + # print(r) + if isinstance(r, dict): + self._value = r[self.addr.upper()] + else: + self._value = r return super().get(*args, **kwargs) @@ -84,8 +87,11 @@ class SmarGonSignalRO(Signal): def get(self, *args, **kwargs): r = self.parent._go_n_get(self.read_addr) - print(r) - self.put(r[self.addr.upper()], force=True) + # print(r) + if isinstance(r, dict): + self.put(r[self.addr.upper()], force=True) + else: + self.put(r, force=True) return super().get(*args, **kwargs) diff --git a/pxiii_bec/devices/__init__.py b/pxiii_bec/devices/__init__.py index d09a6fd..05082c8 100644 --- a/pxiii_bec/devices/__init__.py +++ b/pxiii_bec/devices/__init__.py @@ -6,3 +6,4 @@ Ophyd devices for the PX III beamline, including the MX specific Aerotech A3200 """ from .A3200 import AerotechAbrStage from .A3200utils import A3200Axis +from .SmarGon import SmarGonAxis -- 2.49.1