diff --git a/src/dodal/beamlines/i10.py b/src/dodal/beamlines/i10.py index 56b69cae6b..c6ea29c068 100644 --- a/src/dodal/beamlines/i10.py +++ b/src/dodal/beamlines/i10.py @@ -8,6 +8,7 @@ UndulatorPhaseAxes, ) from dodal.devices.current_amplifiers import CurrentAmpDet +from dodal.devices.i10.diagnostics import I10Diagnostic, I10Diagnotic5ADet from dodal.devices.i10.i10_apple2 import ( I10Apple2, I10Apple2PGM, @@ -24,10 +25,9 @@ PinHole, ) from dodal.devices.i10.rasor.rasor_scaler_cards import RasorScalerCard1 -from dodal.devices.i10.slits import I10PrimarySlits, I10Slits +from dodal.devices.i10.slits import I10Slits, I10SlitsDrainCurrent from dodal.devices.motors import XYZPositioner from dodal.devices.pgm import PGM -from dodal.devices.slits import MinimalSlits from dodal.log import set_beamline as set_log_beamline from dodal.utils import BeamlinePrefix, get_beamline_name @@ -272,6 +272,9 @@ def idd_la_angle( ) +"""Mirrors""" + + @device_factory() def first_mirror() -> PiezoMirror: return PiezoMirror(prefix=f"{PREFIX.beamline_prefix}-OP-COL-01:") @@ -283,58 +286,39 @@ def switching_mirror() -> PiezoMirror: @device_factory() -def slit_1() -> I10PrimarySlits: - return I10PrimarySlits( - prefix=f"{PREFIX.beamline_prefix}-AL-SLITS-01:", - ) +def focusing_mirror() -> PiezoMirror: + return PiezoMirror(prefix=f"{PREFIX.beamline_prefix}-OP-FOCS-01:") -@device_factory() -def slit_2() -> I10Slits: - return I10Slits( - prefix=f"{PREFIX.beamline_prefix}-AL-SLITS-02:", - ) +"""Optic slits""" @device_factory() -def slit_3() -> I10Slits: - return I10Slits( - prefix=f"{PREFIX.beamline_prefix}-AL-SLITS-03:", - ) - - -"""Rasor devices""" +def slits() -> I10Slits: + return I10Slits(prefix=f"{PREFIX.beamline_prefix}-AL-SLITS-") @device_factory() -def focusing_mirror() -> PiezoMirror: - return PiezoMirror(prefix=f"{PREFIX.beamline_prefix}-OP-FOCS-01:") +def slits_current() -> I10SlitsDrainCurrent: + return I10SlitsDrainCurrent(prefix=f"{PREFIX.beamline_prefix}-") -@device_factory() -def slit_4() -> MinimalSlits: - return MinimalSlits( - prefix=f"{PREFIX.beamline_prefix}-AL-SLITS-04:", - x_gap="XSIZE", - y_gap="YSIZE", - ) +"""Diagnostics""" @device_factory() -def slit_5() -> I10Slits: - return I10Slits( - prefix=f"{PREFIX.beamline_prefix}-AL-SLITS-05:", +def diagnostics() -> I10Diagnostic: + return I10Diagnostic( + prefix=f"{PREFIX.beamline_prefix}-DI-", ) @device_factory() -def slit_6() -> I10Slits: - return I10Slits( - prefix=f"{PREFIX.beamline_prefix}-AL-SLITS-06:", - ) +def d5a_det() -> I10Diagnotic5ADet: + return I10Diagnotic5ADet(prefix=f"{PREFIX.beamline_prefix}-DI-") -"Rasor devices" +"""Rasor devices""" @device_factory() diff --git a/src/dodal/devices/i10/diagnostics.py b/src/dodal/devices/i10/diagnostics.py new file mode 100644 index 0000000000..d0f96db4ff --- /dev/null +++ b/src/dodal/devices/i10/diagnostics.py @@ -0,0 +1,260 @@ +from bluesky.protocols import Movable +from ophyd_async.core import ( + AsyncStatus, + Device, + StandardReadable, + StrictEnum, +) +from ophyd_async.core import StandardReadableFormat as Format +from ophyd_async.core._device import DeviceConnector +from ophyd_async.epics.adaravis import AravisDriverIO +from ophyd_async.epics.adcore import SingleTriggerDetector +from ophyd_async.epics.core import ( + epics_signal_r, + epics_signal_rw, +) +from ophyd_async.epics.motor import Motor + +from dodal.devices.current_amplifiers import ( + CurrentAmpDet, + Femto3xxGainTable, + Femto3xxGainToCurrentTable, + Femto3xxRaiseTime, + FemtoDDPCA, + StruckScaler, +) + + +class D3DropDown(StrictEnum): + NOTHING = "Nothing" + GRID = "Grid" + + +class D5DropDown(StrictEnum): + CELL_IN = "Cell In" + CELL_OUT = "Cell Out" + + +class D5ADropDown(StrictEnum): + OUT_OF_THE_BEAM = "Out of the beam" + DIODE = "Diode" + BLADE = "Blade" + LA = "La ref" + GD = "Gd ref" + YB = "Yb ref" + GRID = "Grid" + + +class D6DropDown(StrictEnum): + DIODE_OUT = "Diode Out" + DIODE_IN = "Diode In" + AU_MESH = "Au Mesh" + + +class D7DropDown(StrictEnum): + OUT = "Out" + SHUTTER = "Shutter" + + +class InOutTable(StrictEnum): + MOVE_IN = "Move In" + MOVE_OUT = "Move Out" + RESET = "Reset" + + +class InOutReadBackTable(StrictEnum): + MOVE_IN = "Moving In" + MOVE_OUT = "Moving Out" + IN_BEAM = "In Beam" + FAULT = "Fault" + OUT_OF_BEAM = "Out of Beam" + + +class I10WebCamIODataType(StrictEnum): + UINT8 = "UInt8" + INT16 = "UInt16" + + +class DropDownStage(StandardReadable, Movable): + """1D stage with a enum table to select positions.""" + + def __init__( + self, + prefix: str, + positioner_enum: type[StrictEnum], + positioner_suffix: str = "", + dropdown_pv_suffix: str = ":MP:SELECT", + name: str = "", + ) -> None: + self._stage_motion = Motor(prefix=prefix + positioner_suffix) + with self.add_children_as_readables(Format.CONFIG_SIGNAL): + self.stage_drop_down = epics_signal_rw( + positioner_enum, read_pv=prefix + positioner_suffix + dropdown_pv_suffix + ) + super().__init__(name=name) + self.positioner_enum = positioner_enum + + @AsyncStatus.wrap + async def set(self, value: StrictEnum) -> None: + if value in self.positioner_enum: + await self.stage_drop_down.set(value=value) + else: + raise ValueError( + f"{value} is not an allow position. Position must be: {self.positioner_enum}" + ) + + +class I10PneumaticStage(StandardReadable): + """Pneumatic stage only has two real positions in or out. + Use for fluorescent screen which can be insert into the x-ray beam. + Most often use in conjunction with a webcam to locate the x-ray beam.""" + + def __init__( + self, + prefix: str, + name: str = "", + ) -> None: + with self.add_children_as_readables(Format.HINTED_SIGNAL): + self.stage_drop_down_set = epics_signal_rw( + InOutTable, + read_pv=prefix + "CON", + ) + self.stage_drop_down_readback = epics_signal_r( + InOutReadBackTable, + read_pv=prefix + "STA", + ) + super().__init__(name=name) + + +class I10AravisDriverIO(AravisDriverIO): + """This is the standard webcam that can be found in ophyd_async + with four extra centroid signal. There is also a change in data_type due to it being + an older/different model""" + + def __init__( + self, + prefix: str, + cam_infix: str = "CAM:", + name: str = "", + ) -> None: + super().__init__(prefix + cam_infix, name) + + # data type correction for i10 model. + self.data_type = epics_signal_r( + I10WebCamIODataType, prefix + cam_infix + "DataType_RBV" + ) + + +class ScreenCam(Device): + """Compound device of pneumatic stage(fluorescent screen) and webcam""" + + def __init__( + self, + prefix: str, + cam_infix="DCAM:", + name: str = "", + ) -> None: + self.screen_stage = I10PneumaticStage( + prefix=prefix, + ) + cam_pv = prefix = prefix + cam_infix + self.single_trigger_centroid = SingleTriggerDetector( + drv=I10AravisDriverIO(prefix=cam_pv), + read_uncached=[ + epics_signal_r(float, read_pv=f"{cam_pv}CentroidX_RBV"), + epics_signal_r(float, read_pv=f"{cam_pv}CentroidY_RBV"), + ], + ) + super().__init__(name=name) + + +class FullDiagnostic(Device): + """Compound device of a diagnostic with screen, webcam and dropdown stage.""" + + def __init__( + self, + prefix: str, + positioner_enum: type[StrictEnum], + positioner_suffix: str = "", + dropdown_pv_suffix: str = ":MP:SELECT", + cam_infix: str = "DCAM:", + name: str = "", + ) -> None: + self.positioner = DropDownStage( + prefix=prefix, + positioner_enum=positioner_enum, + positioner_suffix=positioner_suffix, + dropdown_pv_suffix=dropdown_pv_suffix, + ) + self.screen = ScreenCam( + prefix, + cam_infix, + name, + ) + super().__init__(name) + + +class I10Diagnostic(Device): + """Collection of all the diagnostic stage on i10.""" + + def __init__(self, prefix, name: str = "") -> None: + self.d1 = ScreenCam(prefix=prefix + "PHDGN-01:") + self.d2 = ScreenCam(prefix=prefix + "PHDGN-02:") + self.d3 = FullDiagnostic( + prefix=prefix + "PHDGN-03:", + positioner_enum=D3DropDown, + positioner_suffix="DET:X", + ) + self.d4 = ScreenCam(prefix=prefix + "PHDGN-04:") + self.d5 = DropDownStage( + prefix=prefix + "IONC-01:", + positioner_enum=D5DropDown, + positioner_suffix="Y", + ) + + self.d5A = DropDownStage( + prefix=prefix + "PHDGN-06:", + positioner_enum=D5ADropDown, + positioner_suffix="DET:X", + ) + + self.d6 = FullDiagnostic( + prefix=prefix + "PHDGN-05:", + positioner_enum=D6DropDown, + positioner_suffix="DET:X", + ) + self.d7 = DropDownStage( + prefix=prefix + "PHDGN-07:", + positioner_enum=D7DropDown, + positioner_suffix="Y", + ) + super().__init__(name) + + +class I10Diagnotic5ADet(Device): + """Diagnotic 5a detection with drain current and photo diode""" + + def __init__( + self, prefix: str, name: str = "", connector: DeviceConnector | None = None + ) -> None: + self.drain_current = CurrentAmpDet( + current_amp=FemtoDDPCA( + prefix=prefix + "IAMP-06:", + suffix="GAIN", + gain_table=Femto3xxGainTable, + gain_to_current_table=Femto3xxGainToCurrentTable, + raise_timetable=Femto3xxRaiseTime, + ), + counter=StruckScaler(prefix=prefix + "SCLR-02:SCALER2", suffix=".S17"), + ) + self.diode = CurrentAmpDet( + FemtoDDPCA( + prefix=prefix + "IAMP-05:", + suffix="GAIN", + gain_table=Femto3xxGainTable, + gain_to_current_table=Femto3xxGainToCurrentTable, + raise_timetable=Femto3xxRaiseTime, + ), + counter=StruckScaler(prefix=prefix + "SCLR-02:SCALER2", suffix=".S18"), + ) + super().__init__(name, connector) diff --git a/src/dodal/devices/i10/slits.py b/src/dodal/devices/i10/slits.py index 56d4a97f56..017822fb2d 100644 --- a/src/dodal/devices/i10/slits.py +++ b/src/dodal/devices/i10/slits.py @@ -1,15 +1,21 @@ +from ophyd_async.core import Device +from ophyd_async.core._device import DeviceConnector +from ophyd_async.epics.core import epics_signal_r from ophyd_async.epics.motor import Motor -from dodal.devices.slits import Slits +from dodal.devices.slits import MinimalSlits, Slits -class I10Slits(Slits): +class I10SlitsBlades(Slits): + """Slits with extra control for each blade.""" + def __init__(self, prefix: str, name: str = "") -> None: with self.add_children_as_readables(): - self.x_ring_blade = Motor(prefix + "XRING") - self.x_hall_blade = Motor(prefix + "XHALL") - self.y_top_blade = Motor(prefix + "YPLUS") - self.y_bot_blade = Motor(prefix + "YMINUS") + self.ring_blade = Motor(prefix + "XRING") + self.hall_blade = Motor(prefix + "XHALL") + self.top_blade = Motor(prefix + "YPLUS") + self.bot_blade = Motor(prefix + "YMINUS") + super().__init__( prefix=prefix, x_gap="XSIZE", @@ -20,7 +26,40 @@ def __init__(self, prefix: str, name: str = "") -> None: ) +class BladeDrainCurrents(Device): + """ "The drain current measurements on each blade. The drain current are due to + photoelectric effect (https://en.wikipedia.org/wiki/Photoelectric_effect). + Note the readings are in voltage as it is the output of a current amplifier.""" + + def __init__( + self, + prefix: str, + suffix_ring_blade: str = "SIG1", + suffix_hall_blade: str = "SIG2", + suffix_top_blade: str = "SIG3", + suffix_bot_blade: str = "SIG4", + name: str = "", + connector: DeviceConnector | None = None, + ) -> None: + self.ring_blade_current = epics_signal_r( + float, read_pv=prefix + suffix_ring_blade + ) + self.hall_blade_current = epics_signal_r( + float, read_pv=prefix + suffix_hall_blade + ) + self.top_blade_current = epics_signal_r( + float, read_pv=prefix + suffix_top_blade + ) + self.bot_blade_current = epics_signal_r( + float, read_pv=prefix + suffix_bot_blade + ) + + super().__init__(name, connector) + + class I10PrimarySlits(Slits): + """First slits of the beamline with very high power load.""" + def __init__(self, prefix: str, name: str = "") -> None: with self.add_children_as_readables(): self.x_aptr_1 = Motor(prefix + "APTR1:X") @@ -35,3 +74,50 @@ def __init__(self, prefix: str, name: str = "") -> None: y_centre="YCENTRE", name=name, ) + + +class I10Slits(Device): + """Collection of all the i10 slits before end station.""" + + def __init__(self, prefix: str, name: str = "") -> None: + self.s1 = I10PrimarySlits( + prefix=prefix + "01:", + ) + self.s2 = I10SlitsBlades( + prefix=prefix + "02:", + ) + self.s3 = I10SlitsBlades( + prefix=prefix + "03:", + ) + self.s4 = MinimalSlits( + prefix=prefix + "04:", + x_gap="XSIZE", + y_gap="YSIZE", + ) + self.s5 = I10SlitsBlades( + prefix=prefix + "05:", + ) + self.s6 = I10SlitsBlades( + prefix=prefix + "06:", + ) + super().__init__(name=name) + + +class I10SlitsDrainCurrent(Device): + """Collection of all the drain current from i10 slits.""" + + def __init__( + self, prefix: str, name: str = "", connector: DeviceConnector | None = None + ) -> None: + self.s2 = BladeDrainCurrents( + prefix=prefix + "AL-SLITS-02:", + suffix_ring_blade="XRING:I", + suffix_hall_blade="XHALL:I", + suffix_top_blade="YPLUS:I", + suffix_bot_blade="YMINUS:I", + ) + self.s3 = BladeDrainCurrents(prefix=prefix + "DI-IAMP-01:") + self.s4 = BladeDrainCurrents(prefix=prefix + "DI-IAMP-02:") + self.s5 = BladeDrainCurrents(prefix=prefix + "DI-IAMP-03:") + self.s6 = BladeDrainCurrents(prefix=prefix + "DI-IAMP-04:") + super().__init__(name, connector)