diff --git a/src/dodal/beamlines/b07.py b/src/dodal/beamlines/b07.py index 87cdf54b7b..e2b047c2fa 100644 --- a/src/dodal/beamlines/b07.py +++ b/src/dodal/beamlines/b07.py @@ -2,13 +2,11 @@ from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline from dodal.device_manager import DeviceManager from dodal.devices.beamlines.b07 import ( + B07BSpecs150, B07SampleManipulator52B, Grating, - LensMode, - PsuMode, ) from dodal.devices.electron_analyser.base import EnergySource -from dodal.devices.electron_analyser.specs import SpecsDetector from dodal.devices.motors import XYZPolarStage from dodal.devices.pgm import PlaneGratingMonochromator from dodal.log import set_beamline as set_log_beamline @@ -39,13 +37,8 @@ def energy_source(pgm: PlaneGratingMonochromator) -> EnergySource: # CAM:IMAGE will fail to connect outside the beamline network, # see https://github.com/DiamondLightSource/dodal/issues/1852 @devices.factory() -def analyser(energy_source: EnergySource) -> SpecsDetector[LensMode, PsuMode]: - return SpecsDetector[LensMode, PsuMode]( - prefix=f"{B_PREFIX.beamline_prefix}-EA-DET-01:CAM:", - lens_mode_type=LensMode, - psu_mode_type=PsuMode, - energy_source=energy_source, - ) +def analyser(energy_source: EnergySource) -> B07BSpecs150: + return B07BSpecs150(f"{B_PREFIX.beamline_prefix}-EA-DET-01:CAM:", energy_source) @devices.factory() diff --git a/src/dodal/beamlines/b07_1.py b/src/dodal/beamlines/b07_1.py index b2c03102f4..a5c76ae93e 100644 --- a/src/dodal/beamlines/b07_1.py +++ b/src/dodal/beamlines/b07_1.py @@ -1,14 +1,12 @@ from dodal.beamlines.b07_shared import devices as b07_shared_devices from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline from dodal.device_manager import DeviceManager -from dodal.devices.beamlines.b07 import PsuMode from dodal.devices.beamlines.b07_1 import ( + B07CSpecs150, ChannelCutMonochromator, Grating, - LensMode, ) from dodal.devices.electron_analyser.base import EnergySource -from dodal.devices.electron_analyser.specs import SpecsDetector from dodal.devices.motors import XYZPolarAzimuthStage from dodal.devices.pgm import PlaneGratingMonochromator from dodal.log import set_beamline as set_log_beamline @@ -44,11 +42,9 @@ def energy_source(pgm: PlaneGratingMonochromator) -> EnergySource: # CAM:IMAGE will fail to connect outside the beamline network, # see https://github.com/DiamondLightSource/dodal/issues/1852 @devices.factory() -def analyser(energy_source: EnergySource) -> SpecsDetector[LensMode, PsuMode]: - return SpecsDetector[LensMode, PsuMode]( +def analyser(energy_source: EnergySource) -> B07CSpecs150: + return B07CSpecs150( prefix=f"{C_PREFIX.beamline_prefix}-EA-DET-01:CAM:", - lens_mode_type=LensMode, - psu_mode_type=PsuMode, energy_source=energy_source, ) diff --git a/src/dodal/beamlines/i09.py b/src/dodal/beamlines/i09.py index 253c5e62c2..792317ede9 100644 --- a/src/dodal/beamlines/i09.py +++ b/src/dodal/beamlines/i09.py @@ -4,10 +4,9 @@ from dodal.beamlines.i09_2_shared import devices as i09_2_shared_devices from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline from dodal.device_manager import DeviceManager -from dodal.devices.beamlines.i09.enums import LensMode, PassEnergy, PsuMode +from dodal.devices.beamlines.i09 import I09VGScientaEW4000 from dodal.devices.common_dcm import DoubleCrystalMonochromatorWithDSpacing from dodal.devices.electron_analyser.base import DualEnergySource -from dodal.devices.electron_analyser.vgscienta import VGScientaDetector from dodal.devices.fast_shutter import DualFastShutter, GenericFastShutter from dodal.devices.motors import XYZPolarAzimuthStage from dodal.devices.pgm import PlaneGratingMonochromator @@ -80,18 +79,15 @@ def dual_fast_shutter( # see https://github.com/DiamondLightSource/dodal/issues/1852 @devices.factory() def ew4000( - dual_fast_shutter: DualFastShutter, dual_energy_source: DualEnergySource, + dual_fast_shutter: DualFastShutter, source_selector: SourceSelector, -) -> VGScientaDetector[LensMode, PsuMode, PassEnergy]: - return VGScientaDetector[LensMode, PsuMode, PassEnergy]( - prefix=f"{I_PREFIX.beamline_prefix}-EA-DET-01:CAM:", - lens_mode_type=LensMode, - psu_mode_type=PsuMode, - pass_energy_type=PassEnergy, - energy_source=dual_energy_source, - shutter=dual_fast_shutter, - source_selector=source_selector, +) -> I09VGScientaEW4000: + return I09VGScientaEW4000( + f"{I_PREFIX.beamline_prefix}-EA-DET-01:CAM:", + dual_energy_source, + dual_fast_shutter, + source_selector, ) diff --git a/src/dodal/beamlines/i09_1.py b/src/dodal/beamlines/i09_1.py index 769909f5ee..d42cc7fddd 100644 --- a/src/dodal/beamlines/i09_1.py +++ b/src/dodal/beamlines/i09_1.py @@ -1,10 +1,9 @@ from dodal.beamlines.i09_1_shared import devices as i09_1_shared_devices from dodal.common.beamlines.beamline_utils import set_beamline as set_utils_beamline from dodal.device_manager import DeviceManager -from dodal.devices.beamlines.i09_1 import LensMode, PsuMode +from dodal.devices.beamlines.i09_1 import I091SpecsPhoibos225 from dodal.devices.common_dcm import DoubleCrystalMonochromatorWithDSpacing from dodal.devices.electron_analyser.base import EnergySource -from dodal.devices.electron_analyser.specs import SpecsDetector from dodal.devices.motors import XYZPolarAzimuthTiltStage from dodal.devices.synchrotron import Synchrotron from dodal.devices.temperture_controller import Lakeshore336 @@ -33,12 +32,9 @@ def energy_source(dcm: DoubleCrystalMonochromatorWithDSpacing) -> EnergySource: # CAM:IMAGE will fail to connect outside the beamline network, # see https://github.com/DiamondLightSource/dodal/issues/1852 @devices.factory() -def analyser(energy_source: EnergySource) -> SpecsDetector[LensMode, PsuMode]: - return SpecsDetector[LensMode, PsuMode]( - prefix=f"{PREFIX.beamline_prefix}-EA-DET-02:CAM:", - lens_mode_type=LensMode, - psu_mode_type=PsuMode, - energy_source=energy_source, +def analyser(energy_source: EnergySource) -> I091SpecsPhoibos225: + return I091SpecsPhoibos225( + f"{PREFIX.beamline_prefix}-EA-DET-02:CAM:", energy_source ) diff --git a/src/dodal/beamlines/p60.py b/src/dodal/beamlines/p60.py index d0a35b881f..76b0318bb7 100644 --- a/src/dodal/beamlines/p60.py +++ b/src/dodal/beamlines/p60.py @@ -3,12 +3,9 @@ from dodal.devices.beamlines.p60 import ( LabXraySource, LabXraySourceReadable, - LensMode, - PassEnergy, - PsuMode, + P60VGScientaR4000, ) from dodal.devices.electron_analyser.base import DualEnergySource -from dodal.devices.electron_analyser.vgscienta import VGScientaDetector from dodal.devices.selectable_source import SourceSelector from dodal.log import set_beamline as set_log_beamline from dodal.utils import BeamlinePrefix, get_beamline_name @@ -18,13 +15,13 @@ be on the beamline network to access them so a remote `dodal connect p60` will fail. """ -devices = DeviceManager() - BL = get_beamline_name("p60") PREFIX = BeamlinePrefix(BL) set_log_beamline(BL) set_utils_beamline(BL) +devices = DeviceManager() + @devices.factory() def source_selector() -> SourceSelector: @@ -42,7 +39,7 @@ def mg_kalpha_source() -> LabXraySourceReadable: @devices.factory() -def energy_source( +def dual_energy_source( al_kalpha_source: LabXraySourceReadable, mg_kalpha_source: LabXraySourceReadable, source_selector: SourceSelector, @@ -57,13 +54,7 @@ def energy_source( # Connect will work again after this work completed # https://jira.diamond.ac.uk/browse/P60-13 @devices.factory() -def r4000( - energy_source: DualEnergySource, -) -> VGScientaDetector[LensMode, PsuMode, PassEnergy]: - return VGScientaDetector[LensMode, PsuMode, PassEnergy]( - prefix=f"{PREFIX.beamline_prefix}-EA-DET-01:CAM:", - lens_mode_type=LensMode, - psu_mode_type=PsuMode, - pass_energy_type=PassEnergy, - energy_source=energy_source, +def r4000(dual_energy_source: DualEnergySource) -> P60VGScientaR4000: + return P60VGScientaR4000( + f"{PREFIX.beamline_prefix}-EA-DET-01:CAM:", dual_energy_source ) diff --git a/src/dodal/devices/beamlines/b07/__init__.py b/src/dodal/devices/beamlines/b07/__init__.py index cf3fe955bb..3f4f53514c 100644 --- a/src/dodal/devices/beamlines/b07/__init__.py +++ b/src/dodal/devices/beamlines/b07/__init__.py @@ -1,4 +1,20 @@ +from .analyser import ( + B07BElectronAnalyserController, + B07BSpecs150, + B07BSpecsAnalyserDriverIO, + B07BSpecsRegion, + B07BSpecsSequence, +) from .b07_motors import B07SampleManipulator52B -from .enums import Grating, LensMode, PsuMode +from .enums import Grating, LensMode -__all__ = ["B07SampleManipulator52B", "Grating", "LensMode", "PsuMode"] +__all__ = [ + "B07BElectronAnalyserController", + "B07BSpecsAnalyserDriverIO", + "B07BSpecsRegion", + "B07BSpecsSequence", + "B07BSpecs150", + "B07SampleManipulator52B", + "Grating", + "LensMode", +] diff --git a/src/dodal/devices/beamlines/b07/analyser.py b/src/dodal/devices/beamlines/b07/analyser.py new file mode 100644 index 0000000000..93dbc5457f --- /dev/null +++ b/src/dodal/devices/beamlines/b07/analyser.py @@ -0,0 +1,42 @@ +from dodal.devices.beamlines.b07.enums import LensMode +from dodal.devices.beamlines.b07_shared.enums import PsuMode +from dodal.devices.electron_analyser.base.base_controller import ( + ElectronAnalyserController, +) +from dodal.devices.electron_analyser.base.base_detector import ElectronAnalyserDetector +from dodal.devices.electron_analyser.base.energy_sources import EnergySource +from dodal.devices.electron_analyser.specs import ( + SpecsAnalyserDriverIO, + SpecsRegion, + SpecsSequence, +) +from dodal.devices.fast_shutter import FastShutter + +B07BSpecsRegion = SpecsRegion[LensMode, PsuMode] +B07BSpecsSequence = SpecsSequence[LensMode, PsuMode] + + +class B07BSpecsAnalyserDriverIO(SpecsAnalyserDriverIO): + def __init__(self, prefix: str, name: str = ""): + super().__init__(prefix, LensMode, PsuMode, name) + + +B07BElectronAnalyserController = ElectronAnalyserController[ + B07BSpecsAnalyserDriverIO, B07BSpecsRegion +] + + +class B07BSpecs150( + ElectronAnalyserDetector[B07BSpecsAnalyserDriverIO, B07BSpecsRegion] +): + def __init__( + self, + prefix: str, + energy_source: EnergySource, + shutter: FastShutter | None = None, + name: str = "", + ): + controller = B07BElectronAnalyserController( + B07BSpecsAnalyserDriverIO(prefix), energy_source, shutter + ) + super().__init__(controller, name) diff --git a/src/dodal/devices/beamlines/b07/enums.py b/src/dodal/devices/beamlines/b07/enums.py index 58c826bb37..2e27db999d 100644 --- a/src/dodal/devices/beamlines/b07/enums.py +++ b/src/dodal/devices/beamlines/b07/enums.py @@ -25,15 +25,3 @@ class LensMode(SupersetEnum): # option if disconnected. Once it is connected, "Not connected" is replaced with the # options above. This is also why this must be a SupersetEnum. NOT_CONNECTED = "Not connected" - - -class PsuMode(SupersetEnum): - V3500 = "3.5kV" - V1500 = "1.5kV" - V400 = "400V" - V100 = "100V" - V10 = "10V" - # This is connected to the device separately and will only have "Not connected" as - # option if disconnected. Once it is connected, "Not connected" is replaced with the - # options above. This is also why this must be a SupersetEnum. - NOT_CONNECTED = "Not connected" diff --git a/src/dodal/devices/beamlines/b07_1/__init__.py b/src/dodal/devices/beamlines/b07_1/__init__.py index 9ac266043a..72fac821b2 100644 --- a/src/dodal/devices/beamlines/b07_1/__init__.py +++ b/src/dodal/devices/beamlines/b07_1/__init__.py @@ -1,10 +1,22 @@ -from dodal.devices.beamlines.b07_1.ccmc import ( +from .analyser import ( + B07CElectronAnalyserController, + B07CSpecs150, + B07CSpecsAnalyserDriverIO, + B07CSpecsRegion, + B07CSpecsSequence, +) +from .ccmc import ( ChannelCutMonochromator, ChannelCutMonochromatorPositions, ) -from dodal.devices.beamlines.b07_1.enums import Grating, LensMode +from .enums import Grating, LensMode __all__ = [ + "B07CElectronAnalyserController", + "B07CSpecsAnalyserDriverIO", + "B07CSpecsRegion", + "B07CSpecsSequence", + "B07CSpecs150", "Grating", "LensMode", "ChannelCutMonochromator", diff --git a/src/dodal/devices/beamlines/b07_1/analyser.py b/src/dodal/devices/beamlines/b07_1/analyser.py new file mode 100644 index 0000000000..bf36c0b9e8 --- /dev/null +++ b/src/dodal/devices/beamlines/b07_1/analyser.py @@ -0,0 +1,42 @@ +from dodal.devices.beamlines.b07_1.enums import LensMode +from dodal.devices.beamlines.b07_shared.enums import PsuMode +from dodal.devices.electron_analyser.base.base_controller import ( + ElectronAnalyserController, +) +from dodal.devices.electron_analyser.base.base_detector import ElectronAnalyserDetector +from dodal.devices.electron_analyser.base.energy_sources import EnergySource +from dodal.devices.electron_analyser.specs import ( + SpecsAnalyserDriverIO, + SpecsRegion, + SpecsSequence, +) +from dodal.devices.fast_shutter import FastShutter + +B07CSpecsRegion = SpecsRegion[LensMode, PsuMode] +B07CSpecsSequence = SpecsSequence[LensMode, PsuMode] + + +class B07CSpecsAnalyserDriverIO(SpecsAnalyserDriverIO): + def __init__(self, prefix: str, name: str = ""): + super().__init__(prefix, LensMode, PsuMode, name) + + +B07CElectronAnalyserController = ElectronAnalyserController[ + B07CSpecsAnalyserDriverIO, B07CSpecsRegion +] + + +class B07CSpecs150( + ElectronAnalyserDetector[B07CSpecsAnalyserDriverIO, B07CSpecsRegion] +): + def __init__( + self, + prefix: str, + energy_source: EnergySource, + shutter: FastShutter | None = None, + name: str = "", + ): + controller = B07CElectronAnalyserController( + B07CSpecsAnalyserDriverIO(prefix), energy_source, shutter + ) + super().__init__(controller, name) diff --git a/src/dodal/devices/beamlines/b07_shared/__init__.py b/src/dodal/devices/beamlines/b07_shared/__init__.py new file mode 100644 index 0000000000..9fa24dbb0f --- /dev/null +++ b/src/dodal/devices/beamlines/b07_shared/__init__.py @@ -0,0 +1,3 @@ +from .enums import PsuMode + +__all__ = ["PsuMode"] diff --git a/src/dodal/devices/beamlines/b07_shared/enums.py b/src/dodal/devices/beamlines/b07_shared/enums.py new file mode 100644 index 0000000000..0c6bbba3a8 --- /dev/null +++ b/src/dodal/devices/beamlines/b07_shared/enums.py @@ -0,0 +1,13 @@ +from ophyd_async.core import SupersetEnum + + +class PsuMode(SupersetEnum): + V3500 = "3.5kV" + V1500 = "1.5kV" + V400 = "400V" + V100 = "100V" + V10 = "10V" + # This is connected to the device separately and will only have "Not connected" as + # option if disconnected. Once it is connected, "Not connected" is replaced with the + # options above. This is also why this must be a SupersetEnum. + NOT_CONNECTED = "Not connected" diff --git a/src/dodal/devices/beamlines/i09/__init__.py b/src/dodal/devices/beamlines/i09/__init__.py index cfb2a43d07..458ffdd079 100644 --- a/src/dodal/devices/beamlines/i09/__init__.py +++ b/src/dodal/devices/beamlines/i09/__init__.py @@ -1,3 +1,20 @@ -from dodal.devices.beamlines.i09.enums import Grating, LensMode, PassEnergy, PsuMode +from .analyser import ( + I09ElectronAnalyserController, + I09VGScientaAnalyserDriverIO, + I09VGScientaEW4000, + I09VGScientaRegion, + I09VGScientaSequence, +) +from .enums import Grating, LensMode, PassEnergy, PsuMode -__all__ = ["Grating", "LensMode", "PsuMode", "PassEnergy"] +__all__ = [ + "I09ElectronAnalyserController", + "I09VGScientaAnalyserDriverIO", + "I09VGScientaEW4000", + "I09VGScientaRegion", + "I09VGScientaSequence", + "Grating", + "LensMode", + "PsuMode", + "PassEnergy", +] diff --git a/src/dodal/devices/beamlines/i09/analyser.py b/src/dodal/devices/beamlines/i09/analyser.py new file mode 100644 index 0000000000..748933c24d --- /dev/null +++ b/src/dodal/devices/beamlines/i09/analyser.py @@ -0,0 +1,60 @@ +from dodal.devices.beamlines.i09.enums import LensMode, PassEnergy, PsuMode +from dodal.devices.electron_analyser.base import DualEnergySource +from dodal.devices.electron_analyser.base.base_controller import ( + ElectronAnalyserController, +) +from dodal.devices.electron_analyser.base.base_detector import ElectronAnalyserDetector +from dodal.devices.electron_analyser.vgscienta import ( + VGScientaAnalyserDriverIO, + VGScientaRegion, + VGScientaSequence, +) +from dodal.devices.electron_analyser.vgscienta.vgscienta_driver_io import ( + VGScientaAnalyserDriverIO, +) +from dodal.devices.electron_analyser.vgscienta.vgscienta_region import ( + VGScientaRegion, + VGScientaSequence, +) +from dodal.devices.fast_shutter import DualFastShutter +from dodal.devices.selectable_source import SourceSelector + +I09VGScientaRegion = VGScientaRegion[LensMode, PassEnergy] +I09VGScientaSequence = VGScientaSequence[LensMode, PsuMode, PassEnergy] + + +class I09VGScientaAnalyserDriverIO( + VGScientaAnalyserDriverIO[LensMode, PsuMode, PassEnergy] +): + def __init__(self, prefix: str, name: str = ""): + super().__init__(prefix, LensMode, PsuMode, PassEnergy, "ELEMENT_SET", name) + + +I09ElectronAnalyserController = ElectronAnalyserController[ + I09VGScientaAnalyserDriverIO, I09VGScientaRegion +] + + +class I09VGScientaEW4000( + ElectronAnalyserDetector[I09VGScientaAnalyserDriverIO, I09VGScientaRegion] +): + """Implementation of VGScienta Electron Analyser. This model is unique for i09 + beamline because it has access to multiple energy sources and shutters. The selected + source is deterimined by the source_selector device. + """ + + def __init__( + self, + prefix: str, + dual_energy_source: DualEnergySource, + dual_fast_shutter: DualFastShutter, + source_selector: SourceSelector, + name: str = "", + ): + controller = I09ElectronAnalyserController( + I09VGScientaAnalyserDriverIO(prefix), + dual_energy_source, + dual_fast_shutter, + source_selector, + ) + super().__init__(controller, name) diff --git a/src/dodal/devices/beamlines/i09_1/__init__.py b/src/dodal/devices/beamlines/i09_1/__init__.py index b54345b114..fc3d25d176 100644 --- a/src/dodal/devices/beamlines/i09_1/__init__.py +++ b/src/dodal/devices/beamlines/i09_1/__init__.py @@ -1,3 +1,18 @@ +from .analyser import ( + I091ElectronAnalyserController, + I091SpecsAnalyserDriverIO, + I091SpecsPhoibos225, + I091SpecsRegion, + I091SpecsSequence, +) from .enums import LensMode, PsuMode -__all__ = ["LensMode", "PsuMode"] +__all__ = [ + "I091ElectronAnalyserController", + "I091SpecsAnalyserDriverIO", + "I091SpecsPhoibos225", + "I091SpecsRegion", + "I091SpecsSequence", + "LensMode", + "PsuMode", +] diff --git a/src/dodal/devices/beamlines/i09_1/analyser.py b/src/dodal/devices/beamlines/i09_1/analyser.py new file mode 100644 index 0000000000..bc7af96ae8 --- /dev/null +++ b/src/dodal/devices/beamlines/i09_1/analyser.py @@ -0,0 +1,41 @@ +from dodal.devices.beamlines.i09_1.enums import LensMode, PsuMode +from dodal.devices.electron_analyser.base.base_controller import ( + ElectronAnalyserController, +) +from dodal.devices.electron_analyser.base.base_detector import ElectronAnalyserDetector +from dodal.devices.electron_analyser.base.energy_sources import EnergySource +from dodal.devices.electron_analyser.specs import ( + SpecsAnalyserDriverIO, + SpecsRegion, + SpecsSequence, +) +from dodal.devices.fast_shutter import FastShutter + +I091SpecsRegion = SpecsRegion[LensMode, PsuMode] +I091SpecsSequence = SpecsSequence[LensMode, PsuMode] + + +class I091SpecsAnalyserDriverIO(SpecsAnalyserDriverIO): + def __init__(self, prefix: str, name: str = ""): + super().__init__(prefix, LensMode, PsuMode, name) + + +I091ElectronAnalyserController = ElectronAnalyserController[ + I091SpecsAnalyserDriverIO, I091SpecsRegion +] + + +class I091SpecsPhoibos225( + ElectronAnalyserDetector[I091SpecsAnalyserDriverIO, I091SpecsRegion] +): + def __init__( + self, + prefix: str, + energy_source: EnergySource, + shutter: FastShutter | None = None, + name: str = "", + ): + controller = I091ElectronAnalyserController( + I091SpecsAnalyserDriverIO(prefix), energy_source, shutter + ) + super().__init__(controller, name) diff --git a/src/dodal/devices/beamlines/p60/__init__.py b/src/dodal/devices/beamlines/p60/__init__.py index 6a25e7d0ba..0ccdeeaa15 100644 --- a/src/dodal/devices/beamlines/p60/__init__.py +++ b/src/dodal/devices/beamlines/p60/__init__.py @@ -1,7 +1,19 @@ +from .analyser import ( + P60ElectronAnalyserController, + P60VGScientaAnalyserDriverIO, + P60VGScientaR4000, + P60VGScientaSequence, + P60VGScientnaRegion, +) from .enums import LensMode, PassEnergy, PsuMode from .lab_xray_source import LabXraySource, LabXraySourceReadable __all__ = [ + "P60ElectronAnalyserController", + "P60VGScientaAnalyserDriverIO", + "P60VGScientaR4000", + "P60VGScientaSequence", + "P60VGScientnaRegion", "LensMode", "PsuMode", "PassEnergy", diff --git a/src/dodal/devices/beamlines/p60/analyser.py b/src/dodal/devices/beamlines/p60/analyser.py new file mode 100644 index 0000000000..a459a76c98 --- /dev/null +++ b/src/dodal/devices/beamlines/p60/analyser.py @@ -0,0 +1,56 @@ +from dodal.devices.beamlines.p60.enums import LensMode, PassEnergy, PsuMode +from dodal.devices.electron_analyser.base import AbstractEnergySource +from dodal.devices.electron_analyser.base.base_controller import ( + ElectronAnalyserController, +) +from dodal.devices.electron_analyser.base.base_detector import ElectronAnalyserDetector +from dodal.devices.electron_analyser.vgscienta import ( + VGScientaAnalyserDriverIO, + VGScientaRegion, + VGScientaSequence, +) +from dodal.devices.electron_analyser.vgscienta.vgscienta_driver_io import ( + VGScientaAnalyserDriverIO, +) +from dodal.devices.electron_analyser.vgscienta.vgscienta_region import ( + VGScientaRegion, + VGScientaSequence, +) + +P60VGScientnaRegion = VGScientaRegion[LensMode, PassEnergy] +P60VGScientaSequence = VGScientaSequence[LensMode, PsuMode, PassEnergy] + + +class P60VGScientaAnalyserDriverIO( + VGScientaAnalyserDriverIO[LensMode, PsuMode, PassEnergy] +): + def __init__(self, prefix: str, name: str = ""): + super().__init__(prefix, LensMode, PsuMode, PassEnergy, "ELEMENT_SET", name) + + +P60ElectronAnalyserController = ElectronAnalyserController[ + P60VGScientaAnalyserDriverIO, P60VGScientnaRegion +] + + +class P60VGScientaR4000( + ElectronAnalyserDetector[P60VGScientaAnalyserDriverIO, P60VGScientnaRegion] +): + """Lab specific analyser for P60 lab. It does not have any shutters connected so + will be None for this implementation. The selected_source also cannot be dynamically + changed between regions, so will also be None so regions cannot select. + """ + + def __init__( + self, + prefix: str, + energy_source: AbstractEnergySource, + name: str = "", + ): + controller = P60ElectronAnalyserController( + P60VGScientaAnalyserDriverIO(prefix), + energy_source=energy_source, + shutter=None, + source_selector=None, + ) + super().__init__(controller, name) diff --git a/src/dodal/devices/electron_analyser/base/base_detector.py b/src/dodal/devices/electron_analyser/base/base_detector.py index 320acd9735..8fcab34fb6 100644 --- a/src/dodal/devices/electron_analyser/base/base_detector.py +++ b/src/dodal/devices/electron_analyser/base/base_detector.py @@ -10,7 +10,6 @@ TriggerInfo, ) -from dodal.common.data_util import load_json_file_to_class from dodal.devices.electron_analyser.base.base_controller import ( ElectronAnalyserController, ) @@ -20,9 +19,7 @@ ) from dodal.devices.electron_analyser.base.base_region import ( GenericRegion, - GenericSequence, TAbstractBaseRegion, - TAbstractBaseSequence, ) @@ -111,6 +108,9 @@ async def trigger(self) -> None: await super().trigger() +# Used in sm-bluesky, but will hopefully be removed along with +# ElectronAnalyserRegionDetector in future. Blocked by: +# https://github.com/bluesky/bluesky/pull/1978 GenericElectronAnalyserRegionDetector = ElectronAnalyserRegionDetector[ GenericAnalyserDriverIO, GenericRegion ] @@ -123,7 +123,7 @@ async def trigger(self) -> None: class ElectronAnalyserDetector( BaseElectronAnalyserDetector[TAbstractAnalyserDriverIO, TAbstractBaseRegion], Stageable, - Generic[TAbstractBaseSequence, TAbstractAnalyserDriverIO, TAbstractBaseRegion], + Generic[TAbstractAnalyserDriverIO, TAbstractBaseRegion], ): """Electron analyser detector with the additional functionality to load a sequence file and create a list of temporary ElectronAnalyserRegionDetector objects. These @@ -132,13 +132,13 @@ class ElectronAnalyserDetector( def __init__( self, - sequence_class: type[TAbstractBaseSequence], controller: ElectronAnalyserController[ TAbstractAnalyserDriverIO, TAbstractBaseRegion ], name: str = "", ): - self._sequence_class = sequence_class + # Save on device so connect works and names it as child + self.driver = controller.driver super().__init__(controller, name) @AsyncStatus.wrap @@ -160,38 +160,24 @@ async def unstage(self) -> None: """Disarm the detector.""" await self._controller.disarm() - def load_sequence(self, filename: str) -> TAbstractBaseSequence: - """Load the sequence data from a provided json file into a sequence class. - - Args: - filename (str): Path to the sequence file containing the region data. - - Returns: - Pydantic model representing the sequence file. - """ - return load_json_file_to_class(self._sequence_class, filename) - def create_region_detector_list( - self, filename: str, enabled_only=True + self, regions: list[TAbstractBaseRegion] ) -> list[ ElectronAnalyserRegionDetector[TAbstractAnalyserDriverIO, TAbstractBaseRegion] ]: - """Create a list of detectors equal to the number of regions in a sequence file. - Each detector is responsible for setting up a specific region. + """This method can hopefully be dropped when this is merged and released. + https://github.com/bluesky/bluesky/pull/1978. + + Create a list of detectors equal to the number of regions. Each detector is + responsible for setting up a specific region. Args: - filename (str): Path to the sequence file containing the region data. - enabled_only (bool, optional): If true, only include the region if enabled - is True. + regions: The list of regions to give to each region detector. Returns: List of ElectronAnalyserRegionDetector, equal to the number of regions in - the sequence file. + the sequence file. """ - seq = self.load_sequence(filename) - regions: list[TAbstractBaseRegion] = ( - seq.get_enabled_regions() if enabled_only else seq.regions - ) return [ ElectronAnalyserRegionDetector[ TAbstractAnalyserDriverIO, TAbstractBaseRegion @@ -201,7 +187,7 @@ def create_region_detector_list( GenericElectronAnalyserDetector = ElectronAnalyserDetector[ - GenericSequence, GenericAnalyserDriverIO, GenericRegion + GenericAnalyserDriverIO, GenericRegion ] TElectronAnalyserDetector = TypeVar( "TElectronAnalyserDetector", diff --git a/src/dodal/devices/electron_analyser/base/base_driver_io.py b/src/dodal/devices/electron_analyser/base/base_driver_io.py index b266709aa8..25a3704d07 100644 --- a/src/dodal/devices/electron_analyser/base/base_driver_io.py +++ b/src/dodal/devices/electron_analyser/base/base_driver_io.py @@ -56,8 +56,6 @@ class AbstractAnalyserDriverIO( pass_energy_type (type[TPassEnergy]): Can be enum or float, depending on electron analyser model. If enum, it determines the available pass energies for this device. - energy_source: Device that can give us the correct excitation energy (in eV) - and switch sources if applicable. name (str, optional): Name of the device. """ @@ -68,6 +66,7 @@ def __init__( lens_mode_type: type[TLensMode], psu_mode_type: type[TPsuMode], pass_energy_type: type[TPassEnergy], + psu_mode_suffix: str = "PSU_MODE", name: str = "", ) -> None: self.acquisition_mode_type = acquisition_mode_type @@ -108,7 +107,7 @@ def __init__( ) # This is used by each electron analyser, however it depends on the electron # analyser type to know if is moved with region settings. - self.psu_mode = epics_signal_rw(psu_mode_type, prefix + "PSU_MODE") + self.psu_mode = epics_signal_rw(psu_mode_type, prefix + psu_mode_suffix) # This is defined in the parent class, add it as readable configuration. self.add_readables([self.acquire_time], StandardReadableFormat.CONFIG_SIGNAL) diff --git a/src/dodal/devices/electron_analyser/specs/__init__.py b/src/dodal/devices/electron_analyser/specs/__init__.py index 64819341c0..ca5ce2686c 100644 --- a/src/dodal/devices/electron_analyser/specs/__init__.py +++ b/src/dodal/devices/electron_analyser/specs/__init__.py @@ -1,10 +1,8 @@ -from .specs_detector import SpecsDetector from .specs_driver_io import SpecsAnalyserDriverIO from .specs_enums import AcquisitionMode from .specs_region import SpecsRegion, SpecsSequence __all__ = [ - "SpecsDetector", "SpecsAnalyserDriverIO", "AcquisitionMode", "SpecsRegion", diff --git a/src/dodal/devices/electron_analyser/specs/specs_detector.py b/src/dodal/devices/electron_analyser/specs/specs_detector.py deleted file mode 100644 index fb54f47c49..0000000000 --- a/src/dodal/devices/electron_analyser/specs/specs_detector.py +++ /dev/null @@ -1,47 +0,0 @@ -from typing import Generic - -from dodal.devices.electron_analyser.base.base_controller import ( - ElectronAnalyserController, -) -from dodal.devices.electron_analyser.base.base_detector import ElectronAnalyserDetector -from dodal.devices.electron_analyser.base.base_region import TLensMode, TPsuMode -from dodal.devices.electron_analyser.base.energy_sources import AbstractEnergySource -from dodal.devices.electron_analyser.specs.specs_driver_io import SpecsAnalyserDriverIO -from dodal.devices.electron_analyser.specs.specs_region import ( - SpecsRegion, - SpecsSequence, -) -from dodal.devices.fast_shutter import FastShutter -from dodal.devices.selectable_source import SourceSelector - - -class SpecsDetector( - ElectronAnalyserDetector[ - SpecsSequence[TLensMode, TPsuMode], - SpecsAnalyserDriverIO[TLensMode, TPsuMode], - SpecsRegion[TLensMode, TPsuMode], - ], - Generic[TLensMode, TPsuMode], -): - def __init__( - self, - prefix: str, - lens_mode_type: type[TLensMode], - psu_mode_type: type[TPsuMode], - energy_source: AbstractEnergySource, - shutter: FastShutter | None = None, - source_selector: SourceSelector | None = None, - name: str = "", - ): - # Save to class so takes part with connect() - self.driver = SpecsAnalyserDriverIO[TLensMode, TPsuMode]( - prefix, lens_mode_type, psu_mode_type - ) - - controller = ElectronAnalyserController[ - SpecsAnalyserDriverIO[TLensMode, TPsuMode], SpecsRegion[TLensMode, TPsuMode] - ](self.driver, energy_source, shutter, source_selector) - - sequence_class = SpecsSequence[lens_mode_type, psu_mode_type] - - super().__init__(sequence_class, controller, name) diff --git a/src/dodal/devices/electron_analyser/vgscienta/__init__.py b/src/dodal/devices/electron_analyser/vgscienta/__init__.py index c344269cc1..3b746193df 100644 --- a/src/dodal/devices/electron_analyser/vgscienta/__init__.py +++ b/src/dodal/devices/electron_analyser/vgscienta/__init__.py @@ -1,10 +1,8 @@ -from .vgscienta_detector import VGScientaDetector from .vgscienta_driver_io import VGScientaAnalyserDriverIO from .vgscienta_enums import AcquisitionMode, DetectorMode from .vgscienta_region import VGScientaRegion, VGScientaSequence __all__ = [ - "VGScientaDetector", "VGScientaAnalyserDriverIO", "AcquisitionMode", "DetectorMode", diff --git a/src/dodal/devices/electron_analyser/vgscienta/vgscienta_detector.py b/src/dodal/devices/electron_analyser/vgscienta/vgscienta_detector.py deleted file mode 100644 index 6e508fec5f..0000000000 --- a/src/dodal/devices/electron_analyser/vgscienta/vgscienta_detector.py +++ /dev/null @@ -1,53 +0,0 @@ -from typing import Generic - -from dodal.devices.electron_analyser.base.base_controller import ( - ElectronAnalyserController, -) -from dodal.devices.electron_analyser.base.base_detector import ElectronAnalyserDetector -from dodal.devices.electron_analyser.base.base_region import TLensMode, TPsuMode -from dodal.devices.electron_analyser.base.energy_sources import AbstractEnergySource -from dodal.devices.electron_analyser.vgscienta.vgscienta_driver_io import ( - VGScientaAnalyserDriverIO, -) -from dodal.devices.electron_analyser.vgscienta.vgscienta_region import ( - TPassEnergyEnum, - VGScientaRegion, - VGScientaSequence, -) -from dodal.devices.fast_shutter import FastShutter -from dodal.devices.selectable_source import SourceSelector - - -class VGScientaDetector( - ElectronAnalyserDetector[ - VGScientaSequence[TLensMode, TPsuMode, TPassEnergyEnum], - VGScientaAnalyserDriverIO[TLensMode, TPsuMode, TPassEnergyEnum], - VGScientaRegion[TLensMode, TPassEnergyEnum], - ], - Generic[TLensMode, TPsuMode, TPassEnergyEnum], -): - def __init__( - self, - prefix: str, - lens_mode_type: type[TLensMode], - psu_mode_type: type[TPsuMode], - pass_energy_type: type[TPassEnergyEnum], - energy_source: AbstractEnergySource, - shutter: FastShutter | None = None, - source_selector: SourceSelector | None = None, - name: str = "", - ): - # Save to class so takes part with connect() - self.driver = VGScientaAnalyserDriverIO[TLensMode, TPsuMode, TPassEnergyEnum]( - prefix, lens_mode_type, psu_mode_type, pass_energy_type - ) - - controller = ElectronAnalyserController[ - VGScientaAnalyserDriverIO[TLensMode, TPsuMode, TPassEnergyEnum], - VGScientaRegion[TLensMode, TPassEnergyEnum], - ](self.driver, energy_source, shutter, source_selector) - - sequence_class = VGScientaSequence[ - lens_mode_type, psu_mode_type, pass_energy_type - ] - super().__init__(sequence_class, controller, name) diff --git a/src/dodal/devices/electron_analyser/vgscienta/vgscienta_driver_io.py b/src/dodal/devices/electron_analyser/vgscienta/vgscienta_driver_io.py index 3d7db84aa9..2563f0a7e3 100644 --- a/src/dodal/devices/electron_analyser/vgscienta/vgscienta_driver_io.py +++ b/src/dodal/devices/electron_analyser/vgscienta/vgscienta_driver_io.py @@ -40,6 +40,7 @@ def __init__( lens_mode_type: type[TLensMode], psu_mode_type: type[TPsuMode], pass_energy_type: type[TPassEnergyEnum], + psu_mode_suffix: str = "PSU_MODE", name: str = "", ) -> None: with self.add_children_as_readables(StandardReadableFormat.CONFIG_SIGNAL): @@ -60,6 +61,7 @@ def __init__( lens_mode_type, psu_mode_type, pass_energy_type, + psu_mode_suffix, name, ) diff --git a/src/dodal/testing/electron_analyser/__init__.py b/src/dodal/testing/electron_analyser/__init__.py deleted file mode 100644 index dffbe8d492..0000000000 --- a/src/dodal/testing/electron_analyser/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .device_factory import create_detector, create_driver - -__all__ = [ - "create_detector", - "create_driver", -] diff --git a/src/dodal/testing/electron_analyser/device_factory.py b/src/dodal/testing/electron_analyser/device_factory.py deleted file mode 100644 index 961be125b2..0000000000 --- a/src/dodal/testing/electron_analyser/device_factory.py +++ /dev/null @@ -1,57 +0,0 @@ -from typing import Any, get_args, get_origin - -from dodal.devices.electron_analyser.base.base_detector import TElectronAnalyserDetector -from dodal.devices.electron_analyser.base.base_driver_io import ( - TAbstractAnalyserDriverIO, -) -from dodal.devices.electron_analyser.vgscienta import ( - VGScientaAnalyserDriverIO, - VGScientaDetector, -) - - -def create_driver( - driver_class: type[TAbstractAnalyserDriverIO], - **kwargs: Any, -) -> TAbstractAnalyserDriverIO: - """Helper function that helps to reduce the code to setup an analyser driver. The - parameters used for the enum types are taken directly from the subscripts of the - class so the user only needs to provide it in one place. - - Args: - driver_class: The class for the driver which must include the enums in the - subscript, for example MyDriverClass[MyLensMode, ...]. - kwargs: Additional key worded arguments that the driver needs for initalisation. - """ - parameters = { - "lens_mode_type": get_args(driver_class)[0], - "psu_mode_type": get_args(driver_class)[1], - } - if get_origin(driver_class) is VGScientaAnalyserDriverIO: - parameters["pass_energy_type"] = get_args(driver_class)[2] - - return driver_class(**(parameters | kwargs)) - - -def create_detector( - detector_class: type[TElectronAnalyserDetector], - **kwargs: Any, -) -> TElectronAnalyserDetector: - """Helper function that helps to reduce the code to setup an analyser detector. The - parameters used for the enum types are taken directly from the subscripts of the - class so the user only needs to provide it in one place. - - Args: - detector_class: The class for the detector which must include the enums in the - subscript, for example MyDetectorClass[MyLensMode, ...]. - kwargs: Additional key worded arguments that the detector needs for - initalisation. - """ - parameters = { - "lens_mode_type": get_args(detector_class)[0], - "psu_mode_type": get_args(detector_class)[1], - } - if get_origin(detector_class) is VGScientaDetector: - parameters["pass_energy_type"] = get_args(detector_class)[2] - - return detector_class(**(parameters | kwargs)) diff --git a/tests/devices/electron_analyser/base/test_base_controller.py b/tests/devices/electron_analyser/base/test_base_controller.py index aa899c66fc..468d06a73f 100644 --- a/tests/devices/electron_analyser/base/test_base_controller.py +++ b/tests/devices/electron_analyser/base/test_base_controller.py @@ -1,121 +1,32 @@ from unittest.mock import AsyncMock, patch import pytest -from ophyd_async.core import InOut, TriggerInfo, get_mock_put, init_devices +from ophyd_async.core import TriggerInfo, get_mock_put -from dodal.beamlines import b07, i09 from dodal.devices.electron_analyser.base import ( AbstractAnalyserDriverIO, AbstractBaseRegion, - DualEnergySource, - EnergySource, -) -from dodal.devices.electron_analyser.base.base_controller import ( ElectronAnalyserController, + GenericElectronAnalyserController, + GenericElectronAnalyserDetector, + GenericSequence, ) -from dodal.devices.electron_analyser.specs import SpecsAnalyserDriverIO -from dodal.devices.electron_analyser.vgscienta import ( - VGScientaAnalyserDriverIO, -) -from dodal.devices.fast_shutter import DualFastShutter, GenericFastShutter -from dodal.devices.selectable_source import SourceSelector -from dodal.testing.electron_analyser import create_driver from tests.devices.electron_analyser.helper_util import ( TEST_SEQUENCE_REGION_NAMES, - get_test_sequence, -) - - -@pytest.fixture( - params=[ - SpecsAnalyserDriverIO[b07.LensMode, b07.PsuMode], - VGScientaAnalyserDriverIO[i09.LensMode, i09.PsuMode, i09.PassEnergy], - ] + TEST_SEQUENCES, ) -async def sim_driver( - request: pytest.FixtureRequest, -) -> AbstractAnalyserDriverIO: - async with init_devices(mock=True): - sim_detector = create_driver( - request.param, - prefix="TEST:", - ) - return sim_detector @pytest.fixture -def sequence_file_path( - sim_driver: AbstractAnalyserDriverIO, -) -> str: - return get_test_sequence(type(sim_driver)) - - -@pytest.fixture -def shutter1() -> GenericFastShutter[InOut]: - with init_devices(mock=True): - shutter1 = GenericFastShutter[InOut]( - pv="TEST:", - open_state=InOut.OUT, - close_state=InOut.IN, - ) - return shutter1 - - -@pytest.fixture -def shutter2() -> GenericFastShutter[InOut]: - with init_devices(mock=True): - shutter2 = GenericFastShutter[InOut]( - pv="TEST:", - open_state=InOut.OUT, - close_state=InOut.IN, - ) - return shutter2 - - -@pytest.fixture -def dual_fast_shutter( - shutter1: GenericFastShutter[InOut], - shutter2: GenericFastShutter[InOut], - source_selector: SourceSelector, -) -> DualFastShutter[InOut]: - with init_devices(mock=True): - dual_fast_shutter = DualFastShutter[InOut]( - shutter1, - shutter2, - source_selector.selected_source, - ) - return dual_fast_shutter +def sequence(sim_detector: GenericElectronAnalyserDetector) -> GenericSequence: + return TEST_SEQUENCES[type(sim_detector)]() @pytest.fixture def analyser_controller( - sim_driver: AbstractAnalyserDriverIO, - single_energy_source: EnergySource, - dual_energy_source: DualEnergySource, - dual_fast_shutter: DualFastShutter, - source_selector: SourceSelector, -) -> ElectronAnalyserController[AbstractAnalyserDriverIO, AbstractBaseRegion]: - if isinstance(sim_driver, SpecsAnalyserDriverIO): - controller = ElectronAnalyserController[ - AbstractAnalyserDriverIO, AbstractBaseRegion - ]( - sim_driver, - single_energy_source, - source_selector=None, - ) - elif isinstance(sim_driver, VGScientaAnalyserDriverIO): - controller = ElectronAnalyserController[ - AbstractAnalyserDriverIO, AbstractBaseRegion - ]( - sim_driver, - dual_energy_source, - dual_fast_shutter, - source_selector, - ) - else: - raise ValueError(f"sim_driver is of unsupported type {type(sim_driver)}.") - - return controller + sim_detector: GenericElectronAnalyserDetector, +) -> GenericElectronAnalyserController: + return sim_detector._controller async def test_controller_prepare_sets_excitation_energy( diff --git a/tests/devices/electron_analyser/base/test_base_detector.py b/tests/devices/electron_analyser/base/test_base_detector.py index 11f76cfd62..5fe0448399 100644 --- a/tests/devices/electron_analyser/base/test_base_detector.py +++ b/tests/devices/electron_analyser/base/test_base_detector.py @@ -3,45 +3,23 @@ import pytest from bluesky import plan_stubs as bps from bluesky.run_engine import RunEngine -from ophyd_async.core import TriggerInfo, init_devices, set_mock_value +from ophyd_async.core import TriggerInfo from ophyd_async.testing import ( assert_configuration, assert_reading, ) -import dodal.devices.beamlines.b07 as b07 -import dodal.devices.beamlines.i09 as i09 from dodal.devices.electron_analyser.base import ( - EnergySource, GenericBaseElectronAnalyserDetector, GenericElectronAnalyserDetector, + GenericSequence, ) -from dodal.devices.electron_analyser.base.energy_sources import EnergySource -from dodal.devices.electron_analyser.specs import SpecsDetector -from dodal.devices.electron_analyser.vgscienta import VGScientaDetector -from dodal.testing.electron_analyser import create_detector -from tests.devices.electron_analyser.helper_util import get_test_sequence +from tests.devices.electron_analyser.helper_util.sequence import get_test_sequence -@pytest.fixture( - params=[ - VGScientaDetector[i09.LensMode, i09.PsuMode, i09.PassEnergy], - SpecsDetector[b07.LensMode, b07.PsuMode], - ] -) -async def sim_detector( - request: pytest.FixtureRequest, - single_energy_source: EnergySource, -) -> GenericElectronAnalyserDetector: - async with init_devices(mock=True): - sim_detector = create_detector( - request.param, - prefix="TEST:", - energy_source=single_energy_source, - ) - # Needed for specs so we don't get division by zero error. - set_mock_value(sim_detector.driver.slices, 1) - return sim_detector +@pytest.fixture +def sequence(sim_detector: GenericElectronAnalyserDetector) -> GenericSequence: + return get_test_sequence(type(sim_detector)) def test_base_analyser_detector_trigger( @@ -93,18 +71,11 @@ async def test_base_analyser_detector_describe_configuration( assert await sim_detector.describe_configuration() == driver_describe_config -@pytest.fixture -def sequence_file_path( - sim_detector: GenericElectronAnalyserDetector, -) -> str: - return get_test_sequence(type(sim_detector)) - - def test_analyser_detector_loads_sequence_correctly( sim_detector: GenericElectronAnalyserDetector, - sequence_file_path: str, + sequence: GenericSequence, ) -> None: - seq = sim_detector.load_sequence(sequence_file_path) + seq = sim_detector.create_region_detector_list(sequence.get_enabled_regions()) assert seq is not None @@ -130,20 +101,19 @@ async def test_analyser_detector_unstage( def test_analyser_detector_creates_region_detectors( sim_detector: GenericElectronAnalyserDetector, - sequence_file_path: str, + sequence: GenericSequence, ) -> None: - seq = sim_detector.load_sequence(sequence_file_path) - region_detectors = sim_detector.create_region_detector_list(sequence_file_path) - - assert len(region_detectors) == len(seq.get_enabled_regions()) + region_detectors = sim_detector.create_region_detector_list( + sequence.get_enabled_regions() + ) + assert len(region_detectors) == len(sequence.get_enabled_regions()) for det in region_detectors: - assert det.region.enabled is True assert det.name == sim_detector.name + "_" + det.region.name def test_analyser_detector_has_driver_as_child_and_region_detector_does_not( sim_detector: GenericElectronAnalyserDetector, - sequence_file_path: str, + sequence: GenericSequence, ) -> None: # Remove parent name from driver name so it can be checked it exists in # _child_devices dict @@ -154,8 +124,9 @@ def test_analyser_detector_has_driver_as_child_and_region_detector_does_not( assert sim_detector._controller.driver.parent == sim_detector assert sim_detector._child_devices.get(driver_name) is not None - region_detectors = sim_detector.create_region_detector_list(sequence_file_path) - + region_detectors = sim_detector.create_region_detector_list( + sequence.get_enabled_regions() + ) for det in region_detectors: assert det._child_devices.get(driver_name) is None assert det._controller.driver.parent == sim_detector @@ -178,11 +149,10 @@ def test_analyser_detector_trigger_called_controller_prepare( def test_analyser_detector_set_called_controller_setup_with_region( sim_detector: GenericElectronAnalyserDetector, - sequence_file_path: str, + sequence: GenericSequence, run_engine: RunEngine, ) -> None: - seq = sim_detector.load_sequence(sequence_file_path) - region = seq.get_enabled_regions()[0] + region = sequence.get_enabled_regions()[0] sim_detector._controller.setup_with_region = AsyncMock() run_engine(bps.mv(sim_detector, region), wait=True) sim_detector._controller.setup_with_region.assert_awaited_once_with(region) @@ -190,11 +160,11 @@ def test_analyser_detector_set_called_controller_setup_with_region( async def test_analyser_region_detector_trigger_sets_driver_with_region( sim_detector: GenericElectronAnalyserDetector, - sequence_file_path: str, + sequence: GenericSequence, run_engine: RunEngine, ) -> None: region_detectors = sim_detector.create_region_detector_list( - sequence_file_path, enabled_only=False + sequence.get_enabled_regions() ) trigger_info = TriggerInfo() diff --git a/tests/devices/electron_analyser/base/test_base_driver_io.py b/tests/devices/electron_analyser/base/test_base_driver_io.py index d4e56e0c13..cdbe6a717c 100644 --- a/tests/devices/electron_analyser/base/test_base_driver_io.py +++ b/tests/devices/electron_analyser/base/test_base_driver_io.py @@ -2,31 +2,19 @@ from bluesky import plan_stubs as bps from bluesky.run_engine import RunEngine from bluesky.utils import FailedStatus -from ophyd_async.core import StrictEnum, init_devices +from ophyd_async.core import StrictEnum -from dodal.devices.beamlines import b07, i09 -from dodal.devices.electron_analyser.base import GenericAnalyserDriverIO -from dodal.devices.electron_analyser.specs import ( - SpecsAnalyserDriverIO, +from dodal.devices.electron_analyser.base import ( + GenericAnalyserDriverIO, + GenericElectronAnalyserDetector, ) -from dodal.devices.electron_analyser.vgscienta import ( - VGScientaAnalyserDriverIO, -) -from dodal.testing.electron_analyser import create_driver -@pytest.fixture( - params=[ - VGScientaAnalyserDriverIO[i09.LensMode, i09.PsuMode, i09.PassEnergy], - SpecsAnalyserDriverIO[b07.LensMode, b07.PsuMode], - ] -) +@pytest.fixture async def sim_driver( - request: pytest.FixtureRequest, + sim_detector: GenericElectronAnalyserDetector, ) -> GenericAnalyserDriverIO: - async with init_devices(mock=True): - sim_driver = create_driver(request.param, prefix="TEST:") - return sim_driver + return sim_detector.driver def test_driver_throws_error_with_wrong_lens_mode( diff --git a/tests/devices/electron_analyser/base/test_base_region.py b/tests/devices/electron_analyser/base/test_base_region.py index 50615fbba0..a3c1a70705 100644 --- a/tests/devices/electron_analyser/base/test_base_region.py +++ b/tests/devices/electron_analyser/base/test_base_region.py @@ -2,8 +2,8 @@ import pytest -from dodal.common.data_util import load_json_file_to_class -from dodal.devices.beamlines import b07, i09 +from dodal.devices.beamlines.b07 import B07BSpecsRegion, B07BSpecsSequence +from dodal.devices.beamlines.i09 import I09VGScientaRegion, I09VGScientaSequence from dodal.devices.electron_analyser.base import ( AbstractBaseRegion, EnergyMode, @@ -13,25 +13,17 @@ to_binding_energy, to_kinetic_energy, ) -from dodal.devices.electron_analyser.specs import ( - SpecsRegion, - SpecsSequence, -) -from dodal.devices.electron_analyser.vgscienta import VGScientaRegion, VGScientaSequence +from dodal.devices.electron_analyser.specs import SpecsSequence +from dodal.devices.electron_analyser.vgscienta import VGScientaSequence from tests.devices.electron_analyser.helper_util import ( TEST_SEQUENCE_REGION_NAMES, - get_test_sequence, + TEST_SEQUENCES, ) -@pytest.fixture( - params=[ - SpecsSequence[b07.LensMode, b07.PsuMode], - VGScientaSequence[i09.LensMode, i09.PsuMode, i09.PassEnergy], - ] -) +@pytest.fixture(params=[B07BSpecsSequence, I09VGScientaSequence]) def sequence(request: pytest.FixtureRequest) -> GenericSequence: - return load_json_file_to_class(request.param, get_test_sequence(request.param)) + return TEST_SEQUENCES[request.param]() @pytest.fixture @@ -39,9 +31,9 @@ def expected_region_class( sequence: GenericSequence, ) -> type[AbstractBaseRegion]: if isinstance(sequence, SpecsSequence): - return SpecsRegion[b07.LensMode, b07.PsuMode] + return B07BSpecsRegion elif isinstance(sequence, VGScientaSequence): - return VGScientaRegion[i09.LensMode, i09.PassEnergy] + return I09VGScientaRegion raise TypeError(f"Unknown sequence type {type(sequence)}") diff --git a/tests/devices/electron_analyser/conftest.py b/tests/devices/electron_analyser/conftest.py index 7007cf5526..474a0ea17e 100644 --- a/tests/devices/electron_analyser/conftest.py +++ b/tests/devices/electron_analyser/conftest.py @@ -1,35 +1,25 @@ from typing import Any import pytest -from ophyd_async.core import init_devices +from ophyd_async.core import InOut, init_devices, set_mock_value -from dodal.devices.beamlines.i09 import Grating +from dodal.devices.beamlines.b07 import B07BSpecs150 +from dodal.devices.beamlines.i09 import Grating, I09VGScientaEW4000 from dodal.devices.common_dcm import ( DoubleCrystalMonochromatorWithDSpacing, PitchAndRollCrystal, StationaryCrystal, ) from dodal.devices.electron_analyser.base import ( - AbstractAnalyserDriverIO, AbstractBaseRegion, AbstractBaseSequence, DualEnergySource, - ElectronAnalyserController, - ElectronAnalyserDetector, EnergySource, - TAbstractBaseSequence, -) -from dodal.devices.electron_analyser.specs import ( - SpecsAnalyserDriverIO, - SpecsSequence, -) -from dodal.devices.electron_analyser.vgscienta import ( - VGScientaAnalyserDriverIO, - VGScientaSequence, + GenericElectronAnalyserDetector, ) +from dodal.devices.fast_shutter import DualFastShutter, GenericFastShutter from dodal.devices.pgm import PlaneGratingMonochromator from dodal.devices.selectable_source import SourceSelector -from tests.devices.electron_analyser.helper_util import get_test_sequence @pytest.fixture @@ -70,41 +60,103 @@ async def dual_energy_source(source_selector: SourceSelector) -> DualEnergySourc return dual_energy_source +def fast_shutter() -> GenericFastShutter: + with init_devices(mock=True): + fast_shutter = GenericFastShutter[InOut]( + pv="TEST:", + open_state=InOut.OUT, + close_state=InOut.IN, + ) + return fast_shutter + + +@pytest.fixture +def shutter1() -> GenericFastShutter[InOut]: + with init_devices(mock=True): + shutter1 = GenericFastShutter[InOut]( + pv="TEST:", + open_state=InOut.OUT, + close_state=InOut.IN, + ) + return shutter1 + + @pytest.fixture -def sequence_class( - sim_driver: AbstractAnalyserDriverIO, -) -> type[AbstractBaseSequence]: - # We must include the pass energy, lens and psu mode types here, otherwise the - # sequence file can't be loaded as pydantic won't be able to resolve the enums. - if isinstance(sim_driver, VGScientaAnalyserDriverIO): - return VGScientaSequence[ - sim_driver.lens_mode_type, - sim_driver.psu_mode_type, - sim_driver.pass_energy_type, - ] - elif isinstance(sim_driver, SpecsAnalyserDriverIO): - return SpecsSequence[sim_driver.lens_mode_type, sim_driver.psu_mode_type] - raise ValueError("class " + str(sim_driver) + " not recognised") +def shutter2() -> GenericFastShutter[InOut]: + with init_devices(mock=True): + shutter2 = GenericFastShutter[InOut]( + pv="TEST:", + open_state=InOut.OUT, + close_state=InOut.IN, + ) + return shutter2 @pytest.fixture -def sequence( - sim_driver: AbstractAnalyserDriverIO, - sequence_class: type[TAbstractBaseSequence], +def dual_fast_shutter( + shutter1: GenericFastShutter[InOut], + shutter2: GenericFastShutter[InOut], + source_selector: SourceSelector, +) -> DualFastShutter[InOut]: + with init_devices(mock=True): + dual_fast_shutter = DualFastShutter[InOut]( + shutter1, + shutter2, + source_selector.selected_source, + ) + return dual_fast_shutter + + +@pytest.fixture +async def b07b_specs150( single_energy_source: EnergySource, -) -> AbstractBaseSequence: - controller = ElectronAnalyserController(sim_driver, single_energy_source) - det = ElectronAnalyserDetector( - sequence_class=sequence_class, - controller=controller, - ) - return det.load_sequence(get_test_sequence(type(sim_driver))) + shutter1: GenericFastShutter, +) -> B07BSpecs150: + with init_devices(mock=True): + b07b_specs150 = B07BSpecs150( + prefix="TEST:", + energy_source=single_energy_source, + shutter=shutter1, + ) + # Needed for specs so we don't get division by zero error. + set_mock_value(b07b_specs150.driver.slices, 1) + return b07b_specs150 + + +@pytest.fixture +async def ew4000( + dual_energy_source: DualEnergySource, + dual_fast_shutter: DualFastShutter, + source_selector: SourceSelector, +) -> I09VGScientaEW4000: + with init_devices(mock=True): + ew4000 = I09VGScientaEW4000( + prefix="TEST:", + dual_energy_source=dual_energy_source, + dual_fast_shutter=dual_fast_shutter, + source_selector=source_selector, + ) + return ew4000 + + +@pytest.fixture(params=["ew4000", "b07b_specs150"]) +def sim_detector( + request: pytest.FixtureRequest, + ew4000: I09VGScientaEW4000, + b07b_specs150: B07BSpecs150, +) -> GenericElectronAnalyserDetector: + detectors = [ew4000, b07b_specs150] + for detector in detectors: + if detector.name == request.param: + return detector + + raise ValueError(f"Detector with name '{request.param}' not found") @pytest.fixture def region( request: pytest.FixtureRequest, - sequence: AbstractBaseSequence, + sequence: AbstractBaseSequence[AbstractBaseRegion], ) -> AbstractBaseRegion: region = sequence.get_region_by_name(request.param) if region is None: diff --git a/tests/devices/electron_analyser/helper_util/__init__.py b/tests/devices/electron_analyser/helper_util/__init__.py index c3026b1e71..da0f67f56d 100644 --- a/tests/devices/electron_analyser/helper_util/__init__.py +++ b/tests/devices/electron_analyser/helper_util/__init__.py @@ -1,8 +1,11 @@ from .assert_func import assert_region_has_expected_values -from .sequence import TEST_SEQUENCE_REGION_NAMES, get_test_sequence +from .sequence import ( + TEST_SEQUENCE_REGION_NAMES, + TEST_SEQUENCES, +) __all__ = [ "assert_region_has_expected_values", - "get_test_sequence", "TEST_SEQUENCE_REGION_NAMES", + "TEST_SEQUENCES", ] diff --git a/tests/devices/electron_analyser/helper_util/assert_func.py b/tests/devices/electron_analyser/helper_util/assert_func.py index 01433c8712..4e20df0bcf 100644 --- a/tests/devices/electron_analyser/helper_util/assert_func.py +++ b/tests/devices/electron_analyser/helper_util/assert_func.py @@ -11,6 +11,6 @@ def assert_region_has_expected_values( actual_values = r.__dict__ diff = DeepDiff(expected_region_values, actual_values) if diff: - raise AssertionError(f"Region does not match expected values:\n{diff}") + raise AssertionError(f"Region {r.name} does not match expected values:\n{diff}") for key in expected_region_values.keys(): assert actual_values.get(key) is not None diff --git a/tests/devices/electron_analyser/helper_util/sequence.py b/tests/devices/electron_analyser/helper_util/sequence.py index 85d47a8f56..a7e5620f0c 100644 --- a/tests/devices/electron_analyser/helper_util/sequence.py +++ b/tests/devices/electron_analyser/helper_util/sequence.py @@ -1,34 +1,44 @@ -from dodal.devices.electron_analyser.specs import ( - SpecsAnalyserDriverIO, - SpecsDetector, - SpecsSequence, +from dodal.common.data_util import load_json_file_to_class +from dodal.devices.beamlines.b07 import ( + B07BSpecs150, + B07BSpecsAnalyserDriverIO, + B07BSpecsSequence, ) -from dodal.devices.electron_analyser.vgscienta import ( - VGScientaAnalyserDriverIO, - VGScientaDetector, - VGScientaSequence, +from dodal.devices.beamlines.i09 import ( + I09VGScientaAnalyserDriverIO, + I09VGScientaEW4000, + I09VGScientaSequence, ) from tests.devices.electron_analyser.test_data import ( TEST_SPECS_SEQUENCE, TEST_VGSCIENTA_SEQUENCE, ) +TEST_SEQUENCE_REGION_NAMES = ["New_Region", "New_Region1", "New_Region2"] + + +def b07_specs_test_sequence_loader() -> B07BSpecsSequence: + return load_json_file_to_class(B07BSpecsSequence, TEST_SPECS_SEQUENCE) + + +def i09_vgscienta_test_sequence_loader() -> I09VGScientaSequence: + return load_json_file_to_class(I09VGScientaSequence, TEST_VGSCIENTA_SEQUENCE) + + +# Map to know what function to load in sequence an analyser driver should use. TEST_SEQUENCES = { - VGScientaSequence: TEST_VGSCIENTA_SEQUENCE, - VGScientaDetector: TEST_VGSCIENTA_SEQUENCE, - VGScientaAnalyserDriverIO: TEST_VGSCIENTA_SEQUENCE, - SpecsSequence: TEST_SPECS_SEQUENCE, - SpecsDetector: TEST_SPECS_SEQUENCE, - SpecsAnalyserDriverIO: TEST_SPECS_SEQUENCE, + B07BSpecs150: b07_specs_test_sequence_loader, + B07BSpecsAnalyserDriverIO: b07_specs_test_sequence_loader, + B07BSpecsSequence: b07_specs_test_sequence_loader, + I09VGScientaEW4000: i09_vgscienta_test_sequence_loader, + I09VGScientaAnalyserDriverIO: i09_vgscienta_test_sequence_loader, + I09VGScientaSequence: i09_vgscienta_test_sequence_loader, } -def get_test_sequence(key: type) -> str: +def get_test_sequence(key: type): for cls in key.__mro__: # Check for unscripted class only if cls in TEST_SEQUENCES: - return TEST_SEQUENCES[cls] + return TEST_SEQUENCES[cls]() raise KeyError(f"Found no match with type {key}") - - -TEST_SEQUENCE_REGION_NAMES = ["New_Region", "New_Region1", "New_Region2"] diff --git a/tests/devices/electron_analyser/specs/test_specs_detector.py b/tests/devices/electron_analyser/specs/test_specs_detector.py deleted file mode 100644 index 9a1fd9c143..0000000000 --- a/tests/devices/electron_analyser/specs/test_specs_detector.py +++ /dev/null @@ -1,46 +0,0 @@ -import pytest -from ophyd_async.core import init_devices, set_mock_value - -from dodal.devices.beamlines.b07 import LensMode, PsuMode -from dodal.devices.electron_analyser.base import EnergySource -from dodal.devices.electron_analyser.specs import SpecsDetector -from dodal.testing.electron_analyser import create_detector - - -@pytest.fixture -async def sim_detector( - single_energy_source: EnergySource, -) -> SpecsDetector[LensMode, PsuMode]: - async with init_devices(mock=True): - sim_driver = create_detector( - SpecsDetector[LensMode, PsuMode], - prefix="TEST:", - energy_source=single_energy_source, - ) - return sim_driver - - -async def test_analyser_specs_detector_image_shape(sim_detector: SpecsDetector) -> None: - driver = sim_detector.driver - prefix = driver.name + "-" - - low_energy = 1 - high_energy = 10 - slices = 4 - set_mock_value(driver.low_energy, low_energy) - set_mock_value(driver.high_energy, high_energy) - set_mock_value(driver.slices, slices) - - min_angle = 1 - max_angle = 10 - set_mock_value(driver.min_angle_axis, min_angle) - set_mock_value(driver.max_angle_axis, max_angle) - - angle_axis = await driver.angle_axis.get_value() - energy_axis = await driver.energy_axis.get_value() - - describe = await sim_detector.describe() - assert describe[f"{prefix}image"]["shape"] == [ - len(angle_axis), - len(energy_axis), - ] diff --git a/tests/devices/electron_analyser/specs/test_specs_driver_io.py b/tests/devices/electron_analyser/specs/test_specs_driver_io.py index 65e6234816..d7287ff15d 100644 --- a/tests/devices/electron_analyser/specs/test_specs_driver_io.py +++ b/tests/devices/electron_analyser/specs/test_specs_driver_io.py @@ -4,7 +4,7 @@ import pytest from bluesky import plan_stubs as bps from bluesky.run_engine import RunEngine -from ophyd_async.core import get_mock_put, init_devices, set_mock_value +from ophyd_async.core import get_mock_put, set_mock_value from ophyd_async.testing import ( assert_configuration, assert_reading, @@ -12,34 +12,33 @@ partial_reading, ) -from dodal.devices.beamlines.b07 import LensMode, PsuMode +from dodal.devices.beamlines.b07.analyser import ( + B07BSpecsAnalyserDriverIO, + B07BSpecsRegion, +) from dodal.devices.electron_analyser.base import EnergyMode from dodal.devices.electron_analyser.base.base_enums import EnergyMode -from dodal.devices.electron_analyser.specs import ( - AcquisitionMode, - SpecsAnalyserDriverIO, - SpecsRegion, -) -from dodal.testing.electron_analyser import create_driver +from dodal.devices.electron_analyser.specs import AcquisitionMode from tests.devices.electron_analyser.helper_util import ( TEST_SEQUENCE_REGION_NAMES, + TEST_SEQUENCES, ) @pytest.fixture -async def sim_driver() -> SpecsAnalyserDriverIO[LensMode, PsuMode]: - async with init_devices(mock=True): - sim_driver = create_driver( - SpecsAnalyserDriverIO[LensMode, PsuMode], - prefix="TEST:", - ) - return sim_driver +async def sim_driver(b07b_specs150) -> B07BSpecsAnalyserDriverIO: + return b07b_specs150.driver + + +@pytest.fixture +def sequence(sim_driver: B07BSpecsAnalyserDriverIO): + return TEST_SEQUENCES[type(sim_driver)]() @pytest.mark.parametrize("region", TEST_SEQUENCE_REGION_NAMES, indirect=True) async def test_analyser_sets_region_correctly( - sim_driver: SpecsAnalyserDriverIO[LensMode, PsuMode], - region: SpecsRegion[LensMode, PsuMode], + sim_driver: B07BSpecsAnalyserDriverIO, + region: B07BSpecsRegion, run_engine: RunEngine, ) -> None: run_engine(bps.mv(sim_driver, region), wait=True) @@ -95,8 +94,8 @@ async def test_analyser_sets_region_correctly( @pytest.mark.parametrize("region", TEST_SEQUENCE_REGION_NAMES, indirect=True) async def test_analyser_sets_region_and_read_configuration_is_correct( - sim_driver: SpecsAnalyserDriverIO[LensMode, PsuMode], - region: SpecsRegion[LensMode, PsuMode], + sim_driver: B07BSpecsAnalyserDriverIO, + region: B07BSpecsRegion, run_engine: RunEngine, ) -> None: run_engine(bps.mv(sim_driver, region), wait=True) @@ -132,8 +131,8 @@ async def test_analyser_sets_region_and_read_configuration_is_correct( @pytest.mark.parametrize("region", TEST_SEQUENCE_REGION_NAMES, indirect=True) async def test_analyser_sets_region_and_read_is_correct( - sim_driver: SpecsAnalyserDriverIO[LensMode, PsuMode], - region: SpecsRegion[LensMode, PsuMode], + sim_driver: B07BSpecsAnalyserDriverIO, + region: B07BSpecsRegion, run_engine: RunEngine, ) -> None: run_engine(bps.mv(sim_driver, region), wait=True) @@ -155,8 +154,8 @@ async def test_analyser_sets_region_and_read_is_correct( @pytest.mark.parametrize("region", TEST_SEQUENCE_REGION_NAMES, indirect=True) async def test_specs_analyser_binding_energy_axis( - sim_driver: SpecsAnalyserDriverIO[LensMode, PsuMode], - region: SpecsRegion[LensMode, PsuMode], + sim_driver: B07BSpecsAnalyserDriverIO, + region: B07BSpecsRegion, run_engine: RunEngine, ) -> None: run_engine(bps.mv(sim_driver, region)) @@ -177,7 +176,7 @@ async def test_specs_analyser_binding_energy_axis( async def test_specs_analyser_energy_axis( - sim_driver: SpecsAnalyserDriverIO[LensMode, PsuMode], + sim_driver: B07BSpecsAnalyserDriverIO, run_engine: RunEngine, ) -> None: start_energy = 1 @@ -193,7 +192,7 @@ async def test_specs_analyser_energy_axis( async def test_specs_analyser_angle_axis( - sim_driver: SpecsAnalyserDriverIO[LensMode, PsuMode], + sim_driver: B07BSpecsAnalyserDriverIO, run_engine: RunEngine, ) -> None: max_angle = 21 diff --git a/tests/devices/electron_analyser/specs/test_specs_region.py b/tests/devices/electron_analyser/specs/test_specs_region.py index 0ed8c13307..2ce1119e41 100644 --- a/tests/devices/electron_analyser/specs/test_specs_region.py +++ b/tests/devices/electron_analyser/specs/test_specs_region.py @@ -2,24 +2,20 @@ import pytest -from dodal.common.data_util import load_json_file_to_class -from dodal.devices.beamlines.b07 import LensMode, PsuMode +from dodal.devices.beamlines.b07 import B07BSpecsSequence, LensMode +from dodal.devices.beamlines.b07_shared import PsuMode from dodal.devices.electron_analyser.base import EnergyMode -from dodal.devices.electron_analyser.specs import ( - AcquisitionMode, - SpecsSequence, -) +from dodal.devices.electron_analyser.specs import AcquisitionMode from dodal.devices.selectable_source import SelectedSource from tests.devices.electron_analyser.helper_util import ( + TEST_SEQUENCES, assert_region_has_expected_values, - get_test_sequence, ) @pytest.fixture -def sequence() -> SpecsSequence[LensMode, PsuMode]: - seq = SpecsSequence[LensMode, PsuMode] - return load_json_file_to_class(seq, get_test_sequence(seq)) +def sequence() -> B07BSpecsSequence: + return TEST_SEQUENCES[B07BSpecsSequence]() @pytest.fixture @@ -86,7 +82,7 @@ def expected_region_values() -> list[dict[str, Any]]: def test_sequence_get_expected_enabled_region_names( - sequence: SpecsSequence[LensMode, PsuMode], + sequence: B07BSpecsSequence, expected_enabled_region_names: list[str], ) -> None: assert sequence.get_enabled_region_names() == expected_enabled_region_names @@ -95,7 +91,7 @@ def test_sequence_get_expected_enabled_region_names( def test_file_loads_into_class_with_expected_values( - sequence: SpecsSequence[LensMode, PsuMode], + sequence: B07BSpecsSequence, expected_region_values: list[dict[str, Any]], ) -> None: assert len(sequence.regions) == len(expected_region_values) diff --git a/tests/devices/electron_analyser/vgscienta/test_vgscienta_detector.py b/tests/devices/electron_analyser/vgscienta/test_vgscienta_detector.py deleted file mode 100644 index 4d7dadc549..0000000000 --- a/tests/devices/electron_analyser/vgscienta/test_vgscienta_detector.py +++ /dev/null @@ -1,41 +0,0 @@ -import numpy as np -import pytest -from ophyd_async.core import init_devices, set_mock_value - -from dodal.devices.beamlines.i09 import LensMode, PassEnergy, PsuMode -from dodal.devices.electron_analyser.base import DualEnergySource -from dodal.devices.electron_analyser.vgscienta import ( - VGScientaDetector, -) -from dodal.testing.electron_analyser import create_detector - - -@pytest.fixture -async def sim_detector( - dual_energy_source: DualEnergySource, -) -> VGScientaDetector[LensMode, PsuMode, PassEnergy]: - async with init_devices(mock=True): - sim_driver = create_detector( - VGScientaDetector[LensMode, PsuMode, PassEnergy], - prefix="TEST:", - energy_source=dual_energy_source, - ) - return sim_driver - - -async def test_analyser_vgscienta_detector_image_shape( - sim_detector: VGScientaDetector, -) -> None: - driver = sim_detector.driver - prefix = driver.name + "-" - - energy_axis = np.array([1, 2, 3, 4, 5]) - angle_axis = np.array([1, 2]) - set_mock_value(driver.energy_axis, energy_axis) - set_mock_value(driver.angle_axis, angle_axis) - - describe = await sim_detector.describe() - assert describe[f"{prefix}image"]["shape"] == [ - len(angle_axis), - len(energy_axis), - ] diff --git a/tests/devices/electron_analyser/vgscienta/test_vgscienta_driver_io.py b/tests/devices/electron_analyser/vgscienta/test_vgscienta_driver_io.py index a31afd82a7..17bbdbff1c 100644 --- a/tests/devices/electron_analyser/vgscienta/test_vgscienta_driver_io.py +++ b/tests/devices/electron_analyser/vgscienta/test_vgscienta_driver_io.py @@ -5,7 +5,7 @@ from bluesky import plan_stubs as bps from bluesky.run_engine import RunEngine from bluesky.utils import FailedStatus -from ophyd_async.core import StrictEnum, get_mock_put, init_devices, set_mock_value +from ophyd_async.core import StrictEnum, get_mock_put, set_mock_value from ophyd_async.testing import ( assert_configuration, assert_reading, @@ -13,31 +13,31 @@ partial_reading, ) -from dodal.devices.beamlines.i09 import LensMode, PassEnergy, PsuMode -from dodal.devices.electron_analyser.base import EnergyMode -from dodal.devices.electron_analyser.vgscienta import ( - VGScientaAnalyserDriverIO, - VGScientaRegion, +from dodal.devices.beamlines.i09 import ( + I09VGScientaAnalyserDriverIO, + I09VGScientaRegion, ) -from dodal.testing.electron_analyser import create_driver +from dodal.devices.electron_analyser.base import EnergyMode from tests.devices.electron_analyser.helper_util import ( TEST_SEQUENCE_REGION_NAMES, + TEST_SEQUENCES, ) @pytest.fixture -async def sim_driver() -> VGScientaAnalyserDriverIO[LensMode, PsuMode, PassEnergy]: - async with init_devices(mock=True): - sim_driver = create_driver( - VGScientaAnalyserDriverIO[LensMode, PsuMode, PassEnergy], prefix="TEST:" - ) - return sim_driver +async def sim_driver(ew4000) -> I09VGScientaAnalyserDriverIO: + return ew4000.driver + + +@pytest.fixture +def sequence(sim_driver: I09VGScientaAnalyserDriverIO): + return TEST_SEQUENCES[type(sim_driver)]() @pytest.mark.parametrize("region", TEST_SEQUENCE_REGION_NAMES, indirect=True) async def test_analyser_sets_region_correctly( - sim_driver: VGScientaAnalyserDriverIO[LensMode, PsuMode, PassEnergy], - region: VGScientaRegion[LensMode, PassEnergy], + sim_driver: I09VGScientaAnalyserDriverIO, + region: I09VGScientaRegion, run_engine: RunEngine, ) -> None: run_engine(bps.mv(sim_driver, region), wait=True) @@ -94,8 +94,8 @@ async def test_analyser_sets_region_correctly( @pytest.mark.parametrize("region", TEST_SEQUENCE_REGION_NAMES, indirect=True) async def test_analyser_sets_region_and_read_configuration_is_correct( - sim_driver: VGScientaAnalyserDriverIO[LensMode, PsuMode, PassEnergy], - region: VGScientaRegion[LensMode, PassEnergy], + sim_driver: I09VGScientaAnalyserDriverIO, + region: I09VGScientaRegion, run_engine: RunEngine, ) -> None: run_engine(bps.mv(sim_driver, region), wait=True) @@ -136,8 +136,8 @@ async def test_analyser_sets_region_and_read_configuration_is_correct( @pytest.mark.parametrize("region", TEST_SEQUENCE_REGION_NAMES, indirect=True) async def test_analyser_sets_region_and_read_is_correct( - sim_driver: VGScientaAnalyserDriverIO[LensMode, PsuMode, PassEnergy], - region: VGScientaRegion[LensMode, PassEnergy], + sim_driver: I09VGScientaAnalyserDriverIO, + region: I09VGScientaRegion, run_engine: RunEngine, ) -> None: run_engine(bps.mv(sim_driver, region), wait=True) @@ -160,8 +160,8 @@ async def test_analyser_sets_region_and_read_is_correct( @pytest.mark.parametrize("region", TEST_SEQUENCE_REGION_NAMES, indirect=True) async def test_analayser_binding_energy_is_correct( - sim_driver: VGScientaAnalyserDriverIO[LensMode, PsuMode, PassEnergy], - region: VGScientaRegion[LensMode, PassEnergy], + sim_driver: I09VGScientaAnalyserDriverIO, + region: I09VGScientaRegion, run_engine: RunEngine, ) -> None: run_engine(bps.mv(sim_driver, region), wait=True) @@ -183,7 +183,7 @@ async def test_analayser_binding_energy_is_correct( def test_driver_throws_error_with_wrong_pass_energy( - sim_driver: VGScientaAnalyserDriverIO[LensMode, PsuMode, PassEnergy], + sim_driver: I09VGScientaAnalyserDriverIO, run_engine: RunEngine, ) -> None: class PassEnergyTestEnum(StrictEnum): @@ -200,7 +200,7 @@ class PassEnergyTestEnum(StrictEnum): def test_driver_throws_error_with_wrong_detector_mode( - sim_driver: VGScientaAnalyserDriverIO[LensMode, PsuMode, PassEnergy], + sim_driver: I09VGScientaAnalyserDriverIO, run_engine: RunEngine, ) -> None: class DetectorModeTestEnum(StrictEnum): diff --git a/tests/devices/electron_analyser/vgscienta/test_vgsicenta_region.py b/tests/devices/electron_analyser/vgscienta/test_vgsicenta_region.py index 81ea9d03ce..a0466da2d8 100644 --- a/tests/devices/electron_analyser/vgscienta/test_vgsicenta_region.py +++ b/tests/devices/electron_analyser/vgscienta/test_vgsicenta_region.py @@ -2,31 +2,31 @@ import pytest -from dodal.common.data_util import load_json_file_to_class -from dodal.devices.beamlines.i09 import LensMode, PassEnergy, PsuMode +from dodal.devices.beamlines.i09 import LensMode, PassEnergy +from dodal.devices.beamlines.i09.analyser import ( + I09VGScientaRegion, + I09VGScientaSequence, +) from dodal.devices.electron_analyser.base import EnergyMode from dodal.devices.electron_analyser.vgscienta import ( AcquisitionMode, DetectorMode, - VGScientaRegion, - VGScientaSequence, ) from dodal.devices.selectable_source import SelectedSource from tests.devices.electron_analyser.helper_util import ( + TEST_SEQUENCES, assert_region_has_expected_values, - get_test_sequence, ) @pytest.fixture -def sequence() -> VGScientaSequence[LensMode, PsuMode, PassEnergy]: - seq = VGScientaSequence[LensMode, PsuMode, PassEnergy] - return load_json_file_to_class(seq, get_test_sequence(seq)) +def sequence() -> I09VGScientaSequence: + return TEST_SEQUENCES[I09VGScientaSequence]() @pytest.fixture -def expected_region_class() -> type[VGScientaRegion[LensMode, PassEnergy]]: - return VGScientaRegion[LensMode, PassEnergy] +def expected_region_class() -> type[I09VGScientaRegion]: + return I09VGScientaRegion @pytest.fixture @@ -108,7 +108,7 @@ def expected_region_values() -> list[dict[str, Any]]: def test_sequence_get_expected_enabled_region_names( - sequence: VGScientaSequence[LensMode, PsuMode, PassEnergy], + sequence: I09VGScientaSequence, expected_enabled_region_names: list[str], ) -> None: assert sequence.get_enabled_region_names() == expected_enabled_region_names @@ -117,7 +117,7 @@ def test_sequence_get_expected_enabled_region_names( def test_file_loads_into_class_with_expected_values( - sequence: VGScientaSequence[LensMode, PsuMode, PassEnergy], + sequence: I09VGScientaSequence, expected_region_values: list[dict[str, Any]], ) -> None: assert len(sequence.regions) == len(expected_region_values)